config/lang/ast: Eval

This commit is contained in:
Mitchell Hashimoto 2015-01-14 20:37:16 -08:00
parent c4273974de
commit c96b3b9ddc
9 changed files with 122 additions and 1 deletions

View File

@ -15,6 +15,10 @@ type Node interface {
// Type returns the type of this node for the given context.
Type(Scope) (Type, error)
// Eval evaluates this node, returning its final value. The type
// of the final value will match the result of Type with the same scope.
Eval(*EvalContext) (interface{}, error)
}
// Pos is the starting position of an AST node
@ -27,9 +31,11 @@ func (p Pos) String() string {
}
// EvalContext is the context given for evaluation.
//
// It is simple for now with just a Scope but we use a struct in case we
// plan on adding fields in the future.
type EvalContext struct {
Scope Scope
Stack Stack
}
// Visitors are just implementations of this function.

View File

@ -41,3 +41,22 @@ func (n *Call) Type(s Scope) (Type, error) {
return f.ReturnType, nil
}
func (n *Call) Eval(ctx *EvalContext) (interface{}, error) {
f, ok := ctx.Scope.LookupFunc(n.Func)
if !ok {
return TypeInvalid, fmt.Errorf("unknown function: %s", n.Func)
}
args := make([]interface{}, len(n.Args))
for i, arg := range n.Args {
result, err := arg.Eval(ctx)
if err != nil {
return nil, err
}
args[i] = result
}
return f.Callback(args)
}

View File

@ -34,3 +34,24 @@ func TestCallType_invalid(t *testing.T) {
t.Fatal("should error")
}
}
func TestCallEval(t *testing.T) {
c := &Call{Func: "foo"}
scope := &BasicScope{
FuncMap: map[string]Function{
"foo": Function{
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
},
},
},
}
actual, err := c.Eval(&EvalContext{Scope: scope})
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != "42" {
t.Fatalf("bad: %s", actual)
}
}

View File

@ -40,3 +40,17 @@ func (n *Concat) String() string {
func (n *Concat) Type(Scope) (Type, error) {
return TypeString, nil
}
func (n *Concat) Eval(ctx *EvalContext) (interface{}, error) {
var b bytes.Buffer
for _, expr := range n.Exprs {
result, err := expr.Eval(ctx)
if err != nil {
return nil, err
}
b.WriteString(result.(string))
}
return b.String(), nil
}

View File

@ -14,3 +14,21 @@ func TestConcatType(t *testing.T) {
t.Fatalf("bad: %s", actual)
}
}
func TestConcatEval(t *testing.T) {
c := &Concat{
Exprs: []Node{
&LiteralNode{Value: "foo"},
&LiteralNode{Value: "bar"},
},
}
scope := &BasicScope{}
actual, err := c.Eval(&EvalContext{Scope: scope})
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != "foobar" {
t.Fatalf("bad: %s", actual)
}
}

View File

@ -31,3 +31,7 @@ func (n *LiteralNode) String() string {
func (n *LiteralNode) Type(Scope) (Type, error) {
return n.Typex, nil
}
func (n *LiteralNode) Eval(*EvalContext) (interface{}, error) {
return n.Value, nil
}

View File

@ -14,3 +14,16 @@ func TestLiteralNodeType(t *testing.T) {
t.Fatalf("bad: %s", actual)
}
}
func TestLiteralNodeEval(t *testing.T) {
c := &LiteralNode{Value: "42", Typex: TypeString}
scope := &BasicScope{}
actual, err := c.Eval(&EvalContext{Scope: scope})
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != "42" {
t.Fatalf("bad: %s", actual)
}
}

View File

@ -34,3 +34,12 @@ func (n *VariableAccess) Type(s Scope) (Type, error) {
return v.Type, nil
}
func (n *VariableAccess) Eval(ctx *EvalContext) (interface{}, error) {
v, ok := ctx.Scope.LookupVar(n.Name)
if !ok {
return TypeInvalid, fmt.Errorf("unknown variable: %s", n.Name)
}
return v.Value, nil
}

View File

@ -34,3 +34,20 @@ func TestVariableAccessType_invalid(t *testing.T) {
t.Fatal("should error")
}
}
func TestVariableAccessEval(t *testing.T) {
c := &VariableAccess{Name: "foo"}
scope := &BasicScope{
VarMap: map[string]Variable{
"foo": Variable{Value: "42", Type: TypeString},
},
}
actual, err := c.Eval(&EvalContext{Scope: scope})
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != "42" {
t.Fatalf("bad: %s", actual)
}
}