interpreter.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. /*
  2. * EliasDB
  3. *
  4. * Copyright 2016 Matthias Ladkau. All rights reserved.
  5. *
  6. * This Source Code Form is subject to the terms of the Mozilla Public
  7. * License, v. 2.0. If a copy of the MPL was not distributed with this
  8. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  9. */
  10. package ecal
  11. import (
  12. "fmt"
  13. "io/ioutil"
  14. "path/filepath"
  15. "strings"
  16. "devt.de/krotik/common/datautil"
  17. "devt.de/krotik/common/fileutil"
  18. "devt.de/krotik/common/stringutil"
  19. "devt.de/krotik/ecal/cli/tool"
  20. ecalconfig "devt.de/krotik/ecal/config"
  21. "devt.de/krotik/ecal/engine"
  22. "devt.de/krotik/ecal/scope"
  23. "devt.de/krotik/ecal/stdlib"
  24. "devt.de/krotik/ecal/util"
  25. "devt.de/krotik/eliasdb/config"
  26. "devt.de/krotik/eliasdb/ecal/dbfunc"
  27. "devt.de/krotik/eliasdb/graph"
  28. )
  29. /*
  30. ScriptingInterpreter models a ECAL script interpreter instance.
  31. */
  32. type ScriptingInterpreter struct {
  33. GM *graph.Manager // GraphManager for the interpreter
  34. Interpreter *tool.CLIInterpreter // ECAL Interpreter object
  35. Dir string // Root dir for interpreter
  36. EntryFile string // Entry file for the program
  37. LogLevel string // Log level string (Debug, Info, Error)
  38. LogFile string // Logfile (blank for stdout)
  39. RunDebugServer bool // Run a debug server
  40. DebugServerHost string // Debug server host
  41. DebugServerPort string // Debug server port
  42. WebsocketConnections *datautil.MapCache
  43. }
  44. /*
  45. NewScriptingInterpreter returns a new ECAL scripting interpreter.
  46. */
  47. func NewScriptingInterpreter(scriptFolder string, gm *graph.Manager) *ScriptingInterpreter {
  48. return &ScriptingInterpreter{
  49. GM: gm,
  50. Dir: scriptFolder,
  51. EntryFile: filepath.Join(scriptFolder, config.Str(config.ECALEntryScript)),
  52. LogLevel: config.Str(config.ECALLogLevel),
  53. LogFile: config.Str(config.ECALLogFile),
  54. RunDebugServer: config.Bool(config.EnableECALDebugServer),
  55. DebugServerHost: config.Str(config.ECALDebugServerHost),
  56. DebugServerPort: config.Str(config.ECALDebugServerPort),
  57. WebsocketConnections: datautil.NewMapCache(5000, 0),
  58. }
  59. }
  60. /*
  61. dummyEntryFile is a small valid ECAL which does not do anything. It is used
  62. as the default entry file if no entry file exists.
  63. */
  64. const dummyEntryFile = `0 # Write your ECAL code here
  65. `
  66. /*
  67. Run runs the ECAL scripting interpreter.
  68. After this function completes:
  69. - EntryScript in config and all related scripts in the interpreter root dir have been executed
  70. - ECAL Interpreter object is fully initialized
  71. - A debug server might be running which can reload the entry script
  72. - ECAL's event processor has been started
  73. - GraphManager events are being forwarded to ECAL
  74. */
  75. func (si *ScriptingInterpreter) Run() error {
  76. var err error
  77. // Ensure we have a dummy entry point
  78. if ok, _ := fileutil.PathExists(si.EntryFile); !ok {
  79. err = ioutil.WriteFile(si.EntryFile, []byte(dummyEntryFile), 0600)
  80. }
  81. if err == nil {
  82. i := tool.NewCLIInterpreter()
  83. si.Interpreter = i
  84. // Set worker count in ecal config
  85. ecalconfig.Config[ecalconfig.WorkerCount] = config.Config[config.ECALWorkerCount]
  86. i.Dir = &si.Dir
  87. i.LogFile = &si.LogFile
  88. i.LogLevel = &si.LogLevel
  89. i.EntryFile = si.EntryFile
  90. i.LoadPlugins = true
  91. i.CreateRuntimeProvider("eliasdb-runtime")
  92. // Adding functions
  93. AddEliasDBStdlibFunctions(si.GM)
  94. // Adding rules
  95. sockRule := &engine.Rule{
  96. Name: "EliasDB-websocket-communication-rule", // Name
  97. Desc: "Handles a websocket communication", // Description
  98. KindMatch: []string{"db.web.sock.msg"}, // Kind match
  99. ScopeMatch: []string{},
  100. StateMatch: nil,
  101. Priority: 0,
  102. SuppressionList: nil,
  103. Action: si.HandleECALSockEvent,
  104. }
  105. si.Interpreter.CustomRules = append(si.Interpreter.CustomRules, sockRule)
  106. if err == nil {
  107. if si.RunDebugServer {
  108. di := tool.NewCLIDebugInterpreter(i)
  109. addr := fmt.Sprintf("%v:%v", si.DebugServerHost, si.DebugServerPort)
  110. di.DebugServerAddr = &addr
  111. di.RunDebugServer = &si.RunDebugServer
  112. falseFlag := false
  113. di.EchoDebugServer = &falseFlag
  114. di.Interactive = &falseFlag
  115. di.BreakOnStart = &falseFlag
  116. di.BreakOnError = &falseFlag
  117. err = di.Interpret()
  118. } else {
  119. err = i.Interpret(false)
  120. }
  121. // EliasDB graph events are now forwarded to ECAL via the eventbridge.
  122. si.GM.SetGraphRule(&EventBridge{
  123. Processor: i.RuntimeProvider.Processor,
  124. Logger: i.RuntimeProvider.Logger,
  125. })
  126. }
  127. }
  128. // Include a traceback if possible
  129. if ss, ok := err.(util.TraceableRuntimeError); ok {
  130. err = fmt.Errorf("%v\n %v", err.Error(), strings.Join(ss.GetTraceString(), "\n "))
  131. }
  132. return err
  133. }
  134. /*
  135. RegisterECALSock registers a websocket which should be connected to ECAL events.
  136. */
  137. func (si *ScriptingInterpreter) RegisterECALSock(conn *WebsocketConnection) {
  138. si.WebsocketConnections.Put(conn.CommID, conn)
  139. }
  140. /*
  141. DeregisterECALSock removes a registered websocket.
  142. */
  143. func (si *ScriptingInterpreter) DeregisterECALSock(conn *WebsocketConnection) {
  144. si.WebsocketConnections.Remove(conn.CommID)
  145. }
  146. /*
  147. HandleECALSockEvent handles websocket events from the ECAL interpreter (db.web.sock.msg events).
  148. */
  149. func (si *ScriptingInterpreter) HandleECALSockEvent(p engine.Processor, m engine.Monitor, e *engine.Event, tid uint64) error {
  150. state := e.State()
  151. payload := scope.ConvertECALToJSONObject(state["payload"])
  152. shouldClose := stringutil.IsTrueValue(fmt.Sprint(state["close"]))
  153. id := "null"
  154. if commID, ok := state["commID"]; ok {
  155. id = fmt.Sprint(commID)
  156. }
  157. err := fmt.Errorf("Could not send data to unknown websocket - commID: %v", id)
  158. if conn, ok := si.WebsocketConnections.Get(id); ok {
  159. err = nil
  160. wconn := conn.(*WebsocketConnection)
  161. wconn.WriteData(map[string]interface{}{
  162. "commID": id,
  163. "payload": payload,
  164. "close": shouldClose,
  165. })
  166. if shouldClose {
  167. wconn.Close("")
  168. }
  169. }
  170. return err
  171. }
  172. /*
  173. AddEliasDBStdlibFunctions adds EliasDB related ECAL stdlib functions.
  174. */
  175. func AddEliasDBStdlibFunctions(gm *graph.Manager) {
  176. stdlib.AddStdlibPkg("db", "EliasDB related functions")
  177. stdlib.AddStdlibFunc("db", "storeNode", &dbfunc.StoreNodeFunc{GM: gm})
  178. stdlib.AddStdlibFunc("db", "updateNode", &dbfunc.UpdateNodeFunc{GM: gm})
  179. stdlib.AddStdlibFunc("db", "removeNode", &dbfunc.RemoveNodeFunc{GM: gm})
  180. stdlib.AddStdlibFunc("db", "fetchNode", &dbfunc.FetchNodeFunc{GM: gm})
  181. stdlib.AddStdlibFunc("db", "storeEdge", &dbfunc.StoreEdgeFunc{GM: gm})
  182. stdlib.AddStdlibFunc("db", "removeEdge", &dbfunc.RemoveEdgeFunc{GM: gm})
  183. stdlib.AddStdlibFunc("db", "fetchEdge", &dbfunc.FetchEdgeFunc{GM: gm})
  184. stdlib.AddStdlibFunc("db", "traverse", &dbfunc.TraverseFunc{GM: gm})
  185. stdlib.AddStdlibFunc("db", "newTrans", &dbfunc.NewTransFunc{GM: gm})
  186. stdlib.AddStdlibFunc("db", "newRollingTrans", &dbfunc.NewRollingTransFunc{GM: gm})
  187. stdlib.AddStdlibFunc("db", "commit", &dbfunc.CommitTransFunc{GM: gm})
  188. stdlib.AddStdlibFunc("db", "query", &dbfunc.QueryFunc{GM: gm})
  189. stdlib.AddStdlibFunc("db", "graphQL", &dbfunc.GraphQLFunc{GM: gm})
  190. stdlib.AddStdlibFunc("db", "raiseGraphEventHandled", &dbfunc.RaiseGraphEventHandledFunc{})
  191. stdlib.AddStdlibFunc("db", "raiseWebEventHandled", &dbfunc.RaiseWebEventHandledFunc{})
  192. }