Working on #48 : Working
This commit is contained in:
@@ -0,0 +1,482 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
from core.sheerka.services.SheerkaExecute import ParserInput
|
||||
from core.tokenizer import Token, TokenKind, Tokenizer, LexerError
|
||||
from core.utils import tokens_are_matching
|
||||
from parsers.BaseNodeParser import UnrecognizedTokensNode
|
||||
from parsers.BaseParser import Node, ParsingError, BaseParser
|
||||
|
||||
|
||||
class ComparisonType:
|
||||
EQUALS = "EQ"
|
||||
NOT_EQUAlS = "NOT_EQ"
|
||||
LESS_THAN = "LT"
|
||||
LESS_THAN_OR_EQUALS = "LTE"
|
||||
GREATER_THAN = "GT"
|
||||
GREATER_THAN_OR_EQUALS = "GTE"
|
||||
IN = "IN"
|
||||
NOT_IN = "NOT_IN"
|
||||
|
||||
|
||||
@dataclass()
|
||||
class LeftPartNotFoundError(ParsingError):
|
||||
"""
|
||||
When the expression starts with 'or' or 'and'
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@dataclass()
|
||||
class ParenthesisMismatchError(ParsingError):
|
||||
token: Token
|
||||
|
||||
|
||||
@dataclass
|
||||
class ExprNode(Node):
|
||||
"""
|
||||
Base ExprNode
|
||||
eval() must be overridden
|
||||
"""
|
||||
start: int # index of the first token
|
||||
end: int # index of the last token
|
||||
tokens: List[Token]
|
||||
|
||||
def eval(self, obj):
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, ExprNode):
|
||||
return False
|
||||
|
||||
if self.start != other.start or self.end != other.end:
|
||||
return False
|
||||
|
||||
if other.tokens is not None and other.tokens != self.tokens:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.end))
|
||||
|
||||
|
||||
class NameExprNode(ExprNode):
|
||||
def __init__(self, start, end, tokens):
|
||||
super().__init__(start, end, tokens)
|
||||
self.value = "".join([t.str_value for t in self.tokens])
|
||||
|
||||
def eval(self, obj):
|
||||
return self.value
|
||||
|
||||
def get_value(self):
|
||||
return self.value
|
||||
|
||||
def __repr__(self):
|
||||
return f"NameExprNode(start={self.start}, end={self.end}, '{self.value}')"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, NameExprNode):
|
||||
return False
|
||||
|
||||
return super().__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
def to_unrecognized(self):
|
||||
"""
|
||||
UnrecognizedTokensNode with all tokens
|
||||
"""
|
||||
return UnrecognizedTokensNode(self.start, self.end, self.tokens).fix_source()
|
||||
|
||||
def to_str_unrecognized(self):
|
||||
"""
|
||||
UnrecognizedTokensNode with one token, which is a string token of all the tokens
|
||||
"""
|
||||
token = Token(TokenKind.STRING,
|
||||
"'" + self.str_value() + "'",
|
||||
self.tokens[0].index,
|
||||
self.tokens[0].line,
|
||||
self.tokens[0].column)
|
||||
return UnrecognizedTokensNode(self.start, self.end, [token]).fix_source()
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class AndNode(ExprNode):
|
||||
parts: Tuple[ExprNode]
|
||||
|
||||
def __init__(self, start, end, tokens, *parts: ExprNode):
|
||||
super().__init__(start, end, tokens)
|
||||
self.parts = parts
|
||||
|
||||
def eval(self, obj):
|
||||
res = self.parts[0].eval(obj) and self.parts[1].eval(obj)
|
||||
for part in self.parts[2:]:
|
||||
res &= part.eval(obj)
|
||||
return res
|
||||
|
||||
def __repr__(self):
|
||||
return f"AndNode(start={self.start}, end={self.end}, " + ", ".join([repr(p) for p in self.parts]) + ")"
|
||||
|
||||
def __str__(self):
|
||||
return " and ".join([str(p) for p in self.parts])
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, AndNode):
|
||||
return False
|
||||
|
||||
if self.start != other.start or self.end != other.end:
|
||||
return False
|
||||
|
||||
if other.tokens is not None and other.tokens != self.tokens:
|
||||
return False
|
||||
|
||||
return self.parts == other.parts
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.end, self.parts))
|
||||
|
||||
|
||||
@dataclass(init=False)
|
||||
class OrNode(ExprNode):
|
||||
parts: Tuple[ExprNode]
|
||||
|
||||
def __init__(self, start, end, tokens, *parts: ExprNode):
|
||||
super().__init__(start, end, tokens)
|
||||
self.parts = parts
|
||||
|
||||
def eval(self, obj):
|
||||
res = self.parts[0].eval(obj) or self.parts[1].eval(obj)
|
||||
for part in self.parts[2:]:
|
||||
res |= part.eval(obj)
|
||||
return res
|
||||
|
||||
def __repr__(self):
|
||||
return f"OrNode(start={self.start}, end={self.end}, " + ", ".join([repr(p) for p in self.parts]) + ")"
|
||||
|
||||
def __str__(self):
|
||||
return " or ".join([str(p) for p in self.parts])
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, OrNode):
|
||||
return False
|
||||
|
||||
if self.start != other.start or self.end != other.end:
|
||||
return False
|
||||
|
||||
if other.tokens is not None and other.tokens != self.tokens:
|
||||
return False
|
||||
|
||||
return self.parts == other.parts
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.end, self.parts))
|
||||
|
||||
|
||||
@dataclass()
|
||||
class NotNode(ExprNode):
|
||||
node: ExprNode
|
||||
|
||||
def eval(self, obj):
|
||||
return not self.node.eval(obj)
|
||||
|
||||
def get_value(self):
|
||||
return self.node.get_value()
|
||||
|
||||
def __repr__(self):
|
||||
return f"NotNode(start={self.start}, end={self.end}, {self.node!r})"
|
||||
|
||||
def __str__(self):
|
||||
return f"not {self.node}"
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, NotNode):
|
||||
return False
|
||||
|
||||
if self.start != other.start or self.end != other.end:
|
||||
return False
|
||||
|
||||
if other.tokens is not None and other.tokens != self.tokens:
|
||||
return False
|
||||
|
||||
return self.node == other.node
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.end, self.node))
|
||||
|
||||
|
||||
@dataclass()
|
||||
class ParenthesisNode(ExprNode):
|
||||
"""
|
||||
Contains the boundaries of an expression inside parenthesis
|
||||
Need it, just to keep track of the boundaries of the parenthesis
|
||||
"""
|
||||
node: ExprNode
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, ParenthesisNode):
|
||||
return False
|
||||
|
||||
if self.start != other.start or self.end != other.end:
|
||||
return False
|
||||
|
||||
if other.tokens is not None and other.tokens != self.tokens:
|
||||
return False
|
||||
|
||||
return self.node == other.node
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.start, self.end, self.node))
|
||||
|
||||
def __repr__(self):
|
||||
return f"ParenthesisNode(start={self.start}, end={self.end}, node={self.node!r})"
|
||||
|
||||
def __str__(self):
|
||||
return f"({self.node})"
|
||||
|
||||
|
||||
class VariableNode(ExprNode):
|
||||
def __init__(self, start, end, tokens, name, *attributes):
|
||||
super().__init__(start, end, tokens)
|
||||
self.name = name.strip()
|
||||
self.attributes = [attr.strip() for attr in attributes]
|
||||
if len(self.attributes) > 0:
|
||||
self.attributes_str = ".".join(self.attributes)
|
||||
else:
|
||||
self.attributes_str = None
|
||||
|
||||
def __eq__(self, other):
|
||||
if id(self) == id(other):
|
||||
return True
|
||||
|
||||
if not isinstance(other, VariableNode):
|
||||
return False
|
||||
|
||||
return self.name == other.name and self.attributes == other.attributes
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.name, self.attributes))
|
||||
|
||||
def __repr__(self):
|
||||
prefix = f"VariableNode(start={self.start}, end={self.end}, '{self.name}"
|
||||
if len(self.attributes) > 0:
|
||||
return prefix + "." + ".".join(self.attributes) + "')"
|
||||
else:
|
||||
return prefix + "')"
|
||||
|
||||
def __str__(self):
|
||||
if self.attributes:
|
||||
return self.name + "." + ".".join(self.attributes)
|
||||
else:
|
||||
return self.name
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComparisonNode(ExprNode):
|
||||
comp: str
|
||||
left: ExprNode
|
||||
right: ExprNode
|
||||
|
||||
def __eq__(self, other):
|
||||
if id(self) == id(other):
|
||||
return True
|
||||
|
||||
if not isinstance(other, ComparisonNode):
|
||||
return False
|
||||
|
||||
return (self.comp == other.comp and
|
||||
self.left == other.left and
|
||||
self.right == other.right)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.comp, self.left, self.right))
|
||||
|
||||
def __repr__(self):
|
||||
return f"ComparisonNode(start={self.start}, end={self.end}, {self.left!r} {self.comp} {self.right!r})"
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.left} {self.comp} {self.right}"
|
||||
|
||||
|
||||
@dataclass()
|
||||
class FunctionParameter:
|
||||
"""
|
||||
class the represent result of the parameter parsing
|
||||
"""
|
||||
value: NameExprNode # value parsed
|
||||
separator: NameExprNode = None # holds the value and the position of the separator
|
||||
|
||||
def add_sep(self, start, end, tokens):
|
||||
self.separator = NameExprNode(start, end, tokens)
|
||||
|
||||
def value_to_unrecognized(self):
|
||||
return UnrecognizedTokensNode(self.value.start, self.value.end, self.value.tokens).fix_source()
|
||||
|
||||
def separator_to_unrecognized(self):
|
||||
if self.separator is None:
|
||||
return None
|
||||
return UnrecognizedTokensNode(self.separator.start, self.separator.end, self.separator.tokens).fix_source()
|
||||
|
||||
|
||||
@dataclass
|
||||
class FunctionNode(ExprNode):
|
||||
first: NameExprNode # beginning of the function (it should represent the name of the function)
|
||||
last: NameExprNode # last part of the function (it should be the trailing parenthesis)
|
||||
parameters: Union[None, List[FunctionParameter]]
|
||||
|
||||
|
||||
class BaseExpressionParser(BaseParser):
|
||||
|
||||
def parse_input(self, context, parser_input, error_sink):
|
||||
raise NotImplementedError
|
||||
|
||||
def reset_parser_input(self, parser_input: ParserInput, error_sink):
|
||||
try:
|
||||
error_sink.clear()
|
||||
parser_input.reset(self.yield_eof)
|
||||
except LexerError as e:
|
||||
error_sink.add_error(e)
|
||||
return False
|
||||
|
||||
parser_input.next_token()
|
||||
return True
|
||||
|
||||
|
||||
class ExpressionVisitor:
|
||||
"""
|
||||
Pyhtonic implementation of visitors for ExprNode
|
||||
"""
|
||||
|
||||
def visit(self, expr_node):
|
||||
name = expr_node.__class__.__name__
|
||||
|
||||
method = 'visit_' + name
|
||||
visitor = getattr(self, method, self.generic_visit)
|
||||
return visitor(expr_node)
|
||||
|
||||
def generic_visit(self, expr_node):
|
||||
"""Called if no explicit visitor function exists for a node."""
|
||||
for field, value in expr_node.__dict__.items():
|
||||
if isinstance(value, (list, tuple)):
|
||||
for item in value:
|
||||
if isinstance(item, ExprNode):
|
||||
self.visit(item)
|
||||
elif isinstance(value, ExprNode):
|
||||
self.visit(value)
|
||||
|
||||
|
||||
class TrueifyVisitor(ExpressionVisitor):
|
||||
"""
|
||||
Visit an ExprNode
|
||||
replace all the nodes containing a variable to 'trueify' with True
|
||||
The node containing both variables to trueify and to skip are skipped
|
||||
"""
|
||||
|
||||
def __init__(self, to_trueify, to_skip):
|
||||
self.to_trueify = to_trueify
|
||||
self.to_skip = to_skip
|
||||
|
||||
def visit_AndNode(self, expr_node):
|
||||
parts = []
|
||||
for part in expr_node.parts:
|
||||
parts.append(self.visit(part))
|
||||
return AndNode(expr_node.start, expr_node.end, expr_node.tokens, *parts)
|
||||
|
||||
def visit_OrNode(self, expr_node):
|
||||
parts = []
|
||||
for part in expr_node.parts:
|
||||
parts.append(self.visit(part))
|
||||
return OrNode(expr_node.start, expr_node.end, expr_node.tokens, *parts)
|
||||
|
||||
def visit_NameExprNode(self, expr_node):
|
||||
return_true = False
|
||||
for t in expr_node.tokens:
|
||||
if t.type == TokenKind.IDENTIFIER:
|
||||
if t.value in self.to_skip:
|
||||
return expr_node
|
||||
if t.value in self.to_trueify:
|
||||
return_true = True
|
||||
|
||||
return NameExprNode(expr_node.start,
|
||||
expr_node.end,
|
||||
[Token(TokenKind.IDENTIFIER, "True", -1, -1, -1)]) if return_true else expr_node
|
||||
|
||||
|
||||
is_question_tokens = list(Tokenizer("is_question()"))
|
||||
eval_question_requested_in_context = list(Tokenizer("context.in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED)"))
|
||||
|
||||
|
||||
class IsAQuestionVisitor(ExpressionVisitor):
|
||||
"""
|
||||
visit an expression and return True if is_question or context.in_context(BuiltinConcepts.EVAL_QUESTION_REQUESTED)
|
||||
if found.
|
||||
"""
|
||||
|
||||
def visit_NameExprNode(self, expr_node):
|
||||
if tokens_are_matching(expr_node.tokens, is_question_tokens) or \
|
||||
tokens_are_matching(expr_node.tokens, eval_question_requested_in_context):
|
||||
return True
|
||||
return None
|
||||
|
||||
def visit_AndNode(self, expr_node):
|
||||
"""
|
||||
AND | True | False | None
|
||||
------+-------+-------+----------
|
||||
False | False | False | False
|
||||
True | True | False | True
|
||||
None | True | False | None
|
||||
"""
|
||||
res = self.visit(expr_node.parts[0])
|
||||
if isinstance(res, bool) and not res:
|
||||
return res
|
||||
|
||||
for part in expr_node.parts[1:]:
|
||||
visited = self.visit(part)
|
||||
if isinstance(visited, bool):
|
||||
if not visited:
|
||||
return visited
|
||||
else:
|
||||
res = visited
|
||||
|
||||
return res
|
||||
|
||||
def visit_OrNode(self, expr_node):
|
||||
"""
|
||||
OR | True | False | None
|
||||
------+-------+-------+----------
|
||||
True | True | True | True
|
||||
False | True | False | False
|
||||
None | True | False | None
|
||||
"""
|
||||
res = self.visit(expr_node.parts[0])
|
||||
if isinstance(res, bool) and res:
|
||||
return res
|
||||
|
||||
for part in expr_node.parts[1:]:
|
||||
visited = self.visit(part)
|
||||
if isinstance(visited, bool):
|
||||
if visited:
|
||||
return visited
|
||||
else:
|
||||
res = visited
|
||||
|
||||
return res
|
||||
|
||||
def visit_NotNode(self, expr_node):
|
||||
"""
|
||||
| NOT
|
||||
------+-------
|
||||
False | True
|
||||
True | False
|
||||
None | None
|
||||
"""
|
||||
visited = self.visit(expr_node.node)
|
||||
return None if visited is None else not visited
|
||||
|
||||
def is_a_question(self, expr_node):
|
||||
res = self.visit(expr_node)
|
||||
return isinstance(res, bool) and res
|
||||
Reference in New Issue
Block a user