7dcaa9c111
Fixed #77 : Parser: ShortTermMemoryParser should be called separately Fixed #78 : Remove VariableNode usage Fixed #79 : ConceptManager: Implement compile caching Fixed #80 : SheerkaExecute : parsers_key is not correctly computed Fixed #81 : ValidateConceptEvaluator : Validate concept's where and pre clauses right after the parsing Fixed #82 : SheerkaIsAManager: isa() failed when the set as a body Fixed #83 : ValidateConceptEvaluator : Support BNF and SYA Concepts Fixed #84 : ExpressionParser: Implement the parser as a standard parser Fixed #85 : Services: Give order to services Fixed #86 : cannot manage smart_get_attr(the short, color)
187 lines
5.8 KiB
Python
187 lines
5.8 KiB
Python
import ast
|
|
import logging
|
|
from dataclasses import dataclass
|
|
|
|
import core.utils
|
|
from core.builtin_concepts import BuiltinConcepts
|
|
from core.sheerka.services.SheerkaExecute import ParserInput
|
|
from core.tokenizer import TokenKind
|
|
from parsers.BaseParser import Node, ParsingError, BaseParserInputParser
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def get_python_node(obj):
|
|
if isinstance(obj, PythonNode):
|
|
return obj
|
|
if hasattr(obj, "python_node"):
|
|
return obj.python_node
|
|
return None
|
|
|
|
|
|
@dataclass()
|
|
class PythonErrorNode(ParsingError):
|
|
source: str
|
|
exception: Exception
|
|
|
|
def __hash__(self):
|
|
return hash((self.source, self.exception))
|
|
|
|
|
|
@dataclass()
|
|
class ConceptDetectedError(ParsingError):
|
|
"""
|
|
When the Python parser finds an identifier, and that identifier is a concept
|
|
So it's not for the PythonParser to respond
|
|
"""
|
|
name: str
|
|
|
|
|
|
class PythonNode(Node):
|
|
|
|
def __init__(self, source, ast_=None, original_source=None, objects=None):
|
|
self.source = source # what was parsed
|
|
self.original_source = original_source or source # to remember source before concept id replacement
|
|
self.ast_ = ast_ # if ast_ else ast.parse(source, mode="eval") if source else None
|
|
self.objects = objects or {} # when objects (mainly concepts or rules) are recognized in the expression
|
|
self.compiled = None
|
|
self.ast_str = self.get_dump(self.ast_)
|
|
|
|
def init_ast(self):
|
|
if self.ast_ is None and self.source:
|
|
self.ast_ = ast.parse(self.source, mode="eval")
|
|
self.ast_str = self.get_dump(self.ast_)
|
|
return self
|
|
|
|
def get_compiled(self):
|
|
if self.compiled is None:
|
|
self.compiled = compile(self.ast_, "<string>", "eval")
|
|
return self.compiled
|
|
|
|
def __repr__(self):
|
|
ast_type = "expr" if isinstance(self.ast_, ast.Expression) else "module"
|
|
return "PythonNode(" + ast_type + "='" + self.source + "')"
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, PythonNode):
|
|
return False
|
|
|
|
if self.source != other.source:
|
|
return False
|
|
|
|
if self.original_source != other.original_source:
|
|
return False
|
|
|
|
if self.ast_ and other.ast_:
|
|
self_dump = self.get_dump(self.ast_)
|
|
other_dump = self.get_dump(other.ast_)
|
|
return self_dump == other_dump
|
|
|
|
return True
|
|
|
|
def __hash__(self):
|
|
return hash((self.source, self.ast_.hash))
|
|
|
|
def get_python_node(self):
|
|
return self
|
|
|
|
@staticmethod
|
|
def get_dump(ast_):
|
|
if not ast_:
|
|
return None
|
|
dump = ast.dump(ast_)
|
|
for to_remove in [", ctx=Load()", ", kind=None", ", type_ignores=[]"]:
|
|
dump = dump.replace(to_remove, "")
|
|
return dump
|
|
|
|
|
|
class PythonGetNamesVisitor(ast.NodeVisitor):
|
|
"""
|
|
This visitor will find all the name declared in the ast
|
|
"""
|
|
|
|
def __init__(self):
|
|
self.names = set()
|
|
|
|
def visit_Name(self, node):
|
|
self.names.add(node.id)
|
|
|
|
|
|
class PythonParser(BaseParserInputParser):
|
|
"""
|
|
Parse Python scripts
|
|
"""
|
|
|
|
NAME = "Python"
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
BaseParserInputParser.__init__(self, PythonParser.NAME, 50)
|
|
self.source = kwargs.get("source", "<undef>")
|
|
|
|
def parse(self, context, parser_input: ParserInput):
|
|
if not isinstance(parser_input, ParserInput):
|
|
return None
|
|
|
|
sheerka = context.sheerka
|
|
tree = None
|
|
tracker = {} # to keep track of concept tokens (c:xxx:)
|
|
|
|
python_switcher = {
|
|
TokenKind.CONCEPT: lambda t: core.utils.encode_concept(t.value),
|
|
TokenKind.RULE: lambda t: core.utils.encode_concept(t.value, "R")
|
|
}
|
|
|
|
if self.reset_parser(context, parser_input):
|
|
source_code = parser_input.as_text(python_switcher, tracker)
|
|
source_code = source_code.lstrip() # right side spaces must be kept
|
|
|
|
# first, try to parse an expression
|
|
res, tree, error = self.try_parse_expression(source_code)
|
|
if not res:
|
|
# then try to parse a statement
|
|
res, tree, error = self.try_parse_statement(source_code)
|
|
if not res:
|
|
error_node = PythonErrorNode(parser_input.as_text(), error)
|
|
self.error_sink.append(error_node)
|
|
|
|
# Python parser will refuse input that directly refers to a concept
|
|
if isinstance(tree, ast.Expression) and isinstance(tree.body, ast.Name):
|
|
if tree.body.id in tracker or context.sheerka.fast_resolve(tree.body.id, return_new=False) is not None:
|
|
context.log("It's a simple concept. Not for me.", self.name)
|
|
self.error_sink.append(ConceptDetectedError(tree.body.id))
|
|
|
|
if self.has_error:
|
|
ret = sheerka.ret(
|
|
self.name,
|
|
False,
|
|
sheerka.new(
|
|
BuiltinConcepts.NOT_FOR_ME,
|
|
body=parser_input.as_text(),
|
|
reason=self.error_sink))
|
|
else:
|
|
ret = sheerka.ret(
|
|
self.name,
|
|
True,
|
|
sheerka.new(
|
|
BuiltinConcepts.PARSER_RESULT,
|
|
parser=self,
|
|
source=parser_input.as_text(),
|
|
body=PythonNode(source_code, tree, objects=tracker),
|
|
try_parsed=None))
|
|
|
|
self.log_result(context, parser_input.as_text(), ret)
|
|
return ret
|
|
|
|
def try_parse_expression(self, text):
|
|
try:
|
|
return True, ast.parse(text, f"<{self.source}>", 'eval'), None
|
|
except Exception as error:
|
|
return False, None, error
|
|
|
|
def try_parse_statement(self, text):
|
|
try:
|
|
return True, ast.parse(text, f"<{self.source}>", 'exec'), None
|
|
except Exception as error:
|
|
return False, None, error
|