config/lang/ast: Eval
This commit is contained in:
parent
c4273974de
commit
c96b3b9ddc
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue