gpt4 book ai didi

windows - Windows上Haskell中的Unicode控制台I/O

转载 作者:行者123 更新时间:2023-12-02 17:12:36 27 4
gpt4 key购买 nike

在Windows下的Haskell中,使控制台I / O与Unicode字符一起使用似乎很困难。这是祸水的故事:


(初步。)甚至在考虑在Windows下的控制台中执行Unicode I / O之前,您需要确保使用的控制台字体可以呈现所需的字符。光栅字体(默认设置)具有无限差的覆盖范围(并且不允许复制粘贴它们不能代表的字符),而MS提供的truetype选项(consolas,lucida控制台)的覆盖范围不是很大(尽管这些将允许)复制/粘贴他们无法代表的字符)。您可以考虑安装DejaVu Sans Mono(按照here底部的说明进行操作;可能需要重新启动才能工作)。在进行排序之前,没有任何应用程序可以执行很多Unicode I / O。不只是Haskell。
完成此操作后,您会注意到某些应用程序将能够在Windows下执行控制台I / O。但是,使其正常工作仍然相当复杂。在Windows下,基本上有两种写入控制台的方法。 (以下内容适用于所有语言,而不仅限于Haskell;请放心,Haskell稍后会输入图片!)...
选项A是使用通常的c库样式基于字节的I / O函数;希望操作系统将根据某种编码来解释这些字节,从而可以对您想要的所有奇怪而奇妙的字符进行编码。例如,使用Mac OS X上的等效技术(标准系统编码通常为UTF8),效果很好。您发送utf8输出,就会看到漂亮的符号。
在Windows上,效果不佳。 Windows期望的默认编码通常不会是覆盖所有Unicode符号的编码。因此,如果您想以一种或另一种方式查看漂亮的符号,则需要更改编码。一种可能性是您的程序使用SetConsoleCP win32命令。 (因此,您需要绑定到Win32库。)或者,如果您不想这样做,则可以期望程序的用户为您更改代码页(然后他们必须在调用chcp命令之前他们运行您的程序)。
选项B是使用支持Unicode的win32控制台API命令,例如WriteConsoleW。在这里,您将UTF16直接发送到Windows,这使它愉快地呈现出来:没有编码不匹配的危险,因为Windows始终期望具有这些功能的UTF16。


不幸的是,这些选项在Haskell中都无法很好地工作。首先,据我所知没有使用选项B的库,所以这并不容易。剩下选项A。如果您使用Haskell的I / O库(putStrLn等),则库将执行此操作。在现代版本的Haskell中,它将仔细询问Windows当前代码页是什么,并以正确的编码输出您的字符串。这种方法有两个问题:


一个不是风头浪尖的,而是令人讨厌的。如上所述,默认编码几乎不会对所需字符进行编码:您是用户,需要更改为可编码。因此,您的用户需要在运行程序之前先chcp cp65001(您可能会发现强制用户执行此操作很令人讨厌)。或者您需要绑定到SetConsoleCP并在程序内部进行等效操作(然后使用hSetEncoding,以便Haskell库将使用新的编码发送输出),这意味着您需要包装win32库的相关部分使它们具有Haskell可见性。
更严重的是,有一个bug in windows(分辨率:无法修复)导致出现bug in Haskell,这意味着如果您选择了cp65001之类的代码页,它可以覆盖所有Unicode,那么Haskell的I / O例程将故障和失败。因此,从本质上讲,即使您(或您的用户)将编码正确设置为涵盖所有出色的Unicode字符的某种编码,然后在告诉Haskell使用该编码输出内容时“正确执行”,您仍然会迷失方向。


上面列出的错误仍未解决,被列为低优先级;基本结论是,选项A(在我的上面的分类中)不可行,并且需要切换到选项B才能获得可靠的结果。目前尚不清楚要解决这个问题的时间表是什么,因为这似乎是一项艰巨的工作。

问题是:与此同时,任何人都可以提出一种解决方法,以允许Windows下Haskell中使用Unicode控制台I / O。

另请参见此python bug tracker database entry,解决Python 3中的相同问题(已提出修订,但尚未接受代码库)和this stackoverflow answer,提供了针对Python中此问题的解决方法(基于我的“选项B”分类)。

最佳答案

我以为我会回答自己的问题,并列出以下可能的答案,这是我目前正在做的事情。一个人做得更好的可能性很大,这就是为什么我要问这个问题!但是我认为将以下内容提供给人们是有意义的。基本上,这是python workaround for the same issue从Python到Haskell的翻译。它使用问题中提到的“选项B”。

基本思想是创建具有以下内容的模块IOUtil.hs,可以将其import插入代码中:

{-# 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 remain@(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


然后,使用其中包含的I / O函数代替标准库中的I / O函数。他们将检测输出是否被重定向;如果不是这样(即,如果我们正在写一个“真实的”控制台),那么我们将绕过通常的Haskell I / O函数,并使用 WriteConsoleW(可识别Unicode的win32控制台函数)直接写到win32控制台。在非Windows平台上,条件编译意味着此处的函数仅称为标准库函数。

如果需要打印到stderr,则应使用(例如) ePutStrLn,而不是 hPutStrLn stderr;我们没有定义 hPutStrLn。 (定义一个是读者的练习!)

关于windows - Windows上Haskell中的Unicode控制台I/O,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25656809/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com