on master help sign in
Fix multiline string handling and add more tests.
6b255feMax New
compiler/Parse/Helpers.hs+18−7
@@ -312,9 +312,9 @@ markdown interpolation = try (string "[markdown|") >> closeMarkdown "" []
312 ]
312 ]
313
313
314str :: IParser String
314str :: IParser String
315−str =
315+str = expecting "String" $
316− choice [ quote >> manyTill stringChar quote
316+ choice [ quote >> manyTill multilineStringChar quote
317− , expecting "string" . betwixt '"' '"' . many $ stringChar
317+ , char '"' >> manyTill stringChar (char '"')
318 ]
318 ]
319 >>= parseStringLiteral . wrapQuotes '\"' . join
319 >>= parseStringLiteral . wrapQuotes '\"' . join
320 where quote = try (string "\"\"\"")
320 where quote = try (string "\"\"\"")
@@ -323,9 +323,19 @@ wrapQuotes :: Char -> String -> String
323wrapQuotes delim s = (delim:s ++ [delim])
323wrapQuotes delim s = (delim:s ++ [delim])
324
324
325stringChar :: IParser String
325stringChar :: IParser String
326−stringChar = newline <|> escaped '\"' <|> (pure <$> satisfy (/= '\"'))
326+stringChar = newlineChar <|> escaped '\"' <|> (pure <$> satisfy (/= '\"'))
327− where newline = ['\\', 'n'] <$ char '\n'
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'
328− <|> ['\\', 'r'] <$ char '\r'
338+ <|> ['\\', 'r'] <$ char '\r'
329
339
330escaped :: Char -> IParser String
340escaped :: Char -> IParser String
331escaped delim = try $ do
341escaped delim = try $ do
@@ -334,7 +344,8 @@ escaped delim = try $ do
334 return ['\\', c]
344 return ['\\', c]
335
345
336chr :: IParser Char
346chr :: IParser Char
347+chr = (betwixt '\'' '\''
337−chr = (betwixt '\'' '\'' (many1 (escaped '\'' <|> (pure <$> satisfy (/='\''))))
348+ (many1 (escaped '\'' <|> (pure <$> satisfy (/='\''))))
338 >>= parseCharLiteral . wrapQuotes '\'' . join
349 >>= parseCharLiteral . wrapQuotes '\'' . join
339 )
350 )
340 <?> "character"
351 <?> "character"
tests/Tests/Compiler.hs+18−11
@@ -7,14 +7,15 @@ import System.FilePath ((</>))
7import System.FilePath.Find (find, (==?), extension)
7import System.FilePath.Find (find, (==?), extension)
8import Test.Framework
8import Test.Framework
9import Test.Framework.Providers.HUnit (testCase)
9import Test.Framework.Providers.HUnit (testCase)
10−import Test.HUnit ((@=?), Assertion)
10+import Test.HUnit (Assertion, assertFailure, assertBool)
11+import Text.Parsec (ParseError)
11
12
12import Elm.Internal.Utils as Elm
13import Elm.Internal.Utils as Elm
13
14
14compilerTests :: Test
15compilerTests :: Test
15compilerTests = buildTest $ do
16compilerTests = buildTest $ do
16− goods <- mkTests True =<< getElms "good"
17+ goods <- mkTests goodCompile =<< getElms "good"
17− bads <- mkTests False =<< getElms "bad"
18+ bads <- mkTests badCompile =<< getElms "bad"
18 return $ testGroup "Compile Tests"
19 return $ testGroup "Compile Tests"
19 [
20 [
20 testGroup "Good Tests" goods
21 testGroup "Good Tests" goods
@@ -24,15 +25,21 @@ compilerTests = buildTest $ do
24 where getElms :: FilePath -> IO [FilePath]
25 where getElms :: FilePath -> IO [FilePath]
25 getElms fname = find (return True) (extension ==? ".elm") (testsDir </> fname)
26 getElms fname = find (return True) (extension ==? ".elm") (testsDir </> fname)
26
27
27− mkTests :: Bool -> [FilePath] -> IO [Test]
28+ mkTests :: (Either String String -> Assertion) -> [FilePath] -> IO [Test]
28− mkTests b = traverse setupTest
29+ mkTests h = traverse setupTest
29− where setupTest f = testCase f . mkCompileTest b <$> readFile f
30+ where setupTest f = testCase f . mkCompileTest h <$> readFile f
30
31
31− testsDir = "tests" </> "data"
32+ testsDir = "tests" </> "test-files"
32
33
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
34 -> String -- ^ File Contents
43 -> String -- ^ File Contents
35 -> Assertion
44 -> Assertion
36−mkCompileTest succ modul = noCompileErr @=? succ
45+mkCompileTest handle = handle . Elm.compile
37− where noCompileErr = either (const False) (const True) . Elm.compile $ modul
38− expectation = "Compile " ++ if succ then "Success" else "Error"
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.