rules: pass evaluation depth as a parameter
This commit is contained in:
parent
3d15b6dd63
commit
6e2391f973
1 changed files with 22 additions and 25 deletions
|
|
@ -38,7 +38,6 @@ RuleLike = Union[str, 'Rule']
|
||||||
|
|
||||||
class Rule(abc.ABC):
|
class Rule(abc.ABC):
|
||||||
_named_rules: Dict[str, Rule] = dict()
|
_named_rules: Dict[str, Rule] = dict()
|
||||||
_depth: int = 0
|
|
||||||
|
|
||||||
def __init__(self, *sub_rules: RuleLike):
|
def __init__(self, *sub_rules: RuleLike):
|
||||||
self._prepared: bool = False
|
self._prepared: bool = False
|
||||||
|
|
@ -55,7 +54,7 @@ class Rule(abc.ABC):
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def evaluate(self, tokens: List[Token]) -> EvalResult:
|
def evaluate(self, tokens: List[Token], *, depth: int = 0, parent: Optional[Rule] = None) -> EvalResult:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
|
|
@ -84,10 +83,10 @@ class Terminal(Rule):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.token_type = token_type
|
self.token_type = token_type
|
||||||
|
|
||||||
def evaluate(self, tokens: List[Token]) -> EvalResult:
|
def evaluate(self, tokens: List[Token], *, depth: int = 0, parent: Optional[Rule] = None) -> EvalResult:
|
||||||
assert len(tokens) > 0
|
assert len(tokens) > 0
|
||||||
result = EvalResult(name=self.name)
|
result = EvalResult(name=self.name)
|
||||||
logger.debug(f"{Rule._depth}: Terminal: Evaluating terminal token with tokens: '{tokens}'")
|
logger.debug("%s", f"{depth}: Terminal: Evaluating terminal token with tokens: '{tokens}'")
|
||||||
if len(tokens) != 1:
|
if len(tokens) != 1:
|
||||||
result.errors = ParsingError(tokens[0].loc, message=f"Terminal rule must have exactly one token")
|
result.errors = ParsingError(tokens[0].loc, message=f"Terminal rule must have exactly one token")
|
||||||
return result
|
return result
|
||||||
|
|
@ -101,27 +100,26 @@ class Terminal(Rule):
|
||||||
|
|
||||||
result.result = tokens[0]
|
result.result = tokens[0]
|
||||||
|
|
||||||
logger.debug(f"{Rule._depth}: Terminal: Found terminal node: {result}")
|
logger.debug(f"{depth}: Terminal: Found terminal node: {result}")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "{}({})".format(self.__class__.__name__, self.token_type)
|
return "{}({})".format(self.__class__.__name__, self.token_type)
|
||||||
|
|
||||||
|
|
||||||
class Or(Rule):
|
class Or(Rule):
|
||||||
def evaluate(self, tokens: List[Token]) -> (Optional[Exception], Dict[str, Any]):
|
def evaluate(self, tokens: List[Token], *, depth: int = 0, parent: Optional[Rule] = None) -> EvalResult:
|
||||||
result = EvalResult(errors=ParsingError(location=tokens[0].loc), name=self.name)
|
result = EvalResult(errors=ParsingError(location=tokens[0].loc), name=self.name)
|
||||||
rule: Rule
|
rule: Rule
|
||||||
for rule in self.rules:
|
for i, rule in enumerate(self.rules):
|
||||||
logger.debug(f"{Rule._depth}: Or: trying rule: {rule}")
|
logger.debug(f"{depth}: Or: Rule {i + 1}/{len(self.rules)} : trying rule: {rule}")
|
||||||
Rule._depth += 1
|
result = rule.evaluate(tokens, depth=depth + 1)
|
||||||
result = rule.evaluate(tokens)
|
|
||||||
Rule._depth -= 1
|
|
||||||
if result.errors is None:
|
if result.errors is None:
|
||||||
logger.debug(f"{Rule._depth}: Or: Rule '{rule}' matched, result: {result}")
|
logger.debug(f"{depth}: Or: Rule {i + 1}/{len(self.rules)} '{rule}' matched, result: {result}")
|
||||||
break
|
break
|
||||||
|
|
||||||
logger.debug(f"{Rule._depth}: Or: Finished with errors: {result.errors}")
|
logger.debug(f"{depth}: Or: Finished with errors: {result.errors}")
|
||||||
|
|
||||||
result.name = self.name
|
result.name = self.name
|
||||||
|
|
||||||
|
|
@ -129,27 +127,25 @@ class Or(Rule):
|
||||||
|
|
||||||
|
|
||||||
class And(Rule):
|
class And(Rule):
|
||||||
def evaluate(self, tokens: List[Token]) -> (Optional[Exception], Dict[str, Any]):
|
def evaluate(self, tokens: List[Token], *, depth: int = 0, parent: Optional[Rule] = None) -> EvalResult:
|
||||||
result = EvalResult(errors=ParsingError(tokens[0].loc), name=self.name)
|
result = EvalResult(errors=ParsingError(tokens[0].loc), name=self.name)
|
||||||
result.result = []
|
result.result = []
|
||||||
begin = 0
|
begin = 0
|
||||||
end = 0
|
end = 0
|
||||||
for i, rule in enumerate(self.rules):
|
for i, rule in enumerate(self.rules):
|
||||||
logger.debug("%s", f"{Rule._depth}: And: Trying rule '{rule}'")
|
logger.debug("%s", f"{depth}: And: Trying rule '{rule}'")
|
||||||
|
|
||||||
if end == len(tokens):
|
if end == len(tokens):
|
||||||
logger.error("%s", f"{Rule._depth}: And: Oops, reached the end of the tokens")
|
logger.error("%s", f"{depth}: And: Oops, reached the end of the tokens")
|
||||||
|
|
||||||
best_r: Optional[EvalResult] = None
|
best_r: Optional[EvalResult] = None
|
||||||
while end < len(tokens):
|
while end < len(tokens):
|
||||||
tokens_ = tokens[begin:end + 1]
|
tokens_ = tokens[begin:end + 1]
|
||||||
Rule._depth += 1
|
r = rule.evaluate(tokens_, depth=depth + 1)
|
||||||
r = rule.evaluate(tokens_)
|
|
||||||
Rule._depth -= 1
|
|
||||||
|
|
||||||
# No previous match, but we found one
|
# No previous match, but we found one
|
||||||
if best_r is None and r.errors is None:
|
if best_r is None and r.errors is None:
|
||||||
logger.debug(f"{Rule._depth}: And: Rule {i + 1}/{len(self.rules)} '{rule}' matched, result: {r}")
|
logger.debug(f"{depth}: And: Rule {i + 1}/{len(self.rules)} '{rule}' matched, result: {r}")
|
||||||
best_r = r
|
best_r = r
|
||||||
|
|
||||||
# No result at all. That's an error.
|
# No result at all. That's an error.
|
||||||
|
|
@ -159,12 +155,13 @@ class And(Rule):
|
||||||
|
|
||||||
# We had a result, but we still matched with more tokens
|
# We had a result, but we still matched with more tokens
|
||||||
elif best_r is not None and r.errors is None:
|
elif best_r is not None and r.errors is None:
|
||||||
logger.debug(f"{Rule._depth}: And: Rule {i + 1}/{len(self.rules)} '{rule}' **improved**, result: {r}")
|
logger.debug(f"{depth}: And: Rule {i + 1}/{len(self.rules)} '{rule}' **improved**, result: {r}")
|
||||||
best_r = r
|
best_r = r
|
||||||
|
|
||||||
# We already have a match, and we can't improve it. Finish this rule.
|
# We already have a match, and we can't improve it. Finish this rule.
|
||||||
elif best_r is not None and r.errors is not None:
|
elif best_r is not None and r.errors is not None:
|
||||||
logger.debug(f"{Rule._depth}: And: Rule {i + 1}/{len(self.rules)} '{rule}' has FINAL match, result: {best_r}")
|
logger.debug(
|
||||||
|
f"{depth}: And: Rule {i + 1}/{len(self.rules)} '{rule}' has FINAL match, result: {best_r}")
|
||||||
result.result += [best_r]
|
result.result += [best_r]
|
||||||
# Matching rule ended at 'end - 1', meaning next rule will begin at end
|
# Matching rule ended at 'end - 1', meaning next rule will begin at end
|
||||||
begin = end
|
begin = end
|
||||||
|
|
@ -174,10 +171,10 @@ class And(Rule):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"{Rule._depth}: And: Rule {i + 1}/{len(self.rules)} '{rule}' has FINAL match, finishing rule, result: {best_r}")
|
f"{depth}: And: Rule {i + 1}/{len(self.rules)} '{rule}' has FINAL match, finishing rule, result: {best_r}")
|
||||||
result.result += [best_r]
|
result.result += [best_r]
|
||||||
result.errors = None
|
result.errors = None
|
||||||
|
|
||||||
if end != len(tokens):
|
if end != len(tokens):
|
||||||
logger.debug(f"{Rule._depth}: And: Didn't consume all tokens")
|
logger.debug(f"{depth}: And: Didn't consume all tokens")
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue