config/lang/ast: introduce Type

This commit is contained in:
Mitchell Hashimoto 2015-01-14 20:13:35 -08:00
parent cef5f22fed
commit c4273974de
14 changed files with 316 additions and 34 deletions

View File

@ -12,6 +12,9 @@ type Node interface {
// Pos returns the position of this node in some source.
Pos() Pos
// Type returns the type of this node for the given context.
Type(Scope) (Type, error)
}
// Pos is the starting position of an AST node
@ -23,6 +26,12 @@ func (p Pos) String() string {
return fmt.Sprintf("%d:%d", p.Line, p.Column)
}
// EvalContext is the context given for evaluation.
type EvalContext struct {
Scope Scope
Stack Stack
}
// Visitors are just implementations of this function.
//
// The function must return the Node to replace this node with. "nil" is
@ -40,7 +49,7 @@ type Visitor func(Node) Node
//go:generate stringer -type=Type
// Type is the type of a literal.
// Type is the type of any value.
type Type uint32
const (

View File

@ -32,3 +32,12 @@ func (n *Call) String() string {
return fmt.Sprintf("Call(%s, %s)", n.Func, strings.Join(args, ", "))
}
func (n *Call) Type(s Scope) (Type, error) {
f, ok := s.LookupFunc(n.Func)
if !ok {
return TypeInvalid, fmt.Errorf("unknown function: %s", n.Func)
}
return f.ReturnType, nil
}

View File

@ -0,0 +1,36 @@
package ast
import (
"testing"
)
func TestCallType(t *testing.T) {
c := &Call{Func: "foo"}
scope := &BasicScope{
FuncMap: map[string]Function{
"foo": Function{ReturnType: TypeString},
},
}
actual, err := c.Type(scope)
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != TypeString {
t.Fatalf("bad: %s", actual)
}
}
func TestCallType_invalid(t *testing.T) {
c := &Call{Func: "bar"}
scope := &BasicScope{
FuncMap: map[string]Function{
"foo": Function{ReturnType: TypeString},
},
}
_, err := c.Type(scope)
if err == nil {
t.Fatal("should error")
}
}

View File

@ -36,3 +36,7 @@ func (n *Concat) String() string {
return b.String()
}
func (n *Concat) Type(Scope) (Type, error) {
return TypeString, nil
}

View File

@ -0,0 +1,16 @@
package ast
import (
"testing"
)
func TestConcatType(t *testing.T) {
c := &Concat{}
actual, err := c.Type(nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != TypeString {
t.Fatalf("bad: %s", actual)
}
}

View File

@ -8,7 +8,7 @@ import (
// 42 or 3.14159. Based on the Type, the Value can be safely cast.
type LiteralNode struct {
Value interface{}
Type Type
Typex Type
Posx Pos
}
@ -27,3 +27,7 @@ func (n *LiteralNode) GoString() string {
func (n *LiteralNode) String() string {
return fmt.Sprintf("Literal(%s, %v)", n.Type, n.Value)
}
func (n *LiteralNode) Type(Scope) (Type, error) {
return n.Typex, nil
}

View File

@ -0,0 +1,16 @@
package ast
import (
"testing"
)
func TestLiteralNodeType(t *testing.T) {
c := &LiteralNode{Typex: TypeString}
actual, err := c.Type(nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != TypeString {
t.Fatalf("bad: %s", actual)
}
}

65
config/lang/ast/scope.go Normal file
View File

@ -0,0 +1,65 @@
package ast
// Scope is the interface used to look up variables and functions while
// evaluating. How these functions/variables are defined are up to the caller.
type Scope interface {
LookupFunc(string) (Function, bool)
LookupVar(string) (Variable, bool)
}
// Variable is a variable value for execution given as input to the engine.
// It records the value of a variables along with their type.
type Variable struct {
Value interface{}
Type Type
}
// Function defines a function that can be executed by the engine.
// The type checker will validate that the proper types will be called
// to the callback.
type Function struct {
// ArgTypes is the list of types in argument order. These are the
// required arguments.
//
// ReturnType is the type of the returned value. The Callback MUST
// return this type.
ArgTypes []Type
ReturnType Type
// Variadic, if true, says that this function is variadic, meaning
// it takes a variable number of arguments. In this case, the
// VariadicType must be set.
Variadic bool
VariadicType Type
// Callback is the function called for a function. The argument
// types are guaranteed to match the spec above by the type checker.
// The length of the args is strictly == len(ArgTypes) unless Varidiac
// is true, in which case its >= len(ArgTypes).
Callback func([]interface{}) (interface{}, error)
}
// BasicScope is a simple scope that looks up variables and functions
// using a map.
type BasicScope struct {
FuncMap map[string]Function
VarMap map[string]Variable
}
func (s *BasicScope) LookupFunc(n string) (Function, bool) {
if s == nil {
return Function{}, false
}
v, ok := s.FuncMap[n]
return v, ok
}
func (s *BasicScope) LookupVar(n string) (Variable, bool) {
if s == nil {
return Variable{}, false
}
v, ok := s.VarMap[n]
return v, ok
}

View File

@ -0,0 +1,39 @@
package ast
import (
"testing"
)
func TestBasicScope_impl(t *testing.T) {
var _ Scope = new(BasicScope)
}
func TestBasicScopeLookupFunc(t *testing.T) {
scope := &BasicScope{
FuncMap: map[string]Function{
"foo": Function{},
},
}
if _, ok := scope.LookupFunc("bar"); ok {
t.Fatal("should not find bar")
}
if _, ok := scope.LookupFunc("foo"); !ok {
t.Fatal("should find foo")
}
}
func TestBasicScopeLookupVar(t *testing.T) {
scope := &BasicScope{
VarMap: map[string]Variable{
"foo": Variable{},
},
}
if _, ok := scope.LookupVar("bar"); ok {
t.Fatal("should not find bar")
}
if _, ok := scope.LookupVar("foo"); !ok {
t.Fatal("should find foo")
}
}

25
config/lang/ast/stack.go Normal file
View File

@ -0,0 +1,25 @@
package ast
// Stack is a stack of Node.
type Stack struct {
stack []Node
}
func (s *Stack) Len() int {
return len(s.stack)
}
func (s *Stack) Push(n Node) {
s.stack = append(s.stack, n)
}
func (s *Stack) Pop() Node {
x := s.stack[len(s.stack)-1]
s.stack[len(s.stack)-1] = nil
s.stack = s.stack[:len(s.stack)-1]
return x
}
func (s *Stack) Reset() {
s.stack = nil
}

View File

@ -0,0 +1,46 @@
package ast
import (
"reflect"
"testing"
)
func TestStack(t *testing.T) {
var s Stack
if s.Len() != 0 {
t.Fatalf("bad: %d", s.Len())
}
n := &LiteralNode{Value: 42}
s.Push(n)
if s.Len() != 1 {
t.Fatalf("bad: %d", s.Len())
}
actual := s.Pop()
if !reflect.DeepEqual(actual, n) {
t.Fatalf("bad: %#v", actual)
}
if s.Len() != 0 {
t.Fatalf("bad: %d", s.Len())
}
}
func TestStack_reset(t *testing.T) {
var s Stack
n := &LiteralNode{Value: 42}
s.Push(n)
if s.Len() != 1 {
t.Fatalf("bad: %d", s.Len())
}
s.Reset()
if s.Len() != 0 {
t.Fatalf("bad: %d", s.Len())
}
}

View File

@ -25,3 +25,12 @@ func (n *VariableAccess) GoString() string {
func (n *VariableAccess) String() string {
return fmt.Sprintf("Variable(%s)", n.Name)
}
func (n *VariableAccess) Type(s Scope) (Type, error) {
v, ok := s.LookupVar(n.Name)
if !ok {
return TypeInvalid, fmt.Errorf("unknown variable: %s", n.Name)
}
return v.Type, nil
}

View File

@ -0,0 +1,36 @@
package ast
import (
"testing"
)
func TestVariableAccessType(t *testing.T) {
c := &VariableAccess{Name: "foo"}
scope := &BasicScope{
VarMap: map[string]Variable{
"foo": Variable{Type: TypeString},
},
}
actual, err := c.Type(scope)
if err != nil {
t.Fatalf("err: %s", err)
}
if actual != TypeString {
t.Fatalf("bad: %s", actual)
}
}
func TestVariableAccessType_invalid(t *testing.T) {
c := &VariableAccess{Name: "bar"}
scope := &BasicScope{
VarMap: map[string]Variable{
"foo": Variable{Type: TypeString},
},
}
_, err := c.Type(scope)
if err == nil {
t.Fatal("should error")
}
}

View File

@ -227,38 +227,6 @@ type Scope struct {
FuncMap map[string]Function
}
// Variable is a variable value for execution given as input to the engine.
// It records the value of a variables along with their type.
type Variable struct {
Value interface{}
Type ast.Type
}
// Function defines a function that can be executed by the engine.
// The type checker will validate that the proper types will be called
// to the callback.
type Function struct {
// ArgTypes is the list of types in argument order. These are the
// required arguments.
//
// ReturnType is the type of the returned value. The Callback MUST
// return this type.
ArgTypes []ast.Type
ReturnType ast.Type
// Variadic, if true, says that this function is variadic, meaning
// it takes a variable number of arguments. In this case, the
// VariadicType must be set.
Variadic bool
VariadicType ast.Type
// Callback is the function called for a function. The argument
// types are guaranteed to match the spec above by the type checker.
// The length of the args is strictly == len(ArgTypes) unless Varidiac
// is true, in which case its >= len(ArgTypes).
Callback func([]interface{}) (interface{}, error)
}
// LookupFunc will look up a variable by name.
// TODO test
func (s *Scope) LookupFunc(n string) (Function, bool) {