/* * ECAL * * Copyright 2020 Matthias Ladkau. All rights reserved. * * This Source Code Form is subject to the terms of the MIT * License, If a copy of the MIT License was not distributed with this * file, You can obtain one at https://opensource.org/licenses/MIT. */ package interpreter import ( "fmt" "strings" "devt.de/krotik/ecal/parser" "devt.de/krotik/ecal/scope" "devt.de/krotik/ecal/stdlib" "devt.de/krotik/ecal/util" ) /* identifierRuntime is the runtime component for identifiers. */ type identifierRuntime struct { *baseRuntime } /* identifierRuntimeInst returns a new runtime component instance. */ func identifierRuntimeInst(erp *ECALRuntimeProvider, node *parser.ASTNode) parser.Runtime { return &identifierRuntime{newBaseRuntime(erp, node)} } /* Eval evaluate this runtime component. */ func (rt *identifierRuntime) Eval(vs parser.Scope, is map[string]interface{}) (interface{}, error) { return rt.resolveValue(vs, is, rt.node) } /* resolveValue resolves the value of this identifier. */ func (rt *identifierRuntime) resolveValue(vs parser.Scope, is map[string]interface{}, node *parser.ASTNode) (interface{}, error) { var anode *parser.ASTNode var astring string var result interface{} var err error functionResolved := func(astring string, rnode *parser.ASTNode) *parser.ASTNode { res := &parser.ASTNode{ // Create a dummy identifier which models the value evaluation so far Name: parser.NodeIDENTIFIER, Token: &parser.LexToken{ ID: node.Token.ID, Identifier: node.Token.Identifier, Lline: node.Token.Lline, Lpos: node.Token.Lpos, Pos: node.Token.Pos, Val: strings.Replace(astring, ".", ">", -1), }, Children: nil, } for i, c := range rnode.Children { if c.Name == parser.NodeFUNCCALL { res.Children = rnode.Children[i+1:] } } return res } anode, astring, err = buildAccessString(rt.erp, vs, is, node, node.Token.Val) if len(node.Children) == 0 { // Simple case we just have a variable result, _, err = vs.GetValue(node.Token.Val) } else if cval, ok := stdlib.GetStdlibConst(astring); ok { result = cval } else { if rerr, ok := err.(*util.RuntimeError); err == nil || ok && rerr.Type == util.ErrInvalidConstruct { funcCallInAccessStringExecuted := ok && rerr.Type == util.ErrInvalidConstruct if result, _, err = vs.GetValue(astring); err == nil { if funcCallInAccessStringExecuted { result, err = rt.resolveFunction(astring, vs, is, rerr.Node, result, err) node = functionResolved(astring, anode) if len(node.Children) > 0 { // We have more identifiers after the func call - there is more to do ... vs = scope.NewScope("funcresult") vs.SetValue(node.Token.Val, result) result, err = rt.resolveValue(vs, is, node) } } else { result, err = rt.resolveFunction(astring, vs, is, node, result, err) } } } } return result, err } /* resolveFunction execute function calls and return the result. */ func (rt *identifierRuntime) resolveFunction(astring string, vs parser.Scope, is map[string]interface{}, node *parser.ASTNode, result interface{}, err error) (interface{}, error) { for _, funccall := range node.Children { if funccall.Name == parser.NodeFUNCCALL { funcObj, ok := result.(util.ECALFunction) if !ok { // Check for inbuild function funcObj, ok = inbuildFuncMap[astring] if !ok { // Check for stdlib function funcObj, ok = stdlib.GetStdlibFunc(astring) } } if ok { var args []interface{} // Collect the parameter values for _, c := range funccall.Children { var val interface{} if err == nil { val, err = c.Runtime.Eval(vs, is) args = append(args, val) } } if err == nil { // Execute the function and result, err = funcObj.Run(rt.instanceID, vs, is, args) if _, ok := err.(*util.RuntimeError); err != nil && !ok { // Convert into a proper runtime error if necessary rerr := rt.erp.NewRuntimeError(util.ErrRuntimeError, err.Error(), node).(*util.RuntimeError) if err == util.ErrIsIterator || err == util.ErrEndOfIteration || err == util.ErrContinueIteration { rerr.Type = err } err = rerr } } } else { err = rt.erp.NewRuntimeError(util.ErrUnknownConstruct, fmt.Sprintf("Unknown function: %v", node.Token.Val), node) } break } } return result, err } /* Set sets a value to this identifier. */ func (rt *identifierRuntime) Set(vs parser.Scope, is map[string]interface{}, value interface{}) error { var err error if len(rt.node.Children) == 0 { // Simple case we just have a variable err = vs.SetValue(rt.node.Token.Val, value) } else { var as string _, as, err = buildAccessString(rt.erp, vs, is, rt.node, rt.node.Token.Val) if err == nil { // Collect all the children and find the right spot err = vs.SetValue(as, value) } } return err } /* buildAccessString builds an access string using a given node and a prefix. */ func buildAccessString(erp *ECALRuntimeProvider, vs parser.Scope, is map[string]interface{}, node *parser.ASTNode, prefix string) (*parser.ASTNode, string, error) { var err error res := prefix for i, c := range node.Children { if err == nil { // The unexpected construct error is used in two ways: // 1. Error message when a function call is used on the left hand of // an assignment. // 2. Signalling there is a function call involved on the right hand // of an assignment. if c.Name == parser.NodeCOMPACCESS { var val interface{} val, err = c.Children[0].Runtime.Eval(vs, is) res = fmt.Sprintf("%v.%v", res, val) if len(node.Children) > i+1 && node.Children[i+1].Name == parser.NodeFUNCCALL { err = erp.NewRuntimeError(util.ErrInvalidConstruct, "Unexpected construct", node) break } } else if c.Name == parser.NodeIDENTIFIER { res = fmt.Sprintf("%v.%v", res, c.Token.Val) if len(c.Children) > 0 && c.Children[0].Name == parser.NodeFUNCCALL { node = c err = erp.NewRuntimeError(util.ErrInvalidConstruct, "Unexpected construct", node) break } node, res, err = buildAccessString(erp, vs, is, c, res) } } } return node, res, err }