// evaluate simple expressions (no operators) func Test_evaluate_simple(t *testing.T) { // the expression "meep" evaluates to the string "meep" var expect types.FuObject snode := stringnode("meep") rt := minimalRuntime() ns := rt.Namespace() expect = types.MakeFuString("meep") assertEvaluateOK(t, rt, expect, snode) // the expression foo evaluates to the string "meep" if foo is set // to that string ns.Assign("foo", expect) nnode := dsl.NewASTName("foo") assertEvaluateOK(t, rt, expect, nnode) // ... and to an error if the variable is not defined location := dsl.NewStubLocation("hello, sailor") nnode = dsl.NewASTName("boo", location) assertEvaluateFail(t, rt, "hello, sailor: name not defined: 'boo'", nnode) // expression <*.c blah> evaluates to a FinderNode with two // include patterns patterns := []string{"*.c", "blah"} fnode := dsl.NewASTFileFinder(patterns) expect = dag.NewFinderNode("*.c", "blah") assertEvaluateOK(t, rt, expect, fnode) }
// evaluate more complex expressions func Test_evaluate_complex(t *testing.T) { // a + b evaluates to various things, depending on the value // of those two variables addnode := dsl.NewASTAdd( dsl.NewASTName("a", dsl.NewStubLocation("loc1")), dsl.NewASTName("b", dsl.NewStubLocation("loc2"))) // case 1: two strings just get concatenated rt := minimalRuntime() ns := rt.Namespace() ns.Assign("a", types.MakeFuString("foo")) ns.Assign("b", types.MakeFuString("bar")) expect := types.MakeFuString("foobar") assertEvaluateOK(t, rt, expect, addnode) // case 2: adding a function to a string fails ns.Assign("b", types.NewFixedFunction("b", 0, nil)) assertEvaluateFail(t, rt, "loc1loc2: unsupported operation: cannot add function to string", addnode) // case 3: undefined name delete((*ns.(*types.ValueStack))[0].(types.ValueMap), "b") assertEvaluateFail(t, rt, "loc2: name not defined: 'b'", addnode) }
func Test_evaluate_list_errors(t *testing.T) { rt := minimalRuntime() rt.stack.Assign("src", types.MakeFuString("foo.c")) elements := []dsl.ASTExpression{ dsl.NewASTString("\"whee!\""), dsl.NewASTName("bogus"), dsl.NewASTName("src"), dsl.NewASTString("\"bop\""), dsl.NewASTName("morebogus"), } astlist := dsl.NewASTList(elements) actual, errs := rt.evaluate(astlist) if len(errs) != 2 { t.Fatalf("expected 2 errors, but got %d: %v", len(errs), errs) } assert.Equal(t, "name not defined: 'bogus'", errs[0].Error()) assert.Equal(t, "name not defined: 'morebogus'", errs[1].Error()) assert.Nil(t, actual) }
func Test_prepareCall(t *testing.T) { // this is never going to be called, so it's OK that it's nil var fn_dummy func(argsource types.ArgSource) (types.FuObject, []error) var dummy1, dummy2 types.FuCallable dummy1 = types.NewFixedFunction("dummy1", 0, fn_dummy) dummy2 = types.NewFixedFunction("dummy1", 1, fn_dummy) rt := minimalRuntime() ns := rt.Namespace() ns.Assign("dummy1", dummy1) ns.Assign("dummy2", dummy2) ns.Assign("x", types.MakeFuString("whee!")) noargs := []dsl.ASTExpression{} onearg := []dsl.ASTExpression{dsl.NewASTString("\"meep\"")} var astcall *dsl.ASTFunctionCall var callable types.FuCallable var args RuntimeArgs var errs []error // correct (no args) call to dummy1() astcall = dsl.NewASTFunctionCall(dsl.NewASTName("dummy1"), noargs) callable, args, errs = rt.prepareCall(astcall) assert.Equal(t, 0, len(errs)) assert.Equal(t, dummy1, callable) assert.Equal(t, []types.FuObject{}, args.Args()) // and to dummy2() astcall = dsl.NewASTFunctionCall(dsl.NewASTName("dummy2"), onearg) callable, args, errs = rt.prepareCall(astcall) assert.Equal(t, 0, len(errs)) assert.Equal(t, dummy2, callable) assert.Equal(t, []types.FuObject{types.MakeFuString("meep")}, args.Args()) // attempt to call dummy2() incorrectly (1 arg, but it's an undefined name) astcall = dsl.NewASTFunctionCall( dsl.NewASTName("dummy2"), []dsl.ASTExpression{dsl.NewASTName("bogus")}) callable, _, errs = rt.prepareCall(astcall) assert.Equal(t, 1, len(errs)) assert.Equal(t, "name not defined: 'bogus'", errs[0].Error()) // attempt to call non-existent function astcall = dsl.NewASTFunctionCall(dsl.NewASTName("bogus"), noargs) callable, _, errs = rt.prepareCall(astcall) assert.Nil(t, callable) assert.Equal(t, 1, len(errs)) assert.Equal(t, "name not defined: 'bogus'", errs[0].Error()) // attempt to call something that is not a function astcall = dsl.NewASTFunctionCall(dsl.NewASTName("x"), noargs) callable, _, errs = rt.prepareCall(astcall) assert.Nil(t, callable) assert.Equal(t, 1, len(errs)) assert.Equal(t, "not a function or method: 'x'", errs[0].Error()) }
func Test_evaluate_list(t *testing.T) { rt := minimalRuntime() rt.stack.Assign("blop", types.MakeFuString("bar")) elements := []dsl.ASTExpression{ dsl.NewASTString("\"foo\""), dsl.NewASTName("blop"), dsl.NewASTString("\"baz\""), } astlist := dsl.NewASTList(elements) actual, errs := rt.evaluate(astlist) testutils.NoErrors(t, errs) expect := types.MakeStringList("foo", "bar", "baz") assert.Equal(t, expect, actual) }
func Test_assign(t *testing.T) { // AST for a = "foo" node := dsl.NewASTAssignment("a", stringnode("foo")) rt := minimalRuntime() errs := rt.assign(node) assert.Equal(t, 0, len(errs)) expect := types.MakeFuString("foo") assertIn(t, rt.Namespace(), "a", expect) // AST for a = foo (another variable, to provoke an error) node = dsl.NewASTAssignment("b", dsl.NewASTName("foo")) errs = rt.assign(node) assert.Equal(t, "name not defined: 'foo'", errs[0].Error()) _, ok := rt.Lookup("b") assert.False(t, ok) }
func Test_evaluateCall_method(t *testing.T) { // construct AST for "a.b.c(x)" astargs := []dsl.ASTExpression{dsl.NewASTName("x")} astcall := dsl.NewASTFunctionCall( dsl.NewASTSelection( dsl.NewASTSelection(dsl.NewASTName("a"), "b"), "c"), astargs) // make sure a is an object with attributes, and b is one of them // (N.B. having FileNodes be attributes of one another is weird // and would never happen in a real Fubsy script, but it's a // convenient way to setup this method call) aobj := dag.NewFileNode("a.txt") bobj := dag.NewFileNode("b.txt") aobj.ValueMap = types.NewValueMap() aobj.Assign("b", bobj) // make sure a.b.c is a method calls := make([]string, 0) // list of function names var meth_c types.FuCode meth_c = func(argsource types.ArgSource) (types.FuObject, []error) { args := argsource.Args() if len(args) != 1 { panic("c() called with wrong number of args") } calls = append(calls, "c") robj := argsource.Receiver() return nil, []error{ fmt.Errorf("c failed: receiver: %s %v, arg: %s %v", robj.Typename(), robj, args[0].Typename(), args[0])} } bobj.ValueMap = types.NewValueMap() bobj.Assign("c", types.NewFixedFunction("c", 1, meth_c)) rt := minimalRuntime() ns := rt.Namespace() ns.Assign("a", aobj) ns.Assign("x", types.MakeFuString("hello")) // what the hell, let's test the precall feature too var precalledCallable types.FuCallable var precalledArgs types.ArgSource precall := func(callable types.FuCallable, argsource types.ArgSource) { precalledCallable = callable precalledArgs = argsource } callable, args, errs := rt.prepareCall(astcall) assert.Equal(t, "c", callable.(*types.FuFunction).Name()) assert.True(t, args.Receiver() == bobj) assert.Equal(t, 0, len(errs)) result, errs := rt.evaluateCall(callable, args, precall) assert.Equal(t, precalledCallable, callable) assert.Equal(t, precalledArgs.Args(), types.MakeStringList("hello").List()) assert.Nil(t, result) if len(errs) == 1 { assert.Equal(t, "c failed: receiver: FileNode \"b.txt\", arg: string \"hello\"", errs[0].Error()) } else { t.Errorf("expected exactly 1 error, but got: %v", errs) } }