Browse Source

feat: Adding doc function and test fixup

Matthias Ladkau 3 years ago
parent
commit
623daf105d

+ 20 - 0
ecal.md

@@ -425,6 +425,26 @@ Example:
 concat([1,2,3], [4,5,6], [7,8,9])
 ```
 
+#### `dumpenv() : string`
+Returns the current variable environment as a string.
+
+Example:
+```
+dumpenv()
+```
+
+#### `doc(function) : string`
+Returns the doc string of a function.
+
+Parameter | Description
+-|-
+function | A function object
+
+Example:
+```
+doc(len)
+```
+
 Logging Functions
 --
 ECAL has a build-in logging system and provides by default the functions `debug`, `log` and `error` to log messages.

+ 59 - 0
interpreter/func_provider.go

@@ -18,6 +18,7 @@ import (
 	"devt.de/krotik/ecal/engine"
 	"devt.de/krotik/ecal/parser"
 	"devt.de/krotik/ecal/scope"
+	"devt.de/krotik/ecal/stdlib"
 	"devt.de/krotik/ecal/util"
 )
 
@@ -32,6 +33,7 @@ var inbuildFuncMap = map[string]util.ECALFunction{
 	"add":             &addFunc{&inbuildBaseFunc{}},
 	"concat":          &concatFunc{&inbuildBaseFunc{}},
 	"dumpenv":         &dumpenvFunc{&inbuildBaseFunc{}},
+	"doc":             &docFunc{&inbuildBaseFunc{}},
 	"raise":           &raise{&inbuildBaseFunc{}},
 	"addEvent":        &addevent{&inbuildBaseFunc{}},
 	"addEventAndWait": &addeventandwait{&addevent{&inbuildBaseFunc{}}},
@@ -477,6 +479,63 @@ func (rf *dumpenvFunc) DocString() (string, error) {
 	return "Dumpenv returns the current variable environment as a string.", nil
 }
 
+// doc
+// ===
+
+/*
+docFunc returns the docstring of a function.
+*/
+type docFunc struct {
+	*inbuildBaseFunc
+}
+
+/*
+Run executes this function.
+*/
+func (rf *docFunc) Run(instanceID string, vs parser.Scope, is map[string]interface{}, args []interface{}) (interface{}, error) {
+	var res interface{}
+	err := fmt.Errorf("Need a function as parameter")
+
+	if len(args) > 0 {
+
+		funcObj, ok := args[0].(util.ECALFunction)
+
+		if args[0] == nil {
+
+			// Try to lookup by the given identifier
+
+			c := is["astnode"].(*parser.ASTNode).Children[0].Children[0]
+			astring := c.Token.Val
+
+			if len(c.Children) > 0 {
+				astring = fmt.Sprintf("%v.%v", astring, c.Children[0].Token.Val)
+			}
+
+			// Check for stdlib function
+
+			if funcObj, ok = stdlib.GetStdlibFunc(astring); !ok {
+
+				// Check for inbuild function
+
+				funcObj, ok = inbuildFuncMap[astring]
+			}
+		}
+
+		if ok {
+			res, err = funcObj.DocString()
+		}
+	}
+
+	return res, err
+}
+
+/*
+DocString returns a descriptive string.
+*/
+func (rf *docFunc) DocString() (string, error) {
+	return "Doc returns the docstring of a function.", nil
+}
+
 // raise
 // =====
 

+ 86 - 0
interpreter/func_provider_test.go

@@ -12,6 +12,7 @@ package interpreter
 
 import (
 	"fmt"
+	"strings"
 	"testing"
 )
 
@@ -241,6 +242,49 @@ identifier: dumpenv
 		return
 	}
 
+	res, err = UnitTestEval(
+		`
+func foo() {
+	log("hello")
+}
+doc(foo)`, nil)
+
+	if err != nil || fmt.Sprint(res) != `Declared function: foo (Line 2, Pos 1)` {
+		t.Error("Unexpected result: ", res, err)
+		return
+	}
+
+	res, err = UnitTestEval(
+		`doc(len)`, nil)
+
+	if err != nil || fmt.Sprint(res) != `Len returns the size of a list or map.` {
+		t.Error("Unexpected result: ", res, err)
+		return
+	}
+
+	res, err = UnitTestEval(
+		`doc(fmt.Println)`, nil)
+
+	if err != nil || !strings.HasPrefix(fmt.Sprint(res), "Println") {
+		t.Error("Unexpected result: ", res, err)
+		return
+	}
+
+	res, err = UnitTestEval(
+		`
+/*
+Foo is my custom function.
+*/
+func foo() {
+	log("hello")
+}
+doc(foo)`, nil)
+
+	if err != nil || fmt.Sprint(res) != `Foo is my custom function.` {
+		t.Error("Unexpected result: ", res, err)
+		return
+	}
+
 	// Negative case
 
 	res, err = UnitTestEvalAndAST(
@@ -262,3 +306,45 @@ identifier: a
 	}
 
 }
+
+func TestDocstrings(t *testing.T) {
+	for k, v := range inbuildFuncMap {
+		if res, _ := v.DocString(); res == "" {
+			t.Error("Docstring missing for ", k)
+			return
+		}
+	}
+}
+
+func TestErrorConditions(t *testing.T) {
+
+	ib := &inbuildBaseFunc{}
+
+	if _, err := ib.AssertNumParam(1, "bob"); err == nil || err.Error() != "Parameter 1 should be a number" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if _, err := ib.AssertMapParam(1, "bob"); err == nil || err.Error() != "Parameter 1 should be a map" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if _, err := ib.AssertListParam(1, "bob"); err == nil || err.Error() != "Parameter 1 should be a list" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	rf := &rangeFunc{&inbuildBaseFunc{}}
+
+	if _, err := rf.Run("", nil, nil, nil); err == nil || err.Error() != "Need at least an end range as first parameter" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	if _, err := rf.Run("", nil, nil, []interface{}{"bob"}); err == nil || err.Error() != "Parameter 1 should be a number" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+}

+ 40 - 0
interpreter/rt_assign_test.go

@@ -99,6 +99,46 @@ func TestSimpleAssignments(t *testing.T) {
 		t.Error("Unexpected result: ", vsRes, res, err)
 		return
 	}
+
+	_, err = UnitTestEval(
+		`1 := [1, 2]`, vs)
+
+	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Must have a variable or list of variables on the left side of the assignment) (Line:1 Pos:3)" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	_, err = UnitTestEval(
+		`[1] := [1, 2]`, vs)
+
+	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Must have a list of variables on the left side of the assignment) (Line:1 Pos:5)" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	_, err = UnitTestEval(
+		`[a, b] := [1, 2, 3]`, vs)
+
+	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Invalid state (Assigned number of variables is different to number of values (2 variables vs 3 values)) (Line:1 Pos:8)" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	_, err = UnitTestEval(
+		`[a, b] := 1`, vs)
+
+	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Invalid state (Result is not a list (value is 1)) (Line:1 Pos:8)" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
+	_, err = UnitTestEval(
+		`[a, b.c, c] := [1, 2, 3]`, vs)
+
+	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Cannot access variable (Variable b is not a container) (Line:1 Pos:13)" {
+		t.Error("Unexpected result:", err)
+		return
+	}
 }
 
 func TestComplexAssignments(t *testing.T) {

+ 4 - 2
interpreter/rt_boolean.go

@@ -330,6 +330,8 @@ func likeOpRuntimeInst(erp *ECALRuntimeProvider, node *parser.ASTNode) parser.Ru
 Eval evaluate this runtime component.
 */
 func (rt *likeOpRuntime) Eval(vs parser.Scope, is map[string]interface{}) (interface{}, error) {
+	var res interface{}
+
 	_, err := rt.baseRuntime.Eval(vs, is)
 
 	if err == nil {
@@ -347,13 +349,13 @@ func (rt *likeOpRuntime) Eval(vs parser.Scope, is map[string]interface{}) (inter
 				re, err = regexp.Compile(fmt.Sprint(pattern))
 				if err == nil {
 
-					return re.MatchString(fmt.Sprint(str)), nil
+					res = re.MatchString(fmt.Sprint(str))
 				}
 			}
 		}
 	}
 
-	return nil, err
+	return res, err
 }
 
 type beginswithOpRuntime struct {

+ 6 - 2
interpreter/rt_func.go

@@ -13,6 +13,7 @@ package interpreter
 import (
 	"encoding/json"
 	"fmt"
+	"strings"
 
 	"devt.de/krotik/ecal/parser"
 	"devt.de/krotik/ecal/scope"
@@ -155,8 +156,6 @@ func (f *function) Run(instanceID string, vs parser.Scope, is map[string]interfa
 
 				if i < len(args) {
 					val = args[i]
-				} else {
-					val = nil
 				}
 			} else if p.Name == parser.NodePRESET {
 				name = p.Children[0].Token.Val
@@ -195,6 +194,11 @@ func (f *function) Run(instanceID string, vs parser.Scope, is map[string]interfa
 DocString returns a descriptive string.
 */
 func (f *function) DocString() (string, error) {
+
+	if len(f.declaration.Meta) > 0 {
+		return strings.TrimSpace(f.declaration.Meta[0].Value()), nil
+	}
+
 	return fmt.Sprintf("Declared function: %v (%v)", f.name, f.declaration.Token.PosString()), nil
 }
 

+ 16 - 0
interpreter/rt_func_test.go

@@ -310,4 +310,20 @@ result2 := result1.getId() + result1.id
 		t.Error("Unexpected result: ", vsRes, res, err)
 		return
 	}
+
+	_, err = UnitTestEval(`
+Bar := {
+  "super" : "hans"
+
+}
+
+
+result1 := new(Bar)
+`, vs)
+
+	if err == nil || err.Error() != "ECAL error in ECALTestRuntime: Runtime error (Property _super must be a list of super classes) (Line:8 Pos:12)" {
+		t.Error("Unexpected result:", err)
+		return
+	}
+
 }

+ 1 - 1
interpreter/rt_identifier.go

@@ -169,7 +169,7 @@ func (rt *identifierRuntime) resolveFunction(astring string, vs parser.Scope, is
 					var val interface{}
 
 					if err == nil {
-						val, err = c.Runtime.Eval(vs, is)
+						val, err = c.Runtime.Eval(vs, make(map[string]interface{}))
 						args = append(args, val)
 					}
 				}

+ 2 - 1
interpreter/rt_sink_test.go

@@ -96,6 +96,7 @@ sink rule1
         log("rule1 - Handling request: ", event.kind)
         addEvent("Rule1Event1", "not_existing", event.state)
         addEvent("Rule1Event2", "web.log", event.state)
+        addEvent("Rule1Event3", "notexisting", event.state, {})
 	}
 
 sink rule2
@@ -149,7 +150,7 @@ ErrorResult:[
           123
         ],
         "detail": "User bar was seen",
-        "error": "ECAL error in ECALTestRuntime: UserBarWasHere (User bar was seen) (Line:17 Pos:13)",
+        "error": "ECAL error in ECALTestRuntime: UserBarWasHere (User bar was seen) (Line:18 Pos:13)",
         "type": "UserBarWasHere"
       }
     },

+ 11 - 0
scope/helper_test.go

@@ -12,8 +12,19 @@ package scope
 
 import (
 	"testing"
+
+	"devt.de/krotik/ecal/parser"
 )
 
+func TestNameFromASTNode(t *testing.T) {
+	n, _ := parser.Parse("", "foo")
+
+	if res := NameFromASTNode(n); res != "block: identifier (Line:1 Pos:1)" {
+		t.Error("Unexpected result:", res)
+		return
+	}
+}
+
 func TestScopeConversion(t *testing.T) {
 	vs := NewScope("foo")
 

+ 9 - 5
stdlib/stdlib.go

@@ -51,11 +51,15 @@ func GetStdlibFunc(name string) (util.ECALFunction, bool) {
 	return nil, false
 }
 
-func splitModuleAndName(name string) (string, string) {
-	ccSplit := strings.SplitN(name, ".", 2)
+func splitModuleAndName(fullname string) (string, string) {
+	var module, name string
 
-	if len(ccSplit) == 0 {
-		return "", ""
+	ccSplit := strings.SplitN(fullname, ".", 2)
+
+	if len(ccSplit) != 0 {
+		module = ccSplit[0]
+		name = strings.Join(ccSplit[1:], "")
 	}
-	return ccSplit[0], strings.Join(ccSplit[1:], "")
+
+	return module, name
 }