Working on #48 : Working

This commit is contained in:
2021-03-09 09:33:50 +01:00
parent a799ab2bbd
commit 07f0d3670d
6 changed files with 94 additions and 26 deletions
+7 -3
View File
@@ -18,9 +18,9 @@ from sheerkarete.conditions import Condition, AndConditions
class ReteConditionsEmitter: class ReteConditionsEmitter:
def __init__(self, context): def __init__(self, context):
from parsers.ComparisonParser import ComparisonParser from parsers.RelationalOperatorParser import RelationalOperatorParser
self.context = context self.context = context
self.comparison_parser = ComparisonParser() self.comparison_parser = RelationalOperatorParser()
self.var_counter = 0 self.var_counter = 0
self.variables = {} self.variables = {}
@@ -69,8 +69,10 @@ class LogicalOperatorParser(BaseParser):
Or to help to understand why a python expression returns True or False Or to help to understand why a python expression returns True or False
""" """
NAME = "LogicalOperator"
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__("Expression", 50, False, yield_eof=True) super().__init__(self.NAME, 50, False, yield_eof=True)
self.and_tokens = list(Tokenizer(" and ", yield_eof=False)) self.and_tokens = list(Tokenizer(" and ", yield_eof=False))
self.and_not_tokens = list(Tokenizer(" and not ", yield_eof=False)) self.and_not_tokens = list(Tokenizer(" and not ", yield_eof=False))
self.not_tokens = list(Tokenizer("not ", yield_eof=False)) self.not_tokens = list(Tokenizer("not ", yield_eof=False))
@@ -111,6 +113,8 @@ class LogicalOperatorParser(BaseParser):
if token and token.type != TokenKind.EOF: if token and token.type != TokenKind.EOF:
self.add_error(UnexpectedTokenParsingError(f"Unexpected token '{token}'", token, [])) self.add_error(UnexpectedTokenParsingError(f"Unexpected token '{token}'", token, []))
if isinstance(tree, ParenthesisNode):
tree = tree.node
value = self.get_return_value_body(context.sheerka, self.parser_input.as_text(), tree, tree) value = self.get_return_value_body(context.sheerka, self.parser_input.as_text(), tree, tree)
ret = self.sheerka.ret( ret = self.sheerka.ret(
@@ -4,11 +4,12 @@ from core.builtin_concepts_ids import BuiltinConcepts
from core.sheerka.services.SheerkaExecute import ParserInput, SheerkaExecute from core.sheerka.services.SheerkaExecute import ParserInput, SheerkaExecute
from core.tokenizer import TokenKind, Token from core.tokenizer import TokenKind, Token
from core.utils import get_text_from_tokens from core.utils import get_text_from_tokens
from parsers.BaseParser import BaseParser from parsers.BaseParser import BaseParser, UnexpectedTokenParsingError
from parsers.expressions import ComparisonNode, ParenthesisMismatchError, NameExprNode, ComparisonType, VariableNode from parsers.expressions import ComparisonNode, ParenthesisMismatchError, NameExprNode, ComparisonType, VariableNode, \
ParenthesisNode, LeftPartNotFoundError
class ComparisonParser(BaseParser): class RelationalOperatorParser(BaseParser):
""" """
Parses xxx (== | > | < | >= | <= | != | in | not in) yyy Parses xxx (== | > | < | >= | <= | != | in | not in) yyy
Nothing else Nothing else
@@ -47,6 +48,8 @@ class ComparisonParser(BaseParser):
self.parser_input.next_token() self.parser_input.next_token()
node = self.parse_compare() node = self.parse_compare()
if isinstance(node, ParenthesisNode):
node = node.node
value = self.get_return_value_body(context.sheerka, self.parser_input.as_text(), node, node) value = self.get_return_value_body(context.sheerka, self.parser_input.as_text(), node, node)
@@ -67,21 +70,42 @@ class ComparisonParser(BaseParser):
return left return left
right = self.parse_names() right = self.parse_names()
if isinstance(right, ParenthesisNode):
right = right.node
end = right.end if right else self.parser_input.pos end = right.end if right else self.parser_input.pos
return ComparisonNode(start, end, self.parser_input.tokens[start: end + 1], comp, left, right) return ComparisonNode(start, end, self.parser_input.tokens[start: end + 1], comp, left, right)
def parse_names(self): def parse_names(self):
def stop():
return token.type == TokenKind.EOF or \
paren_count == 0 and token.type == TokenKind.RPAR or \
self.eat_comparison(False)
token = self.parser_input.token token = self.parser_input.token
if token.type == TokenKind.EOF: if token.type == TokenKind.EOF:
return None return None
if token.type == TokenKind.LPAR:
start = self.parser_input.pos
self.parser_input.next_token()
expr = self.parse_compare()
token = self.parser_input.token
if token.type != TokenKind.RPAR:
self.error_sink.append(
UnexpectedTokenParsingError(f"Unexpected token '{token}'", token, [TokenKind.RPAR]))
return expr
end = self.parser_input.pos
self.parser_input.next_token()
return ParenthesisNode(start, end, None, expr)
buffer = [] buffer = []
paren_count = 0 paren_count = 0
last_lparen = None last_lparen = None
last_rparen = None last_rparen = None
start = self.parser_input.pos start = self.parser_input.pos
while (paren_count > 0 or not self.eat_comparison(False)) and token.type != TokenKind.EOF: while not stop():
buffer.append(token) buffer.append(token)
if token.type == TokenKind.LPAR: if token.type == TokenKind.LPAR:
last_lparen = token last_lparen = token
@@ -92,8 +116,10 @@ class ComparisonParser(BaseParser):
self.parser_input.next_token(False) self.parser_input.next_token(False)
token = self.parser_input.token token = self.parser_input.token
if paren_count != 0: if len(buffer) == 0:
pass if token.type != TokenKind.RPAR:
self.error_sink.append(LeftPartNotFoundError())
return None
if paren_count > 0: if paren_count > 0:
self.error_sink.append(ParenthesisMismatchError(last_lparen)) self.error_sink.append(ParenthesisMismatchError(last_lparen))
+8 -2
View File
@@ -224,14 +224,20 @@ class ParenthesisNode(ExprNode):
if self.start != other.start or self.end != other.end: if self.start != other.start or self.end != other.end:
return False return False
if other.tokens is not None and other.tokens != self.tokens: # if other.tokens is not None and other.tokens != self.tokens:
return False # return False
return self.node == other.node return self.node == other.node
def __hash__(self): def __hash__(self):
return hash((self.start, self.end, self.node)) 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): class VariableNode(ExprNode):
def __init__(self, start, end, tokens, name, *attributes): def __init__(self, start, end, tokens, name, *attributes):
+22 -9
View File
@@ -13,7 +13,8 @@ from parsers.BaseNodeParser import UnrecognizedTokensNode, SourceCodeNode, RuleN
from parsers.FunctionParser import FunctionNode from parsers.FunctionParser import FunctionNode
from parsers.PythonParser import PythonNode from parsers.PythonParser import PythonNode
from parsers.SyaNodeParser import SyaConceptParserHelper from parsers.SyaNodeParser import SyaConceptParserHelper
from parsers.expressions import NameExprNode, AndNode, OrNode, NotNode, VariableNode, ComparisonNode, ComparisonType from parsers.expressions import NameExprNode, AndNode, OrNode, NotNode, VariableNode, ComparisonNode, ComparisonType, \
ParenthesisNode
from sheerkarete.common import V from sheerkarete.common import V
from sheerkarete.conditions import Condition, AndConditions from sheerkarete.conditions import Condition, AndConditions
@@ -66,56 +67,62 @@ class VAR:
class EQ: class EQ:
left: object left: object
right: object right: object
source = None source: str = None
@dataclass @dataclass
class NEQ: class NEQ:
left: object left: object
right: object right: object
source = None source: str = None
@dataclass @dataclass
class GT: class GT:
left: object left: object
right: object right: object
source = None source: str = None
@dataclass @dataclass
class GTE: class GTE:
left: object left: object
right: object right: object
source = None source: str = None
@dataclass @dataclass
class LT: class LT:
left: object left: object
right: object right: object
source = None source: str = None
@dataclass @dataclass
class LTE: class LTE:
left: object left: object
right: object right: object
source = None source: str = None
@dataclass @dataclass
class IN: class IN:
left: object left: object
right: object right: object
source = None source: str = None
@dataclass @dataclass
class NIN: # for NOT INT class NIN: # for NOT INT
left: object left: object
right: object right: object
source = None source: str = None
@dataclass
class PAREN: # for parenthesis node
node: object
source: str = None
class CC: class CC:
@@ -1010,6 +1017,12 @@ def get_expr_node_from_test_node(full_text, test_node):
return ComparisonNode(start, end, full_text_as_tokens[start: end + 1], return ComparisonNode(start, end, full_text_as_tokens[start: end + 1],
node_type, left_node, right_node) node_type, left_node, right_node)
if isinstance(node, PAREN):
value_as_tokens = list(Tokenizer(node.source, yield_eof=False))
start = tokens_index(full_text_as_tokens, value_as_tokens, 0)
end = start + len(value_as_tokens) - 1
return ParenthesisNode(start, end, value_as_tokens, get_expr_node(node.node))
return get_expr_node(test_node) return get_expr_node(test_node)
@@ -49,6 +49,9 @@ class TestLogicalOperatorParser(TestUsingMemoryBasedSheerka):
("not ((a and b) or (c and d))", NOT(OR(AND(EXPR("a"), EXPR("b")), AND(EXPR("c"), EXPR("d")), ("not ((a and b) or (c and d))", NOT(OR(AND(EXPR("a"), EXPR("b")), AND(EXPR("c"), EXPR("d")),
source="(a and b) or (c and d)"), source="(a and b) or (c and d)"),
source="not ((a and b) or (c and d))")), source="not ((a and b) or (c and d))")),
("(one and two)", AND(EXPR("one"), EXPR("two"))),
("(one or two)", OR(EXPR("one"), EXPR("two"))),
("(not one)", NOT(EXPR("one"))),
]) ])
def test_i_can_parse_expression(self, expression, expected): def test_i_can_parse_expression(self, expression, expected):
sheerka, context, parser = self.init_parser() sheerka, context, parser = self.init_parser()
@@ -3,16 +3,17 @@ import pytest
from core.builtin_concepts_ids import BuiltinConcepts from core.builtin_concepts_ids import BuiltinConcepts
from core.sheerka.services.SheerkaExecute import ParserInput from core.sheerka.services.SheerkaExecute import ParserInput
from core.tokenizer import TokenKind, Tokenizer from core.tokenizer import TokenKind, Tokenizer
from parsers.ComparisonParser import ComparisonParser from parsers.RelationalOperatorParser import RelationalOperatorParser
from parsers.expressions import ParenthesisMismatchError from parsers.expressions import ParenthesisMismatchError
from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka from tests.TestUsingMemoryBasedSheerka import TestUsingMemoryBasedSheerka
from tests.parsers.parsers_utils import get_expr_node_from_test_node, VAR, EXPR, EQ, NEQ, GT, GTE, LT, LTE, IN, NIN from tests.parsers.parsers_utils import get_expr_node_from_test_node, VAR, EXPR, EQ, NEQ, GT, GTE, LT, LTE, IN, NIN, \
PAREN
class TestComparisonParser(TestUsingMemoryBasedSheerka): class TestRelationalOperatorParser(TestUsingMemoryBasedSheerka):
def init_parser(self): def init_parser(self):
sheerka, context = self.init_concepts() sheerka, context = self.init_concepts()
parser = ComparisonParser() parser = RelationalOperatorParser()
return sheerka, context, parser return sheerka, context, parser
def test_i_can_detect_empty_expression(self): def test_i_can_detect_empty_expression(self):
@@ -35,13 +36,16 @@ class TestComparisonParser(TestUsingMemoryBasedSheerka):
("var_name.attr >= 10", GTE(VAR("var_name.attr"), EXPR("10"))), ("var_name.attr >= 10", GTE(VAR("var_name.attr"), EXPR("10"))),
("var_name.attr < 10", LT(VAR("var_name.attr"), EXPR("10"))), ("var_name.attr < 10", LT(VAR("var_name.attr"), EXPR("10"))),
("var_name.attr <= 10", LTE(VAR("var_name.attr"), EXPR("10"))), ("var_name.attr <= 10", LTE(VAR("var_name.attr"), EXPR("10"))),
("var_name.attr in (a, b)", IN(VAR("var_name.attr"), EXPR("(a, b)"))), ("var_name.attr in (a, b)", IN(VAR("var_name.attr"), EXPR("a, b"))),
("var_name.attr not in (a, b)", NIN(VAR("var_name.attr"), EXPR("(a, b)"))), ("var_name.attr not in (a, b)", NIN(VAR("var_name.attr"), EXPR("a, b"))),
("var1.attr1 == var2.attr2", EQ(VAR("var1.attr1"), VAR("var2.attr2"))), ("var1.attr1 == var2.attr2", EQ(VAR("var1.attr1"), VAR("var2.attr2"))),
("var1.attr1 == (var2.attr2)", EQ(VAR("var1.attr1"), VAR("var2.attr2"))),
("var_name.attr in (a.b, b.c)", IN(VAR("var_name.attr"), PAREN(EXPR("a.b, b.c"), source="(a.b, b.c)"))),
("not a var identifier", EXPR("not a var identifier")), ("not a var identifier", EXPR("not a var identifier")),
("func()", EXPR("func()")), ("func()", EXPR("func()")),
("func(a, not an identifier, x >5)", EXPR("func(a, not an identifier, x >5)")), ("func(a, not an identifier, x >5)", EXPR("func(a, not an identifier, x >5)")),
("(var_name.attr != var_name2.attr2)", NEQ(VAR("var_name.attr"), VAR("var_name2.attr2")))
]) ])
def test_i_can_parse_simple_expressions(self, expression, expected): def test_i_can_parse_simple_expressions(self, expression, expected):
sheerka, context, parser = self.init_parser() sheerka, context, parser = self.init_parser()
@@ -82,6 +86,18 @@ class TestComparisonParser(TestUsingMemoryBasedSheerka):
assert res.body.body[0].token.type == parenthesis_type assert res.body.body[0].token.type == parenthesis_type
assert res.body.body[0].token.index == index assert res.body.body[0].token.index == index
def test_i_can_detect_syntax_error(self):
sheerka, context, parser = self.init_parser()
expression = "var in a"
res = parser.parse(context, ParserInput(expression))
assert not res.status
assert sheerka.isinstance(res.body, BuiltinConcepts.ERROR)
assert isinstance(res.body.body[0], UnexpectedTokenError)
def test_i_can_parse_tokens_rather_than_parser_input(self): def test_i_can_parse_tokens_rather_than_parser_input(self):
sheerka, context, parser = self.init_parser() sheerka, context, parser = self.init_parser()
expression = "var1.attr1 == var2.attr2" expression = "var1.attr1 == var2.attr2"