influxdb/query/semantic/graph.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
}