Working on #48 : Working

This commit is contained in:
2021-03-11 15:28:24 +01:00
parent 8f51893f53
commit 30c99b2d67
14 changed files with 60 additions and 55 deletions
+482
View File
@@ -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