1361 lines
32 KiB
Go
1361 lines
32 KiB
Go
package semantic
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strconv"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/influxdata/platform/query/ast"
|
|
)
|
|
|
|
type Node interface {
|
|
node()
|
|
NodeType() string
|
|
Copy() Node
|
|
|
|
json.Marshaler
|
|
}
|
|
|
|
func (*Program) node() {}
|
|
|
|
func (*BlockStatement) node() {}
|
|
func (*OptionStatement) node() {}
|
|
func (*ExpressionStatement) node() {}
|
|
func (*ReturnStatement) node() {}
|
|
func (*NativeVariableDeclaration) node() {}
|
|
func (*ExternalVariableDeclaration) node() {}
|
|
|
|
func (*ArrayExpression) node() {}
|
|
func (*FunctionExpression) node() {}
|
|
func (*BinaryExpression) node() {}
|
|
func (*CallExpression) node() {}
|
|
func (*ConditionalExpression) node() {}
|
|
func (*IdentifierExpression) node() {}
|
|
func (*LogicalExpression) node() {}
|
|
func (*MemberExpression) node() {}
|
|
func (*ObjectExpression) node() {}
|
|
func (*UnaryExpression) node() {}
|
|
|
|
func (*Identifier) node() {}
|
|
func (*Property) node() {}
|
|
func (*FunctionParam) node() {}
|
|
|
|
func (*BooleanLiteral) node() {}
|
|
func (*DateTimeLiteral) node() {}
|
|
func (*DurationLiteral) node() {}
|
|
func (*FloatLiteral) node() {}
|
|
func (*IntegerLiteral) node() {}
|
|
func (*StringLiteral) node() {}
|
|
func (*RegexpLiteral) node() {}
|
|
func (*UnsignedIntegerLiteral) node() {}
|
|
|
|
type Statement interface {
|
|
Node
|
|
stmt()
|
|
}
|
|
|
|
func (*BlockStatement) stmt() {}
|
|
func (*OptionStatement) stmt() {}
|
|
func (*ExpressionStatement) stmt() {}
|
|
func (*ReturnStatement) stmt() {}
|
|
func (*NativeVariableDeclaration) stmt() {}
|
|
func (*ExternalVariableDeclaration) stmt() {}
|
|
|
|
type Expression interface {
|
|
Node
|
|
Type() Type
|
|
expression()
|
|
}
|
|
|
|
func (*ArrayExpression) expression() {}
|
|
func (*BinaryExpression) expression() {}
|
|
func (*BooleanLiteral) expression() {}
|
|
func (*CallExpression) expression() {}
|
|
func (*ConditionalExpression) expression() {}
|
|
func (*DateTimeLiteral) expression() {}
|
|
func (*DurationLiteral) expression() {}
|
|
func (*FloatLiteral) expression() {}
|
|
func (*FunctionExpression) expression() {}
|
|
func (*IdentifierExpression) expression() {}
|
|
func (*IntegerLiteral) expression() {}
|
|
func (*LogicalExpression) expression() {}
|
|
func (*MemberExpression) expression() {}
|
|
func (*ObjectExpression) expression() {}
|
|
func (*RegexpLiteral) expression() {}
|
|
func (*StringLiteral) expression() {}
|
|
func (*UnaryExpression) expression() {}
|
|
func (*UnsignedIntegerLiteral) expression() {}
|
|
|
|
type Literal interface {
|
|
Expression
|
|
literal()
|
|
}
|
|
|
|
func (*BooleanLiteral) literal() {}
|
|
func (*DateTimeLiteral) literal() {}
|
|
func (*DurationLiteral) literal() {}
|
|
func (*FloatLiteral) literal() {}
|
|
func (*IntegerLiteral) literal() {}
|
|
func (*RegexpLiteral) literal() {}
|
|
func (*StringLiteral) literal() {}
|
|
func (*UnsignedIntegerLiteral) literal() {}
|
|
|
|
type Program struct {
|
|
Body []Statement `json:"body"`
|
|
}
|
|
|
|
func (*Program) NodeType() string { return "Program" }
|
|
|
|
func (p *Program) Copy() Node {
|
|
if p == nil {
|
|
return p
|
|
}
|
|
np := new(Program)
|
|
*np = *p
|
|
|
|
if len(p.Body) > 0 {
|
|
np.Body = make([]Statement, len(p.Body))
|
|
for i, s := range p.Body {
|
|
np.Body[i] = s.Copy().(Statement)
|
|
}
|
|
}
|
|
|
|
return np
|
|
}
|
|
|
|
type BlockStatement struct {
|
|
Body []Statement `json:"body"`
|
|
}
|
|
|
|
func (*BlockStatement) NodeType() string { return "BlockStatement" }
|
|
|
|
func (s *BlockStatement) ReturnStatement() *ReturnStatement {
|
|
return s.Body[len(s.Body)-1].(*ReturnStatement)
|
|
}
|
|
|
|
func (s *BlockStatement) Copy() Node {
|
|
if s == nil {
|
|
return s
|
|
}
|
|
ns := new(BlockStatement)
|
|
*ns = *s
|
|
|
|
if len(s.Body) > 0 {
|
|
ns.Body = make([]Statement, len(s.Body))
|
|
for i, stmt := range s.Body {
|
|
ns.Body[i] = stmt.Copy().(Statement)
|
|
}
|
|
}
|
|
|
|
return ns
|
|
}
|
|
|
|
type OptionStatement struct {
|
|
Declaration VariableDeclaration `json:"declaration"`
|
|
}
|
|
|
|
func (s *OptionStatement) NodeType() string { return "OptionStatement" }
|
|
|
|
func (s *OptionStatement) Copy() Node {
|
|
if s == nil {
|
|
return s
|
|
}
|
|
ns := new(OptionStatement)
|
|
*ns = *s
|
|
|
|
ns.Declaration = s.Declaration.Copy().(VariableDeclaration)
|
|
|
|
return ns
|
|
}
|
|
|
|
type ExpressionStatement struct {
|
|
Expression Expression `json:"expression"`
|
|
}
|
|
|
|
func (*ExpressionStatement) NodeType() string { return "ExpressionStatement" }
|
|
|
|
func (s *ExpressionStatement) Copy() Node {
|
|
if s == nil {
|
|
return s
|
|
}
|
|
ns := new(ExpressionStatement)
|
|
*ns = *s
|
|
|
|
ns.Expression = s.Expression.Copy().(Expression)
|
|
|
|
return ns
|
|
}
|
|
|
|
type ReturnStatement struct {
|
|
Argument Expression `json:"argument"`
|
|
}
|
|
|
|
func (*ReturnStatement) NodeType() string { return "ReturnStatement" }
|
|
|
|
func (s *ReturnStatement) Copy() Node {
|
|
if s == nil {
|
|
return s
|
|
}
|
|
ns := new(ReturnStatement)
|
|
*ns = *s
|
|
|
|
ns.Argument = s.Argument.Copy().(Expression)
|
|
|
|
return ns
|
|
}
|
|
|
|
type VariableDeclaration interface {
|
|
Statement
|
|
ID() *Identifier
|
|
InitType() Type
|
|
}
|
|
|
|
type NativeVariableDeclaration struct {
|
|
Identifier *Identifier `json:"identifier"`
|
|
Init Expression `json:"init"`
|
|
}
|
|
|
|
func (d *NativeVariableDeclaration) ID() *Identifier {
|
|
return d.Identifier
|
|
}
|
|
func (d *NativeVariableDeclaration) InitType() Type {
|
|
return d.Init.Type()
|
|
}
|
|
|
|
func (*NativeVariableDeclaration) NodeType() string { return "NativeVariableDeclaration" }
|
|
|
|
func (s *NativeVariableDeclaration) Copy() Node {
|
|
if s == nil {
|
|
return s
|
|
}
|
|
ns := new(NativeVariableDeclaration)
|
|
*ns = *s
|
|
|
|
ns.Identifier = s.Identifier.Copy().(*Identifier)
|
|
|
|
if s.Init != nil {
|
|
ns.Init = s.Init.Copy().(Expression)
|
|
}
|
|
|
|
return ns
|
|
}
|
|
|
|
type ExternalVariableDeclaration struct {
|
|
Identifier *Identifier `json:"identifier"`
|
|
Type Type `json:"type"`
|
|
}
|
|
|
|
func NewExternalVariableDeclaration(name string, typ Type) *ExternalVariableDeclaration {
|
|
return &ExternalVariableDeclaration{
|
|
Identifier: &Identifier{Name: name},
|
|
Type: typ,
|
|
}
|
|
}
|
|
|
|
func (d *ExternalVariableDeclaration) ID() *Identifier {
|
|
return d.Identifier
|
|
}
|
|
func (d *ExternalVariableDeclaration) InitType() Type {
|
|
return d.Type
|
|
}
|
|
|
|
func (*ExternalVariableDeclaration) NodeType() string { return "ExternalVariableDeclaration" }
|
|
|
|
func (s *ExternalVariableDeclaration) Copy() Node {
|
|
if s == nil {
|
|
return s
|
|
}
|
|
ns := new(ExternalVariableDeclaration)
|
|
*ns = *s
|
|
|
|
ns.Identifier = s.Identifier.Copy().(*Identifier)
|
|
|
|
return ns
|
|
}
|
|
|
|
type ArrayExpression struct {
|
|
Elements []Expression `json:"elements"`
|
|
typ atomic.Value // Type
|
|
}
|
|
|
|
func (*ArrayExpression) NodeType() string { return "ArrayExpression" }
|
|
func (e *ArrayExpression) Type() Type {
|
|
t := e.typ.Load()
|
|
if t != nil {
|
|
return t.(Type)
|
|
}
|
|
typ := arrayTypeOf(e)
|
|
e.typ.Store(typ)
|
|
return typ
|
|
}
|
|
|
|
func (e *ArrayExpression) Copy() Node {
|
|
if e == nil {
|
|
return e
|
|
}
|
|
ne := new(ArrayExpression)
|
|
*ne = *e
|
|
|
|
if len(e.Elements) > 0 {
|
|
ne.Elements = make([]Expression, len(e.Elements))
|
|
for i, elem := range e.Elements {
|
|
ne.Elements[i] = elem.Copy().(Expression)
|
|
}
|
|
}
|
|
|
|
return ne
|
|
}
|
|
|
|
type FunctionExpression struct {
|
|
Params []*FunctionParam `json:"params"`
|
|
Body Node `json:"body"`
|
|
typ atomic.Value //Type
|
|
}
|
|
|
|
func (*FunctionExpression) NodeType() string { return "ArrowFunctionExpression" }
|
|
func (e *FunctionExpression) Type() Type {
|
|
t := e.typ.Load()
|
|
if t != nil {
|
|
return t.(Type)
|
|
}
|
|
typ := functionTypeOf(e)
|
|
e.typ.Store(typ)
|
|
return typ
|
|
}
|
|
|
|
func (e *FunctionExpression) Copy() Node {
|
|
if e == nil {
|
|
return e
|
|
}
|
|
ne := new(FunctionExpression)
|
|
*ne = *e
|
|
|
|
if len(e.Params) > 0 {
|
|
ne.Params = make([]*FunctionParam, len(e.Params))
|
|
for i, p := range e.Params {
|
|
ne.Params[i] = p.Copy().(*FunctionParam)
|
|
}
|
|
}
|
|
ne.Body = e.Body.Copy()
|
|
|
|
return ne
|
|
}
|
|
|
|
type FunctionParam struct {
|
|
Key *Identifier `json:"key"`
|
|
Default Expression `json:"default"`
|
|
Piped bool `json:"piped,omitempty"`
|
|
declaration VariableDeclaration
|
|
}
|
|
|
|
func (*FunctionParam) NodeType() string { return "FunctionParam" }
|
|
|
|
func (f *FunctionParam) Type() Type {
|
|
if f.declaration == nil {
|
|
if f.Default != nil {
|
|
f.declaration = &NativeVariableDeclaration{
|
|
Identifier: f.Key,
|
|
Init: f.Default,
|
|
}
|
|
} else {
|
|
return Invalid
|
|
}
|
|
}
|
|
return f.declaration.InitType()
|
|
}
|
|
|
|
func (p *FunctionParam) Copy() Node {
|
|
if p == nil {
|
|
return p
|
|
}
|
|
np := new(FunctionParam)
|
|
*np = *p
|
|
|
|
np.Key = p.Key.Copy().(*Identifier)
|
|
if np.Default != nil {
|
|
np.Default = p.Default.Copy().(Expression)
|
|
}
|
|
|
|
return np
|
|
}
|
|
|
|
type BinaryExpression struct {
|
|
Operator ast.OperatorKind `json:"operator"`
|
|
Left Expression `json:"left"`
|
|
Right Expression `json:"right"`
|
|
}
|
|
|
|
func (*BinaryExpression) NodeType() string { return "BinaryExpression" }
|
|
func (e *BinaryExpression) Type() Type {
|
|
return binaryTypesLookup[binarySignature{
|
|
operator: e.Operator,
|
|
left: e.Left.Type().Kind(),
|
|
right: e.Right.Type().Kind(),
|
|
}]
|
|
}
|
|
|
|
func (e *BinaryExpression) Copy() Node {
|
|
if e == nil {
|
|
return e
|
|
}
|
|
ne := new(BinaryExpression)
|
|
*ne = *e
|
|
|
|
ne.Left = e.Left.Copy().(Expression)
|
|
ne.Right = e.Right.Copy().(Expression)
|
|
|
|
return ne
|
|
}
|
|
|
|
type CallExpression struct {
|
|
Callee Expression `json:"callee"`
|
|
Arguments *ObjectExpression `json:"arguments"`
|
|
}
|
|
|
|
func (*CallExpression) NodeType() string { return "CallExpression" }
|
|
func (e *CallExpression) Type() Type {
|
|
return e.Callee.Type().ReturnType()
|
|
}
|
|
|
|
func (e *CallExpression) Copy() Node {
|
|
if e == nil {
|
|
return e
|
|
}
|
|
ne := new(CallExpression)
|
|
*ne = *e
|
|
|
|
ne.Callee = e.Callee.Copy().(Expression)
|
|
ne.Arguments = e.Arguments.Copy().(*ObjectExpression)
|
|
|
|
return ne
|
|
}
|
|
|
|
type ConditionalExpression struct {
|
|
Test Expression `json:"test"`
|
|
Alternate Expression `json:"alternate"`
|
|
Consequent Expression `json:"consequent"`
|
|
}
|
|
|
|
func (*ConditionalExpression) NodeType() string { return "ConditionalExpression" }
|
|
|
|
func (e *ConditionalExpression) Copy() Node {
|
|
if e == nil {
|
|
return e
|
|
}
|
|
ne := new(ConditionalExpression)
|
|
*ne = *e
|
|
|
|
ne.Test = e.Test.Copy().(Expression)
|
|
ne.Alternate = e.Alternate.Copy().(Expression)
|
|
ne.Consequent = e.Consequent.Copy().(Expression)
|
|
|
|
return ne
|
|
}
|
|
|
|
type LogicalExpression struct {
|
|
Operator ast.LogicalOperatorKind `json:"operator"`
|
|
Left Expression `json:"left"`
|
|
Right Expression `json:"right"`
|
|
}
|
|
|
|
func (*LogicalExpression) NodeType() string { return "LogicalExpression" }
|
|
func (*LogicalExpression) Type() Type { return Bool }
|
|
|
|
func (e *LogicalExpression) Copy() Node {
|
|
if e == nil {
|
|
return e
|
|
}
|
|
ne := new(LogicalExpression)
|
|
*ne = *e
|
|
|
|
ne.Left = e.Left.Copy().(Expression)
|
|
ne.Right = e.Right.Copy().(Expression)
|
|
|
|
return ne
|
|
}
|
|
|
|
type MemberExpression struct {
|
|
Object Expression `json:"object"`
|
|
Property string `json:"property"`
|
|
}
|
|
|
|
func (*MemberExpression) NodeType() string { return "MemberExpression" }
|
|
|
|
func (e *MemberExpression) Type() Type {
|
|
t := e.Object.Type()
|
|
if t.Kind() != Object {
|
|
return Invalid
|
|
}
|
|
return e.Object.Type().PropertyType(e.Property)
|
|
}
|
|
|
|
func (e *MemberExpression) Copy() Node {
|
|
if e == nil {
|
|
return e
|
|
}
|
|
ne := new(MemberExpression)
|
|
*ne = *e
|
|
|
|
ne.Object = e.Object.Copy().(Expression)
|
|
|
|
return ne
|
|
}
|
|
|
|
type ObjectExpression struct {
|
|
Properties []*Property `json:"properties"`
|
|
typ atomic.Value //Type
|
|
}
|
|
|
|
func (*ObjectExpression) NodeType() string { return "ObjectExpression" }
|
|
func (e *ObjectExpression) Type() Type {
|
|
t := e.typ.Load()
|
|
if t != nil {
|
|
return t.(Type)
|
|
}
|
|
typ := objectTypeOf(e)
|
|
e.typ.Store(typ)
|
|
return typ
|
|
}
|
|
|
|
func (e *ObjectExpression) Copy() Node {
|
|
if e == nil {
|
|
return e
|
|
}
|
|
ne := new(ObjectExpression)
|
|
*ne = *e
|
|
|
|
if len(e.Properties) > 0 {
|
|
ne.Properties = make([]*Property, len(e.Properties))
|
|
for i, prop := range e.Properties {
|
|
ne.Properties[i] = prop.Copy().(*Property)
|
|
}
|
|
}
|
|
|
|
return ne
|
|
}
|
|
|
|
type UnaryExpression struct {
|
|
Operator ast.OperatorKind `json:"operator"`
|
|
Argument Expression `json:"argument"`
|
|
}
|
|
|
|
func (*UnaryExpression) NodeType() string { return "UnaryExpression" }
|
|
func (e *UnaryExpression) Type() Type {
|
|
return e.Argument.Type()
|
|
}
|
|
|
|
func (e *UnaryExpression) Copy() Node {
|
|
if e == nil {
|
|
return e
|
|
}
|
|
ne := new(UnaryExpression)
|
|
*ne = *e
|
|
|
|
ne.Argument = e.Argument.Copy().(Expression)
|
|
|
|
return ne
|
|
}
|
|
|
|
type Property struct {
|
|
Key *Identifier `json:"key"`
|
|
Value Expression `json:"value"`
|
|
}
|
|
|
|
func (*Property) NodeType() string { return "Property" }
|
|
|
|
func (p *Property) Copy() Node {
|
|
if p == nil {
|
|
return p
|
|
}
|
|
np := new(Property)
|
|
*np = *p
|
|
|
|
np.Value = p.Value.Copy().(Expression)
|
|
|
|
return np
|
|
}
|
|
|
|
type IdentifierExpression struct {
|
|
Name string `json:"name"`
|
|
// declaration is the node that declares this identifier
|
|
declaration VariableDeclaration
|
|
}
|
|
|
|
func (*IdentifierExpression) NodeType() string { return "IdentifierExpression" }
|
|
|
|
func (e *IdentifierExpression) Type() Type {
|
|
if e.declaration == nil {
|
|
return Invalid
|
|
}
|
|
return e.declaration.InitType()
|
|
}
|
|
|
|
func (e *IdentifierExpression) Copy() Node {
|
|
if e == nil {
|
|
return e
|
|
}
|
|
ne := new(IdentifierExpression)
|
|
*ne = *e
|
|
|
|
if ne.declaration != nil {
|
|
ne.declaration = e.declaration.Copy().(VariableDeclaration)
|
|
}
|
|
|
|
return ne
|
|
}
|
|
|
|
type Identifier struct {
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
func (*Identifier) NodeType() string { return "Identifier" }
|
|
|
|
func (i *Identifier) Copy() Node {
|
|
if i == nil {
|
|
return i
|
|
}
|
|
ni := new(Identifier)
|
|
*ni = *i
|
|
|
|
return ni
|
|
}
|
|
|
|
type BooleanLiteral struct {
|
|
Value bool `json:"value"`
|
|
}
|
|
|
|
func (*BooleanLiteral) NodeType() string { return "BooleanLiteral" }
|
|
func (*BooleanLiteral) Type() Type { return Bool }
|
|
|
|
func (l *BooleanLiteral) Copy() Node {
|
|
if l == nil {
|
|
return l
|
|
}
|
|
nl := new(BooleanLiteral)
|
|
*nl = *l
|
|
|
|
return nl
|
|
}
|
|
|
|
type DateTimeLiteral struct {
|
|
Value time.Time `json:"value"`
|
|
}
|
|
|
|
func (*DateTimeLiteral) NodeType() string { return "DateTimeLiteral" }
|
|
func (*DateTimeLiteral) Type() Type { return Time }
|
|
|
|
func (l *DateTimeLiteral) Copy() Node {
|
|
if l == nil {
|
|
return l
|
|
}
|
|
nl := new(DateTimeLiteral)
|
|
*nl = *l
|
|
|
|
return nl
|
|
}
|
|
|
|
type DurationLiteral struct {
|
|
Value time.Duration `json:"value"`
|
|
}
|
|
|
|
func (*DurationLiteral) NodeType() string { return "DurationLiteral" }
|
|
func (*DurationLiteral) Type() Type { return Duration }
|
|
|
|
func (l *DurationLiteral) Copy() Node {
|
|
if l == nil {
|
|
return l
|
|
}
|
|
nl := new(DurationLiteral)
|
|
*nl = *l
|
|
|
|
return nl
|
|
}
|
|
|
|
type IntegerLiteral struct {
|
|
Value int64 `json:"value"`
|
|
}
|
|
|
|
func (*IntegerLiteral) NodeType() string { return "IntegerLiteral" }
|
|
func (*IntegerLiteral) Type() Type { return Int }
|
|
|
|
func (l *IntegerLiteral) Copy() Node {
|
|
if l == nil {
|
|
return l
|
|
}
|
|
nl := new(IntegerLiteral)
|
|
*nl = *l
|
|
|
|
return nl
|
|
}
|
|
|
|
type FloatLiteral struct {
|
|
Value float64 `json:"value"`
|
|
}
|
|
|
|
func (*FloatLiteral) NodeType() string { return "FloatLiteral" }
|
|
func (*FloatLiteral) Type() Type { return Float }
|
|
|
|
func (l *FloatLiteral) Copy() Node {
|
|
if l == nil {
|
|
return l
|
|
}
|
|
nl := new(FloatLiteral)
|
|
*nl = *l
|
|
|
|
return nl
|
|
}
|
|
|
|
type RegexpLiteral struct {
|
|
Value *regexp.Regexp `json:"value"`
|
|
}
|
|
|
|
func (*RegexpLiteral) NodeType() string { return "RegexpLiteral" }
|
|
func (*RegexpLiteral) Type() Type { return Regexp }
|
|
|
|
func (l *RegexpLiteral) Copy() Node {
|
|
if l == nil {
|
|
return l
|
|
}
|
|
nl := new(RegexpLiteral)
|
|
*nl = *l
|
|
|
|
nl.Value = l.Value.Copy()
|
|
|
|
return nl
|
|
}
|
|
|
|
type StringLiteral struct {
|
|
Value string `json:"value"`
|
|
}
|
|
|
|
func (*StringLiteral) NodeType() string { return "StringLiteral" }
|
|
func (*StringLiteral) Type() Type { return String }
|
|
|
|
func (l *StringLiteral) Copy() Node {
|
|
if l == nil {
|
|
return l
|
|
}
|
|
nl := new(StringLiteral)
|
|
*nl = *l
|
|
|
|
return nl
|
|
}
|
|
|
|
type UnsignedIntegerLiteral struct {
|
|
Value uint64 `json:"value"`
|
|
}
|
|
|
|
func (*UnsignedIntegerLiteral) NodeType() string { return "UnsignedIntegerLiteral" }
|
|
func (*UnsignedIntegerLiteral) Type() Type { return UInt }
|
|
|
|
func (l *UnsignedIntegerLiteral) Copy() Node {
|
|
if l == nil {
|
|
return l
|
|
}
|
|
nl := new(UnsignedIntegerLiteral)
|
|
*nl = *l
|
|
|
|
return nl
|
|
}
|
|
|
|
// New creates a semantic graph from the provided AST and builtin declarations
|
|
// The declarations will be modified for any variable declaration found in the program.
|
|
func New(prog *ast.Program, declarations map[string]VariableDeclaration) (*Program, error) {
|
|
if declarations == nil {
|
|
// NOTE: Calls to New may expect modifications to declarations to persist outside the function.
|
|
// The check is against nil instead of len(declarations) == 0 for this reason.
|
|
declarations = make(map[string]VariableDeclaration)
|
|
}
|
|
return analyzeProgram(prog, DeclarationScope(declarations))
|
|
}
|
|
|
|
// SolveTypes inspects the expression and ensures that all sub expressions konw their type
|
|
func SolveTypes(n Node, declarations DeclarationScope) {
|
|
if declarations == nil {
|
|
declarations = make(DeclarationScope)
|
|
}
|
|
// TODO(nathanielc): Use a formal type inference system like Hindley-Milner
|
|
// TODO(nathanielc): The current implementation only implements the code paths that the current tests and common use cases need.
|
|
// The implementation is by no means complete for all possible expressions.
|
|
v := solverVisitor{
|
|
declarations: declarations,
|
|
}
|
|
Walk(v, n)
|
|
}
|
|
|
|
type solverVisitor struct {
|
|
declarations DeclarationScope
|
|
}
|
|
|
|
func (v solverVisitor) Done() {}
|
|
func (v solverVisitor) Visit(n Node) Visitor {
|
|
switch n := n.(type) {
|
|
case *NativeVariableDeclaration:
|
|
v.declarations[n.Identifier.Name] = n
|
|
case *FunctionExpression:
|
|
funcDeclarations := v.declarations.Copy()
|
|
nv := solverVisitor{
|
|
declarations: funcDeclarations,
|
|
}
|
|
return nv
|
|
case *FunctionParam:
|
|
n.Type()
|
|
if n.declaration != nil {
|
|
v.declarations[n.Key.Name] = n.declaration
|
|
}
|
|
case *IdentifierExpression:
|
|
declaration, ok := v.declarations[n.Name]
|
|
if ok {
|
|
n.declaration = declaration
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
type DeclarationScope map[string]VariableDeclaration
|
|
|
|
func (s DeclarationScope) Copy() DeclarationScope {
|
|
cpy := make(DeclarationScope, len(s))
|
|
for k, v := range s {
|
|
cpy[k] = v
|
|
}
|
|
return cpy
|
|
}
|
|
|
|
func analyzeProgram(prog *ast.Program, declarations DeclarationScope) (*Program, error) {
|
|
p := &Program{
|
|
Body: make([]Statement, len(prog.Body)),
|
|
}
|
|
for i, s := range prog.Body {
|
|
n, err := analyzeStatment(s, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.Body[i] = n
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
func analyzeNode(n ast.Node, declarations DeclarationScope) (Node, error) {
|
|
switch n := n.(type) {
|
|
case ast.Statement:
|
|
return analyzeStatment(n, declarations)
|
|
case ast.Expression:
|
|
return analyzeExpression(n, declarations)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported node %T", n)
|
|
}
|
|
}
|
|
|
|
func analyzeStatment(s ast.Statement, declarations DeclarationScope) (Statement, error) {
|
|
switch s := s.(type) {
|
|
case *ast.BlockStatement:
|
|
return analyzeBlockStatement(s, declarations)
|
|
case *ast.OptionStatement:
|
|
return analyzeOptionStatement(s, declarations)
|
|
case *ast.ExpressionStatement:
|
|
return analyzeExpressionStatement(s, declarations)
|
|
case *ast.ReturnStatement:
|
|
return analyzeReturnStatement(s, declarations)
|
|
case *ast.VariableDeclaration:
|
|
// Expect a single declaration
|
|
if len(s.Declarations) != 1 {
|
|
return nil, fmt.Errorf("only single variable declarations are supported, found %d declarations", len(s.Declarations))
|
|
}
|
|
return analyzeVariableDeclaration(s.Declarations[0], declarations)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported statement %T", s)
|
|
}
|
|
}
|
|
|
|
func analyzeBlockStatement(block *ast.BlockStatement, declarations DeclarationScope) (*BlockStatement, error) {
|
|
declarations = declarations.Copy()
|
|
b := &BlockStatement{
|
|
Body: make([]Statement, len(block.Body)),
|
|
}
|
|
for i, s := range block.Body {
|
|
n, err := analyzeStatment(s, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
b.Body[i] = n
|
|
}
|
|
last := len(b.Body) - 1
|
|
if _, ok := b.Body[last].(*ReturnStatement); !ok {
|
|
return nil, errors.New("missing return statement in block")
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
func analyzeOptionStatement(option *ast.OptionStatement, declarations DeclarationScope) (*OptionStatement, error) {
|
|
declaration, err := analyzeVariableDeclaration(option.Declaration, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &OptionStatement{
|
|
Declaration: declaration,
|
|
}, nil
|
|
}
|
|
|
|
func analyzeExpressionStatement(expr *ast.ExpressionStatement, declarations DeclarationScope) (*ExpressionStatement, error) {
|
|
e, err := analyzeExpression(expr.Expression, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ExpressionStatement{
|
|
Expression: e,
|
|
}, nil
|
|
}
|
|
|
|
func analyzeReturnStatement(ret *ast.ReturnStatement, declarations DeclarationScope) (*ReturnStatement, error) {
|
|
arg, err := analyzeExpression(ret.Argument, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ReturnStatement{
|
|
Argument: arg,
|
|
}, nil
|
|
}
|
|
|
|
func analyzeVariableDeclaration(decl *ast.VariableDeclarator, declarations DeclarationScope) (*NativeVariableDeclaration, error) {
|
|
id, err := analyzeIdentifier(decl.ID, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
init, err := analyzeExpression(decl.Init, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vd := &NativeVariableDeclaration{
|
|
Identifier: id,
|
|
Init: init,
|
|
}
|
|
declarations[vd.Identifier.Name] = vd
|
|
return vd, nil
|
|
}
|
|
|
|
func analyzeExpression(expr ast.Expression, declarations DeclarationScope) (Expression, error) {
|
|
switch expr := expr.(type) {
|
|
case *ast.ArrowFunctionExpression:
|
|
return analyzeArrowFunctionExpression(expr, declarations)
|
|
case *ast.CallExpression:
|
|
return analyzeCallExpression(expr, declarations)
|
|
case *ast.MemberExpression:
|
|
return analyzeMemberExpression(expr, declarations)
|
|
case *ast.PipeExpression:
|
|
return analyzePipeExpression(expr, declarations)
|
|
case *ast.BinaryExpression:
|
|
return analyzeBinaryExpression(expr, declarations)
|
|
case *ast.UnaryExpression:
|
|
return analyzeUnaryExpression(expr, declarations)
|
|
case *ast.LogicalExpression:
|
|
return analyzeLogicalExpression(expr, declarations)
|
|
case *ast.ObjectExpression:
|
|
return analyzeObjectExpression(expr, declarations)
|
|
case *ast.ArrayExpression:
|
|
return analyzeArrayExpression(expr, declarations)
|
|
case *ast.Identifier:
|
|
return analyzeIdentifierExpression(expr, declarations)
|
|
case ast.Literal:
|
|
return analyzeLiteral(expr, declarations)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported expression %T", expr)
|
|
}
|
|
}
|
|
|
|
func analyzeLiteral(lit ast.Literal, declarations DeclarationScope) (Literal, error) {
|
|
switch lit := lit.(type) {
|
|
case *ast.StringLiteral:
|
|
return analyzeStringLiteral(lit, declarations)
|
|
case *ast.BooleanLiteral:
|
|
return analyzeBooleanLiteral(lit, declarations)
|
|
case *ast.FloatLiteral:
|
|
return analyzeFloatLiteral(lit, declarations)
|
|
case *ast.IntegerLiteral:
|
|
return analyzeIntegerLiteral(lit, declarations)
|
|
case *ast.UnsignedIntegerLiteral:
|
|
return analyzeUnsignedIntegerLiteral(lit, declarations)
|
|
case *ast.RegexpLiteral:
|
|
return analyzeRegexpLiteral(lit, declarations)
|
|
case *ast.DurationLiteral:
|
|
return analyzeDurationLiteral(lit, declarations)
|
|
case *ast.DateTimeLiteral:
|
|
return analyzeDateTimeLiteral(lit, declarations)
|
|
case *ast.PipeLiteral:
|
|
return nil, errors.New("a pipe literal may only be used as a default value for an argument in a function definition")
|
|
default:
|
|
return nil, fmt.Errorf("unsupported literal %T", lit)
|
|
}
|
|
}
|
|
|
|
func analyzeArrowFunctionExpression(arrow *ast.ArrowFunctionExpression, declarations DeclarationScope) (*FunctionExpression, error) {
|
|
declarations = declarations.Copy()
|
|
f := &FunctionExpression{
|
|
Params: make([]*FunctionParam, len(arrow.Params)),
|
|
}
|
|
pipedCount := 0
|
|
for i, p := range arrow.Params {
|
|
key, err := analyzeIdentifier(p.Key, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var (
|
|
def Expression
|
|
declaration VariableDeclaration
|
|
piped bool
|
|
)
|
|
if p.Value != nil {
|
|
if _, ok := p.Value.(*ast.PipeLiteral); ok {
|
|
// Special case the PipeLiteral
|
|
piped = true
|
|
pipedCount++
|
|
if pipedCount > 1 {
|
|
return nil, errors.New("only a single argument may be piped")
|
|
}
|
|
} else {
|
|
d, err := analyzeExpression(p.Value, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
def = d
|
|
declaration = &NativeVariableDeclaration{
|
|
Identifier: key,
|
|
Init: def,
|
|
}
|
|
declarations[key.Name] = declaration
|
|
}
|
|
}
|
|
|
|
f.Params[i] = &FunctionParam{
|
|
Key: key,
|
|
Default: def,
|
|
Piped: piped,
|
|
declaration: declaration,
|
|
}
|
|
|
|
}
|
|
|
|
b, err := analyzeNode(arrow.Body, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
f.Body = b
|
|
|
|
return f, nil
|
|
}
|
|
|
|
func analyzeCallExpression(call *ast.CallExpression, declarations DeclarationScope) (*CallExpression, error) {
|
|
callee, err := analyzeExpression(call.Callee, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var args *ObjectExpression
|
|
if l := len(call.Arguments); l > 1 {
|
|
return nil, fmt.Errorf("arguments are not a single object expression %v", args)
|
|
} else if l == 1 {
|
|
obj, ok := call.Arguments[0].(*ast.ObjectExpression)
|
|
if !ok {
|
|
return nil, fmt.Errorf("arguments not an object expression")
|
|
}
|
|
var err error
|
|
args, err = analyzeObjectExpression(obj, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
args = new(ObjectExpression)
|
|
}
|
|
|
|
expr := &CallExpression{
|
|
Callee: callee,
|
|
Arguments: args,
|
|
}
|
|
|
|
declarations = declarations.Copy()
|
|
for _, arg := range args.Properties {
|
|
declarations[arg.Key.Name] = &NativeVariableDeclaration{
|
|
Identifier: arg.Key,
|
|
Init: arg.Value,
|
|
}
|
|
}
|
|
|
|
ApplyNewDeclarations(expr.Callee, declarations)
|
|
return expr, nil
|
|
}
|
|
|
|
func ApplyNewDeclarations(n Node, declarations map[string]VariableDeclaration) {
|
|
v := &applyDeclarationsVisitor{
|
|
declarations: declarations,
|
|
}
|
|
Walk(v, n)
|
|
}
|
|
|
|
type applyDeclarationsVisitor struct {
|
|
declarations DeclarationScope
|
|
}
|
|
|
|
func (v *applyDeclarationsVisitor) Visit(n Node) Visitor {
|
|
switch n := n.(type) {
|
|
case *IdentifierExpression:
|
|
if n.declaration == nil {
|
|
n.declaration = v.declarations[n.Name]
|
|
}
|
|
// No need to walk further down this branch
|
|
return nil
|
|
// TODO(nathanielc): Support polymorphic function arguments
|
|
//case *FunctionExpression:
|
|
// // Remove type information since we may have changed it.
|
|
// n.typ.Store((Type)(Invalid))
|
|
case *FunctionParam:
|
|
if n.declaration == nil {
|
|
n.declaration = v.declarations[n.Key.Name]
|
|
}
|
|
// No need to walk further down this branch
|
|
return nil
|
|
}
|
|
return v
|
|
}
|
|
func (v *applyDeclarationsVisitor) Done() {}
|
|
|
|
func analyzeMemberExpression(member *ast.MemberExpression, declarations DeclarationScope) (*MemberExpression, error) {
|
|
obj, err := analyzeExpression(member.Object, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var propertyName string
|
|
switch p := member.Property.(type) {
|
|
case *ast.Identifier:
|
|
propertyName = p.Name
|
|
case *ast.StringLiteral:
|
|
propertyName = p.Value
|
|
case *ast.IntegerLiteral:
|
|
propertyName = strconv.FormatInt(p.Value, 10)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported member property expression of type %T", member.Property)
|
|
}
|
|
|
|
return &MemberExpression{
|
|
Object: obj,
|
|
Property: propertyName,
|
|
}, nil
|
|
}
|
|
|
|
func analyzePipeExpression(pipe *ast.PipeExpression, declarations DeclarationScope) (*CallExpression, error) {
|
|
call, err := analyzeCallExpression(pipe.Call, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
decl, err := resolveDeclaration(call.Callee)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fnTyp := decl.InitType()
|
|
if fnTyp.Kind() != Function {
|
|
return nil, fmt.Errorf("cannot pipe into non function %q", fnTyp.Kind())
|
|
}
|
|
key := fnTyp.PipeArgument()
|
|
if key == "" {
|
|
return nil, fmt.Errorf("function %q does not have a pipe argument", decl.ID().Name)
|
|
}
|
|
|
|
value, err := analyzeExpression(pipe.Argument, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
property := &Property{
|
|
Key: &Identifier{Name: key},
|
|
Value: value,
|
|
}
|
|
|
|
found := false
|
|
for i, p := range call.Arguments.Properties {
|
|
if key == p.Key.Name {
|
|
found = true
|
|
call.Arguments.Properties[i] = property
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
call.Arguments.Properties = append(call.Arguments.Properties, property)
|
|
}
|
|
return call, nil
|
|
}
|
|
|
|
// resolveDeclaration traverse the expression until a variable declaration is found for the expression.
|
|
func resolveDeclaration(n Node) (VariableDeclaration, error) {
|
|
switch n := n.(type) {
|
|
case *IdentifierExpression:
|
|
if n.declaration == nil {
|
|
return nil, fmt.Errorf("identifier expression %q has no declaration", n.Name)
|
|
}
|
|
return resolveDeclaration(n.declaration)
|
|
case *ExternalVariableDeclaration:
|
|
return n, nil
|
|
case *NativeVariableDeclaration:
|
|
if n.Init == nil {
|
|
return nil, fmt.Errorf("variable declaration %v has no init", n.Identifier)
|
|
}
|
|
if i, ok := n.Init.(*IdentifierExpression); ok {
|
|
return resolveDeclaration(i)
|
|
}
|
|
return n, nil
|
|
}
|
|
return nil, errors.New("no declaration found")
|
|
}
|
|
|
|
func analyzeBinaryExpression(binary *ast.BinaryExpression, declarations DeclarationScope) (*BinaryExpression, error) {
|
|
left, err := analyzeExpression(binary.Left, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
right, err := analyzeExpression(binary.Right, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &BinaryExpression{
|
|
Operator: binary.Operator,
|
|
Left: left,
|
|
Right: right,
|
|
}, nil
|
|
}
|
|
|
|
func analyzeUnaryExpression(unary *ast.UnaryExpression, declarations DeclarationScope) (*UnaryExpression, error) {
|
|
arg, err := analyzeExpression(unary.Argument, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// TODO(nathanielc): validate operand type once we have type inference working with functions.
|
|
//k := arg.Type().Kind()
|
|
//if k != Bool && k != Int && k != Float && k != Duration {
|
|
// return nil, fmt.Errorf("invalid unary operator %v on type %v", unary.Operator, k)
|
|
//}
|
|
return &UnaryExpression{
|
|
Operator: unary.Operator,
|
|
Argument: arg,
|
|
}, nil
|
|
}
|
|
func analyzeLogicalExpression(logical *ast.LogicalExpression, declarations DeclarationScope) (*LogicalExpression, error) {
|
|
left, err := analyzeExpression(logical.Left, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// TODO(nathanielc): Validate operand types once we have type inference working with functions.
|
|
//if k := left.Type().Kind(); k != Bool {
|
|
// return nil, fmt.Errorf("left operand to logical expression is not a boolean, got kind %v", k)
|
|
//}
|
|
right, err := analyzeExpression(logical.Right, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
//if k := right.Type().Kind(); k != Bool {
|
|
// return nil, fmt.Errorf("right operand to logical expression is not a boolean, got kind %v", k)
|
|
//}
|
|
return &LogicalExpression{
|
|
Operator: logical.Operator,
|
|
Left: left,
|
|
Right: right,
|
|
}, nil
|
|
}
|
|
func analyzeObjectExpression(obj *ast.ObjectExpression, declarations DeclarationScope) (*ObjectExpression, error) {
|
|
o := &ObjectExpression{
|
|
Properties: make([]*Property, len(obj.Properties)),
|
|
}
|
|
for i, p := range obj.Properties {
|
|
n, err := analyzeProperty(p, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
o.Properties[i] = n
|
|
}
|
|
return o, nil
|
|
}
|
|
func analyzeArrayExpression(array *ast.ArrayExpression, declarations DeclarationScope) (*ArrayExpression, error) {
|
|
a := &ArrayExpression{
|
|
Elements: make([]Expression, len(array.Elements)),
|
|
}
|
|
for i, e := range array.Elements {
|
|
n, err := analyzeExpression(e, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a.Elements[i] = n
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func analyzeIdentifier(ident *ast.Identifier, declarations DeclarationScope) (*Identifier, error) {
|
|
return &Identifier{
|
|
Name: ident.Name,
|
|
}, nil
|
|
}
|
|
|
|
func analyzeIdentifierExpression(ident *ast.Identifier, declarations DeclarationScope) (*IdentifierExpression, error) {
|
|
return &IdentifierExpression{
|
|
Name: ident.Name,
|
|
declaration: declarations[ident.Name],
|
|
}, nil
|
|
}
|
|
|
|
func analyzeProperty(property *ast.Property, declarations DeclarationScope) (*Property, error) {
|
|
key, err := analyzeIdentifier(property.Key, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value, err := analyzeExpression(property.Value, declarations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Property{
|
|
Key: key,
|
|
Value: value,
|
|
}, nil
|
|
}
|
|
|
|
func analyzeDateTimeLiteral(lit *ast.DateTimeLiteral, declarations DeclarationScope) (*DateTimeLiteral, error) {
|
|
return &DateTimeLiteral{
|
|
Value: lit.Value,
|
|
}, nil
|
|
}
|
|
func analyzeDurationLiteral(lit *ast.DurationLiteral, declarations DeclarationScope) (*DurationLiteral, error) {
|
|
return &DurationLiteral{
|
|
Value: lit.Value,
|
|
}, nil
|
|
}
|
|
func analyzeFloatLiteral(lit *ast.FloatLiteral, declarations DeclarationScope) (*FloatLiteral, error) {
|
|
return &FloatLiteral{
|
|
Value: lit.Value,
|
|
}, nil
|
|
}
|
|
func analyzeIntegerLiteral(lit *ast.IntegerLiteral, declarations DeclarationScope) (*IntegerLiteral, error) {
|
|
return &IntegerLiteral{
|
|
Value: lit.Value,
|
|
}, nil
|
|
}
|
|
func analyzeUnsignedIntegerLiteral(lit *ast.UnsignedIntegerLiteral, declarations DeclarationScope) (*UnsignedIntegerLiteral, error) {
|
|
return &UnsignedIntegerLiteral{
|
|
Value: lit.Value,
|
|
}, nil
|
|
}
|
|
func analyzeStringLiteral(lit *ast.StringLiteral, declarations DeclarationScope) (*StringLiteral, error) {
|
|
return &StringLiteral{
|
|
Value: lit.Value,
|
|
}, nil
|
|
}
|
|
func analyzeBooleanLiteral(lit *ast.BooleanLiteral, declarations DeclarationScope) (*BooleanLiteral, error) {
|
|
return &BooleanLiteral{
|
|
Value: lit.Value,
|
|
}, nil
|
|
}
|
|
func analyzeRegexpLiteral(lit *ast.RegexpLiteral, declarations DeclarationScope) (*RegexpLiteral, error) {
|
|
return &RegexpLiteral{
|
|
Value: lit.Value,
|
|
}, nil
|
|
}
|