vintage-basic-1.0: test/Language/VintageBasic/Parser_test.hs
module Language.VintageBasic.Parser_test where
import Test.HUnit
import Text.ParserCombinators.Parsec(parse)
import Text.ParserCombinators.Parsec.Pos
import Language.VintageBasic.Asserts
import Language.VintageBasic.Builtins
import Language.VintageBasic.LexCommon(Tagged(..))
import Language.VintageBasic.Parser
import Language.VintageBasic.Result
import Language.VintageBasic.Syntax
import Language.VintageBasic.Tokenizer(taggedTokensP)
tokenize source = let (Right taggedToks) = parse taggedTokensP "" source in taggedToks
goodStatements source expectedColAndStatements = TestCase $
assertParseResult withCol SyntaxError statementListP (tokenize source) expectedColAndStatements
badStatements parser source expectedError = TestCase $
assertParseError withCol SyntaxError statementListP (tokenize source) expectedError
goodExpr source expectedExpr = TestCase $
assertParseResult id SyntaxError exprP (tokenize source) expectedExpr
badExpr source expectedError = TestCase $
assertParseError id SyntaxError exprP (tokenize source) expectedError
test_parses_multiple_statements = goodStatements
":?::?: "
[(2,PrintS []), (5,PrintS [])]
test_parse_bare_print = goodStatements
"PRINT"
[(1,PrintS [])]
test_parse_print_string = goodStatements
"PRINT\"hello\""
[(1,PrintS [LitX (StringLit "hello")])]
test_parse_print_string_ending_in_semicolon = goodStatements
"PRINT\"hello\";"
[(1,PrintS [LitX (StringLit "hello"), EmptySeparatorX])]
test_parse_print_string_ending_in_comma = goodStatements
"PRINT\"hello\","
[(1,PrintS [LitX (StringLit "hello"), NextZoneX])]
test_parse_print_with_semicolon_separated_parts = goodStatements
"PRINT\"hello\";\"there\""
[(1,PrintS [LitX (StringLit "hello"), EmptySeparatorX, LitX (StringLit "there")])]
test_parse_print_with_juxtaposed_parts = goodStatements
"PRINT\"hello\"\"there\""
[(1,PrintS [LitX (StringLit "hello"), LitX (StringLit "there")])]
test_parse_print_with_comma_separated_parts = goodStatements
"PRINT\"hello\",\"there\""
[(1,PrintS [LitX (StringLit "hello"), NextZoneX, LitX (StringLit "there")])]
test_parse_print_with_a_comma_at_the_start = goodStatements
"PRINT,\"hello\""
[(1,PrintS [NextZoneX, LitX (StringLit "hello")])]
test_parse_let = goodStatements
"LETA=1"
[(1,LetS (ScalarVar (VarName FloatType "A")) (LitX (FloatLit 1.0)))]
test_parse_let_wo_keyword = goodStatements
"A=1"
[(1,LetS (ScalarVar (VarName FloatType "A")) (LitX (FloatLit 1.0)))]
test_parse_multiple_dims = goodStatements
"DIMA$(5),G(14,20)"
[(1,DimS [(VarName StringType "A", [(LitX (FloatLit 5))]), (VarName FloatType "G", [(LitX (FloatLit 14)), (LitX (FloatLit 20))])])]
test_parse_goto = goodStatements
"GOTO20"
[(1,GotoS 20)]
test_ignores_space_after_target = goodStatements
"GOTO20 "
[(1,GotoS 20)]
test_parse_gosub = goodStatements
"GOSUB20"
[(1,GosubS 20)]
test_parse_on_goto = goodStatements
"ON3GOTO10,20,40"
[(1,OnGotoS (LitX (FloatLit 3)) [10,20,40])]
test_parse_on_gosub = goodStatements
"ON3GOSUB10,20,40"
[(1,OnGosubS (LitX (FloatLit 3)) [10,20,40])]
test_parse_data = goodStatements
"DATA4,5,\"THIS,WORKS\""
[(1,DataS "4,5,\"THIS,WORKS\"")]
test_parse_read = goodStatements
"READA$(5),B"
[(1,ReadS [ArrVar (VarName StringType "A") [(LitX (FloatLit 5))], ScalarVar (VarName FloatType "B")])]
test_parse_restore = goodStatements
"RESTORE"
[(1,RestoreS Nothing)]
test_parse_restore_with_line_number = goodStatements
"RESTORE20"
[(1,RestoreS (Just 20))]
test_parse_def_fn = goodStatements
"DEFFNAN1$(B,CD$)=4+B"
[(1,DefFnS (VarName StringType "AN1") [(VarName FloatType "B"), (VarName StringType "CD")] (BinX AddOp (LitX (FloatLit 4)) (VarX (ScalarVar (VarName FloatType "B")))))]
test_parse_fn = goodStatements
"?FNAN2$(1,\"X\")"
[(1,PrintS [FnX (VarName StringType "AN2") [(LitX (FloatLit 1)), (LitX (StringLit "X"))]])]
test_parse_end = goodStatements
"END"
[(1,EndS)]
test_parse_stop = goodStatements
"STOP"
[(1,StopS)]
fl v = LitX (FloatLit v)
sl s = LitX (StringLit s)
fv s = VarX (ScalarVar (VarName FloatType s))
iv s = VarX (ScalarVar (VarName IntType s))
sv s = VarX (ScalarVar (VarName StringType s))
test_primitive_expressions = TestList [
"round number" ~: goodExpr "234" (fl 234),
"float" ~: goodExpr "2.3" (fl 2.3),
"string" ~: goodExpr "\"Work\"" (sl "Work"),
"builtin" ~: goodExpr "SIN(1)" (BuiltinX SinBI [(fl 1)]),
"builtin2" ~: goodExpr "LEFT$(A$,1)" (BuiltinX LeftBI [sv "A", fl 1]),
"fn" ~: goodExpr "FNAB(1,2,3)" (FnX (VarName FloatType "AB") [fl 1, fl 2, fl 3]),
"float var" ~: goodExpr "BR" (fv "BR"),
"int var" ~: goodExpr "BR%" (iv "BR"),
"string var" ~: goodExpr "BR$" (sv "BR"),
"array var" ~: goodExpr "AB(20, 5)" (VarX (ArrVar (VarName FloatType "AB") [fl 20, fl 5]))
]
test_operators = TestList [
"unary +" ~: goodExpr "+A" (fv "A"),
"unary -" ~: goodExpr "-A" (MinusX (fv "A")),
"num add" ~: goodExpr "1+2" (BinX AddOp (fl 1) (fl 2)),
"num sub" ~: goodExpr "1-2" (BinX SubOp (fl 1) (fl 2)),
"num mul" ~: goodExpr "1*2" (BinX MulOp (fl 1) (fl 2)),
"num div" ~: goodExpr "1/2" (BinX DivOp (fl 1) (fl 2)),
"num pow" ~: goodExpr "1^2" (BinX PowOp (fl 1) (fl 2)),
"num eq" ~: goodExpr "1=2" (BinX EqOp (fl 1) (fl 2)),
"num ne" ~: goodExpr "1<>2" (BinX NEOp (fl 1) (fl 2)),
"num lt" ~: goodExpr "1<2" (BinX LTOp (fl 1) (fl 2)),
"num gt" ~: goodExpr "1>2" (BinX GTOp (fl 1) (fl 2)),
"string concat" ~: goodExpr "\"A\"+\"B\"" (BinX AddOp (sl "A") (sl "B")),
"string lt" ~: goodExpr "\"A\"<\"B\"" (BinX LTOp (sl "A") (sl "B")),
"string gt" ~: goodExpr "\"A\">\"B\"" (BinX GTOp (sl "A") (sl "B")),
"string eq" ~: goodExpr "\"A\"=\"B\"" (BinX EqOp (sl "A") (sl "B")),
"and" ~: goodExpr "1=2AND3=4" (BinX AndOp (BinX EqOp (fl 1) (fl 2)) (BinX EqOp (fl 3) (fl 4))),
"or" ~: goodExpr "1=2OR3=4" (BinX OrOp (BinX EqOp (fl 1) (fl 2)) (BinX EqOp (fl 3) (fl 4))),
"not" ~: goodExpr "NOT1" (NotX (fl 1))
]
test_associativity = TestList [
"or assoc" ~: goodExpr "1OR2OR3" (BinX OrOp (BinX OrOp (fl 1) (fl 2)) (fl 3)),
"and assoc" ~: goodExpr "1AND2AND3" (BinX AndOp (BinX AndOp (fl 1) (fl 2)) (fl 3)),
"eq assoc" ~: goodExpr "1=2=3" (BinX EqOp (BinX EqOp (fl 1) (fl 2)) (fl 3)),
"ne assoc" ~: goodExpr "1<>2<>3" (BinX NEOp (BinX NEOp (fl 1) (fl 2)) (fl 3)),
"lt assoc" ~: goodExpr "1<2<3" (BinX LTOp (BinX LTOp (fl 1) (fl 2)) (fl 3)),
"le assoc" ~: goodExpr "1<=2<=3" (BinX LEOp (BinX LEOp (fl 1) (fl 2)) (fl 3)),
"gt assoc" ~: goodExpr "1>2>3" (BinX GTOp (BinX GTOp (fl 1) (fl 2)) (fl 3)),
"ge assoc" ~: goodExpr "1>=2>=3" (BinX GEOp (BinX GEOp (fl 1) (fl 2)) (fl 3)),
"add assoc" ~: goodExpr "1+2+3" (BinX AddOp (BinX AddOp (fl 1) (fl 2)) (fl 3)),
"sub assoc" ~: goodExpr "1-2-3" (BinX SubOp (BinX SubOp (fl 1) (fl 2)) (fl 3)),
"mul assoc" ~: goodExpr "1*2*3" (BinX MulOp (BinX MulOp (fl 1) (fl 2)) (fl 3)),
"div assoc" ~: goodExpr "1/2/3" (BinX DivOp (BinX DivOp (fl 1) (fl 2)) (fl 3)),
"pow assoc" ~: goodExpr "1^2^3" (BinX PowOp (fl 1) (BinX PowOp (fl 2) (fl 3)))
]
test_precedence = TestList [
"and beats or" ~: goodExpr "1OR2AND3" (BinX OrOp (fl 1) (BinX AndOp (fl 2) (fl 3))),
"not beats and" ~: goodExpr "NOT1AND2" (BinX AndOp (NotX (fl 1)) (fl 2)),
"eq beats not" ~: goodExpr "NOT1=2" (NotX (BinX EqOp (fl 1) (fl 2))),
"eq class" ~: goodExpr "1=2<>3<4<=5>6>=7" (BinX GEOp (BinX GTOp (BinX LEOp (BinX LTOp (BinX NEOp (BinX EqOp (fl 1) (fl 2)) (fl 3)) (fl 4)) (fl 5)) (fl 6)) (fl 7)),
"eq class rev" ~: goodExpr "1>=2>3<=4<5<>6=7" (BinX EqOp (BinX NEOp (BinX LTOp (BinX LEOp (BinX GTOp (BinX GEOp (fl 1) (fl 2)) (fl 3)) (fl 4)) (fl 5)) (fl 6)) (fl 7)),
"add beats eq" ~: goodExpr "1=2+3" (BinX EqOp (fl 1) (BinX AddOp (fl 2) (fl 3))),
"add class" ~: goodExpr "1+2-3" (BinX SubOp (BinX AddOp (fl 1) (fl 2)) (fl 3)),
"add class rev" ~: goodExpr "1-2+3" (BinX AddOp (BinX SubOp (fl 1) (fl 2)) (fl 3)),
"mul beats add" ~: goodExpr "1+2*3" (BinX AddOp (fl 1) (BinX MulOp (fl 2) (fl 3))),
"mul class" ~: goodExpr "1*2/3" (BinX DivOp (BinX MulOp (fl 1) (fl 2)) (fl 3)),
"mul class rev" ~: goodExpr "1/2*3" (BinX MulOp (BinX DivOp (fl 1) (fl 2)) (fl 3)),
"pow beats mul" ~: goodExpr "1*2^3" (BinX MulOp (fl 1) (BinX PowOp (fl 2) (fl 3))),
"unary - beats pow" ~: goodExpr "-A^B" (BinX PowOp (MinusX (fv "A")) (fv "B"))
]
test_parentheses = TestList [
goodExpr "(1+2)*3" (BinX MulOp (ParenX (BinX AddOp (fl 1) (fl 2))) (fl 3)),
goodExpr "-(1+2)*3" (BinX MulOp (MinusX (ParenX (BinX AddOp (fl 1) (fl 2)))) (fl 3)),
goodExpr "(1+(2-3))*4" (BinX MulOp (ParenX (BinX AddOp (fl 1) (ParenX (BinX SubOp (fl 2) (fl 3))))) (fl 4))
]
test_embedded_spaces_are_ignored = TestList [
goodExpr "1 * 2 +3- 4" (BinX SubOp (BinX AddOp (BinX MulOp (fl 1) (fl 2)) (fl 3)) (fl 4)),
goodExpr "CHR$ ( A ) " (BuiltinX ChrBI [fv "A"])
]
test_bad_expressions = TestList [
"starting with operator" ~: badExpr "/4" "UNEXPECTED /",
"ending with operator" ~: badExpr "4/" "UNEXPECTED END OF LINE",
"double operator" ~: badExpr "4/*4" "UNEXPECTED *"
]