func evalTypeDeclaration(node *ast.TypeDeclarationNode, scope *stateful.Scope, predefinedVars, defaultVars map[string]Var, ignoreMissingVars bool) error { var actualType ast.ValueType switch node.Type.Ident { case "int": actualType = ast.TInt case "float": actualType = ast.TFloat case "bool": actualType = ast.TBool case "string": actualType = ast.TString case "regex": actualType = ast.TRegex case "duration": actualType = ast.TDuration case "lambda": actualType = ast.TLambda case "list": actualType = ast.TList case "star": actualType = ast.TStar default: return fmt.Errorf("invalid var type %q", node.Type.Ident) } name := node.Node.Ident desc := "" if node.Comment != nil { desc = node.Comment.CommentString() } defaultVars[name] = Var{ Type: actualType, Value: nil, Description: desc, } if predefinedValue, ok := predefinedVars[name]; ok { if predefinedValue.Type != actualType { return fmt.Errorf("invalid type supplied for %s, got %v exp %v", name, predefinedValue.Type, actualType) } v, err := convertVarToValue(Var{Value: predefinedValue.Value, Type: actualType}) if err != nil { return err } scope.Set(name, v) } else if ignoreMissingVars { // Set zero value on scope, so execution can continue scope.Set(name, ast.ZeroValue(actualType)) } else { return fmt.Errorf("missing value for var %q.", name) } return nil }
// fillScope - given a scope and reference variables, we fill the exact variables from the now, fields and tags. func fillScope(vars *stateful.Scope, referenceVariables []string, now time.Time, fields models.Fields, tags models.Tags) error { for _, refVariableName := range referenceVariables { if refVariableName == "time" { vars.Set("time", now.Local()) continue } // Support the error with tags/fields collision var fieldValue interface{} var isFieldExists bool var tagValue interface{} var isTagExists bool if fieldValue, isFieldExists = fields[refVariableName]; isFieldExists { vars.Set(refVariableName, fieldValue) } if tagValue, isTagExists = tags[refVariableName]; isTagExists { if isFieldExists { return fmt.Errorf("cannot have field and tags with same name %q", refVariableName) } vars.Set(refVariableName, tagValue) } if !isFieldExists && !isTagExists { if !vars.Has(refVariableName) { return fmt.Errorf("no field or tag exists for %s", refVariableName) } } } return nil }
func createPipelineAndVars( script string, sourceEdge EdgeType, scope *stateful.Scope, deadman DeadmanService, predefinedVars map[string]tick.Var, ignoreMissingVars bool, ) (*Pipeline, map[string]tick.Var, error) { p := &Pipeline{ deadman: deadman, } var src Node switch sourceEdge { case StreamEdge: src = newStreamNode() scope.Set("stream", src) case BatchEdge: src = newBatchNode() scope.Set("batch", src) default: return nil, nil, fmt.Errorf("source edge type must be either Stream or Batch not %s", sourceEdge) } p.addSource(src) vars, err := tick.Evaluate(script, scope, predefinedVars, ignoreMissingVars) if err != nil { return nil, nil, err } if deadman.Global() { switch s := src.(type) { case *StreamNode: s.Deadman(deadman.Threshold(), deadman.Interval()) case *BatchNode: s.Deadman(deadman.Threshold(), deadman.Interval()) default: return nil, nil, fmt.Errorf("source edge type must be either Stream or Batch not %s", sourceEdge) } } if err = p.Walk( func(n Node) error { return n.validate() }); err != nil { return nil, nil, err } return p, vars, nil }
func evalChain(p ast.Position, scope *stateful.Scope, stck *stack) error { r := stck.Pop() l := stck.Pop() // Resolve identifier if left, ok := l.(*ast.IdentifierNode); ok { var err error l, err = scope.Get(left.Ident) if err != nil { return err } } switch right := r.(type) { case unboundFunc: ret, err := right(l) if err != nil { return err } stck.Push(ret) case *ast.IdentifierNode: name := right.Ident //Lookup field by name of left object var describer SelfDescriber if d, ok := l.(SelfDescriber); ok { describer = d } else { var err error var extraChainMethods map[string]reflect.Value if pd, ok := l.(PartialDescriber); ok { extraChainMethods = pd.ChainMethods() } describer, err = NewReflectionDescriber(l, extraChainMethods) if err != nil { return wrapError(p, err) } } if describer.HasProperty(name) { stck.Push(describer.Property(name)) } else { return errorf(p, "object %T has no property %s", l, name) } default: return errorf(p, "invalid right operand of type %T to '.' operator", r) } return nil }
// Resolve all identifiers immediately in the tree with their value from the scope. // This operation is performed in place. // Panics if the scope value does not exist or if the value cannot be expressed as a literal. func resolveIdents(n ast.Node, scope *stateful.Scope) (_ ast.Node, err error) { switch node := n.(type) { case *ast.IdentifierNode: v, err := scope.Get(node.Ident) if err != nil { return nil, err } lit, err := ast.ValueToLiteralNode(node, v) if err != nil { return nil, err } return lit, nil case *ast.UnaryNode: node.Node, err = resolveIdents(node.Node, scope) if err != nil { return nil, err } case *ast.BinaryNode: node.Left, err = resolveIdents(node.Left, scope) if err != nil { return nil, err } node.Right, err = resolveIdents(node.Right, scope) case *ast.FunctionNode: for i, arg := range node.Args { node.Args[i], err = resolveIdents(arg, scope) if err != nil { return nil, err } } case *ast.ProgramNode: for i, n := range node.Nodes { node.Nodes[i], err = resolveIdents(n, scope) if err != nil { return nil, err } } } return n, nil }
func evalUnary(p ast.Position, op ast.TokenType, scope *stateful.Scope, stck *stack) error { v := stck.Pop() switch op { case ast.TokenMinus: if ident, ok := v.(*ast.IdentifierNode); ok { value, err := scope.Get(ident.Ident) if err != nil { return err } v = value } switch num := v.(type) { case float64: stck.Push(-1 * num) case int64: stck.Push(-1 * num) case time.Duration: stck.Push(-1 * num) default: return errorf(p, "invalid arugument to '-' %v", v) } case ast.TokenNot: if ident, ok := v.(*ast.IdentifierNode); ok { value, err := scope.Get(ident.Ident) if err != nil { return err } v = value } if b, ok := v.(bool); ok { stck.Push(!b) } else { return errorf(p, "invalid arugument to '!' %v", v) } } return nil }
func evalDeclaration(node *ast.DeclarationNode, scope *stateful.Scope, stck *stack, predefinedVars, defaultVars map[string]Var) error { name := node.Left.Ident if v, _ := scope.Get(name); v != nil { return fmt.Errorf("attempted to redefine %s, vars are immutable", name) } value := stck.Pop() if i, ok := value.(*ast.IdentifierNode); ok { // Resolve identifier v, err := scope.Get(i.Ident) if err != nil { return err } value = v } actualType := ast.TypeOf(value) // Populate set of default vars if actualType != ast.InvalidType { desc := "" if node.Comment != nil { desc = node.Comment.CommentString() } v, err := convertValueToVar(value, actualType, desc) if err != nil { return err } defaultVars[name] = v } // Populate scope, first check for predefined var if predefinedValue, ok := predefinedVars[name]; ok { if predefinedValue.Type != actualType { return fmt.Errorf("invalid type supplied for %s, got %v exp %v", name, predefinedValue.Type, actualType) } v, err := convertVarToValue(Var{Value: predefinedValue.Value, Type: actualType}) if err != nil { return err } value = v } scope.Set(name, value) return nil }
func evalFunc(f *ast.FunctionNode, scope *stateful.Scope, stck *stack, args []interface{}) error { // If the first and only arg is a list use it as the list of args if len(args) == 1 { if a, ok := args[0].([]interface{}); ok { args = a } } rec := func(obj interface{}, errp *error) { e := recover() if e != nil { *errp = fmt.Errorf("line %d char %d: error calling func %q on obj %T: %v", f.Line(), f.Char(), f.Func, obj, e) if strings.Contains((*errp).Error(), "*ast.ReferenceNode") && strings.Contains((*errp).Error(), "type string") { *errp = fmt.Errorf("line %d char %d: cannot assign *ast.ReferenceNode to type string, did you use double quotes instead of single quotes?", f.Line(), f.Char()) } } } fnc := unboundFunc(func(obj interface{}) (_ interface{}, err error) { //Setup recover method if there is a panic during the method call defer rec(obj, &err) if f.Type == ast.GlobalFunc { if obj != nil { return nil, fmt.Errorf("line %d char %d: calling global function on object %T", f.Line(), f.Char(), obj) } // Object is nil, check for func in scope fnc, _ := scope.Get(f.Func) if fnc == nil { return nil, fmt.Errorf("line %d char %d: no global function %q defined", f.Line(), f.Char(), f.Func) } method := reflect.ValueOf(fnc) o, err := callMethodReflection(method, args) return o, wrapError(f, err) } // Get SelfDescriber name := f.Func var describer SelfDescriber if d, ok := obj.(SelfDescriber); ok { describer = d } else { var err error var extraChainMethods map[string]reflect.Value if pd, ok := obj.(PartialDescriber); ok { extraChainMethods = pd.ChainMethods() } describer, err = NewReflectionDescriber(obj, extraChainMethods) if err != nil { return nil, wrapError(f, err) } } // Call correct type of function switch f.Type { case ast.ChainFunc: if describer.HasChainMethod(name) { o, err := describer.CallChainMethod(name, args...) return o, wrapError(f, err) } if describer.HasProperty(name) { return nil, errorf(f, "no chaining method %q on %T, but property does exist. Use '.' operator instead: 'node.%s(..)'.", name, obj, name) } if dm := scope.DynamicMethod(name); dm != nil { return nil, errorf(f, "no chaining method %q on %T, but dynamic method does exist. Use '@' operator instead: 'node@%s(..)'.", name, obj, name) } case ast.PropertyFunc: if describer.HasProperty(name) { o, err := describer.SetProperty(name, args...) return o, wrapError(f, err) } if describer.HasChainMethod(name) { return nil, errorf(f, "no property method %q on %T, but chaining method does exist. Use '|' operator instead: 'node|%s(..)'.", name, obj, name) } if dm := scope.DynamicMethod(name); dm != nil { return nil, errorf(f, "no property method %q on %T, but dynamic method does exist. Use '@' operator instead: 'node@%s(..)'.", name, obj, name) } case ast.DynamicFunc: // Check for dynamic method. if dm := scope.DynamicMethod(name); dm != nil { ret, err := dm(obj, args...) if err != nil { return nil, err } return ret, nil } if describer.HasProperty(name) { return nil, errorf(f, "no dynamic method %q on %T, but property does exist. Use '.' operator instead: 'node.%s(..)'.", name, obj, name) } if describer.HasChainMethod(name) { return nil, errorf(f, "no dynamic method %q on %T, but chaining method does exist. Use '|' operator instead: 'node|%s(..)'.", name, obj, name) } default: return nil, errorf(f, "unknown function type %v on function %T.%s", f.Type, obj, name) } // Ran out of options... return nil, errorf(f, "no method or property %q on %T", name, obj) }) stck.Push(fnc) return nil }
// Evaluate a node using a stack machine in a given scope func eval(n ast.Node, scope *stateful.Scope, stck *stack, predefinedVars, defaultVars map[string]Var, ignoreMissingVars bool) (err error) { switch node := n.(type) { case *ast.BoolNode: stck.Push(node.Bool) case *ast.NumberNode: if node.IsInt { stck.Push(node.Int64) } else { stck.Push(node.Float64) } case *ast.DurationNode: stck.Push(node.Dur) case *ast.StringNode: stck.Push(node.Literal) case *ast.RegexNode: stck.Push(node.Regex) case *ast.UnaryNode: err = eval(node.Node, scope, stck, predefinedVars, defaultVars, ignoreMissingVars) if err != nil { return } err = evalUnary(node, node.Operator, scope, stck) if err != nil { return } case *ast.BinaryNode: // Switch over to using the stateful expressions for evaluating a BinaryNode n, err := resolveIdents(node, scope) if err != nil { return err } expr, err := stateful.NewExpression(n) if err != nil { return err } value, err := expr.Eval(stateful.NewScope()) if err != nil { return err } stck.Push(value) case *ast.LambdaNode: node.Expression, err = resolveIdents(node.Expression, scope) if err != nil { return } stck.Push(node) case *ast.ListNode: nodes := make([]interface{}, len(node.Nodes)) for i, n := range node.Nodes { err = eval(n, scope, stck, predefinedVars, defaultVars, ignoreMissingVars) if err != nil { return } a := stck.Pop() switch typed := a.(type) { case *ast.IdentifierNode: // Resolve identifier a, err = scope.Get(typed.Ident) if err != nil { return err } } nodes[i] = a } stck.Push(nodes) case *ast.TypeDeclarationNode: err = evalTypeDeclaration(node, scope, predefinedVars, defaultVars, ignoreMissingVars) if err != nil { return } case *ast.DeclarationNode: err = eval(node.Right, scope, stck, predefinedVars, defaultVars, ignoreMissingVars) if err != nil { return } err = evalDeclaration(node, scope, stck, predefinedVars, defaultVars) if err != nil { return } case *ast.ChainNode: err = eval(node.Left, scope, stck, predefinedVars, defaultVars, ignoreMissingVars) if err != nil { return } err = eval(node.Right, scope, stck, predefinedVars, defaultVars, ignoreMissingVars) if err != nil { return } err = evalChain(node, scope, stck) if err != nil { return } case *ast.FunctionNode: args := make([]interface{}, len(node.Args)) for i, arg := range node.Args { err = eval(arg, scope, stck, predefinedVars, defaultVars, ignoreMissingVars) if err != nil { return } a := stck.Pop() switch typed := a.(type) { case *ast.IdentifierNode: // Resolve identifier a, err = scope.Get(typed.Ident) if err != nil { return err } case unboundFunc: // Call global func a, err = typed(nil) if err != nil { return err } } args[i] = a } err = evalFunc(node, scope, stck, args) if err != nil { return } case *ast.ProgramNode: for _, n := range node.Nodes { err = eval(n, scope, stck, predefinedVars, defaultVars, ignoreMissingVars) if err != nil { return } // Pop unused result if stck.Len() > 0 { ret := stck.Pop() if f, ok := ret.(unboundFunc); ok { // Call global function _, err := f(nil) if err != nil { return err } } } } default: stck.Push(node) } return nil }