package interpreter import ( "fmt" "strings" "testing" "git.usebox.net/micro-lang/ast" "git.usebox.net/micro-lang/errors" "git.usebox.net/micro-lang/parser" "git.usebox.net/micro-lang/tokenizer" ) func run(input string) (any, error) { tr := tokenizer.NewTokenizer("-", strings.NewReader(input)) toks, err := tr.Scan() if err != nil { return nil, err } p := parser.NewParser(BuiltInTypes()) tree, err := p.Parse(toks) if err != nil { errs := err.(errors.ErrorList) return nil, errs.Errors[0] } i := NewInterpreter() var val any for _, expr := range tree { if val, err = i.Interp(expr); err != nil { return nil, err } } return val, nil } func expectNumber(t *testing.T, input string, n ast.Number) { val, err := run(input) if err != nil { t.Errorf("Unexpected error %s (input: %s)", err, input) } i, ok := val.(ast.Number) if !ok { t.Errorf("Number expected, got %T instead (input: %s)", val, input) } else if i != n { t.Errorf("%d expected, got %d instead (input: %s)", n, i, input) } } func expectBool(t *testing.T, input string, b bool) { val, err := run(input) if err != nil { t.Errorf("Unexpected error %s (input: %s)", err, input) } i, ok := val.(bool) if !ok { t.Errorf("bool expected, got %T instead (input: %s)", val, input) } else if i != b { t.Errorf("%v expected, got %v instead (input: %s)", b, i, input) } } func expectString(t *testing.T, input string, s string) { val, err := run(input) if err != nil { t.Errorf("unexpected error %s (input: %s)", err, input) } i, ok := val.(string) if !ok { t.Errorf("string expected, got %T instead (input: %s)", val, input) } else if i != s { t.Errorf("%s expected, got %s instead (input: %s)", s, i, input) } } func expectError(t *testing.T, input string, code errors.ErrCode) { _, err := run(input) if err == nil { t.Errorf("expected error and didn't happen (input: %s)", input) } if e, ok := err.(errors.Error); !ok { t.Errorf("error not an Error (input: %s): %s", input, err) } else { if e.Code != code { t.Errorf("error %d expected, got %d -> %s (input: %s)", code, e.Code, e, input) } } } func TestNumExpr(t *testing.T) { expectNumber(t, "1;", 1) expectNumber(t, "123;", 123) expectNumber(t, "0x123;", 0x123) expectNumber(t, "0b101;", 0b101) expectNumber(t, "1 + 1;", 2) expectNumber(t, "2 + 3 * 4;", 14) expectNumber(t, "(5 - (3 - 1)) + -1;", 2) } func TestInvalidOp(t *testing.T) { // these operators are only valid for numbers expectError(t, "true + \"a\";", errors.InvalidOperation) expectError(t, "\"a\" + false;", errors.InvalidOperation) expectError(t, "\"a\" + \"a\";", errors.InvalidOperation) expectError(t, "-false;", errors.InvalidOperation) expectError(t, "-true;", errors.InvalidOperation) expectError(t, "~false;", errors.InvalidOperation) expectError(t, "~true;", errors.InvalidOperation) expectError(t, "-\"a\";", errors.InvalidOperation) expectError(t, "~\"a\";", errors.InvalidOperation) for _, op := range []string{ "-", "+", "*", "/", "%", "&", "<<", ">>", "|", "^", "<", ">", "<=", ">=", } { expectError(t, fmt.Sprintf("1 %s true;", op), errors.InvalidOperation) expectError(t, fmt.Sprintf("true %s 1;", op), errors.InvalidOperation) expectError(t, fmt.Sprintf("1 %s \"a\";", op), errors.InvalidOperation) expectError(t, fmt.Sprintf("\"a\" %s 1;", op), errors.InvalidOperation) } // these are invalid with numbers too expectError(t, "1 / 0;", errors.InvalidOperation) expectError(t, "1 % 0;", errors.InvalidOperation) } func TestAssign(t *testing.T) { expectNumber(t, "var a number; a = 1;", 1) expectString(t, "var a string = \"v\"; var b string = a; b;", "v") expectBool(t, "var a number = 1; var b number = 2; var c number = 3; a = b = c; a == b && a== 3;", true) expectBool(t, "var a number; var b number = a = 1; a == b && a == 1;", true) // local expectNumber(t, `{ var a number = 1; if a != 1 { panic("1 expected"); } a = 2; if a != 2 { panic("2 expected"); } println(a = 3); a; }`, 3) // scopes and shadowing expectNumber(t, ` var a number = 1; if a != 1 { panic("1 expected"); } { var a number = 2; if a != 2 { panic("2 expected"); } } a;`, 1) expectError(t, "a = 1;", errors.UndefinedIdent) // syntax error; perhaps should be in the parser if _, err := run("var a number; (a) = 1;"); err == nil { t.Error("error expected by didn't happen") } if _, err := run("var a number; a + a = 1;"); err == nil { t.Error("error expected by didn't happen") } } func TestPrintln(t *testing.T) { expectNumber(t, "println();", 1) expectNumber(t, "println(1);", 2) expectNumber(t, "println(true);", 5) expectNumber(t, "println(\"hello\");", 6) expectNumber(t, "println(1, \"one\");", 5) } func TestEquality(t *testing.T) { expectBool(t, "true == true;", true) expectBool(t, "true == false;", false) expectBool(t, "false == true;", false) expectBool(t, "false == false;", true) expectBool(t, "1 == 1;", true) expectBool(t, "0 != 1;", true) expectBool(t, "\"a\" == \"a\";", true) expectBool(t, "\"a\" != \"b\";", true) expectBool(t, "true != true;", false) expectBool(t, "true != false;", true) expectBool(t, "false != true;", true) expectBool(t, "false != false;", false) expectError(t, "true == 1;", errors.TypeMismatch) expectError(t, "true == \"true\";", errors.TypeMismatch) expectError(t, "false == 0;", errors.TypeMismatch) expectError(t, "false == \"\";", errors.TypeMismatch) expectError(t, "false == \"false\";", errors.TypeMismatch) } func TestNot(t *testing.T) { expectBool(t, "!true;", false) expectBool(t, "!false;", true) expectBool(t, "!0;", true) expectBool(t, "!1;", false) expectBool(t, "!\"\";", true) expectBool(t, "!\"a\";", false) expectBool(t, "!!true;", true) } func TestLogical(t *testing.T) { expectBool(t, "true && true;", true) expectBool(t, "true && false;", false) expectBool(t, "false && true;", false) expectBool(t, "true || true;", true) expectBool(t, "true || false;", true) expectBool(t, "false || true;", true) expectBool(t, "1 == 1 && 1 != 2;", true) expectBool(t, "1 != 1 || 1 != 2;", true) expectBool(t, "1 & 1 && 0 | 1;", true) expectBool(t, "1 & 1 && 2 & 1;", false) expectBool(t, "1 & 1 || 0 | 1;", true) expectBool(t, "2 & 1 || 1 & 2;", false) } func TestIfElse(t *testing.T) { expectBool(t, "if 1 == 1 { true; }", true) val, err := run("if 1 != 1 { true; }") if err != nil { t.Errorf("unexpected error %s", err) } if val != nil { t.Errorf("none expected, %T found", val) } expectBool(t, "if 1 != 1 { false; } else { true; }", true) expectBool(t, "if 1 == 1 { false; } else { true; }", false) expectBool(t, "if 1 != 1 { false; } else { if 1 == 1 { true; }}", true) val, err = run("if 1 != 1 { true; } else { if 1 != 1 { false; }}") if err != nil { t.Errorf("unexpected error %s", err) } if val != nil { t.Errorf("none expected, %T found", val) } } func TestPanic(t *testing.T) { _, err := run("panic(\"error\");") if err == nil { t.Error("error expected and didn't happen") } } func TestFunc(t *testing.T) { expectString(t, ` def wrap() func () string { def inFn() string { return "output"; } return inFn; } var fn func () string = wrap(); fn(); `, "output") expectNumber(t, ` def makeCounter() func () number { var i number; def count() number { i = i + 1; return i; } return count; } makeCounter()(); `, 1) expectBool(t, ` // why not? def loop(from number, to number, fn func (number)) { if from > to { return; } fn(from); // return is needed to trigger the tail-call optimization return loop(from + 1, to, fn); } def wilsonPrime(n number) bool { var acc number = 1; def fact(i number) { acc = (acc * i) % n; } loop(2, n - 1, fact); return acc == n - 1; } wilsonPrime(1789); `, true) expectNumber(t, ` var a number = 1; { var c number; def add() { c = c + a; println(c, " ", a); } add(); // c is 1 var a string; add(); // c is 2 println(c); c; } `, 2) expectBool(t, ` var a bool; def fn() func () { def inFn() { println("works"); a = true; } return inFn; } fn()(); a;`, true) expectBool(t, ` var a bool; def fn(a bool) bool { return a; } fn(true);`, true) expectString(t, ` def fn() { } { def fn() { } } "OK";`, "OK") } func TestFuncError(t *testing.T) { expectString(t, ` var fn func () bool; "OK";`, "OK") expectError(t, ` var fn func () bool; fn();`, errors.NotCallable) expectError(t, ` def a() bool { return true; } var b func () number = a; `, errors.TypeMismatch) expectError(t, ` // return with no function return false; `, errors.InvalidOperation) expectError(t, ` def fn() bool { return 0; } fn(); `, errors.TypeMismatch) expectError(t, ` def fn() bool { } fn(); `, errors.NoReturn) expectError(t, "true();", errors.NotCallable) expectError(t, "\"bad\"();", errors.NotCallable) expectError(t, "10();", errors.NotCallable) expectError(t, "true(1);", errors.NotCallable) expectError(t, "\"bad\"(1);", errors.NotCallable) expectError(t, "10(1);", errors.NotCallable) expectError(t, ` var fn func() number = println; fn("hello");`, errors.CallError) } func TestFor(t *testing.T) { expectString(t, ` for false { panic("oops"); } "OK"; `, "OK") // the value emited by the loops is the last // evaluated value inside the loop expectNumber(t, ` var i number; for i < 10 { i = i + 1; } `, 10) expectNumber(t, ` def mkGen() func () number { var n number; def gen() number { n = n + 1; return n; } return gen; } var c func() number = mkGen(); var acc number; for c() < 10 { acc = acc + 1; } acc; `, 9) expectNumber(t, ` var acc number; var j number; for j < 10 { var i number; for i < 10 { i = i + 1; acc = acc + 1; } j = j + 1; } acc; `, 100) expectError(t, ` var a number; for { a = 1 / a; } `, errors.InvalidOperation) } func TestForIn(t *testing.T) { // the value emited by the loops is the last // evaluated value inside the loop expectNumber(t, ` var arr [3]number = [1, 2, 10]; for i in arr { i; } `, 10) // shadow the array variable expectNumber(t, ` var a [3]number = [1, 2, 10]; for a in a { println(a); a; } `, 10) expectNumber(t, ` var acc number; var j [10]number; for i in j { for i in j { acc = acc + 1; } } acc; `, 100) } func TestContinue(t *testing.T) { expectString(t, ` var a number; for a < 10 { a = a + 1; if a & 1 { continue; panic("oops"); } println(a); } "OK"; `, "OK") expectString(t, ` var arr [5]number = [1, 2, 3, 4, 5]; for a in arr { if a & 1 { continue; panic("oops"); } println(a); } "OK"; `, "OK") expectNumber(t, ` var acc number; var a number; for a < 10 { var b number; for b < 10 { b = b + 1; if b & 1 { continue; panic("oops"); } acc = acc + b; } a = a + 1; } acc; `, (2+4+6+8+10)*10) } func TestBreak(t *testing.T) { expectString(t, ` for { break; panic("oops"); } "OK"; `, "OK") expectString(t, ` var a [10] number; for i in a { break; panic("oops"); } "OK"; `, "OK") expectNumber(t, ` var a number; for { if a == 20 { break; } a = a + 1; for true { a = a + 1; break; a = a + 1; } } a; `, 20) } func TestArray(t *testing.T) { expectError(t, "var a [0]number;", errors.InvalidLength) expectError(t, "var a [1]number; a[-1];", errors.InvalidIndex) expectError(t, "var a [1]number; a[2];", errors.InvalidIndex) expectError(t, "var a [1]number; a[-1] = 1;", errors.InvalidIndex) expectError(t, "var a [1]number; a[2] = 1;", errors.InvalidIndex) expectError(t, "var a [5][5]number;", errors.InvalidType) expectError(t, "var a number; len(a);", errors.TypeMismatch) expectNumber(t, ` var arr [5]number = [1, 2, 3, 4, 5]; var i number = 1; for i < len(arr) { arr[0] = arr[0] + arr[i]; i = i + 1; } arr[0]; `, 1+2+3+4+5) expectString(t, ` var arr [3]string = ["one", "two", "three"]; var arr2 [3]string; var i number; for i < len(arr) { arr2[i] = arr[i]; i = i + 1; } i = 0; for i < len(arr) { if arr2[i] != arr[i] { panic("failed"); } i = i + 1; } "OK"; `, "OK") expectBool(t, ` var a [5]bool; def fn(v [5]bool) { v[2] = true; } fn(a); println(a); a[2]; `, true) expectNumber(t, ` const a [3]number = [1, 2, 3]; const b [3]number = [3, 2, 1]; const c [3]number = a; c = b; c[0]; `, 3) expectNumber(t, ` var acc number; def a() { println("this is a"); acc = acc + 10; } def b() { println("this is b"); acc = acc + 10; } var arr [2]func () = [a, b]; for i in arr { i(); } acc;`, 20) } func TestErrorTag(t *testing.T) { expectString(t, ` def fn() { return !?; } if ? fn() { "KO"; } `, "KO") expectString(t, ` def fn() { return; } if ? fn() { "KO"; } else { "OK"; } `, "OK") expectString(t, ` def fn() number { return !? 0; } if ? fn() { "KO"; } `, "KO") expectString(t, ` def fn() number { return 0; } if !(? fn()) { "OK"; } `, "OK") expectNumber(t, ` def fn() number { return !? 99; } var n number; if ? (n = fn()) { n; } `, 99) expectString(t, ` def fn() { return !?; } fn(); // ? resets the error flag before evaluating the right side if ? true { "KO"; } else { "OK"; } `, "OK") } func TestStruct(t *testing.T) { expectString(t, ` def A { var p string = "OK"; } var a A; a.p; `, "OK") expectNumber(t, ` def A { var p number = 10; } var a A; a.p; `, 10) expectNumber(t, ` def A { var p number; } var a A; a.p = 10; a.p; `, 10) expectNumber(t, ` def A { var p number = 10; } var a A; a.p = 100; var b A; b.p; `, 10) expectNumber(t, ` def A { var p number = 10; } var a [3]A; a[0].p + a[1].p + a[2].p; `, 30) expectNumber(t, ` def A { var arr [3]number = [1, 2, 3]; def p(index number) number { return arr[index]; } } var a A; a.p(0) + a.p(1) + a.p(2); `, 6) expectNumber(t, ` def A { var p number; } def B { var arr [3]A; var i number; def new() A { var r A = arr[i]; r.p = i + 1; i = (i + 1) % 3; return r; } } var b B; b.new().p + b.new().p + b.new().p; // new should have returned a reference b.arr[0].p + b.arr[1].p + b.arr[2].p; `, 6) expectError(t, ` def A { var a A; } `, errors.RecursiveStruct) }