git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Tidying up: represent PureScript strings as sequence of Word16

Changes: * Expand tests for string edge-cases * Remove use of `show` while printing errors * Remove `codePoints` from the export list of Language.PureScript.PSString * Fix an issue with derived Generic instances where colliding Idents were being generated * Change CoreFn/ToJSON so that invalid JSON strings (i.e. invalid UTF-16) will not be generated, since relatively few JSON parsers can cope with it (e.g. aeson) * Various function renaming and rearranging to better match existing conventions inside the compiler. Unfortunately we are forced to break the CoreFn JSON format with this change, as there is no way of generating strings that reliably parse to the value we want if strings are allowed to include invalid UTF-16. The CoreFn JSON changes in the following ways: * String literals are now generated as arrays of integers, where each integer is between 0 and 0xFFFF and represents one UTF-16 code unit (were previously generated as JSON strings). * Record literals are now generated as an array of pairs (two-element arrays), where the first element is the key, generated as an array of code units just like string literals, and the second element is the value.

commit45478c23ccebf7ca56e7f83ace4efb4e48a7fd25 authorHarry Garrood <harry@garrood.me> dateTue 3 Jan 2017, 02:51 (10 years ago) parent773d778
22 files changed+271−132
examples/passing/RecordLabels.purs−5 examples/passing/RecordLabels/RecordLabels.purs−25 examples/passing/StringEdgeCases.purs+9 examples/passing/StringEdgeCases/Records.purs+66 examples/passing/StringEdgeCases/Symbols.purs+30 src/Language/PureScript/CodeGen/JS.hs+8−5 src/Language/PureScript/CodeGen/JS/AST.hs+1−1 src/Language/PureScript/CodeGen/JS/Optimizer/Common.hs+2−2 src/Language/PureScript/CodeGen/JS/Optimizer/Inliner.hs+3−4 src/Language/PureScript/CodeGen/JS/Optimizer/MagicDo.hs+5−5 src/Language/PureScript/CoreFn/ToJSON.hs+2−2 src/Language/PureScript/Docs/RenderedCode/Render.hs+5−4 src/Language/PureScript/Errors.hs+7−8 src/Language/PureScript/Label.hs+3−17 src/Language/PureScript/PSString.hs+83−28 src/Language/PureScript/Pretty.hs+1 src/Language/PureScript/Pretty/Common.hs+4−10 src/Language/PureScript/Pretty/JS.hs+8−5 src/Language/PureScript/Pretty/Types.hs+17−3 src/Language/PureScript/Pretty/Values.hs+4−4 src/Language/PureScript/Sugar/TypeClasses/Deriving.hs+12−3 src/Language/PureScript/Types.hs+1−1
examples/passing/RecordLabels.pursdeleted−5
@@ -1,5 +0,0 @@
1−module Main where
2−
3−import RecordLabels as RecordLabels
4−
5−main = RecordLabels.main
examples/passing/RecordLabels/RecordLabels.pursdeleted−25
@@ -1,25 +0,0 @@
1−module RecordLabels where
2−
3−import Prelude
4−import Data.Generic (class Generic, gShow)
5−import Control.Monad.Eff.Console (log)
6−import Test.Assert (assert')
7−
8−newtype AstralKeys = AstralKeys { "💡" :: Int, "💢" :: Int }
9−newtype LoneSurrogateKeys = LoneSurrogateKeys { "\xdf06" :: Int, "\xd834" :: Int }
10−
11−derive instance genericAstralKeys :: Generic AstralKeys
12−derive instance genericLoneSurrogateKeys :: Generic LoneSurrogateKeys
13−
14−loneSurrogateKeys =
15− gShow (LoneSurrogateKeys { "\xdf06": 0, "\xd834": 1 }) ==
16− """LoneSurrogateKeys { "\xdf06": 0, "\xd834": 1 }"""
17−
18−astralKeys =
19− gShow (AstralKeys { "💡": 0, "💢": 1 }) ==
20− """AstralKeys { "💡": 0, "💢": 1 }"""
21−
22−main = do
23− assert' "lone surrogate keys" loneSurrogateKeys
24− assert' "astral keys" astralKeys
25− log "Done"
examples/passing/StringEdgeCases.pursnew file+9
@@ -0,0 +1,9 @@
1+module Main where
2+
3+import Prelude
4+import Records as Records
5+import Symbols as Symbols
6+
7+main = do
8+ Records.main
9+ Symbols.main
examples/passing/StringEdgeCases/Records.pursnew file+66
@@ -0,0 +1,66 @@
1+module Records where
2+
3+import Prelude
4+import Data.Generic (class Generic, toSpine, GenericSpine(..))
5+import Control.Monad.Eff.Console (log)
6+import Test.Assert (assert')
7+
8+newtype AstralKeys = AstralKeys { "💡" :: Int, "💢" :: Int }
9+newtype LoneSurrogateKeys = LoneSurrogateKeys { "\xdf06" :: Int, "\xd834" :: Int }
10+
11+derive instance genericAstralKeys :: Generic AstralKeys
12+derive instance genericLoneSurrogateKeys :: Generic LoneSurrogateKeys
13+
14+spineOf :: forall a. Generic a => a -> Unit -> GenericSpine
15+spineOf x _ = toSpine x
16+
17+testLoneSurrogateKeys =
18+ let
19+ expected = 5
20+ actual = (_."\xd801" <<< helper) { "\xd800": 5 }
21+ in
22+ assert' ("lone surrogate keys: " <> show actual) (expected == actual)
23+
24+ where
25+ helper :: { "\xd800" :: Int } -> { "\xd801" :: Int }
26+ helper o =
27+ case o."\xd800" of
28+ x -> { "\xd801": x }
29+
30+testAstralKeys =
31+ let
32+ expected = 5
33+ actual = (_."💢" <<< helper) { "💡": 5 }
34+ in
35+ assert' ("astral keys: " <> show actual) (expected == actual)
36+
37+ where
38+ helper :: { "💡" :: Int } -> { "💢" :: Int }
39+ helper o =
40+ case o."💡" of
41+ x -> { "💢": x }
42+
43+testGenericLoneSurrogateKeys = do
44+ let expected = SProd "Records.LoneSurrogateKeys"
45+ [ \_ -> SRecord [ {recLabel: "\xd834", recValue: spineOf 1}
46+ , {recLabel: "\xdf06", recValue: spineOf 0}
47+ ]
48+ ]
49+ actual = toSpine (LoneSurrogateKeys { "\xdf06": 0, "\xd834": 1 })
50+ assert' ("generic lone surrogate keys: " <> show actual) (expected == actual)
51+
52+testGenericAstralKeys = do
53+ let expected = SProd "Records.AstralKeys"
54+ [ \_ -> SRecord [ {recLabel: "💡", recValue: spineOf 0}
55+ , {recLabel: "💢", recValue: spineOf 1}
56+ ]
57+ ]
58+ actual = toSpine (AstralKeys { "💡": 0, "💢": 1 })
59+ assert' ("generic astral keys: " <> show actual) (expected == actual)
60+
61+main = do
62+ testLoneSurrogateKeys
63+ testAstralKeys
64+ testGenericLoneSurrogateKeys
65+ testGenericAstralKeys
66+ log "Done"
examples/passing/StringEdgeCases/Symbols.pursnew file+30
@@ -0,0 +1,30 @@
1+-- This is similar to StringEscapes except we are performing the same tests
2+-- with Symbols (at the type level).
3+
4+module Symbols where
5+
6+import Prelude
7+import Control.Monad.Eff.Console (log)
8+import Type.Data.Symbol (SProxy(..), class AppendSymbol, appendSymbol, reflectSymbol)
9+import Test.Assert (assert')
10+
11+highS :: SProxy "\xd834"
12+highS = SProxy
13+
14+lowS :: SProxy "\xdf06"
15+lowS = SProxy
16+
17+loneSurrogates :: Boolean
18+loneSurrogates = reflectSymbol (appendSymbol highS lowS) == "\x1d306"
19+
20+outOfOrderSurrogates :: Boolean
21+outOfOrderSurrogates = reflectSymbol (appendSymbol lowS highS) == "\xdf06\xd834"
22+
23+notReplacing :: Boolean
24+notReplacing = reflectSymbol lowS /= "\xfffd"
25+
26+main = do
27+ assert' "lone surrogates may be combined into a surrogate pair" loneSurrogates
28+ assert' "lone surrogates may be combined out of order to remain lone surrogates" outOfOrderSurrogates
29+ assert' "lone surrogates are not replaced with the Unicode replacement character U+FFFD" notReplacing
30+ log "Done"
src/Language/PureScript/CodeGen/JS.hs+8−5
@@ -35,7 +35,7 @@ import Language.PureScript.Errors (ErrorMessageHint(..), SimpleErrorMessage(..),
3535 errorMessage, rethrowWithPosition, addHint)
3636import Language.PureScript.Names
3737import Language.PureScript.Options
38−import Language.PureScript.PSString (PSString, mkString, codePoints)
38+import Language.PureScript.PSString (PSString, mkString, decodeString)
3939import Language.PureScript.Traversals (sndM)
4040import qualified Language.PureScript.Constants as C
4141
@@ -183,8 +183,11 @@ moduleToJs (Module coms mn imps exps foreigns decls) foreign_ =
183183
184184 accessorString :: PSString -> JS -> JS
185185 accessorString prop =
186− let quoted = JSIndexer Nothing (JSStringLiteral Nothing prop) in
187− either (const quoted) (\t -> if identNeedsEscaping t then quoted else JSAccessor Nothing prop) $ codePoints prop
186+ case decodeString prop of
187+ Just s | not (identNeedsEscaping s) ->
188+ JSAccessor Nothing s
189+ _ ->
190+ JSIndexer Nothing (JSStringLiteral Nothing prop)
188191
189192 -- |
190193 -- Generate code in the simplified Javascript intermediate representation for a value or expression.
@@ -259,7 +262,7 @@ moduleToJs (Module coms mn imps exps foreigns decls) foreign_ =
259262 (JSUnary Nothing JSNew $ JSApp Nothing (JSVar Nothing (properToJs ctor)) []) ]
260263 valueToJs' (Constructor _ _ (ProperName ctor) fields) =
261264 let constructor =
262− let body = [ JSAssignment Nothing (JSAccessor Nothing (mkString $ identToJs f) (JSVar Nothing "this")) (var f) | f <- fields ]
265+ let body = [ JSAssignment Nothing (JSAccessor Nothing (identToJs f) (JSVar Nothing "this")) (var f) | f <- fields ]
263266 in JSFunction Nothing (Just (properToJs ctor)) (identToJs `map` fields) (JSBlock Nothing body)
264267 createFn =
265268 let body = JSUnary Nothing JSNew $ JSApp Nothing (JSVar Nothing (properToJs ctor)) (var `map` fields)
@@ -394,7 +397,7 @@ moduleToJs (Module coms mn imps exps foreigns decls) foreign_ =
394397 argVar <- freshName
395398 done'' <- go remain done'
396399 js <- binderToJs argVar done'' binder
397− return (JSVariableIntroduction Nothing argVar (Just (JSAccessor Nothing (mkString $ identToJs field) (JSVar Nothing varName))) : js)
400+ return (JSVariableIntroduction Nothing argVar (Just (JSAccessor Nothing (identToJs field) (JSVar Nothing varName))) : js)
398401 binderToJs' _ _ ConstructorBinder{} =
399402 internalError "binderToJs: Invalid ConstructorBinder in binderToJs"
400403 binderToJs' varName done (NamedBinder _ ident binder) = do
src/Language/PureScript/CodeGen/JS/AST.hs+1−1
@@ -161,7 +161,7 @@ data JS
161161 -- |
162162 -- An object property accessor expression
163163 --
164− | JSAccessor (Maybe SourceSpan) PSString JS
164+ | JSAccessor (Maybe SourceSpan) Text JS
165165 -- |
166166 -- A function introduction (optional name, arguments, body)
167167 --
src/Language/PureScript/CodeGen/JS/Optimizer/Common.hs+2−2
@@ -72,13 +72,13 @@ removeFromBlock _ js = js
7272
7373isFn :: (Text, Text) -> JS -> Bool
7474isFn (moduleName, fnName) (JSAccessor _ x (JSVar _ y)) =
75− x == mkString fnName && y == moduleName
75+ x == fnName && y == moduleName
7676isFn (moduleName, fnName) (JSIndexer _ (JSStringLiteral _ x) (JSVar _ y)) =
7777 x == mkString fnName && y == moduleName
7878isFn _ _ = False
7979
8080isDict :: (Text, Text) -> JS -> Bool
81−isDict (moduleName, dictName) (JSAccessor _ x (JSVar _ y)) = x == mkString dictName && y == moduleName
81+isDict (moduleName, dictName) (JSAccessor _ x (JSVar _ y)) = x == dictName && y == moduleName
8282isDict _ _ = False
8383
8484isDict' :: [(Text, Text)] -> JS -> Bool
src/Language/PureScript/CodeGen/JS/Optimizer/Inliner.hs+3−4
@@ -23,7 +23,6 @@ import qualified Data.Text as T
2323import Language.PureScript.CodeGen.JS.AST
2424import Language.PureScript.CodeGen.JS.Optimizer.Common
2525import qualified Language.PureScript.Constants as C
26−import Language.PureScript.PSString (mkString)
2726
2827-- TODO: Potential bug:
2928-- Shouldn't just inline this case: { var x = 0; x.toFixed(10); }
@@ -214,7 +213,7 @@ inlineCommonOperators = applyAll $
214213
215214 isNFn :: Text -> Int -> JS -> Bool
216215 isNFn prefix n (JSVar _ name) = name == (prefix <> T.pack (show n))
217− isNFn prefix n (JSAccessor _ name (JSVar _ dataFunctionUncurried)) | dataFunctionUncurried == C.dataFunctionUncurried = name == mkString (prefix <> T.pack (show n))
216+ isNFn prefix n (JSAccessor _ name (JSVar _ dataFunctionUncurried)) | dataFunctionUncurried == C.dataFunctionUncurried = name == (prefix <> T.pack (show n))
218217 isNFn _ _ _ = False
219218
220219 runFn :: Int -> JS -> JS
@@ -236,11 +235,11 @@ inlineCommonOperators = applyAll $
236235 convert other = other
237236
238237 isModFn :: (Text, Text) -> JS -> Bool
239− isModFn (m, op) (JSAccessor _ op' (JSVar _ m')) = m == m' && mkString op == op'
238+ isModFn (m, op) (JSAccessor _ op' (JSVar _ m')) = m == m' && op == op'
240239 isModFn _ _ = False
241240
242241 isModFnWithDict :: (Text, Text) -> JS -> Bool
243− isModFnWithDict (m, op) (JSApp _ (JSAccessor _ op' (JSVar _ m')) [JSVar _ _]) = m == m' && mkString op == op'
242+ isModFnWithDict (m, op) (JSApp _ (JSAccessor _ op' (JSVar _ m')) [JSVar _ _]) = m == m' && op == op'
244243 isModFnWithDict _ _ = False
245244
246245-- (f <<< g $ x) = f (g x)
src/Language/PureScript/CodeGen/JS/Optimizer/MagicDo.hs+5−5
@@ -68,7 +68,7 @@ magicDo' = everywhereOnJS undo . everywhereOnJSTopDown convert
6868 -- Check if an expression represents the polymorphic pure or return function
6969 isPurePoly = isFn (C.controlApplicative, C.pure')
7070 -- Check if an expression represents a function in the Eff module
71− isEffFunc name (JSAccessor _ name' (JSVar _ eff)) = eff == C.eff && mkString name == name'
71+ isEffFunc name (JSAccessor _ name' (JSVar _ eff)) = eff == C.eff && name == name'
7272 isEffFunc _ _ = False
7373
7474 -- Remove __do function applications which remain after desugaring
@@ -107,14 +107,14 @@ inlineST = everywhereOnJS convertBlock
107107 convert agg (JSApp s1 f [arg]) | isSTFunc C.newSTRef f =
108108 JSFunction s1 Nothing [] (JSBlock s1 [JSReturn s1 $ if agg then arg else JSObjectLiteral s1 [(mkString C.stRefValue, arg)]])
109109 convert agg (JSApp _ (JSApp s1 f [ref]) []) | isSTFunc C.readSTRef f =
110− if agg then ref else JSAccessor s1 (mkString C.stRefValue) ref
110+ if agg then ref else JSAccessor s1 C.stRefValue ref
111111 convert agg (JSApp _ (JSApp _ (JSApp s1 f [ref]) [arg]) []) | isSTFunc C.writeSTRef f =
112− if agg then JSAssignment s1 ref arg else JSAssignment s1 (JSAccessor s1 (mkString C.stRefValue) ref) arg
112+ if agg then JSAssignment s1 ref arg else JSAssignment s1 (JSAccessor s1 C.stRefValue ref) arg
113113 convert agg (JSApp _ (JSApp _ (JSApp s1 f [ref]) [func]) []) | isSTFunc C.modifySTRef f =
114− if agg then JSAssignment s1 ref (JSApp s1 func [ref]) else JSAssignment s1 (JSAccessor s1 (mkString C.stRefValue) ref) (JSApp s1 func [JSAccessor s1 (mkString C.stRefValue) ref])
114+ if agg then JSAssignment s1 ref (JSApp s1 func [ref]) else JSAssignment s1 (JSAccessor s1 C.stRefValue ref) (JSApp s1 func [JSAccessor s1 C.stRefValue ref])
115115 convert _ other = other
116116 -- Check if an expression represents a function in the ST module
117− isSTFunc name (JSAccessor _ name' (JSVar _ st)) = st == C.st && mkString name == name'
117+ isSTFunc name (JSAccessor _ name' (JSVar _ st)) = st == C.st && name == name'
118118 isSTFunc _ _ = False
119119 -- Find all ST Refs initialized in this block
120120 findSTRefsIn = everythingOnJS (++) isSTRef
src/Language/PureScript/CoreFn/ToJSON.hs+2−2
@@ -17,7 +17,7 @@ import qualified Data.Text as T
1717import Language.PureScript.AST.Literals
1818import Language.PureScript.CoreFn
1919import Language.PureScript.Names
20−import Language.PureScript.PSString (PSString, codePoints)
20+import Language.PureScript.PSString (PSString)
2121
2222literalToJSON :: (a -> Value) -> Literal a -> Value
2323literalToJSON _ (NumericLiteral (Left n)) = toJSON ("IntLiteral", n)
@@ -53,7 +53,7 @@ bindToJSON (NonRec _ n e) = object [ runIdent n .= exprToJSON e ]
5353bindToJSON (Rec bs) = object $ map (\((_, n), e) -> runIdent n .= exprToJSON e) bs
5454
5555recordToJSON :: (a -> Value) -> [(PSString, a)] -> Value
56−recordToJSON f = object . map (\(key, a) -> either T.pack id (codePoints key) .= f a)
56+recordToJSON f = toJSON . map (\(key, a) -> (toJSON key, f a))
5757
5858exprToJSON :: Expr a -> Value
5959exprToJSON (Var _ i) = toJSON ( "Var"
src/Language/PureScript/Docs/RenderedCode/Render.hs+5−4
@@ -26,8 +26,9 @@ import Language.PureScript.Environment
2626import Language.PureScript.Kinds
2727import Language.PureScript.Names
2828import Language.PureScript.Pretty.Kinds
29+import Language.PureScript.Pretty.Types
2930import Language.PureScript.Types
30−import Language.PureScript.Label (Label(..), renderPSLabel)
31+import Language.PureScript.Label (Label)
3132
3233typeLiterals :: Pattern () Type RenderedCode
3334typeLiterals = mkPattern match
@@ -86,7 +87,7 @@ renderHead = mintersperse (syntax "," <> sp) . map renderLabel
8687renderLabel :: (Label, Type) -> RenderedCode
8788renderLabel (label, ty) =
8889 mintersperse sp
89− [ syntax $ renderPSLabel label
90+ [ syntax $ prettyPrintLabel label
9091 , syntax "::"
9192 , renderType ty
9293 ]
@@ -126,9 +127,9 @@ explicitParens = mkPattern match
126127 match _ = Nothing
127128
128129matchTypeAtom :: Pattern () Type RenderedCode
129−matchTypeAtom = typeLiterals <+> fmap parens matchType
130+matchTypeAtom = typeLiterals <+> fmap parens_ matchType
130131 where
131− parens x = syntax "(" <> x <> syntax ")"
132+ parens_ x = syntax "(" <> x <> syntax ")"
132133
133134matchType :: Pattern () Type RenderedCode
134135matchType = buildPrettyPrinter operators matchTypeAtom
src/Language/PureScript/Errors.hs+7−8
@@ -32,7 +32,6 @@ import Language.PureScript.Names
3232import Language.PureScript.Pretty
3333import Language.PureScript.Traversals
3434import Language.PureScript.Types
35−import Language.PureScript.PSString (renderPSString)
3635import Language.PureScript.Label (Label(..))
3736import Language.PureScript.Pretty.Common (before, endWith)
3837import qualified Language.PureScript.Bundle as Bundle
@@ -671,8 +670,8 @@ prettyPrintSingleError (PPEOptions codeColor full level showDocs) e = flip evalS
671670 ]
672671 renderSimpleErrorMessage (CannotFindDerivingType nm) =
673672 line $ "Cannot derive a type class instance, because the type declaration for " <> markCode (runProperName nm) <> " could not be found."
674− renderSimpleErrorMessage (DuplicateLabel (Label l) expr) =
675− paras $ [ line $ "Label " <> T.pack (show l) <> " appears more than once in a row type." ]
673+ renderSimpleErrorMessage (DuplicateLabel l expr) =
674+ paras $ [ line $ "Label " <> prettyPrintLabel l <> " appears more than once in a row type." ]
676675 <> foldMap (\expr' -> [ line "Relevant expression: "
677676 , markCodeBox $ indent $ prettyPrintValue valueDepth expr'
678677 ]) expr
@@ -704,10 +703,10 @@ prettyPrintSingleError (PPEOptions codeColor full level showDocs) e = flip evalS
704703 , line "does not have type"
705704 , markCodeBox $ indent $ typeAsBox ty
706705 ]
707− renderSimpleErrorMessage (PropertyIsMissing (Label prop)) =
708− line $ "Type of expression lacks required label " <> T.pack (show prop) <> "."
709− renderSimpleErrorMessage (AdditionalProperty (Label prop)) =
710− line $ "Type of expression contains additional label " <> T.pack (show prop) <> "."
706+ renderSimpleErrorMessage (PropertyIsMissing prop) =
707+ line $ "Type of expression lacks required label " <> prettyPrintLabel prop <> "."
708+ renderSimpleErrorMessage (AdditionalProperty prop) =
709+ line $ "Type of expression contains additional label " <> prettyPrintLabel prop <> "."
711710 renderSimpleErrorMessage TypeSynonymInstance =
712711 line "Type class instances for type synonyms are disallowed."
713712 renderSimpleErrorMessage (OrphanInstance nm cnm ts) =
@@ -1264,7 +1263,7 @@ renderBox = unlines
12641263 whiteSpace = all isSpace
12651264
12661265toTypelevelString :: Type -> Maybe Box.Box
1267−toTypelevelString (TypeLevelString s) = Just $ Box.text $ T.unpack $ renderPSString s
1266+toTypelevelString (TypeLevelString s) = Just $ Box.text $ T.unpack $ prettyPrintString s
12681267toTypelevelString (TypeApp (TypeConstructor f) x)
12691268 | f == primName "TypeString" = Just $ typeAsBox x
12701269toTypelevelString (TypeApp (TypeApp (TypeConstructor f) x) ret)
src/Language/PureScript/Label.hs+3−17
@@ -1,30 +1,16 @@
11{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2−module Language.PureScript.Label (Label(..), renderPSLabel) where
2+module Language.PureScript.Label (Label(..)) where
33
44import Prelude.Compat hiding (lex)
55import Data.Monoid ()
66import Data.String (IsString(..))
7−import Data.Text (Text)
8−import Text.Parsec.Combinator (eof)
97import qualified Data.Aeson as A
108
11−import Language.PureScript.Parser.Common (runTokenParser)
12−import Language.PureScript.Parser.Lexer (TokenParser, lname, lex)
13−import Language.PureScript.PSString (PSString, codePoints, renderPSString)
9+import Language.PureScript.PSString (PSString)
1410
1511-- |
1612-- Labels are used as record keys and row entry names. Labels newtype PSString
1713-- because records are indexable by PureScript strings at runtime.
1814--
19−newtype Label = Label PSString
15+newtype Label = Label { runLabel :: PSString }
2016 deriving (Show, Eq, Ord, IsString, Monoid, A.ToJSON, A.FromJSON)
21−
22−renderPSLabel :: Label -> Text
23−renderPSLabel (Label s) =
24− let quoted = const $ renderPSString s in
25− either quoted (\t -> either quoted (const t) $ runParser lname t) $ codePoints s
26−
27−runParser :: TokenParser a -> Text -> Either String a
28−runParser p s = either (Left . show) Right $ do
29− ts <- lex "" s
30− runTokenParser "" (p <* eof) ts
src/Language/PureScript/PSString.hs+83−28
@@ -1,19 +1,35 @@
11{-# LANGUAGE GeneralizedNewtypeDeriving #-}
22{-# LANGUAGE OverloadedStrings #-}
3−module Language.PureScript.PSString (PSString, mkString, renderPSString, renderJSON, toUTF16CodeUnits, codePoints, containsLoneSurrogates) where
3+module Language.PureScript.PSString
4+ ( PSString
5+ , toUTF16CodeUnits
6+ , decodeString
7+ , decodeStringEither
8+ , prettyPrintString
9+ , prettyPrintStringJS
10+ , mkString
11+ ) where
412
513import Prelude.Compat
6−import Numeric (showHex)
14+import Control.Exception (try, evaluate)
15+import Data.Char (chr)
16+import Data.Bits (shiftR)
717import Data.List (unfoldr)
818import Data.Monoid ((<>))
919import Data.Scientific (toBoundedInteger)
1020import Data.String (IsString(..))
21+import Data.ByteString (ByteString)
22+import qualified Data.ByteString as BS
1123import Data.Text (Text)
12−import Data.Word (Word16)
24+import qualified Data.Text as T
25+import Data.Text.Encoding (decodeUtf16BE)
26+import Data.Text.Encoding.Error (UnicodeException)
27+import qualified Data.Vector as V
28+import Data.Word (Word16, Word8)
29+import Numeric (showHex)
30+import System.IO.Unsafe (unsafePerformIO)
1331import qualified Data.Aeson as A
1432import qualified Data.Aeson.Types as A
15−import qualified Data.Vector as V
16−import qualified Data.Text as T
1733
1834-- |
1935-- Strings in PureScript are sequences of UTF-16 code units, which do not
@@ -21,36 +37,71 @@ import qualified Data.Text as T
2137-- for a string to contain *lone surrogates,* i.e. characters in the range
2238-- U+D800 to U+DFFF which do not appear as a part of a surrogate pair.
2339--
40+-- The Show instance for PSString produces a string literal which would
41+-- represent the same data were it inserted into a PureScript source file.
2442--
25−newtype PSString = PSString [Word16]
43+newtype PSString = PSString { toUTF16CodeUnits :: [Word16] }
2644 deriving (Eq, Ord, Monoid)
2745
2846instance Show PSString where
29− show = either show show . codePoints
47+ show = show . codePoints
3048
31−renderPSString :: PSString -> Text
32−renderPSString = T.pack . either show show . codePoints
49+-- Decode a PSString to a String, representing any lone surrogates as the
50+-- reserved code point with that index. Warning: if there are any lone
51+-- surrogates, converting the result to Text via Data.Text.pack will result in
52+-- loss of information as those lone surrogates will be replaced with U+FFFD
53+-- REPLACEMENT CHARACTER. Because this function requires care to use correctly,
54+-- we do not export it.
55+codePoints :: PSString -> String
56+codePoints = map (either (chr . fromIntegral) id) . decodeStringEither
3357
34−-- NOTE: lone surrogates in the given PSString are represented in the resulting
35−-- String as the reserved code point with that index
36−codePoints :: PSString -> Either String Text
37−codePoints s = (if containsLoneSurrogates s then Left . unfoldr decode else Right . T.unfoldr decode) $ toUTF16CodeUnits s
58+-- |
59+-- Decode a PSString as UTF-16. Lone surrogates in the input are represented in
60+-- the output with the Left constructor; characters which were successfully
61+-- decoded are represented with the Right constructor.
62+--
63+decodeStringEither :: PSString -> [Either Word16 Char]
64+decodeStringEither = unfoldr decode . toUTF16CodeUnits
3865 where
39− decode :: [Word16] -> Maybe (Char, [Word16])
40− decode (h:l:rest) | isLead h && isTrail l = Just (unsurrogate h l, rest)
41− decode (c:rest) = Just (toChar c, rest)
66+ decode :: [Word16] -> Maybe (Either Word16 Char, [Word16])
67+ decode (h:l:rest) | isLead h && isTrail l = Just (Right (unsurrogate h l), rest)
68+ decode (c:rest) | isSurrogate c = Just (Left c, rest)
69+ decode (c:rest) = Just (Right (toChar c), rest)
4270 decode [] = Nothing
4371
4472 unsurrogate :: Word16 -> Word16 -> Char
4573 unsurrogate h l = toEnum ((toInt h - 0xD800) * 0x400 + (toInt l - 0xDC00) + 0x10000)
4674
47−containsLoneSurrogates :: PSString -> Bool
48−containsLoneSurrogates = or . unfoldr headIsLoneSurrogate . toUTF16CodeUnits
75+-- |
76+-- Pretty print a PSString, using Haskell/PureScript escape sequences.
77+-- This is identical to the Show instance except that we get a Text out instead
78+-- of a String.
79+--
80+prettyPrintString :: PSString -> Text
81+prettyPrintString = T.pack . show
82+
83+-- |
84+-- Attempt to decode a PSString as UTF-16 text. This will fail (returning
85+-- Nothing) if the argument contains lone surrogates.
86+--
87+decodeString :: PSString -> Maybe Text
88+decodeString = hush . decodeEither . BS.pack . concatMap unpair . toUTF16CodeUnits
4989 where
50− headIsLoneSurrogate :: [Word16] -> Maybe (Bool, [Word16])
51− headIsLoneSurrogate (h:l:rest) | isLead h && isTrail l = Just (False, rest)
52− headIsLoneSurrogate (c:rest) = Just (isLead c || isTrail c, rest)
53− headIsLoneSurrogate [] = Nothing
90+ unpair w = [highByte w, lowByte w]
91+
92+ lowByte :: Word16 -> Word8
93+ lowByte = fromIntegral
94+
95+ highByte :: Word16 -> Word8
96+ highByte = fromIntegral . (`shiftR` 8)
97+
98+ -- Based on a similar function from Data.Text.Encoding for utf8. This is a
99+ -- safe usage of unsafePerformIO because there are no side effects after
100+ -- handling any thrown UnicodeExceptions.
101+ decodeEither :: ByteString -> Either UnicodeException Text
102+ decodeEither = unsafePerformIO . try . evaluate . decodeUtf16BE
103+
104+ hush = either (const Nothing) Just
54105
55106instance IsString PSString where
56107 fromString a = PSString $ concatMap encodeUTF16 a
@@ -71,12 +122,16 @@ instance A.FromJSON PSString where
71122 parseJSON a = PSString <$> parseArrayOfCodeUnits a
72123 where
73124 parseArrayOfCodeUnits :: A.Value -> A.Parser [Word16]
74− parseArrayOfCodeUnits = A.withArray "array of UTF-16 code units" $ \b -> sequence (parseCodeUnit <$> V.toList b)
125+ parseArrayOfCodeUnits = A.withArray "array of UTF-16 code units" (traverse parseCodeUnit . V.toList)
75126 parseCodeUnit :: A.Value -> A.Parser Word16
76127 parseCodeUnit b = A.withScientific "two-byte non-negative integer" (maybe (A.typeMismatch "" b) return . toBoundedInteger) b
77128
78−renderJSON :: PSString -> Text
79−renderJSON s = "\"" <> foldMap encodeChar (toUTF16CodeUnits s) <> "\""
129+-- |
130+-- Pretty print a PSString, using JavaScript escape sequences. Intended for
131+-- use in compiled JS output.
132+--
133+prettyPrintStringJS :: PSString -> Text
134+prettyPrintStringJS s = "\"" <> foldMap encodeChar (toUTF16CodeUnits s) <> "\""
80135 where
81136 encodeChar :: Word16 -> Text
82137 encodeChar c | c > 0xFF = "\\u" <> hex 4 c
@@ -102,6 +157,9 @@ isLead h = h >= 0xD800 && h <= 0xDBFF
102157isTrail :: Word16 -> Bool
103158isTrail l = l >= 0xDC00 && l <= 0xDFFF
104159
160+isSurrogate :: Word16 -> Bool
161+isSurrogate c = isLead c || isTrail c
162+
105163toChar :: Word16 -> Char
106164toChar = toEnum . fromIntegral
107165
@@ -113,6 +171,3 @@ toInt = fromIntegral
113171
114172mkString :: Text -> PSString
115173mkString = fromString . T.unpack
116−
117−toUTF16CodeUnits :: PSString -> [Word16]
118−toUTF16CodeUnits (PSString s) = s
src/Language/PureScript/Pretty.hs+1
@@ -15,3 +15,4 @@ import Language.PureScript.Pretty.JS as P
1515import Language.PureScript.Pretty.Kinds as P
1616import Language.PureScript.Pretty.Types as P
1717import Language.PureScript.Pretty.Values as P
18+import Language.PureScript.PSString as P (prettyPrintString)
src/Language/PureScript/Pretty/Common.hs+4−10
@@ -15,8 +15,7 @@ import Data.Text (Text)
1515import qualified Data.Text as T
1616
1717import Language.PureScript.AST (SourcePos(..), SourceSpan(..))
18−import Language.PureScript.PSString (PSString)
19−import Language.PureScript.Label (Label(..), renderPSLabel)
18+import Language.PureScript.Parser.Lexer (isUnquotedKey, reservedPsNames)
2019
2120import Text.PrettyPrint.Boxes hiding ((<>))
2221import qualified Text.PrettyPrint.Boxes as Box
@@ -147,14 +146,9 @@ prettyPrintMany f xs = do
147146 indentString <- currentIndent
148147 return $ intercalate (emit "\n") $ map (mappend indentString) ss
149148
150−-- |
151−-- Prints an object key, escaping reserved names.
152−--
153−prettyPrintObjectKey :: PSString -> Text
154−prettyPrintObjectKey = prettyPrintLabel . Label
155−
156−prettyPrintLabel :: Label -> Text
157−prettyPrintLabel = renderPSLabel
149+objectKeyRequiresQuoting :: Text -> Bool
150+objectKeyRequiresQuoting s =
151+ s `elem` reservedPsNames || isUnquotedKey s
158152
159153-- | Place a box before another, vertically when the first box takes up multiple lines.
160154before :: Box -> Box -> Box
src/Language/PureScript/Pretty/JS.hs+8−5
@@ -25,7 +25,7 @@ import Language.PureScript.CodeGen.JS.Common
2525import Language.PureScript.Comments
2626import Language.PureScript.Crash
2727import Language.PureScript.Pretty.Common
28−import Language.PureScript.PSString (PSString, codePoints, renderJSON)
28+import Language.PureScript.PSString (PSString, decodeString, prettyPrintStringJS)
2929
3030-- TODO (Christoph): Get rid of T.unpack / pack
3131
@@ -37,7 +37,7 @@ literals = mkPattern' match'
3737
3838 match :: (Emit gen) => JS -> StateT PrinterState Maybe gen
3939 match (JSNumericLiteral _ n) = return $ emit $ T.pack $ either show show n
40− match (JSStringLiteral _ s) = return $ emit $ renderJSON s
40+ match (JSStringLiteral _ s) = return $ emit $ prettyPrintStringJS s
4141 match (JSBooleanLiteral _ True) = return $ emit "true"
4242 match (JSBooleanLiteral _ False) = return $ emit "false"
4343 match (JSArrayLiteral _ xs) = mconcat <$> sequence
@@ -59,8 +59,11 @@ literals = mkPattern' match'
5959 where
6060 objectPropertyToString :: (Emit gen) => PSString -> gen
6161 objectPropertyToString s =
62− let quoted = renderJSON s in
63− emit $ either (const quoted) (\t -> if identNeedsEscaping t then quoted else t) $ codePoints s
62+ emit $ case decodeString s of
63+ Just s' | not (identNeedsEscaping s') ->
64+ s'
65+ _ ->
66+ prettyPrintStringJS s
6467 match (JSBlock _ sts) = mconcat <$> sequence
6568 [ return $ emit "{\n"
6669 , withIndent $ prettyStatements sts
@@ -160,7 +163,7 @@ accessor :: (Emit gen) => Pattern PrinterState JS (gen, JS)
160163accessor = mkPattern match
161164 where
162165 -- WARN: if `prop` does not match the `IdentifierName` grammar, this will generate invalid code; see #2513
163− match (JSAccessor _ prop val) = either (const Nothing) (\t -> Just (emit t, val)) $ codePoints prop
166+ match (JSAccessor _ prop val) = Just (emit prop, val)
164167 match _ = Nothing
165168
166169indexer :: (Emit gen) => Pattern PrinterState JS (gen, JS)
src/Language/PureScript/Pretty/Types.hs+17−3
@@ -10,6 +10,8 @@ module Language.PureScript.Pretty.Types
1010 , prettyPrintTypeAtom
1111 , prettyPrintRowWith
1212 , prettyPrintRow
13+ , prettyPrintLabel
14+ , prettyPrintObjectKey
1315 ) where
1416
1517import Prelude.Compat
@@ -18,6 +20,7 @@ import Control.Arrow ((<+>))
1820import Control.PatternArrows as PA
1921
2022import Data.Maybe (fromMaybe)
23+import Data.Text (Text)
2124import qualified Data.Text as T
2225
2326import Language.PureScript.Crash
@@ -27,8 +30,8 @@ import Language.PureScript.Names
2730import Language.PureScript.Pretty.Common
2831import Language.PureScript.Pretty.Kinds
2932import Language.PureScript.Types
30−import Language.PureScript.PSString (renderPSString)
31−import Language.PureScript.Label (Label)
33+import Language.PureScript.PSString (PSString, prettyPrintString, decodeString)
34+import Language.PureScript.Label (Label(..))
3235
3336import Text.PrettyPrint.Boxes hiding ((<+>))
3437
@@ -118,7 +121,7 @@ matchTypeAtom suggesting =
118121 typeLiterals = mkPattern match where
119122 match TypeWildcard{} = Just $ text "_"
120123 match (TypeVar var) = Just $ text $ T.unpack var
121− match (TypeLevelString s) = Just . text $ T.unpack $ renderPSString s
124+ match (TypeLevelString s) = Just $ text $ T.unpack $ prettyPrintString s
122125 match (PrettyPrintObject row) = Just $ prettyPrintRowWith '{' '}' row
123126 match (TypeConstructor ctor) = Just $ text $ T.unpack $ runProperName $ disqualify ctor
124127 match (TUnknown u)
@@ -188,3 +191,14 @@ prettyPrintType = render . typeAsBoxImpl False
188191-- | Generate a pretty-printed string representing a suggested 'Type'
189192prettyPrintSuggestedType :: Type -> String
190193prettyPrintSuggestedType = render . typeAsBoxImpl True
194+
195+prettyPrintLabel :: Label -> Text
196+prettyPrintLabel (Label s) =
197+ case decodeString s of
198+ Just s' | not (objectKeyRequiresQuoting s') ->
199+ s'
200+ _ ->
201+ prettyPrintString s
202+
203+prettyPrintObjectKey :: PSString -> Text
204+prettyPrintObjectKey = prettyPrintLabel . Label
src/Language/PureScript/Pretty/Values.hs+4−4
@@ -20,9 +20,9 @@ import Language.PureScript.AST
2020import Language.PureScript.Crash
2121import Language.PureScript.Names
2222import Language.PureScript.Pretty.Common
23−import Language.PureScript.Pretty.Types (typeAsBox, typeAtomAsBox)
23+import Language.PureScript.Pretty.Types (typeAsBox, typeAtomAsBox, prettyPrintObjectKey)
2424import Language.PureScript.Types (Constraint(..))
25−import Language.PureScript.PSString (PSString, renderPSString)
25+import Language.PureScript.PSString (PSString, prettyPrintString)
2626
2727import Text.PrettyPrint.Boxes
2828
@@ -106,7 +106,7 @@ prettyPrintValueAtom d expr = (text "(" <> prettyPrintValue d expr) `before` tex
106106
107107prettyPrintLiteralValue :: Int -> Literal Expr -> Box
108108prettyPrintLiteralValue _ (NumericLiteral n) = text $ either show show n
109−prettyPrintLiteralValue _ (StringLiteral s) = text $ T.unpack $ renderPSString s
109+prettyPrintLiteralValue _ (StringLiteral s) = text $ T.unpack $ prettyPrintString s
110110prettyPrintLiteralValue _ (CharLiteral c) = text $ show c
111111prettyPrintLiteralValue _ (BooleanLiteral True) = text "true"
112112prettyPrintLiteralValue _ (BooleanLiteral False) = text "false"
@@ -170,7 +170,7 @@ prettyPrintBinderAtom (BinaryNoParensBinder op b1 b2) =
170170prettyPrintBinderAtom (ParensInBinder b) = parensT (prettyPrintBinder b)
171171
172172prettyPrintLiteralBinder :: Literal Binder -> Text
173−prettyPrintLiteralBinder (StringLiteral str) = renderPSString str
173+prettyPrintLiteralBinder (StringLiteral str) = prettyPrintString str
174174prettyPrintLiteralBinder (CharLiteral c) = T.pack (show c)
175175prettyPrintLiteralBinder (NumericLiteral num) = either (T.pack . show) (T.pack . show) num
176176prettyPrintLiteralBinder (BooleanLiteral True) = "true"
src/Language/PureScript/Sugar/TypeClasses/Deriving.hs+12−3
@@ -11,6 +11,7 @@ import Control.Monad.Error.Class (MonadError(..))
1111import Control.Monad.Supply.Class (MonadSupply)
1212import Data.List (foldl', find, sortBy, unzip5)
1313import qualified Data.Map as M
14+import Data.Monoid ((<>))
1415import Data.Maybe (fromMaybe, mapMaybe)
1516import Data.Ord (comparing)
1617import Data.Text (Text)
@@ -24,7 +25,7 @@ import Language.PureScript.Externs
2425import Language.PureScript.Kinds
2526import Language.PureScript.Names
2627import Language.PureScript.Label (Label(..))
27−import Language.PureScript.PSString (mkString, codePoints)
28+import Language.PureScript.PSString (mkString, decodeStringEither)
2829import Language.PureScript.Types
2930import Language.PureScript.TypeChecker (checkNewtype)
3031import Language.PureScript.TypeChecker.Synonyms (SynonymMap, replaceAllTypeSynonymsM)
@@ -433,7 +434,7 @@ deriveGenericRep mn syns ds tyConNm tyConArgs repTy = do
433434 makeArg :: Type -> m (Type, Binder, Expr, Binder, Expr)
434435 makeArg arg | Just rec <- objectType arg = do
435436 let fields = decomposeRec rec
436− fieldNames <- traverse freshIdent (map ((\(Label s) -> either T.pack id $ codePoints s) . fst) fields)
437+ fieldNames <- traverse freshIdent (map (runIdent . labelToIdent . fst) fields)
437438 pure ( TypeApp (TypeConstructor record)
438439 (foldr1 (\f -> TypeApp (TypeApp (TypeConstructor productName) f))
439440 (map (\((Label name), ty) ->
@@ -736,8 +737,16 @@ mkVarMn mn = Var . Qualified mn
736737mkVar :: Ident -> Expr
737738mkVar = mkVarMn Nothing
738739
740+-- This function may seem a little obtuse, but it's only this way to ensure
741+-- that it is injective. Injectivity is important here; without it, we can end
742+-- up with accidental variable shadowing in the generated code.
739743labelToIdent :: Label -> Ident
740−labelToIdent (Label l) = Ident $ either T.pack id $ codePoints l
744+labelToIdent =
745+ Ident . foldMap (either loneSurrogate char) . decodeStringEither . runLabel
746+ where
747+ char '_' = "__"
748+ char c = T.singleton c
749+ loneSurrogate x = "_" <> T.pack (show x) <> "_"
741750
742751objectType :: Type -> Maybe Type
743752objectType (TypeApp (TypeConstructor (Qualified (Just (ModuleName [ProperName "Prim"])) (ProperName "Record"))) rec) = Just rec
src/Language/PureScript/Types.hs+1−1
@@ -2,7 +2,7 @@
22{-# LANGUAGE TemplateHaskell #-}
33
44-- |
5−-- Data types for PureScript types and compile-time representation of PureScript terms
5+-- Data types for types
66--
77module Language.PureScript.Types where
88