2012-05-28 2 views
22

पर हास्केल में यूनिकोड कंसोल I/O विंडोज़ के तहत हास्केल में यूनिकोड वर्णों के साथ काम करने के लिए कंसोल I/O प्राप्त करना मुश्किल लगता है। यहाँ शोक की कहानी है:विंडोज

  1. (प्रारंभिक।) इससे पहले कि आप भी खिड़कियों के तहत कंसोल में कर यूनिकोड आई/ओ पर विचार करें, आप यह सुनिश्चित करें कि आप एक सांत्वना फ़ॉन्ट जो वर्ण प्रदान कर सकते हैं का उपयोग कर रहे बनाने की जरूरत है तुम्हें चाहिए। रास्टर फोंट (डिफ़ॉल्ट) में असीमित रूप से खराब कवरेज होता है (और उन वर्णों की प्रतिलिपि बनाने की अनुमति न दें जिन्हें वे प्रतिनिधित्व नहीं कर सकते हैं), और ट्रूटाइप विकल्प एमएस प्रदान करता है (कंसोलस, लुसीडा कंसोल) में बहुत अच्छा कवरेज नहीं है (हालांकि ये अनुमति देंगे उन पात्रों की प्रतिलिपि/पेस्टिंग जो वे प्रतिनिधित्व नहीं कर सकते हैं)। आप DejaVu Sans Mono इंस्टॉल करने पर विचार कर सकते हैं (नीचे here पर निर्देशों का पालन करें; आपको इसे काम करने से पहले रीबूट करना पड़ सकता है)। जब तक यह सॉर्ट नहीं किया जाता है, तब तक कोई भी ऐप्स यूनिकोड I/O नहीं कर पाएगा; सिर्फ हास्केल नहीं।
  2. ऐसा करने के बाद, आप देखेंगे कि कुछ ऐप्स विंडोज़ के तहत कंसोल I/O करने में सक्षम होंगे। लेकिन इसे काम करने के लिए काफी जटिल बना हुआ है। खिड़कियों के नीचे कंसोल को लिखने के मूल रूप से दो तरीके हैं। (किसी भी भाषा के लिए क्या सही है, न केवल हास्केल; चिंता न करें, हास्केल थोड़ी देर में तस्वीर दर्ज करेगा!) ...
  3. विकल्प ए सामान्य सी-लाइब्रेरी शैली बाइट-आधारित i/ओ कार्य; आशा है कि ओएस कुछ एन्कोडिंग के अनुसार इन बाइट्स की व्याख्या करेगा जो आपके इच्छित सभी अजीब और अद्भुत पात्रों को एन्कोड कर सकता है। उदाहरण के लिए, मैक ओएस एक्स पर समतुल्य तकनीक का उपयोग करते हुए, जहां मानक सिस्टम एन्कोडिंग आमतौर पर यूटीएफ 8 होता है, यह बहुत अच्छा काम करता है; आप utf8 आउटपुट भेजते हैं, आप सुंदर प्रतीक देखते हैं।
  4. विंडोज़ पर, यह कम अच्छी तरह से काम करता है। विंडोज़ की अपेक्षा की जाने वाली डिफ़ॉल्ट एन्कोडिंग आम तौर पर सभी यूनिकोड प्रतीकों को कवर करने वाला एन्कोडिंग नहीं होगी। इसलिए यदि आप इस तरह से सुंदर प्रतीकों को देखना चाहते हैं, एक तरफ या किसी अन्य, आपको एन्कोडिंग को बदलने की आवश्यकता है। आपके प्रोग्राम के लिए SetConsoleCP win32 कमांड का उपयोग करने की संभावना एक संभावना होगी। (तो फिर आपको Win32 लाइब्रेरी से जुड़ना होगा।) या, यदि आप ऐसा नहीं करना चाहते हैं, तो आप अपने प्रोग्राम के उपयोगकर्ता को कोड पेज बदलने की उम्मीद कर सकते हैं (फिर उन्हें चलाने से पहले chcp कमांड को कॉल करना होगा आपका कार्यक्रम)।
  5. विकल्प बी यूनिकोड-जागरूक win32 कंसोल एपीआई कमांड जैसे WriteConsoleW का उपयोग करना है। यहां आप विंडोज़ को सीधे यूटीएफ 16 भेजते हैं, जो इसे खुशी से प्रस्तुत करता है: एन्कोडिंग विसंगति का कोई खतरा नहीं है क्योंकि विंडोज हमेशा इन कार्यों के साथ यूटीएफ 16 की अपेक्षा करता है।

दुर्भाग्य से, इनमें से कोई भी विकल्प हास्केल से बहुत अच्छा काम नहीं करता है। सबसे पहले, ऐसी कोई पुस्तकालय नहीं है जो मुझे पता है कि विकल्प बी का उपयोग करें, इसलिए यह बहुत आसान नहीं है। यह विकल्प ए छोड़ देता है। यदि आप हास्केल की I/O लाइब्रेरी (putStrLn और इसी तरह) का उपयोग करते हैं, तो लाइब्रेरी यही करेगी। हास्केल के आधुनिक संस्करणों में, यह सावधानीपूर्वक विंडोज़ से पूछेगा कि वर्तमान कोड पेज क्या है, और उचित तारों में अपने तारों को आउटपुट करें। इस दृष्टिकोण के साथ दो समस्याएं हैं:

  • कोई शोस्टॉपर नहीं है, लेकिन परेशान है। जैसा ऊपर बताया गया है, डिफ़ॉल्ट एन्कोडिंग लगभग आपके इच्छित अक्षरों को एन्कोड नहीं करेगा: आपको उपयोगकर्ता को एन्कोडिंग में बदलने की आवश्यकता है जो करता है। इस प्रकार आपके उपयोगकर्ता को आपके प्रोग्राम चलाने से पहले chcp cp65001 की आवश्यकता होती है (आप इसे अपने उपयोगकर्ताओं को ऐसा करने के लिए मजबूर कर सकते हैं)। या आपको SetConsoleCP से जुड़ना होगा और अपने प्रोग्राम के समतुल्य समतुल्य करना होगा (और फिर hSetEncoding का उपयोग करें ताकि हास्केल लाइब्रेरी नए एन्कोडिंग का उपयोग करके आउटपुट भेज सकें), जिसका अर्थ है कि आपको Win32 पुस्तकालयों के प्रासंगिक भाग को Haskell बनाने के लिए लपेटने की आवश्यकता है -visible।
  • अधिक गंभीरता से, bug in windows (रिज़ॉल्यूशन: ठीक नहीं होगा) जो की ओर जाता है जिसका अर्थ है कि यदि आपने सीपी 65001 जैसे किसी भी कोड पेज का चयन किया है जो यूनिकोड के सभी को कवर कर सकता है, हास्केल के आई/ओ रूटीन खराब हो जाएंगे और असफल तो अनिवार्य रूप से, भले ही आप (या आपका उपयोगकर्ता) एन्कोडिंग को कुछ एन्कोडिंग में ठीक से सेट करते हैं जो सभी अद्भुत यूनिकोड वर्णों को कवर करता है, और फिर उस एन्कोडिंग का उपयोग करके आउटपुट चीजों को आउटपुट करने के लिए हास्केल को बताने में 'सब कुछ ठीक करें', आप अभी भी हार जाते हैं।

ऊपर सूचीबद्ध बग अभी भी अनसुलझा है और कम प्राथमिकता के रूप में सूचीबद्ध है; मूल निष्कर्ष यह है कि विकल्प ए (उपरोक्त मेरे वर्गीकरण में) अप्रभावी है और विश्वसनीय परिणामों को प्राप्त करने के लिए किसी को विकल्प बी पर स्विच करने की आवश्यकता है। यह स्पष्ट नहीं है कि इस समाधान के लिए समय सीमा क्या होगी, क्योंकि यह कुछ महत्वपूर्ण काम की तरह दिखता है।

सवाल यह है कि: इस बीच, क्या कोई भी विंडोज़ के तहत हास्केल में यूनिकोड कंसोल I/O के उपयोग की अनुमति देने के लिए एक समाधान का सुझाव दे सकता है।

भी देखें इस python bug tracker database entry,, अजगर 3 (ठीक प्रस्तावित है, लेकिन अभी तक codebase में स्वीकार नहीं), और this stackoverflow answer में एक ही समस्या के साथ जूझ अजगर में इस समस्या के लिए एक समाधान दे (पर 'विकल्प बी' में स्थित मेरा वर्गीकरण)।

उत्तर

19

मैंने सोचा कि मैं अपने स्वयं के प्रश्न का उत्तर दूंगा, और के रूप में सूचीबद्ध एक संभावित उत्तर, निम्न है, जो मैं वास्तव में इस समय कर रहा हूं। यह काफी संभव है कि कोई बेहतर कर सके, यही कारण है कि मैं सवाल पूछ रहा हूं! लेकिन मैंने सोचा कि लोगों को निम्नलिखित उपलब्ध कराने के लिए यह समझदारी होगी। यह मूल रूप से इस python workaround for the same issue के पाइथन से हास्केल का अनुवाद है। यह प्रश्न में उल्लिखित 'विकल्प बी' का उपयोग करता है।

{-# LANGUAGE ForeignFunctionInterface #-} 
{-# LANGUAGE CPP #-} 
{-# LANGUAGE NoImplicitPrelude #-} 
module IOUtil (
    IOUtil.interact, 
    IOUtil.putChar, IOUtil.putStr, IOUtil.putStrLn, IOUtil.print, 
    IOUtil.getChar, IOUtil.getLine, IOUtil.getContents, IOUtil.readIO, 
    IOUtil.readLn, 
    ePutChar, ePutStr, ePutStrLn, ePrint, 
    trace, traceIO 
) where 

#ifdef mingw32_HOST_OS 

import System.Win32.Types (BOOL, HANDLE, DWORD, LPDWORD, LPWSTR, LPCWSTR, LPVOID) 
import Foreign.C.Types (CWchar) 
import Foreign 
import Prelude hiding (getContents, putStr, putStrLn) --(IO, Read, Show, String) 
--import qualified System.IO 
import qualified System.IO (getContents) 
import System.IO hiding (getContents, putStr, putStrLn) 
import Data.Char (ord) 

{- <http://msdn.microsoft.com/en-us/library/ms683231(VS.85).aspx> 
    HANDLE WINAPI GetStdHandle(DWORD nStdHandle); 
    returns INVALID_HANDLE_VALUE, NULL, or a valid handle -} 

foreign import stdcall unsafe "GetStdHandle" win32GetStdHandle :: DWORD -> IO (HANDLE) 

std_OUTPUT_HANDLE = -11 :: DWORD -- all DWORD arithmetic is performed modulo 2^n 
std_ERROR_HANDLE = -12 :: DWORD 

{- <http://msdn.microsoft.com/en-us/library/aa364960(VS.85).aspx> 
    DWORD WINAPI GetFileType(HANDLE hFile); -} 

foreign import stdcall unsafe "GetFileType" win32GetFileType :: HANDLE -> IO (DWORD) 
_FILE_TYPE_CHAR = 0x0002 :: DWORD 
_FILE_TYPE_REMOTE = 0x8000 :: DWORD 

{- <http://msdn.microsoft.com/en-us/library/ms683167(VS.85).aspx> 
    BOOL WINAPI GetConsoleMode(HANDLE hConsole, LPDWORD lpMode); -} 

foreign import stdcall unsafe "GetConsoleMode" win32GetConsoleMode :: HANDLE -> LPDWORD -> IO (BOOL) 
_INVALID_HANDLE_VALUE = (intPtrToPtr $ -1) :: HANDLE 

is_a_console :: HANDLE -> IO (Bool) 
is_a_console handle 
    = if (handle == _INVALID_HANDLE_VALUE) then return False 
     else do ft <- win32GetFileType handle 
       if ((ft .&. complement _FILE_TYPE_REMOTE) /= _FILE_TYPE_CHAR) then return False 
       else do ptr <- malloc 
         cm <- win32GetConsoleMode handle ptr 
         free ptr 
         return cm 

real_stdout :: IO (Bool) 
real_stdout = is_a_console =<< win32GetStdHandle std_OUTPUT_HANDLE 

real_stderr :: IO (Bool) 
real_stderr = is_a_console =<< win32GetStdHandle std_ERROR_HANDLE 

{- BOOL WINAPI WriteConsoleW(HANDLE hOutput, LPWSTR lpBuffer, DWORD nChars, 
           LPDWORD lpCharsWritten, LPVOID lpReserved); -} 

foreign import stdcall unsafe "WriteConsoleW" win32WriteConsoleW 
    :: HANDLE -> LPWSTR -> DWORD -> LPDWORD -> LPVOID -> IO (BOOL) 

data ConsoleInfo = ConsoleInfo Int (Ptr CWchar) (Ptr DWORD) HANDLE 

writeConsole :: ConsoleInfo -> [Char] -> IO() 
writeConsole (ConsoleInfo bufsize buf written handle) string 
    = let fillbuf :: Int -> [Char] -> IO() 
     fillbuf i [] = emptybuf buf i [] 
     fillbuf i [email protected](first:rest) 
      | i + 1 < bufsize && ordf <= 0xffff = do pokeElemOff buf i asWord 
                fillbuf (i+1) rest 
      | i + 1 < bufsize && ordf > 0xffff = do pokeElemOff buf i word1 
                pokeElemOff buf (i+1) word2 
                fillbuf (i+2) rest 
      | otherwise       = emptybuf buf i remain 
      where ordf = ord first 
       asWord = fromInteger (toInteger ordf) :: CWchar 
       sub = ordf - 0x10000 
       word1' = ((shiftR sub 10) .&. 0x3ff) + 0xD800 
       word2' = (sub .&. 0x3FF)    + 0xDC00 
       word1 = fromInteger . toInteger $ word1' 
       word2 = fromInteger . toInteger $ word2' 


     emptybuf :: (Ptr CWchar) -> Int -> [Char] -> IO() 
     emptybuf _ 0 []  = return() 
     emptybuf _ 0 remain = fillbuf 0 remain 
     emptybuf ptr nLeft remain 
      = do let nLeft' = fromInteger . toInteger $ nLeft 
       ret   <- win32WriteConsoleW handle ptr nLeft' written nullPtr 
       nWritten  <- peek written 
       let nWritten' = fromInteger . toInteger $ nWritten 
       if ret && (nWritten > 0) 
        then emptybuf (ptr `plusPtr` (nWritten' * szWChar)) (nLeft - nWritten') remain 
        else fail "WriteConsoleW failed.\n" 

    in fillbuf 0 string 

szWChar = sizeOf (0 :: CWchar) 

makeConsoleInfo :: DWORD -> Handle -> IO (Either ConsoleInfo Handle) 
makeConsoleInfo nStdHandle fallback 
    = do handle  <- win32GetStdHandle nStdHandle 
     is_console <- is_a_console handle 
     let bufsize = 10000 
     if not is_console then return $ Right fallback 
     else do buf  <- mallocBytes (szWChar * bufsize) 
       written <- malloc 
       return . Left $ ConsoleInfo bufsize buf written handle 

{-# NOINLINE stdoutConsoleInfo #-} 
stdoutConsoleInfo :: Either ConsoleInfo Handle 
stdoutConsoleInfo = unsafePerformIO $ makeConsoleInfo std_OUTPUT_HANDLE stdout 

{-# NOINLINE stderrConsoleInfo #-} 
stderrConsoleInfo :: Either ConsoleInfo Handle 
stderrConsoleInfo = unsafePerformIO $ makeConsoleInfo std_ERROR_HANDLE stderr 

interact  :: (String -> String) -> IO() 
interact f = do s <- getContents 
        putStr (f s) 

conPutChar ci = writeConsole ci . replicate 1 
conPutStr  = writeConsole 
conPutStrLn ci = writeConsole ci . (++ "\n") 

putChar  :: Char -> IO() 
putChar  = (either conPutChar hPutChar) stdoutConsoleInfo 

putStr  :: String -> IO() 
putStr  = (either conPutStr hPutStr ) stdoutConsoleInfo 

putStrLn  :: String -> IO() 
putStrLn  = (either conPutStrLn hPutStrLn) stdoutConsoleInfo 

print  :: Show a => a -> IO() 
print  = putStrLn . show 

getChar  = System.IO.getChar 
getLine  = System.IO.getLine 
getContents = System.IO.getContents 

readIO  :: Read a => String -> IO a 
readIO  = System.IO.readIO 

readLn  :: Read a => IO a 
readLn  = System.IO.readLn 

ePutChar  :: Char -> IO() 
ePutChar  = (either conPutChar hPutChar) stderrConsoleInfo 

ePutStr  :: String -> IO() 
ePutStr  = (either conPutStr hPutStr ) stderrConsoleInfo 

ePutStrLn :: String -> IO() 
ePutStrLn = (either conPutStrLn hPutStrLn) stderrConsoleInfo 

ePrint  :: Show a => a -> IO() 
ePrint  = ePutStrLn . show 

#else 

import qualified System.IO 
import Prelude (IO, Read, Show, String) 

interact  = System.IO.interact 
putChar  = System.IO.putChar 
putStr  = System.IO.putStr 
putStrLn  = System.IO.putStrLn 
getChar  = System.IO.getChar 
getLine  = System.IO.getLine 
getContents = System.IO.getContents 
ePutChar  = System.IO.hPutChar System.IO.stderr 
ePutStr  = System.IO.hPutStr System.IO.stderr 
ePutStrLn = System.IO.hPutStrLn System.IO.stderr 

print  :: Show a => a -> IO() 
print  = System.IO.print 

readIO  :: Read a => String -> IO a 
readIO  = System.IO.readIO 

readLn  :: Read a => IO a 
readLn  = System.IO.readLn 

ePrint  :: Show a => a -> IO() 
ePrint  = System.IO.hPrint System.IO.stderr 

#endif 

trace :: String -> a -> a 
trace string expr = unsafePerformIO $ do 
    traceIO string 
    return expr 

traceIO :: String -> IO() 
traceIO = ePutStrLn 

फिर, आप आई/ओ कार्यों उसमें बजाय निहित का उपयोग करें:

मूल विचार है कि आप निम्न सामग्री है, जो आप अपने कोड में import सकते हैं के साथ एक मॉड्यूल IOUtil.hs बनाने के लिए, है मानक लाइब्रेरी वाले। वे पता लगाएंगे कि आउटपुट रीडायरेक्ट किया गया है या नहीं; यदि नहीं (यानी अगर हम 'असली' कंसोल पर लिख रहे हैं) तो हम सामान्य हास्केल I/O फ़ंक्शंस को बाईपास करेंगे और WriteConsoleW का उपयोग करके Win32 कंसोल पर सीधे लिखेंगे, यूनिकोड-जागरूक win32 कंसोल फ़ंक्शन। गैर-विंडोज प्लेटफ़ॉर्म पर, सशर्त संकलन का अर्थ है कि यहां फ़ंक्शंस केवल मानक-लाइब्रेरी वाले को कॉल करते हैं।

यदि आपको stderr पर प्रिंट करने की आवश्यकता है, तो आपको (उदा।) ePutStrLn का उपयोग करना चाहिए, hPutStrLn stderr नहीं; हम hPutStrLn परिभाषित नहीं करते हैं। (एक को परिभाषित करने वाला पाठक के लिए एक अभ्यास है!)

+2

मुझे यकीन नहीं है कि मैंने उन सभी का पालन किया है, लेकिन यदि यह विंडोज़ पर कंसोल आईओ करने का सही तरीका है, तो आप एक [पुस्तकालय प्रस्ताव तैयार करने के बारे में कैसा महसूस करेंगे ] (http://www.haskell.org/haskellwiki/Library_submissions) इन कार्यों को Win32 पैकेज में जोड़ने के लिए और/या इन कार्यों को बेस लाइब्रेरी के 'putStrLn' आदि से कॉल करें)? –

+0

@DanielWagner: ठीक है, मुझे लगता है कि यह देखने के लिए थोड़ा सा इंतजार हो सकता है कि किसी और के पास कोई सुझाव है या नहीं ... मैं काफी हद तक निश्चित हूं कि मैं जो करता हूं वह Win32/यूनिकोड बिंदु दृश्य से 'दाएं' है ; कम निश्चित यह हैस्केल में बेनकाब करने के लिए सबसे अच्छा इंटरफ़ेस है। एक और बात यह है कि मुझे शायद वरिष्ठ रखरखावकर्ताओं से हाथ से पकड़े जाने की संख्या की आवश्यकता होगी, मैं इसे 'आधिकारिक' बनाने की कोशिश कर रहा था: मैंने पहले कभी कोर हास्केल पुस्तकालयों को हैक नहीं किया है। इसलिए (यह देखते हुए कि इस मुद्दे को आधिकारिक तौर पर कम प्राथमिकता दी गई है) मुझे चिंता है कि यह वरिष्ठ रखरखाव के समय को बर्बाद कर सकता है। –

+0

@DanielWagner; ओटीओएच, मानते हुए कि कोई अन्य जवाब नहीं है, और यदि आपको लगता है कि रखरखाव एक सुझाए गए पैच का स्वागत करेंगे, तो मुझे उपरोक्त को पुस्तकालय प्रस्ताव के रूप में, समय पर अनुमति देने के लिए बहुत खुशी होगी। –