Browse Source

docs: Adding an embedding example

Matthias Ladkau 3 years ago
parent
commit
4ec28fe95f

+ 1 - 0
.gitignore

@@ -7,6 +7,7 @@ ecal
 /coverage.html
 /dist
 /build
+/examples/embedding/embedding
 /ecal-support/node_modules
 /ecal-support/out
 /ecal-support/*.vsix

+ 5 - 0
examples/embedding/go.mod

@@ -0,0 +1,5 @@
+module example.com/ecal/embedding
+
+go 1.12
+
+require devt.de/krotik/ecal v1.0.1

+ 4 - 0
examples/embedding/go.sum

@@ -0,0 +1,4 @@
+devt.de/krotik/common v1.3.9 h1:/DdNkkaaplUXHeA+6juY3f+WMUOkc5zycar2TdPXHB0=
+devt.de/krotik/common v1.3.9/go.mod h1:X4nsS85DAxyHkwSg/Tc6+XC2zfmGeaVz+37F61+eSaI=
+devt.de/krotik/ecal v1.0.1 h1:V+CqMpT9xAG2+YPfd9CH6DH+MGxELcFXyhBZcN1k44E=
+devt.de/krotik/ecal v1.0.1/go.mod h1:eZrJYQRIcWLUwNy6s8f+5N+PEduCu7XUABa8ZD0nBKA=

+ 134 - 0
examples/embedding/main.go

@@ -0,0 +1,134 @@
+/*
+ * ECAL Embedding Example
+ */
+
+package main
+
+import (
+	"fmt"
+	"log"
+
+	"devt.de/krotik/ecal/engine"
+	"devt.de/krotik/ecal/interpreter"
+	"devt.de/krotik/ecal/parser"
+	"devt.de/krotik/ecal/scope"
+	"devt.de/krotik/ecal/stdlib"
+	"devt.de/krotik/ecal/util"
+)
+
+func main() {
+
+	// The code to execute
+
+	code := `
+sink mysink
+    kindmatch [ "foo.*" ],
+    {
+        log("Handling: ", event)
+        log("Result: ", event.state.op1 + event.state.op2)
+    }
+
+sink mysink2
+    kindmatch [ "foo.*" ],
+    {
+        raise("Some error")
+    }
+
+func compute(x) {
+	let result := x + 1
+	return result
+}
+
+mystuff.add(compute(5), 1)
+`
+
+	// Add a stdlib function
+
+	stdlib.AddStdlibPkg("mystuff", "My special functions")
+
+	// A single instance if the ECALFunction struct will be used for all function calls across all threads
+
+	stdlib.AddStdlibFunc("mystuff", "add", &AddFunc{})
+
+	// Logger for log() statements in the code
+
+	logger := util.NewMemoryLogger(100)
+
+	// Import locator when using import statements in the code
+
+	importLocator := &util.MemoryImportLocator{Files: make(map[string]string)}
+
+	// Runtime provider which contains all objects needed by the interpreter
+
+	rtp := interpreter.NewECALRuntimeProvider("Embedded Example", importLocator, logger)
+
+	// First we need to parse the code into an Abstract Syntax Tree
+
+	ast, err := parser.ParseWithRuntime("code1", code, rtp)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// Then we need to validate the code - this prepares certain runtime bits
+	// of the AST for execution.
+
+	if err = ast.Runtime.Validate(); err != nil {
+		log.Fatal(err)
+	}
+
+	// We need a global variable scope which contains all declared variables - use
+	// this object to inject initialization values into the ECAL program.
+
+	vs := scope.NewScope(scope.GlobalScope)
+
+	// Each thread which evaluates the Runtime of an AST should get a unique thread ID
+
+	var threadId uint64 = 1
+
+	// Evaluate the Runtime of an AST with a variable scope
+
+	res, err := ast.Runtime.Eval(vs, make(map[string]interface{}), threadId)
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	// The executed code returns the value of the last statement
+
+	fmt.Println("Computation result:", res)
+
+	// We can also react to events
+
+	rtp.Processor.Start()
+	monitor, err := rtp.Processor.AddEventAndWait(engine.NewEvent("MyEvent", []string{"foo", "bar"}, map[interface{}]interface{}{
+		"op1": float64(5.2),
+		"op2": float64(5.3),
+	}), nil)
+
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	fmt.Println("Event result:", monitor.RootMonitor().AllErrors())
+
+	fmt.Println("Log:", logger.String())
+}
+
+/*
+AddFunc is a simple add function which calculates the sum of two numbers.
+*/
+type AddFunc struct {
+}
+
+func (f *AddFunc) Run(instanceID string, vs parser.Scope, is map[string]interface{}, tid uint64, args []interface{}) (interface{}, error) {
+
+	// This should have some proper error checking
+
+	// Arguments are either of type string, float64, map[interface{}]interface{}
+	// or []interface{}
+
+	return args[0].(float64) + args[1].(float64), nil
+}
+
+func (f *AddFunc) DocString() (string, error) {
+	return "Sum up two numbers", nil
+}

+ 11 - 2
interpreter/rt_sink.go

@@ -199,13 +199,22 @@ func (rt *sinkRuntime) Eval(vs parser.Scope, is map[string]interface{}, tid uint
 								sre.Environment = sinkVS
 
 							} else {
+								var data interface{}
+								rerr := rt.erp.NewRuntimeError(util.ErrSink, err.Error(), rt.node).(*util.RuntimeError)
+
+								if e, ok := err.(*util.RuntimeError); ok {
+									rerr = e
+								} else if r, ok := err.(*returnValue); ok {
+									rerr = r.RuntimeError
+									data = r.returnValue
+								}
 
 								// Provide additional information for unexpected errors
 
 								err = &util.RuntimeErrorWithDetail{
-									RuntimeError: err.(*util.RuntimeError),
+									RuntimeError: rerr,
 									Environment:  sinkVS,
-									Data:         nil,
+									Data:         data,
 								}
 							}
 						}

+ 20 - 1
interpreter/rt_sink_test.go

@@ -113,6 +113,7 @@ sink rule3
     kindmatch [ "web.log" ],
 	{
         log("rule3 - Logging user:", event.state.user)
+        return 123
 	}
 
 res := addEventAndWait("request", "web.page.index", {
@@ -140,7 +141,25 @@ log("ErrorResult:", res, " ", res == null)
 rule1 - Handling request: web.page.index
 rule2 - Tracking user:foo
 rule3 - Logging user:foo
-ErrorResult:null true
+ErrorResult:[
+  {
+    "errors": {
+      "rule3": {
+        "data": 123,
+        "detail": "Return value: 123",
+        "error": "ECAL error in ECALTestRuntime: *** return *** (Return value: 123) (Line:26 Pos:9)",
+        "type": "*** return ***"
+      }
+    },
+    "event": {
+      "kind": "web.log",
+      "name": "Rule1Event2",
+      "state": {
+        "user": "foo"
+      }
+    }
+  }
+] false
 rule2 - Tracking user:bar
 ErrorResult:[
   {