Skip to content
This repository was archived by the owner on Oct 18, 2021. It is now read-only.

Commit b24bd29

Browse files
committed
Fix a whole bunch of Lua parser/printing issues
- Correctly emit semicolons when needed. We're a little overeager in emitting them in some cases, but this is sufficiently good heuristic right now. - Correct binary operator's precedence within the Lua parser. (Closes #194)
1 parent 2e560f1 commit b24bd29

File tree

17 files changed

+207
-58
lines changed

17 files changed

+207
-58
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ install:
2121

2222
script:
2323
- stack --no-terminal build --fast --ghc-options "-Werror -fmax-pmcheck-iterations=5000000"
24-
- env AMC_LIBRARY_PATH=$PWD/lib/ stack --no-terminal test --fast --ghc-options "-Werror -fmax-pmcheck-iterations=5000000" --test-arguments "--xml junit.xml --display t"
24+
- env AMC_LIBRARY_PATH=$PWD/lib/ stack --no-terminal test --fast --ghc-options "-Werror -fmax-pmcheck-iterations=5000000" --test-arguments "--xml junit.xml --display t --hedgehog-tests 10000"
2525
- stack --no-terminal exec --package=hlint -- hlint --git

amuletml.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@ test-suite tests
145145

146146
, Test.Parser.Lexer
147147
, Test.Parser.Parser
148+
149+
, Test.Lua.Gen
148150
, Test.Lua.Parser
149151
default-language: Haskell2010
150152

compiler/Test/Lua/Gen.hs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
{-# LANGUAGE GADTs, OverloadedStrings #-}
2+
module Test.Lua.Gen
3+
( genExpr
4+
, genStmt
5+
, genStmts
6+
) where
7+
8+
import Control.Monad.Identity
9+
10+
import qualified Data.Set as Set
11+
import qualified Data.Text as T
12+
13+
import Language.Lua.Syntax
14+
15+
import qualified Hedgehog.Range as Range
16+
import qualified Hedgehog.Gen as Gen
17+
import Hedgehog
18+
19+
genIdent :: (MonadGen m, GenBase m ~ Identity) => m T.Text
20+
genIdent = Gen.filter (`Set.notMember` keywords) $ do
21+
first <- Gen.element "abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_"
22+
rest <- Gen.text (Range.linear 0 25) (Gen.element "abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_")
23+
pure (T.cons first rest)
24+
25+
genVar :: (MonadGen m, GenBase m ~ Identity) => m LuaVar
26+
genVar =
27+
Gen.recursive Gen.choice
28+
[ genName ]
29+
[ LuaIndex <$> genExpr <*> genKey ]
30+
31+
genName :: (MonadGen m, GenBase m ~ Identity) => m LuaVar
32+
genName = LuaName <$> genIdent
33+
34+
genKey :: (MonadGen m, GenBase m ~ Identity) => m LuaExpr
35+
genKey = Gen.frequency [(3, LuaString <$> genIdent), (1, genExpr)]
36+
37+
genExpr :: (MonadGen m, GenBase m ~ Identity) => m LuaExpr
38+
genExpr =
39+
Gen.recursive Gen.choice
40+
[ pure LuaNil, pure LuaTrue, pure LuaFalse, pure LuaDots
41+
, LuaNumber . (/2^(3::Int)) . fromInteger <$> Gen.integral (Range.exponential 0 (2^dR))
42+
, LuaInteger <$> Gen.int (Range.exponential 0 (2^iR))
43+
, LuaString <$> Gen.text (Range.linear 0 200) Gen.lower -- TODO Gen.ascii
44+
, LuaRef <$> genName
45+
]
46+
[ LuaCallE <$> genCall
47+
, LuaRef <$> genVar
48+
, LuaFunction <$> Gen.list (Range.linear 0 5) genName <*> genStmts
49+
, LuaTable <$> Gen.list (Range.linear 0 15) ((,) <$> genKey <*> genExpr)
50+
, LuaBinOp <$> genExpr <*> genBin <*> genExpr
51+
, LuaUnOp <$> genUn <*> genExpr
52+
]
53+
54+
where
55+
dR , iR :: Int
56+
dR = 10
57+
iR = 24
58+
59+
genBin = Gen.element
60+
[ "+", "-", "*", "/", "%", "^", "..", "==", "~=", ">", "<", ">=", "<="
61+
, "and", "or" ]
62+
genUn = Gen.element ["-", "not"]
63+
64+
genCall :: (MonadGen m, GenBase m ~ Identity) => m LuaCall
65+
genCall = Gen.frequency
66+
[(5, LuaCall <$> genExpr <*> Gen.list (Range.linear 0 5) genExpr)
67+
,(1, LuaInvoke <$> genExpr <*> genIdent <*> Gen.list (Range.linear 0 5) genExpr)
68+
]
69+
70+
genStmts :: (MonadGen m, GenBase m ~ Identity) => m [LuaStmt]
71+
genStmts =
72+
(++) <$> Gen.list (Range.linear 0 10) genStmt
73+
<*> Gen.frequency
74+
[ (5, pure [])
75+
, (1, pure . LuaReturn <$> Gen.list (Range.linear 0 5) genExpr)
76+
, (1, pure [LuaBreak])
77+
]
78+
79+
genStmt :: (MonadGen m, GenBase m ~ Identity) => m LuaStmt
80+
genStmt =
81+
Gen.recursive Gen.choice
82+
[ ]
83+
[ LuaDo <$> genStmts
84+
, LuaAssign <$> Gen.list (Range.linear 1 5) genVar <*> Gen.list (Range.linear 1 5) genExpr
85+
, LuaWhile <$> genExpr <*> genStmts
86+
, LuaRepeat <$> genStmts <*> genExpr
87+
, LuaFornum <$> genName <*> genExpr <*> genExpr <*> genCounter <*> genStmts
88+
, LuaFor <$> Gen.list (Range.linear 1 5) genName <*> Gen.list (Range.linear 1 5) genExpr <*> genStmts
89+
, LuaLocal <$> Gen.list (Range.linear 1 5) genName <*> Gen.list (Range.linear 0 5) genExpr
90+
, LuaLocalFun <$> genName <*> Gen.list (Range.linear 0 5) genName <*> genStmts
91+
, LuaIfElse <$> Gen.list (Range.linear 1 5) ((,) <$> genElseExpr <*> genStmts)
92+
]
93+
94+
where
95+
genElseExpr = Gen.frequency [(3, genExpr), (1, pure LuaTrue)]
96+
genCounter = Gen.frequency [(3, pure (LuaInteger 1)), (1, genExpr)]

compiler/Test/Lua/Parser.hs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
{-# LANGUAGE TemplateHaskell #-}
12
module Test.Lua.Parser (tests) where
23

4+
import Test.Lua.Gen
35
import Test.Tasty
46
import Test.Util
57

@@ -11,6 +13,8 @@ import Language.Lua.Parser
1113
import qualified Text.Pretty.Note as N
1214
import Text.Pretty.Semantic
1315

16+
import Hedgehog
17+
1418
result :: String -> T.Text -> T.Text
1519
result file contents =
1620
case parseStmts (SourcePos file 1 1) (L.fromStrict contents) of
@@ -19,5 +23,21 @@ result file contents =
1923

2024
where prettyErr = N.format (N.fileSpans [(file, contents)] N.defaultHighlight)
2125

26+
prop_roundtripStmts :: Property
27+
prop_roundtripStmts = withTests 1000 . property $ do
28+
stmts <- forAllWith (show . pretty) genStmts
29+
tripping stmts (display . renderPretty 0.4 100 . pretty) (parseStmts (SourcePos "in" 1 1) . L.fromStrict)
30+
31+
prop_roundtripExpr :: Property
32+
prop_roundtripExpr = withTests 1000 . property $ do
33+
stmts <- forAllWith (show . pretty) genExpr
34+
tripping stmts (display . renderPretty 0.4 100 . pretty) (parseExpr (SourcePos "in" 1 1) . L.fromStrict)
35+
36+
2237
tests :: IO TestTree
23-
tests = testGroup "Test.Lua.Parser" <$> goldenDir result "tests/lua_parse/" ".lua"
38+
tests = do
39+
golden <- goldenDir result "tests/lua_parse/" ".lua"
40+
pure $ testGroup "Test.Lua.Parser"
41+
[ testGroup "Golden" golden
42+
, hedgehog $ $$(discover)
43+
]

compiler/Test/Types/Unify.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import Syntax
1616

1717
prop_unifyMakesGoodCoercion :: Property
1818
prop_unifyMakesGoodCoercion = property $ do
19-
aty <- forAllWith (displayS . displayType) genType
19+
aty <- forAllWith (show . displayType) genType
2020
case unify aty aty of
21-
Left e -> (footnote . displayS . pretty . toList $ e) *> failure
21+
Left e -> (footnote . show . pretty . toList $ e) *> failure
2222
Right x | (ca, cb) <- provenCoercion x -> do
2323
footnote . displayS $
2424
keyword "Given type:" <+> displayType aty

src/Language/Lua/Parser/Error.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ data ParseError
4141
| UnexpectedToken Token [String]
4242
-- | An expression, where a statement was expected
4343
| MalformedStatement
44-
deriving (Show)
44+
deriving (Eq, Show)
4545

4646
instance Pretty ParseError where
4747
pretty (Failure _ s) = string s

src/Language/Lua/Parser/Parser.y

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,13 @@ import Language.Lua.Syntax
9999
float { Token (TcFloat _) _ _ }
100100
string { Token (TcString _) _ _ }
101101

102-
%right '^'
103-
%left '*' '/' '%'
104-
%left '+' '-'
105-
%right '..'
106-
%left '<' '>' '<=' '>=' '~=' '=='
107-
%left and
108102
%left or
103+
%left and
104+
%left '<' '>' '<=' '>=' '~=' '=='
105+
%right '..'
106+
%left '+' '-'
107+
%left '*' '/' '%'
108+
%right '^'
109109
%%
110110

111111
Ident :: { LuaVar }
@@ -203,10 +203,11 @@ Stmt :: { LuaStmt }
203203
| for Ident '=' Expr ',' Expr do Stmts end { LuaFornum $2 $4 $6 (LuaInteger 1) $8 }
204204
| for Ident '=' Expr ',' Expr ',' Expr do Stmts end { LuaFornum $2 $4 $6 $8 $10 }
205205
| for List1(Ident, ',') in List1(Expr, ',') do Stmts end { LuaFor $2 $4 $6 }
206+
| local List1(Ident, ',') { LuaLocal $2 [] }
206207
| local List1(Ident, ',') '=' List1(Expr, ',') { LuaLocal $2 $4 }
207208
| local function Ident '(' List(Ident, ',') ')' Stmts end { LuaLocalFun $3 $5 $7 }
208209
| function Ident '(' List(Ident, ',') ')' Stmts end { LuaAssign [$2] [LuaFunction $4 $6] }
209-
| return List1(Expr, ',') { LuaReturn $2 }
210+
| return List(Expr, ',') { LuaReturn $2 }
210211
| break { LuaBreak }
211212

212213
ElseIfs :: { [(LuaExpr, [LuaStmt])] }

src/Language/Lua/Parser/Token.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ instance Show TokenClass where
140140
show TcEOF = "<eof>"
141141

142142
-- | A token, with its class, start, and end position.
143-
data Token = Token !TokenClass !SourcePos !SourcePos deriving Show
143+
data Token = Token !TokenClass !SourcePos !SourcePos
144+
deriving (Eq, Show)
144145

145146
instance Spanned Token where
146147
annotation (Token _ s e) = mkSpanUnsafe s e

src/Language/Lua/Syntax.hs

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,47 @@ assocOf ".." = ARight
103103
assocOf "^" = ARight
104104
assocOf _ = ALeft
105105

106-
-- | Emit an indented block of objects, with a header and footer
106+
contained :: Doc -> [Doc] -> Doc -> Doc
107+
contained header body footer = header <> nest 2 (line <> vsep body) <> line <> footer
108+
109+
-- | Emit an indented block of statements, with a header and footer
107110
--
108111
-- We have this weird asymmetry of 'line' as we need to indent the first line
109112
-- line of the body but don't want to indent the footer.
110-
block :: Doc -> [Doc] -> Doc -> Doc
111-
block header body footer = header <> nest 2 (line <> vsep body) <> line <> footer
113+
block :: Doc -> [LuaStmt] -> Doc -> Doc
114+
block header body footer = header <> nest 2 (line <> stmts body) <> line <> footer
115+
116+
stmts :: [LuaStmt] -> Doc
117+
stmts [] = mempty
118+
stmts [x] = pretty x
119+
stmts (a:b:cs)
120+
| trailingExpr a && leadingS b = pretty a <> ";" <#> stmts (b:cs)
121+
| otherwise = pretty a <#> stmts (b:cs) where
122+
trailingExpr LuaAssign{} = True
123+
trailingExpr LuaRepeat{} = True
124+
trailingExpr LuaReturn{} = True
125+
trailingExpr LuaLocal{} = True
126+
trailingExpr LuaCallS{} = True
127+
trailingExpr _ = False
128+
129+
leadingS (LuaCallS c) = leadingC c
130+
leadingS (LuaAssign (v:_) _) = leadingVar v
131+
leadingS _ = False
112132

113-
-- | A 'block', which may potentially be simplified to a single line.
114-
miniBlock :: Doc -> Doc -> Doc -> Doc
115-
miniBlock header body footer = header <> nest 2 (softline <> body) <> softline <> footer
133+
leadingC (LuaCall e _) = leadingFn e
134+
leadingC (LuaInvoke e _ _) = leadingFn e
116135

136+
leadingFn (LuaCallE c) = leadingC c
137+
leadingFn (LuaRef LuaName{}) = False
138+
leadingFn _ = True
139+
140+
leadingVar LuaName{} = False
141+
leadingVar (LuaIndex (LuaRef v) _) = leadingVar v
142+
leadingVar (LuaIndex _ _) = True
143+
leadingVar LuaQuoteV{} = False
117144

118145
-- | A variant of 'block' but with an empty footer
119-
headedBlock :: Doc -> [Doc] -> Doc
146+
headedBlock :: Doc -> [LuaStmt] -> Doc
120147
headedBlock header body = block header body empty
121148

122149
-- | Build a series of function arguments
@@ -126,42 +153,42 @@ args = parens . hsep . punctuate comma
126153
instance Pretty LuaStmt where
127154
pretty (LuaDo xs) =
128155
block (keyword "do")
129-
(map pretty xs)
156+
xs
130157
(keyword "end")
131158
pretty (LuaAssign ns xs) = hsep (punctuate comma (map pretty ns)) <+> equals <+> hsep (punctuate comma (map pretty xs))
132159
pretty (LuaWhile c t) =
133160
block (keyword "while" <+> pretty c <+> keyword "do")
134-
(map pretty t)
161+
t
135162
(keyword "end")
136163
pretty (LuaRepeat t c) =
137164
block (keyword "repeat")
138-
(map pretty t)
165+
t
139166
(keyword "until" <+> pretty c)
140167
pretty (LuaIfElse [(c,[t])]) =
141-
miniBlock (keyword "if" <+> pretty c <+> keyword "then") (pretty t) (keyword "end")
168+
group $ block (keyword "if" <+> pretty c <+> keyword "then") [t] (keyword "end")
142169
pretty (LuaIfElse ((c,t):bs)) =
143170
let pprintElse [] = keyword "end"
144171
pprintElse [(LuaTrue, b)] =
145-
headedBlock (keyword "else") (map pretty b)
172+
headedBlock (keyword "else") b
146173
<> keyword "end"
147174
pprintElse ((c, b):xs) =
148175
headedBlock (keyword "elseif" <+> pretty c <+> keyword "then")
149-
(map pretty b)
176+
b
150177
<> pprintElse xs
151178
in headedBlock (keyword "if" <+> pretty c <+> keyword "then")
152-
(map pretty t)
179+
t
153180
<> pprintElse bs
154181
pretty (LuaIfElse []) = error "impossible"
155182
pretty (LuaFornum v s e i b) =
156183
block ( keyword "for" <+> pretty v <+> equals
157184
<+> pretty s <+> comma <+> pretty e <+> comma <+> pretty i <+> keyword "do" )
158-
(map pretty b)
185+
b
159186
(keyword "end")
160187
pretty (LuaFor vs es b) =
161188
block ( keyword "for" <+> hsep (punctuate comma (map pretty vs))
162189
<+> keyword "in" <+> hsep (punctuate comma (map pretty es))
163190
<+> keyword "do" )
164-
(map pretty b)
191+
b
165192
(keyword "end")
166193
pretty (LuaLocalFun n a b) =
167194
funcBlock (keyword "local function" <+> pretty n <> args (map pretty a))
@@ -172,9 +199,12 @@ instance Pretty LuaStmt where
172199
<+> equals <+> hsep (punctuate comma (map pretty xs))
173200
pretty (LuaQuoteS x) = "@" <> text x
174201
pretty LuaBreak = keyword "break"
175-
pretty (LuaReturn v) = keyword "return" <+> pretty v
202+
pretty (LuaReturn []) = keyword "return"
203+
pretty (LuaReturn vs) = keyword "return" <+> hsep (punctuate comma (map pretty vs))
176204
pretty (LuaCallS x) = pretty x
177205

206+
prettyList = stmts
207+
178208
instance Pretty LuaVar where
179209
pretty (LuaName x) = text x
180210
pretty (LuaIndex e (LuaString k))
@@ -206,16 +236,16 @@ instance Pretty LuaExpr where
206236
op "and" = skeyword "and"
207237
op "or" = skeyword "or"
208238
op o = text o
209-
pretty e@(LuaUnOp o x) = op o <> prettyWith (precedenceOf e) x where
210-
op "not" = skeyword "not "
211-
op o = text o
239+
pretty e@(LuaUnOp "not" x) = skeyword "not " <> prettyWith (precedenceOf e) x
240+
pretty (LuaUnOp "-" x@LuaUnOp{}) = text "-" <> parens (pretty x)
241+
pretty e@(LuaUnOp o x) = text o <> prettyWith (precedenceOf e) x
212242
pretty (LuaRef x) = pretty x
213243
pretty (LuaFunction a b) =
214244
funcBlock (keyword "function" <> args (map pretty a))
215245
b
216246
(keyword "end")
217247
pretty (LuaTable []) = lbrace <> rbrace
218-
pretty (LuaTable ps) = group (block lbrace (punctuate comma . entries 1 $ ps) rbrace) where
248+
pretty (LuaTable ps) = group (contained lbrace (punctuate comma . entries 1 $ ps) rbrace) where
219249
entries _ [] = []
220250
entries n ((LuaString k, v):es) | validKey k = text k <+> value v : entries n es
221251
entries n ((LuaInteger k, v):es) | k == n = pretty v : entries (n + 1) es
@@ -239,8 +269,8 @@ prettyWith desired expr =
239269
-- | An alternative to 'block' which may group simple functions onto one line
240270
funcBlock :: Doc -> [LuaStmt] -> Doc -> Doc
241271
funcBlock header [] = group . block header []
242-
funcBlock header [r@LuaReturn{}] = group . block header [pretty r]
243-
funcBlock header body = block header (map pretty body)
272+
funcBlock header r@[LuaReturn{}] = group . block header r
273+
funcBlock header body = block header body
244274

245275
validKey :: Text -> Bool
246276
validKey t = case T.uncons t of

tests/lua/emit_ifs.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ do
33
local function _amp_amp(a) return function(b) return a and b end end
44
local function _bar_bar(a) return function(b) return a or b end end
55
local function _not(a) return not a end
6-
(nil)({ ands = _amp_amp, ors = _bar_bar, ["not"] = _not })
6+
(nil)({ ands = _amp_amp, ors = _bar_bar, ["not"] = _not });
77
(nil)(function(tmp)
88
if true then return print("L") end
99
print("R")
1010
return print("R")
11-
end)
11+
end);
1212
(nil)(function(tmp)
1313
if not true then return print("R") end
1414
print("L")

0 commit comments

Comments
 (0)