on master help sign in
Merge pull request #443 from maxsnew/strchr

Fix String/Char Parse/Print Bugs

commitaba241bf5bf923de8b3a19d7c027c96b94e53a3e authorEvan Czaplicki <info@elm-lang.org> dateWed 8 Jan 2014, 00:06 (13 years ago) parentae343716b255fe
21 files changed+107−52
compiler/Parse/Helpers.hs+71−39 compiler/SourceSyntax/Literal.hs+2−2 tests/Tests/Compiler.hs+18−11 tests/data/bad/BBTArgs → tests/test-files/bad/BBTArgs tests/data/bad/InfiniteType.elm → tests/test-files/bad/InfiniteType.elm tests/data/bad/NonElementMain.elm → tests/test-files/bad/NonElementMain.elm tests/test-files/bad/Strings/ExtraClose.elm+4 tests/data/good/AliasSubstitution.elm → tests/test-files/good/AliasSubstitution.elm tests/data/good/NoExpressions.elm → tests/test-files/good/NoExpressions.elm tests/data/good/Otherwise.elm → tests/test-files/good/Otherwise.elm tests/data/good/QuotesAndComments.elm → tests/test-files/good/QuotesAndComments.elm tests/data/good/Soundness/Apply.elm → tests/test-files/good/Soundness/Apply.elm tests/data/good/Soundness/ApplyAnnotated.elm → tests/test-files/good/Soundness/ApplyAnnotated.elm tests/data/good/Soundness/Id.elm → tests/test-files/good/Soundness/Id.elm tests/data/good/Soundness/IdAnnotated.elm → tests/test-files/good/Soundness/IdAnnotated.elm tests/data/good/Soundness/TrickyId.elm → tests/test-files/good/Soundness/TrickyId.elm tests/data/good/Soundness/TrickyIdAnnotated.elm → tests/test-files/good/Soundness/TrickyIdAnnotated.elm tests/test-files/good/Strings/Multiline.elm+6 tests/test-files/good/Strings/MultilineNormal.elm+6 tests/data/good/Unify/LockedVars.elm → tests/test-files/good/Unify/LockedVars.elm tests/data/good/Unify/NonHomogeneousRecords.elm → tests/test-files/good/Unify/NonHomogeneousRecords.elm
compiler/Parse/Helpers.hs+71−39
@@ -1,19 +1,21 @@
11module Parse.Helpers where
22
33import Prelude hiding (until)
4−import Control.Applicative ((<$>),(<*>))
4+import Control.Applicative ((<$>),(<*>), (<$), pure)
55import Control.Monad
66import Control.Monad.State
77import Data.Char (isUpper)
88import qualified Data.Set as Set
99import qualified Data.Map as Map
10+import Text.Parsec hiding (newline,spaces,State)
11+import Text.Parsec.Indent
12+import qualified Text.Parsec.Token as P
13+
1014import SourceSyntax.Helpers as Help
1115import SourceSyntax.Location as Location
1216import SourceSyntax.Expression
1317import SourceSyntax.PrettyPrint
1418import SourceSyntax.Declaration (Assoc)
15−import Text.Parsec hiding (newline,spaces,State)
16−import Text.Parsec.Indent
1719
1820reserveds = [ "if", "then", "else"
1921 , "case", "of"
@@ -45,7 +47,8 @@ jsReserveds = Set.fromList
4547expecting = flip (<?>)
4648
4749type OpTable = Map.Map String (Int, Assoc)
48−type IParser a = ParsecT String OpTable (State SourcePos) a
50+type SourceM = State SourcePos
51+type IParser a = ParsecT String OpTable SourceM a
4952
5053iParse :: IParser a -> String -> Either ParseError a
5154iParse = iParseWithTable "" Map.empty
@@ -54,23 +57,6 @@ iParseWithTable :: SourceName -> OpTable -> IParser a -> String -> Either ParseE
5457iParseWithTable sourceName table aParser input =
5558 runIndent sourceName $ runParserT aParser table sourceName input
5659
57−readMaybe :: Read a => String -> Maybe a
58−readMaybe s =
59− case [ x | (x,t) <- reads s, ("","") <- lex t ] of
60− [x] -> Just x
61− _ -> Nothing
62−
63−backslashed :: IParser Char
64−backslashed = do
65− char '\\'
66− c <- anyChar
67− case readMaybe ['\'','\\',c,'\''] of
68− Just chr -> return chr
69− Nothing ->
70− fail $ "Did not recognize character '\\" ++ [c] ++
71− "'. If the backslash is meant to be a character of its own, " ++
72− "it should be escaped like this: \"\\\\" ++ [c] ++ "\""
73−
7460var :: IParser String
7561var = makeVar (letter <|> char '_' <?> "variable")
7662
@@ -326,23 +312,69 @@ markdown interpolation = try (string "[markdown|") >> closeMarkdown "" []
326312 ]
327313
328314str :: IParser String
329−str = choice [ quote >> dewindows <$> manyTill (backslashed <|> anyChar) quote
330− , liftM dewindows . expecting "string" . betwixt '"' '"' . many $
331− backslashed <|> satisfy (/='"')
332− ]
333− where
334− quote = try (string "\"\"\"")
335−
336− -- Remove \r from strings to fix generated JavaScript
337− dewindows [] = []
338− dewindows cs =
339− let (pre, suf) = break (`elem` ['\r','\n']) cs
340− in pre ++ case suf of
341− ('\r':'\n':rest) -> '\n' : dewindows rest
342− ('\n':rest) -> '\n' : dewindows rest
343− ('\r':rest) -> '\n' : dewindows rest
344− _ -> []
315+str = expecting "String" $
316+ choice [ quote >> manyTill multilineStringChar quote
317+ , char '"' >> manyTill stringChar (char '"')
318+ ]
319+ >>= parseStringLiteral . wrapQuotes '\"' . join
320+ where quote = try (string "\"\"\"")
321+
322+wrapQuotes :: Char -> String -> String
323+wrapQuotes delim s = (delim:s ++ [delim])
324+
325+stringChar :: IParser String
326+stringChar = newlineChar <|> escaped '\"' <|> (pure <$> satisfy (/= '\"'))
327+
328+multilineStringChar :: IParser String
329+multilineStringChar = noEnd
330+ >> (newlineChar <|> escaped '\"' <|> expandQuote <$> anyChar)
331+ where noEnd = notFollowedBy (string "\"\"\"")
332+ expandQuote c = if c == '\"'
333+ then "\\\""
334+ else [c]
335+
336+newlineChar :: IParser String
337+newlineChar = ['\\', 'n'] <$ char '\n'
338+ <|> ['\\', 'r'] <$ char '\r'
339+
340+escaped :: Char -> IParser String
341+escaped delim = try $ do
342+ char '\\'
343+ c <- char '\\' <|> char delim
344+ return ['\\', c]
345345
346346chr :: IParser Char
347−chr = betwixt '\'' '\'' (backslashed <|> satisfy (/='\''))
348− <?> "character"
347+chr = (betwixt '\'' '\''
348+ (many1 (escaped '\'' <|> (pure <$> satisfy (/='\''))))
349+ >>= parseCharLiteral . wrapQuotes '\'' . join
350+ )
351+ <?> "character"
352+
353+calloutParser :: String -> IParser a -> IParser a
354+calloutParser inp p = case iParse p inp of
355+ Left err -> fail . show $ err
356+ Right s -> return s
357+
358+parseStringLiteral :: String -> IParser String
359+parseStringLiteral s = calloutParser s . P.stringLiteral $ lexer
360+
361+parseCharLiteral :: String -> IParser Char
362+parseCharLiteral s = calloutParser s . P.charLiteral $ lexer
363+
364+lexer = P.makeTokenParser elmDef
365+
366+-- I don't know how many of these are necessary for charLiteral/stringLiteral
367+elmDef :: P.GenLanguageDef String u SourceM
368+elmDef = P.LanguageDef {
369+ P.commentStart = "{-"
370+ , P.commentEnd = "-}"
371+ , P.commentLine = "--"
372+ , P.nestedComments = True
373+ , P.identStart = undefined
374+ , P.identLetter = undefined
375+ , P.opStart = undefined
376+ , P.opLetter = undefined
377+ , P.reservedNames = reserveds
378+ , P.reservedOpNames = [":", "->", "<-", "|"]
379+ , P.caseSensitive = True
380+ }
compiler/SourceSyntax/Literal.hs+2−2
@@ -16,6 +16,6 @@ instance Pretty Literal where
1616 case literal of
1717 IntNum n -> PP.int n
1818 FloatNum n -> PP.double n
19− Chr c -> PP.quotes (PP.char c)
20− Str s -> PP.text (show s)
19+ Chr c -> PP.text . show $ c
20+ Str s -> PP.text . show $ s
2121 Boolean bool -> PP.text (show bool)
tests/Tests/Compiler.hs+18−11
@@ -7,14 +7,15 @@ import System.FilePath ((</>))
77import System.FilePath.Find (find, (==?), extension)
88import Test.Framework
99import Test.Framework.Providers.HUnit (testCase)
10−import Test.HUnit ((@=?), Assertion)
10+import Test.HUnit (Assertion, assertFailure, assertBool)
11+import Text.Parsec (ParseError)
1112
1213import Elm.Internal.Utils as Elm
1314
1415compilerTests :: Test
1516compilerTests = buildTest $ do
16− goods <- mkTests True =<< getElms "good"
17− bads <- mkTests False =<< getElms "bad"
17+ goods <- mkTests goodCompile =<< getElms "good"
18+ bads <- mkTests badCompile =<< getElms "bad"
1819 return $ testGroup "Compile Tests"
1920 [
2021 testGroup "Good Tests" goods
@@ -24,15 +25,21 @@ compilerTests = buildTest $ do
2425 where getElms :: FilePath -> IO [FilePath]
2526 getElms fname = find (return True) (extension ==? ".elm") (testsDir </> fname)
2627
27− mkTests :: Bool -> [FilePath] -> IO [Test]
28− mkTests b = traverse setupTest
29− where setupTest f = testCase f . mkCompileTest b <$> readFile f
28+ mkTests :: (Either String String -> Assertion) -> [FilePath] -> IO [Test]
29+ mkTests h = traverse setupTest
30+ where setupTest f = testCase f . mkCompileTest h <$> readFile f
3031
31− testsDir = "tests" </> "data"
32+ testsDir = "tests" </> "test-files"
3233
33−mkCompileTest :: Bool -- ^ Expect success?
34+goodCompile :: Either String String -> Assertion
35+goodCompile (Left err) = assertFailure err
36+goodCompile (Right _) = assertBool "" True
37+
38+badCompile :: Either String String -> Assertion
39+badCompile (Left _) = assertBool "" True
40+badCompile (Right _) = assertFailure "Compilation succeeded but should have failed"
41+
42+mkCompileTest :: ((Either String String) -> Assertion) -- ^ Handler
3443 -> String -- ^ File Contents
3544 -> Assertion
36−mkCompileTest succ modul = noCompileErr @=? succ
37− where noCompileErr = either (const False) (const True) . Elm.compile $ modul
38− expectation = "Compile " ++ if succ then "Success" else "Error"
45+mkCompileTest handle = handle . Elm.compile
tests/data/bad/BBTArgs → tests/test-files/bad/BBTArgsrenamed

The content is the same.

tests/data/bad/InfiniteType.elm → tests/test-files/bad/InfiniteType.elmrenamed

The content is the same.

tests/data/bad/NonElementMain.elm → tests/test-files/bad/NonElementMain.elmrenamed

The content is the same.

tests/test-files/bad/Strings/ExtraClose.elmnew file+4
@@ -0,0 +1,4 @@
1+
2+s = " " "
3+
4+main = plainText s
tests/data/good/AliasSubstitution.elm → tests/test-files/good/AliasSubstitution.elmrenamed

The content is the same.

tests/data/good/NoExpressions.elm → tests/test-files/good/NoExpressions.elmrenamed

The content is the same.

tests/data/good/Otherwise.elm → tests/test-files/good/Otherwise.elmrenamed

The content is the same.

tests/data/good/QuotesAndComments.elm → tests/test-files/good/QuotesAndComments.elmrenamed

The content is the same.

tests/data/good/Soundness/Apply.elm → tests/test-files/good/Soundness/Apply.elmrenamed

The content is the same.

tests/data/good/Soundness/ApplyAnnotated.elm → tests/test-files/good/Soundness/ApplyAnnotated.elmrenamed

The content is the same.

tests/data/good/Soundness/Id.elm → tests/test-files/good/Soundness/Id.elmrenamed

The content is the same.

tests/data/good/Soundness/IdAnnotated.elm → tests/test-files/good/Soundness/IdAnnotated.elmrenamed

The content is the same.

tests/data/good/Soundness/TrickyId.elm → tests/test-files/good/Soundness/TrickyId.elmrenamed

The content is the same.

tests/data/good/Soundness/TrickyIdAnnotated.elm → tests/test-files/good/Soundness/TrickyIdAnnotated.elmrenamed

The content is the same.

tests/test-files/good/Strings/Multiline.elmnew file+6
@@ -0,0 +1,6 @@
1+s = """
2+here's a quote: "
3+
4+"""
5+
6+main = plainText s
tests/test-files/good/Strings/MultilineNormal.elmnew file+6
@@ -0,0 +1,6 @@
1+s = "
2+here's a quote: \"
3+
4+"
5+
6+main = plainText s
tests/data/good/Unify/LockedVars.elm → tests/test-files/good/Unify/LockedVars.elmrenamed

The content is the same.

tests/data/good/Unify/NonHomogeneousRecords.elm → tests/test-files/good/Unify/NonHomogeneousRecords.elmrenamed

The content is the same.