on master help sign in
Simplify escape characters

Escape characters in String and Char are now limited to the following set: \n \t \” \’ \\ \u{0041} I also improved the error message when folks use single quotes instead of double quotes for strings. It now parses the entire token, so the error message can underline the whole thing.

commit5fb0d04a106ab6318b4fe0f8d3270521e9b25034 authorEvan Czaplicki <evancz@users.noreply.github.com> dateThu 7 Sep 2017, 11:51 (9 years ago) parentab16fc8
3 files changed+353−170
src/Parse/Primitives.hs+255−158 src/Reporting/Error/Syntax.hs+97−11 src/Reporting/Helpers.hs+1−1
src/Parse/Primitives.hs+255−158
@@ -23,6 +23,8 @@ import Prelude hiding (length)
2323import qualified Control.Applicative as Applicative ( Applicative(..), Alternative(..) )
2424import Control.Monad
2525import qualified Data.ByteString as BS
26+import qualified Data.ByteString.Builder as BS (toLazyByteString, word16HexFixed)
27+import qualified Data.ByteString.Lazy as BS (toStrict)
2628import qualified Data.Char as Char
2729import Data.Monoid ((<>))
2830import qualified Data.Set as Set
@@ -33,7 +35,7 @@ import qualified Data.Text.Encoding as Text
3335import qualified Data.Text.Internal as Text
3436import qualified Data.Text.Unsafe as Text
3537import qualified Data.Text.Lazy as LT
36−import qualified Data.Text.Lazy.Builder as LB
38+import qualified Data.Text.Lazy.Builder as B
3739import Data.Text.Internal.Encoding.Utf16 (chr2)
3840import Data.Text.Internal.Unsafe.Char (unsafeChr)
3941import GHC.Word (Word8, Word16)
@@ -107,19 +109,24 @@ runAt sRow sCol parser (Text.Text array offset length) =
107109 Err (ParseError row col problem) ->
108110 let
109111 pos = R.Position row col
110− region = R.Region pos pos
111112 mkError overallRegion subRegion =
112113 Left (A.A overallRegion (E.Parse subRegion problem))
113114 in
114115 case problem of
116+ BadChar endCol ->
117+ mkError (R.Region pos (R.Position row endCol)) Nothing
118+
119+ BadEscape width _ ->
120+ mkError (R.Region pos (R.Position row (col + width))) Nothing
121+
115122 BadOp _ ((_, start) : _) ->
116− mkError (R.Region start pos) (Just region)
123+ mkError (R.Region start pos) (Just (R.Region pos pos))
117124
118125 Theories ((_, start) : _) _ ->
119− mkError (R.Region start pos) (Just region)
126+ mkError (R.Region start pos) (Just (R.Region pos pos))
120127
121128 _ ->
122− mkError region Nothing
129+ mkError (R.Region pos pos) Nothing
123130
124131
125132data Result a
@@ -323,7 +330,7 @@ moveCursor array offset length row col =
323330 if word == 0x000A {- \n -} then
324331 moveCursor array (offset + 1) (length - 1) (row + 1) 1
325332
326− else if word < 0xD800 || word > 0xDBFF then
333+ else if word < 0xD800 || 0xDBFF < word then
327334 moveCursor array (offset + 1) (length - 1) row (col + 1)
328335
329336 else
@@ -447,7 +454,7 @@ peek array offset =
447454 let
448455 !word = Text.unsafeIndex array offset
449456 in
450− if word < 0xD800 || word > 0xDBFF then
457+ if word < 0xD800 || 0xDBFF < word then
451458 Text.Iter (unsafeChr word) 1
452459 else
453460 Text.Iter (chr2 word (Text.unsafeIndex array (offset + 1))) 2
@@ -459,7 +466,7 @@ peekChar array offset =
459466 let
460467 !word = Text.unsafeIndex array offset
461468 in
462− if word < 0xD800 || word > 0xDBFF then
469+ if word < 0xD800 || 0xDBFF < word then
463470 unsafeChr word
464471 else
465472 chr2 word (Text.unsafeIndex array (offset + 1))
@@ -661,7 +668,7 @@ eatLineCommentHelp array offset length row col =
661668 if word == 0x000A {- \n -} then
662669 eatSpaces array (offset + 1) (length - 1) (row + 1) 1
663670
664− else if word < 0xD800 || word > 0xDBFF then
671+ else if word < 0xD800 || 0xDBFF < word then
665672 eatLineCommentHelp array (offset + 1) (length - 1) row (col + 1)
666673
667674 else
@@ -715,7 +722,7 @@ eatMultiCommentHelp array offset length row col openComments =
715722
716723 eatMultiCommentHelp array (offset + 2) (length - 2) row (col + 2) (openComments + 1)
717724
718− else if word < 0xD800 || word > 0xDBFF then
725+ else if word < 0xD800 || 0xDBFF < word then
719726
720727 eatMultiCommentHelp array (offset + 1) (length - 1) row (col + 1) openComments
721728
@@ -771,7 +778,7 @@ eatDocs array offset length row col =
771778 if word == 0x000A {- \n -} then
772779 eatDocs array (offset + 1) (length - 1) (row + 1) 1
773780
774− else if word < 0xD800 || word > 0xDBFF then
781+ else if word < 0xD800 || 0xDBFF < word then
775782 eatDocs array (offset + 1) (length - 1) row (col + 1)
776783
777784 else
@@ -819,7 +826,7 @@ string =
819826
820827 Right ( newOffset, newLength, newCol, builder ) ->
821828 cok
822− (LT.toStrict (LB.toLazyText builder))
829+ (LT.toStrict (B.toLazyText builder))
823830 (State array newOffset newLength indent row newCol ctx)
824831 noError
825832
@@ -834,7 +841,7 @@ isQuote array offset =
834841-- SINGLE STRINGS
835842
836843
837−singleString :: Text.Array -> Int -> Int -> Int -> Int -> Int -> LB.Builder -> Either ParseError ( Int, Int, Int, LB.Builder )
844+singleString :: Text.Array -> Int -> Int -> Int -> Int -> Int -> B.Builder -> Either ParseError ( Int, Int, Int, B.Builder )
838845singleString array offset length row col initialOffset builder =
839846 if length == 0 then
840847 Left (ParseError row col EndOfFile_String)
@@ -844,75 +851,55 @@ singleString array offset length row col initialOffset builder =
844851 !word = Text.unsafeIndex array offset
845852 in
846853 if word == 0x0022 {- " -} then
847− let
848− finalBuilder =
849− mappend builder $ LB.fromText $
850− Text.Text array initialOffset (offset - initialOffset)
851− in
852− Right ( offset + 1, length - 1, col + 1, finalBuilder )
854+ Right
855+ ( offset + 1
856+ , length - 1
857+ , col + 1
858+ , builder <> B.fromText (copyText array initialOffset (offset - initialOffset))
859+ )
853860
854861 else if word == 0x000A {- \n -} then
855862 Left (ParseError row col NewLineInString)
856863
857864 else if word == 0x0027 {- ' -} then
858−
859− let
860− !newOffset = offset + 1
861− chunk = Text.Text array initialOffset (offset - initialOffset)
862− newBuilder = builder <> LB.fromText chunk <> LB.fromText "\\'"
863− in
864− singleString array newOffset (length - 1) row (col + 1) newOffset newBuilder
865+ singleStringHelp array offset length row col initialOffset builder 1 "\\'"
865866
866867 else if word == 0x005C {- \ -} then
867− case eatEscape array (offset + 1) (length - 1) row (col + 1) EndOfFile_String of
868− Left err ->
869− Left err
868+ case eatEscape array (offset + 1) (length - 1) of
869+ EscapeNormal ->
870+ singleString array (offset + 2) (length - 2) row (col + 2) initialOffset builder
870871
871− Right size ->
872− singleString array (offset + size) (length - size) row (col + size) initialOffset builder
872+ EscapeUnicode delta code ->
873+ singleStringHelp array offset length row col initialOffset builder delta code
873874
874− else if word < 0xD800 || word > 0xDBFF then
875+ EscapeProblem newOffset problem ->
876+ Left (ParseError row col (BadEscape (newOffset - offset) problem))
877+
878+ EscapeEndOfFile ->
879+ Left (ParseError row (col + 1) EndOfFile_String)
880+
881+ else if word < 0xD800 || 0xDBFF < word then
875882 singleString array (offset + 1) (length - 1) row (col + 1) initialOffset builder
876883
877884 else
878885 singleString array (offset + 2) (length - 2) row (col + 1) initialOffset builder
879886
880887
881−eatEscape :: Text.Array -> Int -> Int -> Int -> Int -> Problem -> Either ParseError Int
882−eatEscape array offset length row col problem =
883− if length == 0 then
884− Left (ParseError row col problem)
885−
886− else
887− case Text.unsafeIndex array offset of
888− 0x0061 {- a -} -> Right 2
889− 0x0062 {- b -} -> Right 2
890− 0x0066 {- f -} -> Right 2
891− 0x006E {- n -} -> Right 2
892− 0x0072 {- r -} -> Right 2
893− 0x0074 {- t -} -> Right 2
894− 0x0076 {- v -} -> Right 2
895− 0x0022 {- " -} -> Right 2
896− 0x005C {- \ -} -> Right 2
897− 0x0027 {- ' -} -> Right 2
898− 0x0075 {- u -} | length >= 5 && fourHex array offset -> Right 6
899− _ ->
900− Left (ParseError row col BadEscape)
901−
902−
903−fourHex :: Text.Array -> Int -> Bool
904−fourHex array offset =
905− isHex (Text.unsafeIndex array (offset + 1))
906− && isHex (Text.unsafeIndex array (offset + 2))
907− && isHex (Text.unsafeIndex array (offset + 3))
908− && isHex (Text.unsafeIndex array (offset + 4))
888+singleStringHelp :: Text.Array -> Int -> Int -> Int -> Int -> Int -> B.Builder -> Int -> Text.Text -> Either ParseError ( Int, Int, Int, B.Builder )
889+singleStringHelp array offset length row col initialOffset builder delta escape =
890+ let
891+ chunk = copyText array initialOffset (offset - initialOffset)
892+ newBuilder = builder <> B.fromText chunk <> B.fromText escape
893+ !newOffset = offset + delta
894+ in
895+ singleString array newOffset (length - delta) row (col + delta) newOffset newBuilder
909896
910897
911898
912899-- MULTI STRINGS
913900
914901
915−multiString :: Text.Array -> Int -> Int -> Int -> Int -> Int -> LB.Builder -> Either ParseError ( Int, Int, Int, LB.Builder )
902+multiString :: Text.Array -> Int -> Int -> Int -> Int -> Int -> B.Builder -> Either ParseError ( Int, Int, Int, B.Builder )
916903multiString array offset length row col initialOffset builder =
917904 if length < 3 then
918905 Left (ParseError row col EndOfFile_MultiString)
@@ -922,48 +909,51 @@ multiString array offset length row col initialOffset builder =
922909 !word = Text.unsafeIndex array offset
923910 in
924911 if word == 0x0022 {- " -} && isQuote array (offset + 1) && isQuote array (offset + 2) then
925−
926− let
927− finalBuilder =
928− mappend builder $ LB.fromText $
929− Text.Text array initialOffset (offset - initialOffset)
930− in
931− Right ( offset + 3, length - 3, col + 3, finalBuilder )
912+ Right
913+ ( offset + 3
914+ , length - 3
915+ , col + 3
916+ , builder <> B.fromText (copyText array initialOffset (offset - initialOffset))
917+ )
932918
933919 else if word == 0x0027 {- ' -} then
934−
935− let
936− !newOffset = offset + 1
937− chunk = Text.Text array initialOffset (offset - initialOffset)
938− newBuilder = builder <> LB.fromText chunk <> LB.fromText "\\'"
939− in
940− multiString array newOffset (length - 1) row (col + 1) newOffset newBuilder
920+ multiStringHelp array offset length row col initialOffset builder 1 "\\'"
941921
942922 else if word == 0x000A {- \n -} then
943−
944− let
945− !newOffset = offset + 1
946− chunk = Text.Text array initialOffset (offset - initialOffset)
947− newBuilder = builder <> LB.fromText chunk <> LB.fromText "\\n"
948− in
949− multiString array newOffset (length - 1) (row + 1) 1 newOffset newBuilder
923+ multiStringHelp array offset length row col initialOffset builder 1 "\\n"
950924
951925 else if word == 0x005C {- \ -} then
952926
953− case eatEscape array (offset + 1) (length - 1) row (col + 1) EndOfFile_MultiString of
954− Left err ->
955− Left err
927+ case eatEscape array (offset + 1) (length - 1) of
928+ EscapeNormal ->
929+ multiString array (offset + 2) (length - 2) row (col + 2) initialOffset builder
930+
931+ EscapeUnicode delta code ->
932+ multiStringHelp array offset length row col initialOffset builder delta code
956933
957− Right size ->
958− multiString array (offset + size) (length - size) row (col + size) initialOffset builder
934+ EscapeProblem newOffset problem ->
935+ Left (ParseError row col (BadEscape (newOffset - offset) problem))
959936
960− else if word < 0xD800 || word > 0xDBFF then
937+ EscapeEndOfFile ->
938+ Left (ParseError row (col + 1) EndOfFile_MultiString)
939+
940+ else if word < 0xD800 || 0xDBFF < word then
961941 multiString array (offset + 1) (length - 1) row (col + 1) initialOffset builder
962942
963943 else
964944 multiString array (offset + 2) (length - 2) row (col + 1) initialOffset builder
965945
966946
947+multiStringHelp :: Text.Array -> Int -> Int -> Int -> Int -> Int -> B.Builder -> Int -> Text.Text -> Either ParseError ( Int, Int, Int, B.Builder )
948+multiStringHelp array offset length row col initialOffset builder delta escape =
949+ let
950+ chunk = copyText array initialOffset (offset - initialOffset)
951+ newBuilder = builder <> B.fromText chunk <> B.fromText escape
952+ !newOffset = offset + delta
953+ in
954+ multiString array newOffset (length - delta) row (col + delta) newOffset newBuilder
955+
956+
967957
968958-- CHARACTER
969959
@@ -978,49 +968,136 @@ character =
978968 if Text.unsafeIndex array offset /= 0x0027 {- ' -} then
979969 eerr noError
980970
981− else if length < 3 then
982− cerr (ParseError row col BadChar)
983−
984971 else
985− case characterHelp array (offset + 1) (length - 1) row (col + 1) of
972+ case chompChar array (offset + 1) (length - 1) row (col + 1) 0 "" of
986973 Left err ->
987974 cerr err
988975
989− Right (endCol, size) ->
990− let
991− !newOffset = offset + size + 1
992− !newLength = length - size - 1
993− in
994− if newLength == 0 || Text.unsafeIndex array newOffset /= 0x0027 {- ' -} then
995− cerr (ParseError row col BadChar)
976+ Right ( newOffset, newLength, newCol, numChars, mostRecentChar ) ->
977+ if numChars /= 1 then
978+ cerr (ParseError row col (BadChar newCol))
979+ else
980+ cok mostRecentChar
981+ (State array newOffset newLength indent row newCol ctx)
982+ noError
996983
997− else
998− cok
999− (copyText array (offset + 1) size)
1000− (State array (newOffset + 1) (newLength - 1) indent row endCol ctx)
1001− noError
984+chompChar :: Text.Array -> Int -> Int -> Int -> Int -> Int -> Text.Text -> Either ParseError ( Int, Int, Int, Int, Text.Text )
985+chompChar array offset length row col numChars mostRecentChar =
986+ if length == 0 then
987+ Left (ParseError row col EndOfFile_Char)
1002988
989+ else
990+ let
991+ !word = Text.unsafeIndex array offset
992+ in
993+ if word == 0x0027 {- ' -} then
994+ Right
995+ ( offset + 1
996+ , length - 1
997+ , col + 1
998+ , numChars
999+ , mostRecentChar
1000+ )
10031001
1004−characterHelp :: Text.Array -> Int -> Int -> Int -> Int -> Either ParseError ( Int, Int )
1005−characterHelp array offset length row col =
1006− let
1007− !word = Text.unsafeIndex array offset
1008− in
1009− if word == 0x0027 {- ' -} || word == 0x000A {- \n -} then
1010− Left (ParseError row col BadChar)
1002+ else if word == 0x000A {- \n -} then
1003+ Left (ParseError row col NewLineInChar)
1004+
1005+ else if word == 0x0022 {- " -} then
1006+ chompChar array (offset + 1) (length - 1) row (col + 1) (numChars + 1) "\\\""
10111007
1012− else if word == 0x005C {- \ -} then
1013− do n <- eatEscape array (offset + 1) (length - 1) row (col + 1) BadChar
1014− return (col + n + 2, n)
1008+ else if word == 0x005C {- \ -} then
1009+ case eatEscape array (offset + 1) (length - 1) of
1010+ EscapeNormal ->
1011+ chompChar array (offset + 2) (length - 2) row (col + 2) (numChars + 1) (copyText array offset 2)
1012+
1013+ EscapeUnicode delta code ->
1014+ chompChar array (offset + delta) (length - delta) row (col + delta) (numChars + 1) code
1015+
1016+ EscapeProblem newOffset problem ->
1017+ Left (ParseError row col (BadEscape (newOffset - offset) problem))
1018+
1019+ EscapeEndOfFile ->
1020+ Left (ParseError row col EndOfFile_Char)
1021+
1022+ else if word < 0xD800 || 0xDBFF < word then
1023+ chompChar array (offset + 1) (length - 1) row (col + 1) (numChars + 1) (copyText array offset 1)
1024+
1025+ else
1026+ chompChar array (offset + 2) (length - 2) row (col + 1) (numChars + 1) (copyText array offset 2)
10151027
1016− else if word < 0xD800 || word > 0xDBFF then
1017− Right (col + 3, 1)
10181028
1019− else if length > 2 then
1020− Right (col + 3, 2)
1029+
1030+-- ESCAPE CHARACTERS
1031+
1032+
1033+data Escape
1034+ = EscapeNormal
1035+ | EscapeUnicode !Int Text.Text
1036+ | EscapeProblem !Int E.EscapeProblem
1037+ | EscapeEndOfFile
1038+
1039+
1040+eatEscape :: Text.Array -> Int -> Int -> Escape
1041+eatEscape array offset length =
1042+ if length == 0 then
1043+ EscapeEndOfFile
1044+
1045+ else
1046+ case Text.unsafeIndex array offset of
1047+ 0x006E {- n -} -> EscapeNormal
1048+ 0x0074 {- t -} -> EscapeNormal
1049+ 0x0022 {- " -} -> EscapeNormal
1050+ 0x0027 {- ' -} -> EscapeNormal
1051+ 0x005C {- \ -} -> EscapeNormal
1052+ 0x0075 {- u -} -> eatUnicode array (offset + 1) (length - 1)
1053+ _ -> EscapeProblem offset E.UnknownEscape
1054+
1055+
1056+eatUnicode :: Text.Array -> Int -> Int -> Escape
1057+eatUnicode array offset length =
1058+ if length == 0 || Text.unsafeIndex array offset /= 0x007B {- { -} then
1059+ EscapeProblem offset E.UnicodeSyntax
1060+ else
1061+ let
1062+ !digitOffset =
1063+ offset + 1
1064+
1065+ (# newOffset, newLength, code #) =
1066+ chompHex array digitOffset (length - 1)
1067+
1068+ !numDigits =
1069+ newOffset - digitOffset
1070+ in
1071+ if newLength == 0 || Text.unsafeIndex array newOffset /= 0x007D {- } -} then
1072+ EscapeProblem newOffset E.UnicodeSyntax
1073+
1074+ else if code < 0 || 0x10FFFF < code then
1075+ EscapeProblem (newOffset + 1) E.UnicodeRange
1076+
1077+ else if numDigits < 4 || 6 < numDigits then
1078+ EscapeProblem (newOffset + 1) $
1079+ E.UnicodeLength numDigits (copyText array digitOffset numDigits)
10211080
10221081 else
1023− Left (ParseError row col BadChar)
1082+ EscapeUnicode (numDigits + 4) (codePointToBuilder code)
1083+
1084+
1085+{-# INLINE codePointToBuilder #-}
1086+codePointToBuilder :: Int -> Text.Text
1087+codePointToBuilder code =
1088+ if code < 0xFFFF then
1089+ "\\u" <> toHex code
1090+
1091+ else
1092+ "\\u" <> toHex ((code `div` 0x400) + 0xD800)
1093+ <>
1094+ "\\u" <> toHex ((code `mod` 0x400) + 0xDC00)
1095+
1096+
1097+toHex :: Int -> Text.Text
1098+toHex word16 =
1099+ Text.decodeUtf8 $ BS.toStrict $ BS.toLazyByteString $
1100+ BS.word16HexFixed (fromIntegral word16)
10241101
10251102
10261103
@@ -1198,7 +1275,7 @@ chompZero array startOffset offset length =
11981275 !word = Text.unsafeIndex array offset
11991276 in
12001277 if word == 0x0078 {- x -} then
1201− chompHex array startOffset (offset + 1) (length - 1)
1278+ chompHexNumber array (offset + 1) (length - 1)
12021279
12031280 else if word == 0x002E {- . -} then
12041281 chompFraction array startOffset (offset + 1) (length - 1)
@@ -1210,28 +1287,16 @@ chompZero array startOffset offset length =
12101287 Right ( offset, length, L.IntNum 0 )
12111288
12121289
1213−chompHex :: Text.Array -> Int -> Int -> Int -> Either (Int, Problem) (Int, Int, L.Literal)
1214−chompHex array startOffset offset length =
1215− if length == 0 then
1216− Left (offset, BadNumberHex)
1217−
1218− else if isHex (Text.unsafeIndex array offset) then
1219− chompHexHelp array startOffset (offset + 1) (length - 1)
1220−
1221− else
1222− Left (offset, BadNumberHex)
1223−
1224−
1225−chompHexHelp :: Text.Array -> Int -> Int -> Int -> Either (Int, Problem) (Int, Int, L.Literal)
1226−chompHexHelp array startOffset offset length =
1227− if length == 0 then
1228− Right ( offset, length, readInt array startOffset offset )
1229−
1230− else if isHex (Text.unsafeIndex array offset) then
1231− chompHexHelp array startOffset (offset + 1) (length - 1)
1232−
1290+chompHexNumber :: Text.Array -> Int -> Int -> Either (Int, Problem) (Int, Int, L.Literal)
1291+chompHexNumber array offset length =
1292+ let
1293+ (# newOffset, newLength, hexNumber #) =
1294+ chompHex array offset length
1295+ in
1296+ if hexNumber == -1 then
1297+ Left ( newOffset, BadNumberHex )
12331298 else
1234− Right ( offset, length, readInt array startOffset offset )
1299+ Right ( newOffset, newLength, L.IntNum hexNumber )
12351300
12361301
12371302
@@ -1244,18 +1309,6 @@ isDigit word =
12441309 word <= 0x0039 {- 9 -} && word >= 0x0030 {- 0 -}
12451310
12461311
1247−{-# INLINE isHex #-}
1248−isHex :: Word16 -> Bool
1249−isHex word =
1250− word <= 0x0066 {- f -} && (word >= 0x0061 {- a -} || isDigit word || isCapHex word)
1251−
1252−
1253−{-# INLINE isCapHex #-}
1254−isCapHex :: Word16 -> Bool
1255−isCapHex word =
1256− 0x0041 {- A -} <= word && word <= 0x0046 {- F -}
1257−
1258−
12591312{-# INLINE readInt #-}
12601313readInt :: Text.Array -> Int -> Int -> L.Literal
12611314readInt array startOffset endOffset =
@@ -1271,6 +1324,50 @@ readFloat array startOffset endOffset =
12711324
12721325
12731326
1327+-- CHOMP HEX
1328+
1329+
1330+-- Return -1 if it has NO digits
1331+-- Return -2 if it has BAD digits
1332+
1333+{-# INLINE chompHex #-}
1334+chompHex :: Text.Array -> Int -> Int -> (# Int, Int, Int #)
1335+chompHex array offset length =
1336+ chompHexHelp array offset length (-1) 0
1337+
1338+
1339+chompHexHelp :: Text.Array -> Int -> Int -> Int -> Int -> (# Int, Int, Int #)
1340+chompHexHelp array offset length finalNumber hexNumber =
1341+ if length == 0 then
1342+ (# offset, length, finalNumber #)
1343+ else
1344+ let
1345+ !newNumber =
1346+ stepHex (Text.unsafeIndex array offset) hexNumber
1347+ in
1348+ if newNumber < 0 then
1349+ (# offset, length, if newNumber == -1 then finalNumber else -2 #)
1350+ else
1351+ chompHexHelp array (offset + 1) (length - 1) newNumber newNumber
1352+
1353+
1354+{-# INLINE stepHex #-}
1355+stepHex :: Word16 -> Int -> Int
1356+stepHex word hexNumber =
1357+ if word <= 0x0039 {- 9 -} && word >= 0x0030 {- 0 -} then
1358+ 16 * hexNumber + fromIntegral (word - 0x0030 {- 0 -})
1359+
1360+ else if word <= 0x0066 {- f -} && word >= 0x0061 {- a -} then
1361+ 16 * hexNumber + 10 + fromIntegral (word - 0x0061 {- a -})
1362+
1363+ else if word <= 0x0046 {- F -} && word >= 0x0041 {- A -} then
1364+ 16 * hexNumber + 10 + fromIntegral (word - 0x0041 {- A -})
1365+
1366+ else
1367+ -1
1368+
1369+
1370+
12741371-- SHADER
12751372
12761373
@@ -1306,7 +1403,7 @@ eatShader array offset length row col =
13061403 else if word == 0x000A {- \n -} then
13071404 eatShader array (offset + 1) (length - 1) (row + 1) 1
13081405
1309− else if word < 0xD800 || word > 0xDBFF then
1406+ else if word < 0xD800 || 0xDBFF < word then
13101407 eatShader array (offset + 1) (length - 1) row (col + 1)
13111408
13121409 else
@@ -1333,7 +1430,7 @@ kernelChunk =
13331430 chompChunk array offset length row col
13341431
13351432 !javascript =
1336− Text.encodeUtf8 (Text.Text array offset (jsOffset - offset))
1433+ Text.encodeUtf8 (copyText array offset (jsOffset - offset))
13371434
13381435 !newState =
13391436 State array newOffset newLength indent newRow newCol ctx
@@ -1356,7 +1453,7 @@ chompChunk array offset length row col =
13561453 else if word == 0x000A {- \n -} then
13571454 chompChunk array (offset + 1) (length - 1) (row + 1) 1
13581455
1359− else if word < 0xD800 || word > 0xDBFF then
1456+ else if word < 0xD800 || 0xDBFF < word then
13601457 chompChunk array (offset + 1) (length - 1) row (col + 1)
13611458
13621459 else
@@ -1374,10 +1471,10 @@ chompChunkTag array jsOffset offset length row col =
13741471
13751472 !chunkTag =
13761473 if word == 0x0024 {- $ -} then
1377− KernelElmField (Text.Text array (offset + 1) (newOffset - offset - 1))
1474+ KernelElmField (copyText array (offset + 1) (newOffset - offset - 1))
13781475
13791476 else
1380− let !chunk = Text.Text array offset (newOffset - offset) in
1477+ let !chunk = copyText array offset (newOffset - offset) in
13811478 if isDigit word then
13821479 KernelEnum (fromIntegral (word - 0x0030 {- 0 -})) chunk
13831480
src/Reporting/Error/Syntax.hs+97−11
@@ -4,6 +4,7 @@ module Reporting.Error.Syntax
44 ( Error(..)
55 , ParseError(..)
66 , Problem(..), Theory(..)
7+ , EscapeProblem(..)
78 , ContextStack, Context(..)
89 , BadOp(..), Next(..)
910 , toReport
@@ -18,7 +19,7 @@ import qualified Reporting.Render.Type as RenderType
1819import qualified Reporting.Region as R
1920import qualified Reporting.Report as Report
2021import qualified Reporting.Helpers as Help
21−import Reporting.Helpers ((<>), dullyellow, hsep, i2t, reflowParagraph, text)
22+import Reporting.Helpers ((<>), dullyellow, green, hsep, i2t, reflowParagraph, text)
2223
2324
2425
@@ -66,9 +67,11 @@ data Problem
6667 | EndOfFile_Shader
6768 | EndOfFile_String
6869 | EndOfFile_MultiString
70+ | EndOfFile_Char
6971 | NewLineInString
70− | BadEscape
71− | BadChar
72+ | NewLineInChar
73+ | BadEscape Int EscapeProblem
74+ | BadChar Int
7275 | BadNumberDot
7376 | BadNumberEnd
7477 | BadNumberExp
@@ -80,6 +83,13 @@ data Problem
8083 | Theories ContextStack [Theory]
8184
8285
86+data EscapeProblem
87+ = UnknownEscape
88+ | UnicodeSyntax
89+ | UnicodeRange
90+ | UnicodeLength Int Text
91+
92+
8393data BadOp = HasType | Equals | Arrow | Pipe | Dot
8494
8595
@@ -449,6 +459,14 @@ problemToReport subRegion problem =
449459 \ to be missing."
450460 )
451461
462+ EndOfFile_Char ->
463+ parseReport
464+ "I got to the end of the file while parsing a character."
465+ (reflowParagraph $
466+ "Characters look like 'x' with single quotes on each end.\
467+ \ So the closing single quote seems to be missing."
468+ )
469+
452470 NewLineInString ->
453471 parseReport
454472 "This string is missing the closing quote."
@@ -461,16 +479,17 @@ problemToReport subRegion problem =
461479 ]
462480 )
463481
464− BadEscape ->
482+ NewLineInChar ->
465483 parseReport
466− "A backslash starts an escaped character, but I do not recognize this one:"
467− ( reflowParagraph
468− "Elm allows typical escape characters like \\\\ and \\n and \\t. You can\
469− \ also say \\u0040 to refer to unicode characters by their hex code.\
470− \ Maybe there is some typo?"
484+ "This character is missing the closing quote."
485+ ( reflowParagraph $
486+ "Elm characters must start and end with a single quote, like 'a' and 'b'"
471487 )
472488
473− BadChar ->
489+ BadEscape _ escapeProblem ->
490+ uncurry parseReport (escapeProblemToReport escapeProblem)
491+
492+ BadChar _ ->
474493 parseReport
475494 "Ran into a bad use of single quotes."
476495 ( Help.stack
@@ -479,7 +498,7 @@ problemToReport subRegion problem =
479498 , Help.indent 4 $
480499 dullyellow (text "'this'")
481500 <> text " => "
482− <> dullyellow (text "\"this\"")
501+ <> green (text "\"this\"")
483502 , reflowParagraph $
484503 "Unlike JavaScript, Elm distinguishes between strings like \"hello\"\
485504 \ and individual characters like 'A' and '3'. If you really do want\
@@ -609,6 +628,73 @@ problemToReport subRegion problem =
609628 ++ map (bullet . theoryToText stack) theories
610629
611630
631+escapeProblemToReport :: EscapeProblem -> ( Text, Help.Doc )
632+escapeProblemToReport escapeProblem =
633+ case escapeProblem of
634+ UnknownEscape ->
635+ ( "A backslash starts an escaped character, but I do not recognize this one:"
636+ , Help.stack
637+ [ "Maybe there is some typo?"
638+ , Help.toHint $ "Valid escape characters include:"
639+ , Help.indent 4 $ Help.vcat $
640+ [ "\\n"
641+ , "\\t"
642+ , "\\\""
643+ , "\\\'"
644+ , "\\\\"
645+ , "\\u{03BB}"
646+ ]
647+ , reflowParagraph $
648+ "The last one lets encode ANY character by its Unicode code\
649+ \ point, so there are no other escape characters in Elm."
650+ ]
651+ )
652+
653+ UnicodeSyntax ->
654+ ( "I ran into an invalid Unicode escape character:"
655+ , Help.stack
656+ [ "Here are some examples of valid Unicode escape characters:"
657+ , Help.indent 4 $ Help.vcat $
658+ [ "\\u{0041}"
659+ , "\\u{03BB}"
660+ , "\\u{6728}"
661+ , "\\u{1F60A}"
662+ ]
663+ , reflowParagraph $
664+ "Notice that the code point is always surrounded by curly\
665+ \ braces. They are required!"
666+ ]
667+ )
668+
669+ UnicodeRange ->
670+ ( "This is not a real Unicode code point:"
671+ , "All valid Unicode code points are between 0 and 10FFFF."
672+ )
673+
674+ UnicodeLength numDigits badCode ->
675+ let
676+ moreOrFewer =
677+ if numDigits < 4 then "more" else "fewer"
678+
679+ goodCode =
680+ Text.justifyRight 4 '0' $ Text.dropWhile (== '0') badCode
681+
682+ suggestion =
683+ [ "Try"
684+ , "using"
685+ , dullyellow $ text $ "\\u{" <> goodCode <> "}"
686+ , "instead."
687+ ]
688+ in
689+ ( "This Unicode code point needs " <> moreOrFewer <> " digits:"
690+ , hsep suggestion
691+ )
692+
693+
694+
695+-- HELPERS
696+
697+
612698bullet :: Text -> Help.Doc
613699bullet point =
614700 Help.hang 4 (" - " <> reflowParagraph point)
src/Reporting/Helpers.hs+1−1
@@ -3,7 +3,7 @@
33module Reporting.Helpers
44 ( (|>)
55 -- re-exports
6− , Doc, (<+>), (<>), cat, dullyellow, fillSep, hang
6+ , Doc, (<+>), (<>), cat, dullyellow, fillSep, green, hang
77 , hsep, indent, parens, sep, text, underline, vcat
88 -- custom helpers
99 , i2t