module Language.VintageBasic.Interpreter_test where import Test.HUnit import Language.VintageBasic.Asserts import Language.VintageBasic.Executer import Language.VintageBasic.BasicMonad import Control.Monad.CPST.DurableTraps testProgramOutput source expected = TestCase $ do i <- noInput o <- mkOutput runProgram i o (execute "" source >> done) assertOutputEq o expected testProgramOutputWithInput source input expected = TestCase $ do i <- mkInput input o <- mkOutput runProgram i o (execute "" source >> done) assertOutputEq o expected testExpression source expected = testProgramOutput ("1?" ++ source ++ "\n") (expected ++ "\n") testExpressions ts = TestList $ map (uncurry testExpression) ts test_does_nothing = testProgramOutput "" "" test_executes_lines_in_sequence = testProgramOutput "10 ?\"A\"\n20 ?\"B\"\n" "A\nB\n" test_var_defaults = testProgramOutput "10 ?A:?A%:?A$\n" " 0 \n 0 \n\n" test_float_assignment = testProgramOutput "10 A=5:PRINTA\n" " 5 \n" test_int_assignment_pos = testProgramOutput "10 A%=5.9:?A%\n" " 5 \n" test_int_assignment_neg = testProgramOutput "10 A%=-5.1:?A%\n" "-6 \n" test_string_assignment = testProgramOutput "10 A$=\"WOW\":?A$\n" "WOW\n" test_variables_of_different_types_with_the_same_name_are_different = testProgramOutput "10 A=4.5:A%=3:A$=\"HI\"\n20 ?A:?A%:?A$\n" " 4.5 \n 3 \nHI\n" test_var_names_are_only_significant_to_two_characters = testProgramOutput "10 SOMETHING=4:SAMETHING=2:?SOWHAT:?SALLY\n" " 4 \n 2 \n" test_first_digit_of_var_name_is_significant_too = testProgramOutput "10 SOMETHING12=4:SOMETHING22=2:?SOMETHING13:?SOMETHING24\n" " 4 \n 2 \n" test_dim = testProgramOutput "10 DIMA(20):A(0)=5:A(20)=6:PRINTA(20);A(0)\n" " 6 5 \n" test_dim_default_size_is_at_least_10 = testProgramOutput "10 A(10)=6:PRINTA(10)\n" " 6 \n" test_dim_default_size_is_at_most_10 = testProgramOutput "10 A(11)=6:PRINTA(11)\n" "!OUT OF ARRAY BOUNDS IN LINE 10\n" test_dim_with_negative_dimension = testProgramOutput "10 DIMA(-1)\n" "!NEGATIVE ARRAY DIM IN LINE 10\n" test_multiple_dimensions = testProgramOutput "10 DIMA(1,3):A(1,3)=12:PRINTA(1,3)\n" " 12 \n" test_array_dim_rounding = testProgramOutput "10 DIMA(11.9):A(11.9)=5:?A(11.1):A(12)=6:?A(6)\n" " 5 \n!OUT OF ARRAY BOUNDS IN LINE 10\n" test_for_loop_with_array = testProgramOutput "10 FORI=1TO10:A(I)=10-I:NEXT:PRINTA(3)\n" " 7 \n" test_int_array = testProgramOutput "10 A%(5)=2.8:?A%(1);A%(5)\n" " 0 2 \n" test_string_array = testProgramOutput "10 A$(5)=\"HI\":?A$(1);A$(5)\n" "HI\n" test_arithmetic_expression = testProgramOutput "10 A=5:?A^2+3*A-1/2\n" " 39.5 \n" test_parentheses = testProgramOutput "10 A=5:?A^(2+3)*(A-1)/2\n" " 6250 \n" test_relational_operators_on_numbers = TestList $ [ "eq" ~: testProgramOutput "10 ?1= 1:?1= 2:?2= 1\n" "-1 \n 0 \n 0 \n", "ne" ~: testProgramOutput "10 ?1<>1:?1<>2:?2<>1\n" " 0 \n-1 \n-1 \n", "lt" ~: testProgramOutput "10 ?1< 1:?1< 2:?2< 1\n" " 0 \n-1 \n 0 \n", "le" ~: testProgramOutput "10 ?1<=1:?1<=2:?2<=1\n" "-1 \n-1 \n 0 \n", "gt" ~: testProgramOutput "10 ?1> 1:?1> 2:?2> 1\n" " 0 \n 0 \n-1 \n", "ge" ~: testProgramOutput "10 ?1>=1:?1>=2:?2>=1\n" "-1 \n 0 \n-1 \n" ] test_relational_operators_on_strings = TestList $ [ "eq" ~: testProgramOutput "10 ?\"A\"= \"A\":?\"A\"= \"B\":?\"B\"= \"A\"\n" "-1 \n 0 \n 0 \n", "ne" ~: testProgramOutput "10 ?\"A\"<>\"A\":?\"A\"<>\"B\":?\"B\"<>\"A\"\n" " 0 \n-1 \n-1 \n", "lt" ~: testProgramOutput "10 ?\"A\"< \"A\":?\"A\"< \"B\":?\"B\"< \"A\"\n" " 0 \n-1 \n 0 \n", "le" ~: testProgramOutput "10 ?\"A\"<=\"A\":?\"A\"<=\"B\":?\"B\"<=\"A\"\n" "-1 \n-1 \n 0 \n", "gt" ~: testProgramOutput "10 ?\"A\"> \"A\":?\"A\"> \"B\":?\"B\"> \"A\"\n" " 0 \n 0 \n-1 \n", "ge" ~: testProgramOutput "10 ?\"A\">=\"A\":?\"A\">=\"B\":?\"B\">=\"A\"\n" "-1 \n 0 \n-1 \n" ] test_boolean_logic = TestList $ [ "and" ~: testProgramOutput "10 ?0AND0:?0AND-1:?-1AND0:?-1AND-1\n" " 0 \n 0 \n 0 \n-1 \n", "or" ~: testProgramOutput "10 ?0OR 0:?0OR -1:?-1OR 0:?-1OR -1\n" " 0 \n-1 \n-1 \n-1 \n", "not" ~: testProgramOutput "10 ?NOT0:?NOT-1:?NOT2\n" "-1 \n 0 \n 0 \n" ] test_boolean_logic_with_nonintegers = TestList $ [ "and" ~: testProgramOutput "10 ?0.1AND-0.1:?0.1AND0:?0AND0.1\n" " .1 \n 0 \n 0 \n", "or" ~: testProgramOutput "10 ?0OR-0.1:?0.1OR0:?0OR0\n" "-.1 \n .1 \n 0 \n", "not" ~: testProgramOutput "10 ?NOT-0.9:?NOT-0.1:?NOT0.1:?NOT0.9:?NOT1.1\n" " 0 \n 0 \n 0 \n 0 \n 0 \n" ] test_negation = testProgramOutput "10 A=5:?-A:?-(-A)\n" "-5 \n 5 \n" test_abs = testExpressions [ ("ABS()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("ABS(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("ABS( 5.7)", " 5.7 "), ("ABS( 0 )", " 0 "), ("ABS(-5.7)", " 5.7 "), ("ABS(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_asc = testExpressions [ ("ASC()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("ASC(65)", "!TYPE MISMATCH IN LINE 1"), ("ASC(\"\")", "!INVALID ARGUMENT IN LINE 1"), ("ASC(\"A\")", " 65 "), ("ASC(\"ABC\")", " 65 "), ("ASC(\"0\")", " 48 "), ("ASC(\"0\",\"0\")", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_atn = testExpressions [ ("ATN()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("ATN(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("ATN(0)", " 0 " ), ("ATN(1)", " .7853982 "), ("ATN(10)", " 1.4711276 "), ("ATN(-1)", "-.7853982 "), ("ATN(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_chr = testExpressions [ ("CHR$()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("CHR$(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("CHR$(-1)", "!INVALID ARGUMENT IN LINE 1"), ("CHR$(10)", "\n"), ("CHR$(13)", "\r"), ("CHR$(32)", " "), ("CHR$(48)", "0"), ("CHR$(65)", "A"), ("CHR$(256)", "!INVALID ARGUMENT IN LINE 1"), ("CHR$(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_cos = testExpressions [ ("COS()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("COS(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("COS(0)", " 1 "), ("COS(1)", " .5403023 "), ("COS(1.5707964)", "-4.371139E-8 "), ("COS(3.1415927)", "-1 "), ("COS(4.712389)", " 1.1924881E-8 "), ("COS(6.2831855)", " 1 "), ("COS(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_exp = testExpressions [ ("EXP()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("EXP(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("EXP(-1)", " .36787945 "), ("EXP(0)", " 1 "), ("EXP(1)", " 2.7182817 "), ("EXP(2)", " 7.389056 "), ("EXP(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_int = testExpressions [ ("INT()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("INT(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("INT(-23.1)", "-24 "), ("INT(23)", " 23 "), ("INT(23.1)", " 23 "), ("INT(23.9)", " 23 "), ("INT(24)", " 24 "), ("INT(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_left = testExpressions [ ("LEFT$()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("LEFT$(\"A\")", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("LEFT$(\"ABC\",\"A\")", "!TYPE MISMATCH IN LINE 1"), ("LEFT$(1,1)", "!TYPE MISMATCH IN LINE 1"), ("LEFT$(\"ABC\",-1)", "!INVALID ARGUMENT IN LINE 1"), ("LEFT$(\"ABC\",-.1)", "!INVALID ARGUMENT IN LINE 1"), ("LEFT$(\"ABC\", 0)", ""), ("LEFT$(\"ABC\", 1)", "A"), ("LEFT$(\"ABC\", 2)", "AB"), ("LEFT$(\"ABC\", 2.9)", "AB"), ("LEFT$(\"ABC\", 3)", "ABC"), ("LEFT$(\"ABC\", 4)", "ABC"), ("LEFT$(\"A\",1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_len = testExpressions [ ("LEN()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("LEN(1)", "!TYPE MISMATCH IN LINE 1"), ("LEN(\"\")", " 0 "), ("LEN(\"A\")", " 1 "), ("LEN(\" \" + CHR$(0) + \"23$!\")", " 6 "), ("LEN(\"A\", \"B\")", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_log = testExpressions [ ("LOG()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("LOG(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("LOG(-1)", "!INVALID ARGUMENT IN LINE 1"), ("LOG( 0)", "!INVALID ARGUMENT IN LINE 1"), ("LOG( 0.1)", "-2.3025851 "), ("LOG( 1)", " 0 "), ("LOG(2.7182817)", " .99999994 "), ("LOG(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_mid = testExpressions [ ("MID$()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("MID$(\"ABC\")", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("MID$(1,1)", "!TYPE MISMATCH IN LINE 1"), ("MID$(\"ABC\",\"A\")", "!TYPE MISMATCH IN LINE 1"), ("MID$(\"ABC\",-1)", "!INVALID ARGUMENT IN LINE 1"), ("MID$(\"ABC\", 0)", "!INVALID ARGUMENT IN LINE 1"), ("MID$(\"ABC\", 1)", "ABC"), ("MID$(\"ABC\", 2)", "BC"), ("MID$(\"ABC\", 3)", "C"), ("MID$(\"ABC\", 4)", ""), ("MID$(1,1)", "!TYPE MISMATCH IN LINE 1"), ("MID$(\"ABC\",\"A\",1)", "!TYPE MISMATCH IN LINE 1"), ("MID$(\"ABC\",1,\"A\")", "!TYPE MISMATCH IN LINE 1"), ("MID$(\"ABC\",-1, 1)", "!INVALID ARGUMENT IN LINE 1"), ("MID$(\"ABC\", 0, 1)", "!INVALID ARGUMENT IN LINE 1"), ("MID$(\"ABC\", 0,-1)", "!INVALID ARGUMENT IN LINE 1"), ("MID$(\"ABC\", 1,0)", ""), ("MID$(\"ABC\", 1,1)", "A"), ("MID$(\"ABC\", 1,2)", "AB"), ("MID$(\"ABC\", 1,3)", "ABC"), ("MID$(\"ABC\", 1,4)", "ABC"), ("MID$(\"ABC\", 2,0)", ""), ("MID$(\"ABC\", 2,1)", "B"), ("MID$(\"ABC\", 2,2)", "BC"), ("MID$(\"ABC\", 2.9, 2.9)", "BC"), ("MID$(\"ABC\", 2,3)", "BC"), ("MID$(\"ABC\", 3,0)", ""), ("MID$(\"ABC\", 3,1)", "C"), ("MID$(\"ABC\", 3,2)", "C"), ("MID$(\"ABC\", 4,0)", ""), ("MID$(\"ABC\", 4,1)", ""), ("MID$(\"ABC\", 1, 1, 1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_right = testExpressions [ ("RIGHT$()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("RIGHT$(\"A\")", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("RIGHT$(\"ABC\",\"A\")", "!TYPE MISMATCH IN LINE 1"), ("RIGHT$(1,1)", "!TYPE MISMATCH IN LINE 1"), ("RIGHT$(\"ABC\",-1)", "!INVALID ARGUMENT IN LINE 1"), ("RIGHT$(\"ABC\", 0)", ""), ("RIGHT$(\"ABC\", 1)", "C"), ("RIGHT$(\"ABC\", 2)", "BC"), ("RIGHT$(\"ABC\", 2.9)", "BC"), ("RIGHT$(\"ABC\", 3)", "ABC"), ("RIGHT$(\"ABC\", 4)", "ABC"), ("RIGHT$(\"A\",1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_rnd = testProgramOutput "1 ?RND(-1):?RND(1):?RND(1):?RND(0):?RND(10):?RND(-2.5)\n" " .35925463 \n .3306234 \n .13648525 \n .13648525 \n .53864 \n .10321328 \n" test_rnd_errors = testExpressions [ ("RND()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("RND(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("RND(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] -- RANDOMIZE is difficult to test because it depends on system time test_sgn = testExpressions [ ("SGN()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("SGN(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("SGN( 5.7)", " 1 "), ("SGN( 0 )", " 0 "), ("SGN(-5.7)", "-1 "), ("SGN(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_sin = testExpressions [ ("SIN()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("SIN(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("SIN(0)", " 0 "), ("SIN(1)", " .84147096 "), ("SIN(1.5707964)", " 1 "), ("SIN(3.1415927)", "-8.742278E-8 "), ("SIN(4.712389)", "-1 "), ("SIN(6.2831855)", " 1.7484555E-7 "), ("SIN(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_spc = testExpressions [ ("SPC()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("SPC(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("SPC(-1)", "!INVALID ARGUMENT IN LINE 1"), ("SPC(0)", ""), ("SPC(1)", " "), ("SPC(2.9)", " "), ("SPC(10)", " "), ("SPC(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_sqr = testExpressions [ ("SQR()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("SQR(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("SQR(-1)", "!INVALID ARGUMENT IN LINE 1"), ("SQR(0)", " 0 "), ("SQR(2)", " 1.4142135 "), ("SQR(4)", " 2 "), ("SQR(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_str = testExpressions [ ("STR$()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("STR$(\"ABC\")", "!TYPE MISMATCH IN LINE 1"), ("STR$(1E2)", " 100"), ("STR$(.000000001)", " 1.E-9"), ("STR$(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_tan = testExpressions [ ("TAN()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("TAN(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("TAN(0)", " 0 "), ("TAN(.7853982)", " 1 "), ("TAN(1)", " 1.5574077 "), ("TAN(1.5707964)", "-22877332 "), ("TAN(3.1415927)", " 8.742278E-8 "), ("TAN(4.712389)", "-83858280 "), ("TAN(6.2831855)", " 1.7484555E-7 "), ("TAN(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_val = testExpressions [ ("VAL()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("VAL(1)", "!TYPE MISMATCH IN LINE 1"), ("VAL(\"\")", " 0 "), ("VAL(\"123\")", " 123 "), ("VAL(\" 123 \")", " 123 "), ("VAL(\" 1 2\")", " 1 "), ("VAL(\"1A\")", " 1 "), ("VAL(\"A\")", " 0 "), ("VAL(\"1\",\"1\")", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_goto_forwards = testProgramOutput "10GOTO30\n20?20\n30?30\n" " 30 \n" test_goto_backwards = testProgramOutput "10GOTO50\n20?20\n30?30\n40END\n50GOTO30\n" " 30 \n" test_gosub = testProgramOutput "1?1\n2GOSUB5\n3?3\n4END\n5?5\n6RETURN\n" " 1 \n 5 \n 3 \n" test_nested_gosub = testProgramOutput (unlines [ " 1 ?1 ", " 2 GOSUB5 ", " 3 ?3 ", " 4 END ", " 5 ?5 ", " 6 GOSUB9 ", " 7 ?7 ", " 8 RETURN ", " 9 ?9 ", " 10 RETURN " ]) " 1 \n 5 \n 9 \n 7 \n 3 \n" test_return_does_double_duty = testProgramOutput (unlines [ "10 ?10:GOSUB30", "20 ?20:END", "30 ?30:GOSUB40", "40 ?40:RETURN" ]) " 10 \n 30 \n 40 \n 40 \n 20 \n" test_on_goto = TestList [ testProgramOutput "1 ON\"A\"GOTO2\n" "!TYPE MISMATCH IN LINE 1\n", testProgramOutput "1 A=-1 :ONAGOTO3,4,5\n2 ?2\n3 ?3\n4 ?4\n5 ?5\n6 ?6\n" " 2 \n 3 \n 4 \n 5 \n 6 \n", testProgramOutput "1 A=-0.1:ONAGOTO3,4,5\n2 ?2\n3 ?3\n4 ?4\n5 ?5\n6 ?6\n" " 2 \n 3 \n 4 \n 5 \n 6 \n", testProgramOutput "1 A= 0 :ONAGOTO3,4,5\n2 ?2\n3 ?3\n4 ?4\n5 ?5\n6 ?6\n" " 2 \n 3 \n 4 \n 5 \n 6 \n", testProgramOutput "1 A= 1 :ONAGOTO3,4,5\n2 ?2\n3 ?3\n4 ?4\n5 ?5\n6 ?6\n" " 3 \n 4 \n 5 \n 6 \n", testProgramOutput "1 A= 2 :ONAGOTO3,4,5\n2 ?2\n3 ?3\n4 ?4\n5 ?5\n6 ?6\n" " 4 \n 5 \n 6 \n", testProgramOutput "1 A= 2.9:ONAGOTO3,4,5\n2 ?2\n3 ?3\n4 ?4\n5 ?5\n6 ?6\n" " 4 \n 5 \n 6 \n", testProgramOutput "1 A= 3 :ONAGOTO3,4,5\n2 ?2\n3 ?3\n4 ?4\n5 ?5\n6 ?6\n" " 5 \n 6 \n", testProgramOutput "1 A= 4 :ONAGOTO3,4,5\n2 ?2\n3 ?3\n4 ?4\n5 ?5\n6 ?6\n" " 2 \n 3 \n 4 \n 5 \n 6 \n", testProgramOutput "1 A= 2 :ONAGOTO3,5,4\n2 ?2\n3 ?3\n4 ?4\n5 ?5\n6 ?6\n" " 5 \n 6 \n" ] test_on_gosub = TestList [ testProgramOutput "1 ON\"A\"GOSUB2\n" "!TYPE MISMATCH IN LINE 1\n", testProgramOutput "1 A=-1 :ONAGOSUB3,4,5:END\n2 ?2:RETURN\n3 ?3:RETURN\n4 ?4:RETURN\n5 ?5:RETURN\n6 ?6:RETURN\n" "", testProgramOutput "1 A=-0.1:ONAGOSUB3,4,5:END\n2 ?2:RETURN\n3 ?3:RETURN\n4 ?4:RETURN\n5 ?5:RETURN\n6 ?6:RETURN\n" "", testProgramOutput "1 A= 0 :ONAGOSUB3,4,5:END\n2 ?2:RETURN\n3 ?3:RETURN\n4 ?4:RETURN\n5 ?5:RETURN\n6 ?6:RETURN\n" "", testProgramOutput "1 A= 1 :ONAGOSUB3,4,5:END\n2 ?2:RETURN\n3 ?3:RETURN\n4 ?4:RETURN\n5 ?5:RETURN\n6 ?6:RETURN\n" " 3 \n", testProgramOutput "1 A= 2 :ONAGOSUB3,4,5:END\n2 ?2:RETURN\n3 ?3:RETURN\n4 ?4:RETURN\n5 ?5:RETURN\n6 ?6:RETURN\n" " 4 \n", testProgramOutput "1 A= 2.9:ONAGOSUB3,4,5:END\n2 ?2:RETURN\n3 ?3:RETURN\n4 ?4:RETURN\n5 ?5:RETURN\n6 ?6:RETURN\n" " 4 \n", testProgramOutput "1 A= 3 :ONAGOSUB3,4,5:END\n2 ?2:RETURN\n3 ?3:RETURN\n4 ?4:RETURN\n5 ?5:RETURN\n6 ?6:RETURN\n" " 5 \n", testProgramOutput "1 A= 4 :ONAGOSUB3,4,5:END\n2 ?2:RETURN\n3 ?3:RETURN\n4 ?4:RETURN\n5 ?5:RETURN\n6 ?6:RETURN\n" "", testProgramOutput "1 A= 2 :ONAGOSUB3,5,4:END\n2 ?2:RETURN\n3 ?3:RETURN\n4 ?4:RETURN\n5 ?5:RETURN\n6 ?6:RETURN\n" " 5 \n" ] test_if = TestList [ testProgramOutput "1 IF\"A\"THEN?1\n" "!TYPE MISMATCH IN LINE 1\n", testProgramOutput "1 IF-11THEN?1:?2\n2 ?3\n" " 1 \n 2 \n 3 \n", testProgramOutput "1 IF 0THEN?1:?2\n2 ?3\n" " 3 \n", testProgramOutput "1 IF 1THEN?1:?2\n2 ?3\n" " 1 \n 2 \n 3 \n", testProgramOutput "1 IF .1THEN?1:?2\n2 ?3\n" " 1 \n 2 \n 3 \n", testProgramOutput "1 IF -.1THEN?1:?2\n2 ?3\n" " 1 \n 2 \n 3 \n", testProgramOutput "1 IF 2.1THEN?1:?2\n2 ?3\n" " 1 \n 2 \n 3 \n" ] test_for = TestList [ testProgramOutput "1 FORA$=1TO10\n" "!TYPE MISMATCH IN LINE 1\n", testProgramOutput "1 FORA%=1TO10\n" "!TYPE MISMATCH IN LINE 1\n", testProgramOutput "1 FORA=\"A\"TO3\n" "!TYPE MISMATCH IN LINE 1\n", testProgramOutput "1 FORA=1TO\"A\"\n" "!TYPE MISMATCH IN LINE 1\n", testProgramOutput "1 FORA=1TO3:?A:NEXT:?99:?A\n" " 1 \n 2 \n 3 \n 99 \n 4 \n", testProgramOutput "1 FORA=2TO8STEP2:?A:NEXT:?99:?A\n" " 2 \n 4 \n 6 \n 8 \n 99 \n 10 \n", testProgramOutput "1 FORA=8TO2STEP-2:?A:NEXT:?99:?A\n" " 8 \n 6 \n 4 \n 2 \n 99 \n 0 \n", testProgramOutput "1 FORA=1TO0:?A:NEXT:?99:?A\n" " 1 \n 99 \n 2 \n", testProgramOutput "1 FORA=1TO3\n2 ?A\n3 NEXT\n" " 1 \n 2 \n 3 \n", testProgramOutput "1 FORA=1TO3\n2 ?A\n3 NEXTA\n" " 1 \n 2 \n 3 \n", testProgramOutput "1 FORA=1TO3\n2 ?A\n3 NEXTB\n" " 1 \n!NEXT WITHOUT FOR ERROR (VAR B) IN LINE 3\n" ] test_nested_for = testProgramOutput (unlines [ "1 FORA=10TO20STEP10", "2 FORB=1TO3", "3 ?A+B", "4 NEXT", "5 NEXT" ]) " 11 \n 12 \n 13 \n 21 \n 22 \n 23 \n" test_improperly_nested_for = testProgramOutput (unlines [ "1 FORA=10TO20STEP10", "2 FORB=1TO3", "3 ?A+B", "4 NEXTA", "5 NEXTB" ]) " 11 \n 21 \n 32 \n!NEXT WITHOUT FOR ERROR (VAR A) IN LINE 4\n" test_return_in_for = testProgramOutput (unlines [ "10 GOSUB40", "20 NEXT", "30 END", "40 FORI=1TO3", "50 PRINT\"HELLO\"", "60 RETURN" ]) "HELLO\nHELLO\n!RETURN WITHOUT GOSUB ERROR IN LINE 60\n" test_print_literals = testProgramOutput "10 PRINT\"NUMBER\";5;\"AND\";-2\n" "NUMBER 5 AND-2 \n" test_float_representation = testProgramOutput "10 ?1E17:?5.55555\n" " 1.E+17 \n 5.55555 \n" test_tab = testProgramOutput "10 ?TAB(4);\"A\";TAB(10);\"B\";TAB(11);\"C\";TAB(4);\"D\"\n" " A BCD\n" test_tab_with_nonintegers = testProgramOutput "10 ?TAB(2.9);\"A\"\n" " A\n" test_tab_with_newlines_and_linefeeds = testProgramOutput "10 ?\"HI\";CHR$(13);\"TO\";TAB(5);\"YOU\";CHR$(10);\"AND\";TAB(10);\"ME\"\n" "HI\rTO YOU\nAND ME\n" test_tab_wrongargs = testExpressions [ ("TAB()", "!WRONG NUMBER OF ARGUMENTS IN LINE 1"), ("TAB(\"A\")", "!TYPE MISMATCH IN LINE 1"), ("TAB(-1)", "!INVALID ARGUMENT IN LINE 1"), ("TAB(1,1)", "!WRONG NUMBER OF ARGUMENTS IN LINE 1") ] test_print_zones = testProgramOutput "1 ?\"A\",\"B\",,\"C\":?\"123456789012345\",\"X\":?1,;:?2,:?,3\n" "A B C\n123456789012345 X\n 1 2 3 \n" test_input = TestList [ "multiple values and types" ~: testProgramOutputWithInput "1 INPUT\"WHAT\";A$,B,CD$(9),M%\n2 ?:?A$:?B:?CD$(9):?M%\n" " Bob 4 , 3 ,2 1, 5.2\n" "WHAT? \nBob 4\n 3 \n2 1\n 5 \n", "trims whitespace" ~: testProgramOutputWithInput "1 INPUTA$:?:?A$\n" " \t Bob 4 \n" "? \nBob 4\n", "quoted strings" ~: testProgramOutputWithInput "1 INPUTA$\n2 ?:?A$\n" " \t\" Bob 4 \" \t \n" "? \n Bob 4 \n", "re-prompts for invalid number" ~: testProgramOutputWithInput "1 INPUTA:?:?A\n" "X\n4\n" "? !NUMBER EXPECTED - RETRY INPUT LINE\n? \n 4 \n", "re-prompts if not enough data on line" ~: testProgramOutputWithInput "1 INPUT\"NUM\";A,B:?:?A:?B\n" "3\n4\n" "NUM? ?? \n 3 \n 4 \n", "extra input ignored" ~: testProgramOutputWithInput "1 INPUTA\n" "1,2\n" "? !EXTRA INPUT IGNORED\n", "error if we reach eof without enough data" ~: testProgramOutputWithInput "1 INPUTA,B:?:?A:?B\n" "4\n" "? ?? !END OF INPUT IN LINE 1\n", "error if we reach eof without enough data (no newline)" ~: testProgramOutputWithInput "1 INPUTA:?:?A\n" "" "? !END OF INPUT IN LINE 1\n" ] test_read_data = TestList [ "multiple values and types" ~: testProgramOutput "1 READ A$,B,CD$(9),M%\n2 ?A$:?B:?CD$(9):?M%\n3 DATA Bob 4 , 3 ,2 1, 5.2\n" "Bob 4\n 3 \n2 1\n 5 \n", "trims whitespace" ~: testProgramOutput "1 READA$:?A$\n2 DATA \t Bob 4 \n" "Bob 4\n", "quoted strings" ~: testProgramOutput "1 READA$\n2 ?A$\n3 DATA \t\" Bob 4 \" \t \n" " Bob 4 \n", "type mismatch for invalid number" ~: testProgramOutput "1 READA:?:?A\n2 DATAX\n" "!TYPE MISMATCH IN LINE 1\n", "data on multiple lines" ~: testProgramOutput "1 READA,B:?A:?B\n2 DATA3\n3 DATA4\n" " 3 \n 4 \n", "extra data ignored" ~: testProgramOutput "1 READA:?A\n2 DATA1,2\n" " 1 \n", "error if we reach eof without enough data" ~: testProgramOutput "1 READA,B:?A:?B\n2 DATA4\n" "!OUT OF DATA IN LINE 1\n", "don't care where data appears" ~: testProgramOutput (unlines [ "1 DATA A:?7", "2 READA$,B$,C:DATAmore", "3 DATA 5", "4 ?A$:?B$:?C" ]) "A:?7\nmore\n 5 \n" ] test_restore = testProgramOutput (unlines [ "1 READ A,B,C:RESTORE 3:READ D,E:RESTORE:READ F,G,H:?A:?B:?C:?D:?E:?F:?G:?H\n", "2 DATA1", "3 DATA2", "4 DATA3" ]) " 1 \n 2 \n 3 \n 2 \n 3 \n 1 \n 2 \n 3 \n" test_def_fn = TestList [ "simple float" ~: testProgramOutput "1 DEFFNA(X)=X^2+1:?FNA(4)\n" " 17 \n", "multi-arg float" ~: testProgramOutput "1 DEFFNA(X,Y,Z$)=LEN(Z$)^2+2*X+Y:?FNA(2,3,\"HELLO\")\n" " 32 \n", "multi-arg string" ~: testProgramOutput "1 DEFFNA$(X$,Y,Z)=MID$(X$,Y-1,Z+1):?FNA$(\"MAGILLICUDDY\",3,4)\n" "AGILL\n", "wrong num args" ~: testProgramOutput "1 DEFFNA(X)=X*2:?FNA()\n" "!WRONG NUMBER OF ARGUMENTS IN LINE 1\n", "type mismatch" ~: testProgramOutput "1 DEFFNA(X)=X*2:?A(\"X\")\n" "!TYPE MISMATCH IN LINE 1\n", "undefined" ~: testProgramOutput "1 ?FNA(5):DEFFNA(X)=X*2\n" "!UNDEFINED FUNCTION A IN LINE 1\n" ] test_stop = testProgramOutput "1 ?1:STOP:?2\n" " 1 \n" test_rem = testProgramOutput "1 ?1:REM COMMENT:?2\n2 ?3\n" " 1 \n 3 \n"