Переглянути джерело

Added support for importing Python libraries into action language.

Joeri Exelmans 5 роки тому
батько
коміт
48c68c584e

+ 0 - 0
src/sccd/action_lang/lib/__init__.py


+ 14 - 0
src/sccd/action_lang/lib/demo_native_lib.py

@@ -0,0 +1,14 @@
+# Demo of a library that can be imported into the action language with the import-statement.
+
+import time
+
+def meaning_of_life():
+    time.sleep(0.001) # takes a while to compute ;)
+    return 42
+
+from sccd.action_lang.static.types import *
+
+SCCD_EXPORTS = {
+    # adapt native Python to action language's static type system
+    "meaning_of_life": (meaning_of_life, SCCDFunction([], SCCDInt)), 
+}

+ 3 - 0
src/sccd/action_lang/parser/action_lang.g

@@ -118,11 +118,14 @@ TIME_D: "d" // for zero-duration
 
 ?block: (stmt)*
 
+MODULE_NAME: IDENTIFIER ("." IDENTIFIER)*
+
 ?stmt: assignment ";"
      | expr ";" -> expression_stmt
      | "return" expr ";" -> return_stmt
      | "{" block "}" -> block
      | "if" "(" expr ")" stmt ("else" stmt)? -> if_stmt
+     | "import" MODULE_NAME ";" -> import_stmt
 
 assignment: lhs assign_operator expr
 

+ 3 - 0
src/sccd/action_lang/parser/text.py

@@ -86,6 +86,9 @@ class ExpressionTransformer(Transformer):
     else:
       return IfStatement(cond=node[0], if_body=node[1], else_body=node[2])
 
+  def import_stmt(self, node):
+    return ImportStatement(module_name=node[0])
+
   params_decl = list
 
   def param_decl(self, node):

+ 2 - 0
src/sccd/action_lang/static/expression.py

@@ -36,6 +36,7 @@ class StaticTypeError(ModelError):
     pass
 
 class Expression(ABC):
+    # Run static analysis on the expression.
     # Must be called exactly once on each expression, before any call to eval is made.
     # Determines the static type of the expression. May throw if there is a type error.
     # Returns static type of expression.
@@ -43,6 +44,7 @@ class Expression(ABC):
     def init_expr(self, scope: Scope) -> SCCDType:
         pass
 
+    # Evaluate the expression.
     # Evaluation may have side effects.
     @abstractmethod
     def eval(self, memory: MemoryInterface):

+ 38 - 4
src/sccd/action_lang/static/statement.py

@@ -75,14 +75,16 @@ AlwaysReturns = lambda t: ReturnBehavior(ReturnBehavior.When.ALWAYS, t)
 
 # A statement is NOT an expression.
 class Statement(ABC):
-    # Execution typically has side effects.
+    # Run static analysis on the statement.
+    # Looks up identifiers in the given scope, and adds new identifiers to the scope.
     @abstractmethod
-    def exec(self, memory: MemoryInterface) -> Return:
+    def init_stmt(self, scope: Scope) -> ReturnBehavior:
         pass
 
-    # Looks up identifiers in the given scope, and adds new identifiers to the scope.
+    # Execute the statement.
+    # Execution typically has side effects.
     @abstractmethod
-    def init_stmt(self, scope: Scope) -> ReturnBehavior:
+    def exec(self, memory: MemoryInterface) -> Return:
         pass
 
     @abstractmethod
@@ -197,3 +199,35 @@ class IfStatement(Statement):
 
     def render(self) -> str:
         return "if (%s) [[" % self.cond.render() + self.if_body.render() + "]]"
+
+
+@dataclass
+class ImportStatement(Statement):
+    module_name: str
+
+    # Offsets and values of imported stuff
+    declarations: Optional[List[Tuple[int, Any]]] = None
+
+    def init_stmt(self, scope: Scope) -> ReturnBehavior:
+        import importlib
+        self.module = importlib.import_module(self.module_name)
+        self.declarations = []
+        for name, (value, type) in self.module.SCCD_EXPORTS.items():
+            offset = scope.declare(name, type, const=True)
+            if isinstance(type, SCCDFunction):
+                # Function values are a bit special, in the action language they are secretly passed a MemoryInterface object as first parameter, followed by the other (visible) parameters.
+                # I don't really like this solution, but it works for now.
+                def wrapper(memory: MemoryInterface, *params):
+                    return value(*params)
+                self.declarations.append((offset, wrapper))
+            else:
+                self.declarations.append((offset, value))
+        return NeverReturns
+
+    def exec(self, memory: MemoryInterface) -> Return:
+        for offset, value in self.declarations:
+            memory.store(offset, value)
+        return DontReturn
+
+    def render(self) -> str:
+        return "import " + self.module_name + ";"

+ 2 - 2
src/sccd/statechart/dynamic/builtin_scope.py

@@ -14,7 +14,7 @@ BuiltIn.declare("float_to_int", SCCDFunction([SCCDFloat], SCCDInt), const=True)
 BuiltIn.declare("log", SCCDFunction([SCCDString]), const=True)
 BuiltIn.declare("int_to_str", SCCDFunction([SCCDInt], SCCDString), const=True)
 
-def load_builtins(memory: MemoryInterface, state):
+def load_builtins(memory: MemoryInterface, execution):
   import math
   import termcolor
   
@@ -23,7 +23,7 @@ def load_builtins(memory: MemoryInterface, state):
   # Wrapper functions of the signature expected by the action language:
 
   def in_state(memory: MemoryInterface, state_list: List[str]) -> bool:
-    return state.in_state(state_list)
+    return execution.in_state(state_list)
 
   def log10(memory: MemoryInterface, i: int) -> float:
     return math.log10(i)

+ 1 - 1
src/sccd/statechart/parser/xml.py

@@ -243,7 +243,7 @@ def statechart_parser_rules(globals, path, load_external = True, parse_f = parse
           def parse_attr_cond(cond):
             expr = parse_expression(globals, cond)
             guard_type = expr.init_expr(scope)
-            if guard_type != SCCDBool:
+            if guard_type is not SCCDBool:
               raise XmlError("Guard should be an expression evaluating to 'bool'.")
             transition.guard = expr
 

+ 0 - 1
src/sccd/statechart/static/tree.py

@@ -32,7 +32,6 @@ class State(Freezable):
             self.parent.children.append(self)
 
     # Subset of descendants that are always entered when this state is the target of a transition, minus history states.
-
     def _effective_targets(self) -> Bitmap:
         if self.default_state:
             # this state + recursion on 'default state'

test/test_files/day_atlee/test_04_counter_many_srcdstortho.xml → test/test_files/day_atlee/todo_04_counter_many_srcdstortho.xml


+ 39 - 0
test/test_files/features/action_lang/test_import.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" ?>
+<test>
+  <statechart>
+    <datamodel><![CDATA[
+      import sccd.action_lang.lib.demo_native_lib;
+
+      ok = (meaning_of_life() == 42);
+    ]]></datamodel>
+
+    <inport name="in">
+      <event name="start"/>
+    </inport>
+
+    <outport name="out">
+      <event name="ok"/>
+    </outport>
+
+    <root initial="ready">
+      <state id="ready">
+        <transition event="start" target="../final"
+          cond="ok">
+          <raise event="ok"/>
+        </transition>
+      </state>
+
+      <state id="final"/>
+    </root>
+  </statechart>
+
+  <input>
+    <event port="in" name="start" time="0 d"/>
+  </input>
+
+  <output>
+    <big_step>
+      <event port="out" name="ok"/>
+    </big_step>
+  </output>
+</test>