config/lang/ast: introduce Type
This commit is contained in:
parent
cef5f22fed
commit
c4273974de
|
@ -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 (
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -36,3 +36,7 @@ func (n *Concat) String() string {
|
|||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (n *Concat) Type(Scope) (Type, error) {
|
||||
return TypeString, nil
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue