git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
fixes #2434 and #2438: PureScript chars must be UTF-16 code units (#2454)
93a52e2Michael Ficarra
examples/failing/2434.pursnew file+5
@@ -0,0 +1,5 @@
1+-- @shouldFailWith ErrorParsingModule
2+module Main where
3+
4+x :: Char
5+x = '\x10000'
examples/passing/2438.pursnew file+8
@@ -0,0 +1,8 @@
1+module Main where
2+
3+import Control.Monad.Eff.Console (log)
4+
5+done :: String
6+done = {"๐Œ†": "Done"}."๐Œ†"
7+
8+main = log done
examples/passing/StringEscapes.purs+15โˆ’7
@@ -1,18 +1,26 @@
1module Main where
1module Main where
2
2
3โˆ’import Prelude ((==), bind)
3+import Prelude ((==), (/=), (<>), bind)
4โˆ’import Test.Assert (assert)
4+import Test.Assert (assert, assert')
5import Control.Monad.Eff.Console (log)
5import Control.Monad.Eff.Console (log)
6
6
7singleCharacter = "\0\b\t\n\v\f\r\"\\" == "\x0\x8\x9\xA\xB\xC\xD\x22\x5C"
7singleCharacter = "\0\b\t\n\v\f\r\"\\" == "\x0\x8\x9\xA\xB\xC\xD\x22\x5C"
8hex = "\x1D306\x2603\x3C6\xE0\x0" == "๐Œ†โ˜ƒฯ†ร \0"
8hex = "\x1D306\x2603\x3C6\xE0\x0" == "๐Œ†โ˜ƒฯ†ร \0"
9decimal = "\119558\9731\966\224\0" == "๐Œ†โ˜ƒฯ†ร \0"
9decimal = "\119558\9731\966\224\0" == "๐Œ†โ˜ƒฯ†ร \0"
10surrogatePair = "\xD834\xDF06" == "\x1D306"
10surrogatePair = "\xD834\xDF06" == "\x1D306"
11+highSurrogate = "\xD834"
12+lowSurrogate = "\xDF06"
13+loneSurrogates = (highSurrogate <> lowSurrogate) == "\x1D306"
14+outOfOrderSurrogates = (lowSurrogate <> highSurrogate) == "\xDF06\xD834"
15+replacement = "\xFFFD"
16+notReplacing = replacement /= highSurrogate
11
17
12main = do
18main = do
13โˆ’ assert singleCharacter
19+ assert' "single-character escape sequences" singleCharacter
14โˆ’ assert hex
20+ assert' "hex escape sequences" hex
15โˆ’ assert decimal
21+ assert' "decimal escape sequences" decimal
16โˆ’-- TODO: Broken in #2418 should be fixed after #2434 is fixed
22+ assert' "astral code points are represented as a UTF-16 surrogate pair" surrogatePair
17โˆ’-- assert surrogatePair
23+ assert' "lone surrogates may be combined into a surrogate pair" loneSurrogates
24+ assert' "lone surrogates may be combined out of order to remain lone surrogates" outOfOrderSurrogates
25+ -- assert' "lone surrogates are not replaced with the Unicode replacement character U+FFFD" notReplacing
18 log "Done"
26 log "Done"
src/Language/PureScript/CodeGen/JS/Common.hs+1โˆ’1
@@ -40,7 +40,7 @@ properToJs name
40-- Test if a string is a valid JS identifier without escaping.
40-- Test if a string is a valid JS identifier without escaping.
41--
41--
42identNeedsEscaping :: Text -> Bool
42identNeedsEscaping :: Text -> Bool
43โˆ’identNeedsEscaping s = s /= identToJs (Ident s) || T.null s
43+identNeedsEscaping s = s /= properToJs s || T.null s
44
44
45-- |
45-- |
46-- Attempts to find a human-readable name for a symbol, if none has been specified returns the
46-- Attempts to find a human-readable name for a symbol, if none has been specified returns the
src/Language/PureScript/Parser/Lexer.hs+19โˆ’2
@@ -248,11 +248,28 @@ parseToken = P.choice
248 symbolChar :: Lexer u Char
248 symbolChar :: Lexer u Char
249 symbolChar = P.satisfy isSymbolChar
249 symbolChar = P.satisfy isSymbolChar
250
250
251+ surrogates :: Char -> (Char, Char)
252+ surrogates c = (high, low)
253+ where
254+ (h, l) = divMod (fromEnum c - 0x10000) 0x400
255+ high = toEnum (h + 0xD800)
256+ low = toEnum (l + 0xDC00)
257+
258+ expandAstralCodePointToUTF16Surrogates :: Char -> [Char]
259+ expandAstralCodePointToUTF16Surrogates c | fromEnum c > 0xFFFF = [high, low]
260+ where (high, low) = surrogates c
261+ expandAstralCodePointToUTF16Surrogates c = [c]
262+
251 parseCharLiteral :: Lexer u Char
263 parseCharLiteral :: Lexer u Char
264+ parseCharLiteral = P.try $ do {
252โˆ’ parseCharLiteral = PT.charLiteral tokenParser
265+ c <- PT.charLiteral tokenParser;
266+ if fromEnum c > 0xFFFF
267+ then P.unexpected "astral code point in character literal; characters must be valid UTF-16 code units"
268+ else return c
269+ }
253
270
254 parseStringLiteral :: Lexer u Text
271 parseStringLiteral :: Lexer u Text
255โˆ’ parseStringLiteral = blockString <|> T.pack <$> PT.stringLiteral tokenParser
272+ parseStringLiteral = blockString <|> T.pack <$> concatMap expandAstralCodePointToUTF16Surrogates <$> PT.stringLiteral tokenParser
256 where
273 where
257 delimiter = P.try (P.string "\"\"\"")
274 delimiter = P.try (P.string "\"\"\"")
258 blockString = delimiter *> (T.pack <$> P.manyTill P.anyChar delimiter)
275 blockString = delimiter *> (T.pack <$> P.manyTill P.anyChar delimiter)
src/Language/PureScript/Pretty/JS.hs+4โˆ’6
@@ -59,7 +59,7 @@ literals = mkPattern' match'
59 ]
59 ]
60 where
60 where
61 objectPropertyToString :: (Emit gen) => Text -> gen
61 objectPropertyToString :: (Emit gen) => Text -> gen
62โˆ’ objectPropertyToString s | identNeedsEscaping s = emit $ T.pack $ show s
62+ objectPropertyToString s | identNeedsEscaping s = string s
63 | otherwise = emit s
63 | otherwise = emit s
64 match (JSBlock _ sts) = mconcat <$> sequence
64 match (JSBlock _ sts) = mconcat <$> sequence
65 [ return $ emit "{\n"
65 [ return $ emit "{\n"
@@ -162,11 +162,9 @@ string s = emit $ "\"" <> T.concatMap encodeChar s <> "\""
162 encodeChar '\r' = "\\r"
162 encodeChar '\r' = "\\r"
163 encodeChar '"' = "\\\""
163 encodeChar '"' = "\\\""
164 encodeChar '\\' = "\\\\"
164 encodeChar '\\' = "\\\\"
165+ -- PureScript strings are sequences of UTF-16 code units, so this case should never be hit.
166+ -- If it is somehow hit, though, output the designated Unicode replacement character U+FFFD.
165โˆ’ encodeChar c | fromEnum c > 0xFFFF = "\\u" <> showHex' highSurrogate ("\\u" ++ showHex lowSurrogate "")
167+ encodeChar c | fromEnum c > 0xFFFF = "\\uFFFD"
166โˆ’ where
167โˆ’ (h, l) = divMod (fromEnum c - 0x10000) 0x400
168โˆ’ highSurrogate = h + 0xD800
169โˆ’ lowSurrogate = l + 0xDC00
170 encodeChar c | fromEnum c > 0xFFF = "\\u" <> showHex' (fromEnum c) ""
168 encodeChar c | fromEnum c > 0xFFF = "\\u" <> showHex' (fromEnum c) ""
171 encodeChar c | fromEnum c > 0xFF = "\\u0" <> showHex' (fromEnum c) ""
169 encodeChar c | fromEnum c > 0xFF = "\\u0" <> showHex' (fromEnum c) ""
172 encodeChar c | fromEnum c < 0x10 = "\\x0" <> showHex' (fromEnum c) ""
170 encodeChar c | fromEnum c < 0x10 = "\\x0" <> showHex' (fromEnum c) ""