102 lines
2.8 KiB
Python
102 lines
2.8 KiB
Python
from parsers.BaseParser import BaseParser, Node, ErrorNode
|
|
from dataclasses import dataclass
|
|
import ast
|
|
import copy
|
|
import logging
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass()
|
|
class PythonErrorNode(ErrorNode):
|
|
source: str
|
|
exception: Exception
|
|
|
|
def __post_init__(self):
|
|
log.debug("-> PythonErrorNode: " + str(self.exception))
|
|
|
|
@dataclass()
|
|
class PythonNode(Node):
|
|
source: str
|
|
ast: ast.AST
|
|
|
|
def __repr__(self):
|
|
return "PythonNode(" + ast.dump(self.ast) + ")"
|
|
# return "PythonNode(" + self.source + ")"
|
|
|
|
|
|
class PythonParser(BaseParser):
|
|
"""
|
|
Parse Python scripts
|
|
"""
|
|
def __init__(self, source="<undef>"):
|
|
|
|
BaseParser.__init__(self, "PythonParser")
|
|
self.source = source
|
|
|
|
def parse(self, context, text):
|
|
text = text if isinstance(text, str) else self.get_text_from_tokens(text)
|
|
text = text.strip()
|
|
|
|
# first, try to parse an expression
|
|
res, tree, error = self.try_parse_expression(text)
|
|
if not res:
|
|
# then try to parse a statement
|
|
res, tree, error = self.try_parse_statement(text)
|
|
if not res:
|
|
self.has_error = True
|
|
error_node = PythonErrorNode(text, error)
|
|
self.error_sink.append(error_node)
|
|
return error_node
|
|
|
|
log.debug("Recognized python code.")
|
|
return PythonNode(text, tree)
|
|
|
|
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
|
|
|
|
def expr_to_expression(self, expr):
|
|
expr.lineno = 0
|
|
expr.col_offset = 0
|
|
result = ast.Expression(expr.value, lineno=0, col_offset=0)
|
|
|
|
return result
|
|
|
|
def exec_with_return(self, code):
|
|
code_ast = ast.parse(code)
|
|
|
|
init_ast = copy.deepcopy(code_ast)
|
|
init_ast.body = code_ast.body[:-1]
|
|
|
|
last_ast = copy.deepcopy(code_ast)
|
|
last_ast.body = code_ast.body[-1:]
|
|
|
|
exec(compile(init_ast, "<ast>", "exec"), globals())
|
|
if type(last_ast.body[0]) == ast.Expr:
|
|
return eval(compile(self.expr_to_expression(last_ast.body[0]), "<ast>", "eval"), globals())
|
|
else:
|
|
exec(compile(last_ast, "<ast>", "exec"), globals())
|
|
|
|
|
|
class PythonGetNamesVisitor(ast.NodeVisitor):
|
|
"""
|
|
This visitor will find all the name declared in the ast
|
|
"""
|
|
def __init__(self):
|
|
self.names = set()
|
|
log.debug("Searching for names.")
|
|
|
|
def visit_Name(self, node):
|
|
log.debug(f"Found name : {node.id}")
|
|
self.names.add(node.id)
|
|
|