-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
⚗️ add a simple expression parser using lark. #13
- Loading branch information
1 parent
d43733d
commit 04fe114
Showing
4 changed files
with
145 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from typing import Optional, Union | ||
|
||
from .expression_parser import parse_trigger | ||
from .expression_ast import AstRoot | ||
|
||
|
||
class Expression(object): | ||
def __init__(self, expression_str): | ||
self.free = False # type: bool | ||
self.ast = None # type: Optional[AstRoot] | ||
self.expression_str = expression_str | ||
|
||
def create_ast(self, parent_node): | ||
self.parse_expression() | ||
self.ast.set_parent_node(parent_node) | ||
|
||
def evaluate(self) -> bool: | ||
return self.ast.evaluate() | ||
|
||
def parse_expression(self): | ||
self.ast = parse_trigger(self.expression_str) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
from dataclasses import dataclass | ||
from typing import Optional | ||
|
||
from .node import Node | ||
from .state import NodeStatus | ||
|
||
|
||
@dataclass | ||
class AstBase: | ||
def set_parent_node(self, node: Node): | ||
pass | ||
|
||
def value(self): | ||
raise NotImplemented() | ||
|
||
def evaluate(self) -> bool: | ||
raise NotImplemented() | ||
|
||
|
||
@dataclass | ||
class AstRoot(AstBase): | ||
left: Optional[AstBase] = None | ||
right: Optional[AstBase] = None | ||
|
||
def set_parent_node(self, node: Node): | ||
self.left.set_parent_node(node) | ||
self.right.set_parent_node(node) | ||
|
||
|
||
@dataclass | ||
class AstOpEq(AstRoot): | ||
def evaluate(self): | ||
return self.left.value() == self.right.value() | ||
|
||
|
||
@dataclass | ||
class AstNodePath(AstBase): | ||
node_path: str | ||
parent_node: Optional[Node] = None | ||
_reference_node: Optional[Node] = None | ||
|
||
def set_parent_node(self, node: Node): | ||
self.parent_node = node | ||
|
||
def value(self): | ||
ref_node = self.get_reference_node() | ||
if ref_node is not None: | ||
return ref_node.state.node_status | ||
else: | ||
return NodeStatus.unknown | ||
|
||
def get_reference_node(self) -> Optional[Node]: | ||
""" | ||
Find node only once. | ||
""" | ||
if self._reference_node is not None: | ||
return self._reference_node | ||
|
||
if self.parent_node is not None: | ||
self._reference_node = self.parent_node.find_node(self.node_path) | ||
return self._reference_node | ||
return None | ||
|
||
|
||
@dataclass | ||
class AstNodeStatus(AstBase): | ||
node_status: NodeStatus | ||
|
||
def value(self): | ||
return self.node_status |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from lark import Lark, Transformer | ||
|
||
from .expression_ast import AstNodePath, AstOpEq, AstNodeStatus, AstRoot | ||
from .state import NodeStatus | ||
|
||
|
||
class ExpressionTransformer(Transformer): | ||
def node_path(self, items): | ||
return AstNodePath("".join(items)) | ||
|
||
def node_name(self, s): | ||
(s, ) = s | ||
return s | ||
|
||
def st_complete(self, _): | ||
return AstNodeStatus(NodeStatus.complete) | ||
|
||
def op_eq(self, _): | ||
return AstOpEq() | ||
|
||
def expression(self, s): | ||
s[1].left = s[0] | ||
s[1].right = s[2] | ||
return s[1] | ||
|
||
|
||
trigger_parser = Lark(r""" | ||
!node_path: "/"node_name("/"node_name)* | ||
op_eq: "==" | "eq" | ||
op_gt: ">" | ||
op_ge: ">=" | ||
?operator: op_eq | op_gt | op_ge | ||
st_complete: "complete" | ||
st_aborted: "aborted" | ||
?status: st_complete | st_aborted | ||
node_name: CNAME | ||
expression: node_path operator status | ||
%import common.CNAME | ||
%import common.WORD | ||
%import common.WS | ||
%ignore WS | ||
""", start="expression") | ||
|
||
|
||
def parse_trigger(trigger_text: str) -> AstRoot: | ||
tree = trigger_parser.parse(trigger_text) | ||
expression_ast = ExpressionTransformer().transform(tree) | ||
return expression_ast |