config/lang: use the new AST stuff

This commit is contained in:
Mitchell Hashimoto 2015-01-14 20:58:46 -08:00
parent c96b3b9ddc
commit 57adfe53f6
12 changed files with 125 additions and 277 deletions

View File

@ -8,16 +8,16 @@ import (
// NOTE: All builtins are tested in engine_test.go
func registerBuiltins(scope *Scope) {
func registerBuiltins(scope *ast.BasicScope) {
if scope.FuncMap == nil {
scope.FuncMap = make(map[string]Function)
scope.FuncMap = make(map[string]ast.Function)
}
scope.FuncMap["__builtin_IntToString"] = builtinIntToString()
scope.FuncMap["__builtin_StringToInt"] = builtinStringToInt()
}
func builtinIntToString() Function {
return Function{
func builtinIntToString() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {
@ -26,8 +26,8 @@ func builtinIntToString() Function {
}
}
func builtinStringToInt() Function {
return Function{
func builtinStringToInt() ast.Function {
return ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {

View File

@ -11,7 +11,7 @@ import (
// resolve properly and that the right number of arguments are passed
// to functions.
type IdentifierCheck struct {
Scope *Scope
Scope ast.Scope
err error
lock sync.Mutex

View File

@ -9,20 +9,20 @@ import (
func TestIdentifierCheck(t *testing.T) {
cases := []struct {
Input string
Scope *Scope
Scope ast.Scope
Error bool
}{
{
"foo",
&Scope{},
&ast.BasicScope{},
false,
},
{
"foo ${bar} success",
&Scope{
VarMap: map[string]Variable{
"bar": Variable{
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "baz",
Type: ast.TypeString,
},
@ -33,15 +33,15 @@ func TestIdentifierCheck(t *testing.T) {
{
"foo ${bar}",
&Scope{},
&ast.BasicScope{},
true,
},
{
"foo ${rand()} success",
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
@ -54,15 +54,15 @@ func TestIdentifierCheck(t *testing.T) {
{
"foo ${rand()}",
&Scope{},
&ast.BasicScope{},
true,
},
{
"foo ${rand(42)} ",
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
@ -75,9 +75,9 @@ func TestIdentifierCheck(t *testing.T) {
{
"foo ${rand()} ",
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeInt,
@ -92,9 +92,9 @@ func TestIdentifierCheck(t *testing.T) {
{
"foo ${rand(42)} ",
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeInt,
@ -109,9 +109,9 @@ func TestIdentifierCheck(t *testing.T) {
{
"foo ${rand(\"foo\", 42)} ",
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Variadic: true,

View File

@ -16,7 +16,7 @@ import (
// this structure but we'd rather do that than duplicate the type checking
// logic multiple times.
type TypeCheck struct {
Scope *Scope
Scope ast.Scope
// Implicit is a map of implicit type conversions that we can do,
// and that shouldn't error. The key of the first map is the from type,
@ -140,7 +140,7 @@ func (v *TypeCheck) visitConcat(n *ast.Concat) {
}
func (v *TypeCheck) visitLiteral(n *ast.LiteralNode) {
v.stackPush(n.Type)
v.stackPush(n.Typex)
}
func (v *TypeCheck) visitVariableAccess(n *ast.VariableAccess) {

View File

@ -9,20 +9,20 @@ import (
func TestTypeCheck(t *testing.T) {
cases := []struct {
Input string
Scope *Scope
Scope ast.Scope
Error bool
}{
{
"foo",
&Scope{},
&ast.BasicScope{},
false,
},
{
"foo ${bar}",
&Scope{
VarMap: map[string]Variable{
"bar": Variable{
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "baz",
Type: ast.TypeString,
},
@ -33,9 +33,9 @@ func TestTypeCheck(t *testing.T) {
{
"foo ${rand()}",
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
@ -48,9 +48,9 @@ func TestTypeCheck(t *testing.T) {
{
`foo ${rand("42")}`,
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
@ -64,9 +64,9 @@ func TestTypeCheck(t *testing.T) {
{
`foo ${rand(42)}`,
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
@ -80,9 +80,9 @@ func TestTypeCheck(t *testing.T) {
{
`foo ${rand()}`,
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: nil,
ReturnType: ast.TypeString,
Variadic: true,
@ -98,9 +98,9 @@ func TestTypeCheck(t *testing.T) {
{
`foo ${rand("42")}`,
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: nil,
ReturnType: ast.TypeString,
Variadic: true,
@ -116,9 +116,9 @@ func TestTypeCheck(t *testing.T) {
{
`foo ${rand("42", 42)}`,
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ArgTypes: nil,
ReturnType: ast.TypeString,
Variadic: true,
@ -134,9 +134,9 @@ func TestTypeCheck(t *testing.T) {
{
"foo ${bar}",
&Scope{
VarMap: map[string]Variable{
"bar": Variable{
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 42,
Type: ast.TypeInt,
},
@ -147,9 +147,9 @@ func TestTypeCheck(t *testing.T) {
{
"foo ${rand()}",
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeInt,
Callback: func([]interface{}) (interface{}, error) {
return 42, nil
@ -184,14 +184,14 @@ func TestTypeCheck_implicit(t *testing.T) {
cases := []struct {
Input string
Scope *Scope
Scope *ast.BasicScope
Error bool
}{
{
"foo ${bar}",
&Scope{
VarMap: map[string]Variable{
"bar": Variable{
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 42,
Type: ast.TypeInt,
},
@ -202,9 +202,9 @@ func TestTypeCheck_implicit(t *testing.T) {
{
"foo ${foo(42)}",
&Scope{
FuncMap: map[string]Function{
"foo": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"foo": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
ReturnType: ast.TypeString,
},
@ -215,9 +215,9 @@ func TestTypeCheck_implicit(t *testing.T) {
{
`foo ${foo("42", 42)}`,
&Scope{
FuncMap: map[string]Function{
"foo": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"foo": ast.Function{
ArgTypes: []ast.Type{ast.TypeString},
Variadic: true,
VariadicType: ast.TypeString,
@ -237,9 +237,9 @@ func TestTypeCheck_implicit(t *testing.T) {
// Modify the scope to add our conversion functions.
if tc.Scope.FuncMap == nil {
tc.Scope.FuncMap = make(map[string]Function)
tc.Scope.FuncMap = make(map[string]ast.Function)
}
tc.Scope.FuncMap["intToString"] = Function{
tc.Scope.FuncMap["intToString"] = ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
}

View File

@ -12,7 +12,7 @@ import (
// prior to running Execute.
type Engine struct {
// GlobalScope is the global scope of execution for this engine.
GlobalScope *Scope
GlobalScope *ast.BasicScope
// SemanticChecks is a list of additional semantic checks that will be run
// on the tree prior to executing it. The type checker, identifier checker,
@ -61,8 +61,8 @@ func (e *Engine) Execute(root ast.Node) (interface{}, ast.Type, error) {
return v.Visit(root)
}
func (e *Engine) scope() *Scope {
var scope Scope
func (e *Engine) scope() ast.Scope {
var scope ast.BasicScope
if e.GlobalScope != nil {
scope = *e.GlobalScope
}
@ -75,7 +75,7 @@ func (e *Engine) scope() *Scope {
// a program. Note at this point it is assumed that the types check out
// and the identifiers exist.
type executeVisitor struct {
Scope *Scope
Scope ast.Scope
stack EngineStack
err error
@ -102,7 +102,12 @@ func (v *executeVisitor) Visit(root ast.Node) (interface{}, ast.Type, error) {
v.stack.Reset()
v.err = nil
return result.Value, result.Type, resultErr
t, err := result.Type(v.Scope)
if err != nil {
return nil, ast.TypeInvalid, err
}
return result.Value, t, resultErr
}
func (v *executeVisitor) visit(raw ast.Node) ast.Node {
@ -151,7 +156,7 @@ func (v *executeVisitor) visitCall(n *ast.Call) {
// Push the result
v.stack.Push(&ast.LiteralNode{
Value: result,
Type: function.ReturnType,
Typex: function.ReturnType,
})
}
@ -170,7 +175,7 @@ func (v *executeVisitor) visitConcat(n *ast.Concat) {
v.stack.Push(&ast.LiteralNode{
Value: buf.String(),
Type: ast.TypeString,
Typex: ast.TypeString,
})
}
@ -188,7 +193,7 @@ func (v *executeVisitor) visitVariableAccess(n *ast.VariableAccess) {
v.stack.Push(&ast.LiteralNode{
Value: variable.Value,
Type: variable.Type,
Typex: variable.Type,
})
}
@ -218,33 +223,3 @@ func (s *EngineStack) Pop() *ast.LiteralNode {
func (s *EngineStack) Reset() {
s.stack = nil
}
// Scope represents a lookup scope for execution.
type Scope struct {
// VarMap and FuncMap are the mappings of identifiers to functions
// and variable values.
VarMap map[string]Variable
FuncMap map[string]Function
}
// LookupFunc will look up a variable by name.
// TODO test
func (s *Scope) LookupFunc(n string) (Function, bool) {
if s == nil {
return Function{}, false
}
v, ok := s.FuncMap[n]
return v, ok
}
// LookupVar will look up a variable by name.
// TODO test
func (s *Scope) LookupVar(n string) (Variable, bool) {
if s == nil {
return Variable{}, false
}
v, ok := s.VarMap[n]
return v, ok
}

View File

@ -11,7 +11,7 @@ import (
func TestEngineExecute(t *testing.T) {
cases := []struct {
Input string
Scope *Scope
Scope *ast.BasicScope
Error bool
Result interface{}
ResultType ast.Type
@ -26,9 +26,9 @@ func TestEngineExecute(t *testing.T) {
{
"foo ${bar}",
&Scope{
VarMap: map[string]Variable{
"bar": Variable{
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: "baz",
Type: ast.TypeString,
},
@ -41,9 +41,9 @@ func TestEngineExecute(t *testing.T) {
{
"foo ${rand()}",
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Callback: func([]interface{}) (interface{}, error) {
return "42", nil
@ -58,9 +58,9 @@ func TestEngineExecute(t *testing.T) {
{
`foo ${rand("foo", "bar")}`,
&Scope{
FuncMap: map[string]Function{
"rand": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"rand": ast.Function{
ReturnType: ast.TypeString,
Variadic: true,
VariadicType: ast.TypeString,
@ -83,9 +83,9 @@ func TestEngineExecute(t *testing.T) {
{
"foo ${bar}",
&Scope{
VarMap: map[string]Variable{
"bar": Variable{
&ast.BasicScope{
VarMap: map[string]ast.Variable{
"bar": ast.Variable{
Value: 42,
Type: ast.TypeInt,
},
@ -98,9 +98,9 @@ func TestEngineExecute(t *testing.T) {
{
`foo ${foo("42")}`,
&Scope{
FuncMap: map[string]Function{
"foo": Function{
&ast.BasicScope{
FuncMap: map[string]ast.Function{
"foo": ast.Function{
ArgTypes: []ast.Type{ast.TypeInt},
ReturnType: ast.TypeString,
Callback: func(args []interface{}) (interface{}, error) {

View File

@ -33,7 +33,7 @@ top:
{
parserResult = &ast.LiteralNode{
Value: "",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
}
}
@ -50,7 +50,7 @@ top:
// it makes for an easy literal check later (to check if a string
// has any interpolations).
if _, ok := $1.(*ast.Concat); !ok {
if n, ok := $1.(*ast.LiteralNode); !ok || n.Type != ast.TypeString {
if n, ok := $1.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString {
parserResult = &ast.Concat{
Exprs: []ast.Node{$1},
Posx: $1.Pos(),
@ -104,7 +104,7 @@ expr:
{
$$ = &ast.LiteralNode{
Value: $1.Value.(int),
Type: ast.TypeInt,
Typex: ast.TypeInt,
Posx: $1.Pos,
}
}
@ -112,7 +112,7 @@ expr:
{
$$ = &ast.LiteralNode{
Value: $1.Value.(float64),
Type: ast.TypeFloat,
Typex: ast.TypeFloat,
Posx: $1.Pos,
}
}
@ -143,7 +143,7 @@ literal:
{
$$ = &ast.LiteralNode{
Value: $1.Value.(string),
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: $1.Pos,
}
}

View File

@ -18,7 +18,7 @@ func TestParse(t *testing.T) {
false,
&ast.LiteralNode{
Value: "",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
},
@ -28,7 +28,7 @@ func TestParse(t *testing.T) {
false,
&ast.LiteralNode{
Value: "foo",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
},
@ -38,7 +38,7 @@ func TestParse(t *testing.T) {
false,
&ast.LiteralNode{
Value: "${var.foo}",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
},
@ -51,7 +51,7 @@ func TestParse(t *testing.T) {
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.VariableAccess{
@ -70,7 +70,7 @@ func TestParse(t *testing.T) {
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.VariableAccess{
@ -79,7 +79,7 @@ func TestParse(t *testing.T) {
},
&ast.LiteralNode{
Value: " baz",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 15, Line: 1},
},
},
@ -94,12 +94,12 @@ func TestParse(t *testing.T) {
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.LiteralNode{
Value: "bar",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 7, Line: 1},
},
},
@ -114,12 +114,12 @@ func TestParse(t *testing.T) {
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.LiteralNode{
Value: 42,
Type: ast.TypeInt,
Typex: ast.TypeInt,
Posx: ast.Pos{Column: 7, Line: 1},
},
},
@ -134,12 +134,12 @@ func TestParse(t *testing.T) {
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.LiteralNode{
Value: 3.14159,
Type: ast.TypeFloat,
Typex: ast.TypeFloat,
Posx: ast.Pos{Column: 7, Line: 1},
},
},
@ -239,7 +239,7 @@ func TestParse(t *testing.T) {
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "foo ",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
},
&ast.Concat{
@ -247,7 +247,7 @@ func TestParse(t *testing.T) {
Exprs: []ast.Node{
&ast.LiteralNode{
Value: "bar ",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 7, Line: 1},
},
&ast.VariableAccess{

View File

@ -1,53 +0,0 @@
package lang
import (
"fmt"
"github.com/hashicorp/terraform/config/lang/ast"
)
// LookupType looks up the type of the given node with the given scope.
func LookupType(raw ast.Node, scope *Scope) (ast.Type, error) {
switch n := raw.(type) {
case *ast.LiteralNode:
return typedLiteralNode{n}.Type(scope)
case *ast.VariableAccess:
return typedVariableAccess{n}.Type(scope)
default:
if t, ok := raw.(TypedNode); ok {
return t.Type(scope)
}
return ast.TypeInvalid, fmt.Errorf(
"unknown node to get type of: %T", raw)
}
}
// TypedNode is an interface that custom AST nodes should implement
// if they want to work with LookupType. All the builtin AST nodes have
// implementations of this.
type TypedNode interface {
Type(*Scope) (ast.Type, error)
}
type typedLiteralNode struct {
n *ast.LiteralNode
}
func (n typedLiteralNode) Type(s *Scope) (ast.Type, error) {
return n.n.Type, nil
}
type typedVariableAccess struct {
n *ast.VariableAccess
}
func (n typedVariableAccess) Type(s *Scope) (ast.Type, error) {
v, ok := s.LookupVar(n.n.Name)
if !ok {
return ast.TypeInvalid, fmt.Errorf(
"%s: couldn't find variable %s", n.n.Pos(), n.n.Name)
}
return v.Type, nil
}

View File

@ -1,74 +0,0 @@
package lang
import (
"testing"
"github.com/hashicorp/terraform/config/lang/ast"
)
func TestLookupType(t *testing.T) {
cases := []struct {
Input ast.Node
Scope *Scope
Output ast.Type
Error bool
}{
{
&customUntyped{},
nil,
ast.TypeInvalid,
true,
},
{
&customTyped{},
nil,
ast.TypeString,
false,
},
{
&ast.LiteralNode{
Value: 42,
Type: ast.TypeInt,
},
nil,
ast.TypeInt,
false,
},
{
&ast.VariableAccess{
Name: "foo",
},
&Scope{
VarMap: map[string]Variable{
"foo": Variable{Type: ast.TypeInt},
},
},
ast.TypeInt,
false,
},
}
for _, tc := range cases {
actual, err := LookupType(tc.Input, tc.Scope)
if (err != nil) != tc.Error {
t.Fatalf("bad: %s\n\nInput: %#v", err, tc.Input)
}
if actual != tc.Output {
t.Fatalf("bad: %s\n\nInput: %#v", actual, tc.Input)
}
}
}
type customUntyped struct{}
func (n customUntyped) Accept(ast.Visitor) ast.Node { return n }
func (n customUntyped) Pos() (v ast.Pos) { return }
type customTyped struct{}
func (n customTyped) Accept(ast.Visitor) ast.Node { return n }
func (n customTyped) Pos() (v ast.Pos) { return }
func (n customTyped) Type(*Scope) (ast.Type, error) { return ast.TypeString, nil }

View File

@ -346,7 +346,7 @@ parserdefault:
{
parserResult = &ast.LiteralNode{
Value: "",
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: ast.Pos{Column: 1, Line: 1},
}
}
@ -364,7 +364,7 @@ parserdefault:
// it makes for an easy literal check later (to check if a string
// has any interpolations).
if _, ok := parserS[parserpt-0].node.(*ast.Concat); !ok {
if n, ok := parserS[parserpt-0].node.(*ast.LiteralNode); !ok || n.Type != ast.TypeString {
if n, ok := parserS[parserpt-0].node.(*ast.LiteralNode); !ok || n.Typex != ast.TypeString {
parserResult = &ast.Concat{
Exprs: []ast.Node{parserS[parserpt-0].node},
Posx: parserS[parserpt-0].node.Pos(),
@ -417,7 +417,7 @@ parserdefault:
{
parserVAL.node = &ast.LiteralNode{
Value: parserS[parserpt-0].token.Value.(int),
Type: ast.TypeInt,
Typex: ast.TypeInt,
Posx: parserS[parserpt-0].token.Pos,
}
}
@ -426,7 +426,7 @@ parserdefault:
{
parserVAL.node = &ast.LiteralNode{
Value: parserS[parserpt-0].token.Value.(float64),
Type: ast.TypeFloat,
Typex: ast.TypeFloat,
Posx: parserS[parserpt-0].token.Pos,
}
}
@ -460,7 +460,7 @@ parserdefault:
{
parserVAL.node = &ast.LiteralNode{
Value: parserS[parserpt-0].token.Value.(string),
Type: ast.TypeString,
Typex: ast.TypeString,
Posx: parserS[parserpt-0].token.Pos,
}
}