Reimplemented explain feature

This commit is contained in:
2020-06-04 18:43:15 +02:00
parent c498b394e3
commit d7573f095f
27 changed files with 1673 additions and 1161 deletions
-361
View File
@@ -1,361 +0,0 @@
from dataclasses import dataclass, field
from typing import List, Dict
from core.builtin_concepts import BuiltinConcepts
from core.tokenizer import LexerError, Token
from parsers.BaseParser import Node, UnexpectedTokenErrorNode, BaseSplitIterParser, UnexpectedEof, ErrorNode
from parsers.ExpressionParser import ExprNode, TrueNode, PropertyEqualsNode, PropertyContainsNode, OrNode, AndNode
@dataclass()
class ValueErrorNode(ErrorNode):
"""
When the value parse has an incorrect type or value
"""
message: str
token: Token # token when the error is detected
@dataclass()
class MultipleDigestError(ErrorNode):
message: str
token: Token
@dataclass()
class ExplanationNode(Node):
digest: str # digest of the event to explain
command: str # original explain command
expr: ExprNode = None
record_digest: bool = False
@dataclass
class FilterNode(ExprNode):
"""
Wraps predicates
"""
expr: ExprNode
directives: List[ExprNode] = field(default_factory=list)
def eval(self, obj):
return self.expr.eval(obj)
@dataclass
class RecurseDefNode(ExprNode):
"""
It is used to defined the depth of the recursion
"""
depth: int
@dataclass
class FormatLNode(ExprNode):
"""
Define the template to use for ExecutionContext when printed in line
"""
template: str
@dataclass
class FormatDNode(ExprNode):
"""
Defines the properties to display, and their format
"""
properties: Dict[str, str]
@dataclass
class UnionNode(ExprNode):
"""
Define the template to use for ExecutionContext when printed in line
"""
filters: List[FilterNode]
def eval(self, obj):
if len(self.filters) == 0:
return False
if len(self.filters) == 0:
return self.filters[0].eval(obj)
res = False
for f in self.filters[1:]:
res |= f.eval(obj)
return res
class ExplainParser(BaseSplitIterParser):
def __init__(self, **kwargs):
super().__init__("Explain", 81, none_on_eof=True)
def parse_explain(self):
token = self.get_token()
if token is None:
return BuiltinConcepts.IS_EMPTY
if token.value != 'explain':
self.add_error(UnexpectedTokenErrorNode("", token, ["explain"]))
return BuiltinConcepts.NOT_FOR_ME
digest = ""
record_digest = False
expr_node = UnionNode([FilterNode(TrueNode(), [])])
self.next_token()
while True:
# no need to continue when error
if self.has_error:
return None
token = self.get_token()
if token is None:
break
if token.value == "-f" or token.value == "--filter":
self.next_token()
expr_node.filters.append(self.parse_filter())
elif token.value in ("-r", "--recurse"):
self.next_token()
expr_node.filters[-1].directives.append(self.parse_recurse())
elif token.value == "--format_l":
self.next_token()
expr_node.filters[-1].directives.append(self.parse_format_l())
elif token.value == "--format_d":
self.next_token()
expr_node.filters[-1].directives.append(self.parse_format_d())
elif token.value in ("-d", "--digest"):
self.next_token()
digest = self.parse_digest(digest)
record_digest = True
elif token.value.startswith("-"):
self.add_error(UnexpectedTokenErrorNode("", token, []))
else:
digest = self.parse_digest(digest)
return ExplanationNode(digest, self.text, expr=expr_node, record_digest=record_digest)
def parse_digest(self, digest):
token = self.get_token()
if token is None or token.value.startswith("-"):
return ""
if digest != "":
self.add_error(MultipleDigestError("Too many digest", token))
return None
digest = token.value
self.next_token()
return digest
def parse_filter(self):
node = self.parse_or()
if node is None:
return None
return FilterNode(node)
def parse_or(self):
parts = []
node = self.parse_and()
if node is None:
return None
parts.append(node)
while True:
token = self.get_token()
if token is None or token.value != "or":
break
self.next_token()
node = self.parse_and()
if node is None:
return None
else:
parts.append(node)
return parts[0] if len(parts) == 1 else OrNode(*parts)
def parse_and(self):
parts = []
node = self.parse_predicate()
if node is None:
return None
parts.append(node)
while True:
token = self.get_token()
if token is None or token.value != "and":
break
self.next_token()
node = self.parse_predicate()
if node is None:
return None
else:
parts.append(node)
return parts[0] if len(parts) == 1 else AndNode(*parts)
def parse_predicate(self):
token = self.get_token()
if token is None:
self.add_error(UnexpectedEof("Unexpected EOF while parsing filter"))
return None
if token.value == "(":
self.next_token()
expr = self.parse_or()
token = self.get_token()
if token is None:
self.add_error(UnexpectedEof("Missing right parenthesis"))
return None
if token.value != ")":
self.add_error(UnexpectedTokenErrorNode("Parenthesis mismatch", token, [")"]))
return None
self.next_token()
else:
expr = self.parse_property_predicate()
return expr
def parse_recurse(self):
token = self.get_token()
if token is None:
self.add_error(UnexpectedEof("Unexpected EOF while parsing recurse"))
return None
try:
depth = int(token.value)
self.next_token()
return RecurseDefNode(depth)
except ValueError:
self.add_error(ValueErrorNode(f"'{token.value}' is not an integer", token))
return None
def parse_format_l(self):
token = self.get_token()
if token is None:
self.add_error(UnexpectedEof("Unexpected EOF while parsing format_l"))
return None
if token.value.startswith("-"):
self.add_error(UnexpectedTokenErrorNode("parsing format_l", token, ["<property name>"]))
return None
template = token.value
self.next_token()
return FormatLNode(template)
def parse_format_d(self):
props = {}
while TrueNode:
token = self.get_token()
if token is None:
self.add_error(UnexpectedEof("Unexpected EOF while parsing format_d"))
return None
if token.value.startswith("-"):
self.add_error(UnexpectedTokenErrorNode("parsing format_d", token, ["<property name>"]))
return None
parts = token.value.split(':')
if len(parts) == 1:
props[token.value] = "{" + token.value + "}"
else:
props[parts[0]] = parts[1]
self.next_token()
token = self.get_token()
if token is None or token.value.startswith("-"):
break
elif token.value == ",":
self.next_token()
else:
self.add_error(UnexpectedTokenErrorNode("parsing format_d", token, ["<eof>", ","]))
return FormatDNode(props)
def parse_property_predicate(self):
token = self.get_token()
if token is None:
self.add_error(UnexpectedEof("Unexpected EOF while parsing predicate"))
return None
prop_name = token.value
if prop_name.startswith("-"):
self.add_error(UnexpectedTokenErrorNode("while parsing predicate", token, ["<property_name>"]))
return None
self.next_token()
token = self.get_token()
if token is None:
self.add_error(UnexpectedEof("Unexpected EOF while parsing predicate"))
return None
operand = token.value
if operand not in ("=", "=="):
self.add_error(UnexpectedTokenErrorNode("Unexpected token when parsing predicate", token, ['=', "=="]))
return None
self.next_token()
token = self.get_token()
if token is None:
self.add_error(UnexpectedEof("Unexpected EOF while parsing filter"))
return None
self.next_token()
prop_value = token.value
return PropertyEqualsNode(prop_name, prop_value) if operand == "==" else \
PropertyContainsNode(prop_name, prop_value)
def parse(self, context, parser_input):
"""
parser_input can be string, but text can also be an list of tokens
:param context:
:param parser_input:
:return:
"""
context.log(f"Parsing '{parser_input}'", self.name)
sheerka = context.sheerka
if not isinstance(parser_input, str):
return sheerka.ret(self.name, False, sheerka.new(BuiltinConcepts.NOT_FOR_ME, reason=parser_input))
explanation_node = None
try:
self.reset_parser(context, parser_input)
self.next_token()
explanation_node = self.parse_explain()
except LexerError as e:
self.add_error(e, False)
if self.has_error or not isinstance(explanation_node, ExplanationNode):
if explanation_node in (BuiltinConcepts.NOT_FOR_ME, BuiltinConcepts.IS_EMPTY):
error_body = sheerka.new(
BuiltinConcepts.NOT_FOR_ME,
body=parser_input,
reason=self.error_sink if self.has_error else BuiltinConcepts.IS_EMPTY)
else:
error_body = sheerka.new(
BuiltinConcepts.ERROR,
body=self.error_sink)
ret = sheerka.ret(self.name, False, error_body)
else:
ret = sheerka.ret(self.name, True,
sheerka.new(
BuiltinConcepts.PARSER_RESULT,
parser=self,
source=parser_input,
body=explanation_node))
self.log_result(context, parser_input, ret)
return ret