@@ -145,3 +145,30 @@ class Parent(Visitor): | |||||
assert not hasattr(subtree, 'parent') | assert not hasattr(subtree, 'parent') | ||||
subtree.parent = tree | subtree.parent = tree | ||||
``` | ``` | ||||
## Unwinding VisitError after a transformer/visitor exception | |||||
Errors that happen inside visitors and transformers get wrapped inside a `VisitError` exception. | |||||
This can often be inconvenient, if you wish the actual error to propagate upwards, or if you want to catch it. | |||||
But, it's easy to unwrap it at the point of calling the transformer, by catching it and raising the `VisitError.orig_exc` attribute. | |||||
For example: | |||||
```python | |||||
from lark import Lark, Transformer | |||||
from lark.visitors import VisitError | |||||
tree = Lark('start: "a"').parse('a') | |||||
class T(Transformer): | |||||
def start(self, x): | |||||
raise KeyError("Original Exception") | |||||
t = T() | |||||
try: | |||||
print( t.transform(tree)) | |||||
except VisitError as e: | |||||
raise e.orig_exc | |||||
``` |
@@ -148,7 +148,7 @@ class UnexpectedEOF(ParseError, UnexpectedInput): | |||||
class UnexpectedCharacters(LexError, UnexpectedInput): | class UnexpectedCharacters(LexError, UnexpectedInput): | ||||
def __init__(self, seq, lex_pos, line, column, allowed=None, considered_tokens=None, state=None, token_history=None, | def __init__(self, seq, lex_pos, line, column, allowed=None, considered_tokens=None, state=None, token_history=None, | ||||
terminals_by_name=None): | |||||
terminals_by_name=None, considered_rules=None): | |||||
# TODO considered_tokens and allowed can be figured out using state | # TODO considered_tokens and allowed can be figured out using state | ||||
self.line = line | self.line = line | ||||
self.column = column | self.column = column | ||||
@@ -158,6 +158,7 @@ class UnexpectedCharacters(LexError, UnexpectedInput): | |||||
self.allowed = allowed | self.allowed = allowed | ||||
self.considered_tokens = considered_tokens | self.considered_tokens = considered_tokens | ||||
self.considered_rules = considered_rules | |||||
self.token_history = token_history | self.token_history = token_history | ||||
if isinstance(seq, bytes): | if isinstance(seq, bytes): | ||||
@@ -169,7 +170,7 @@ class UnexpectedCharacters(LexError, UnexpectedInput): | |||||
super(UnexpectedCharacters, self).__init__() | super(UnexpectedCharacters, self).__init__() | ||||
def __str__(self): | def __str__(self): | ||||
message = "No terminal defined for '%s' at line %d col %d" % (self.char, self.line, self.column) | |||||
message = "No terminal matches '%s' in the current parser context, at line %d col %d" % (self.char, self.line, self.column) | |||||
message += '\n\n' + self._context | message += '\n\n' + self._context | ||||
if self.allowed: | if self.allowed: | ||||
message += self._format_expected(self.allowed) | message += self._format_expected(self.allowed) | ||||
@@ -114,8 +114,11 @@ class Parser(BaseParser): | |||||
del delayed_matches[i+1] # No longer needed, so unburden memory | del delayed_matches[i+1] # No longer needed, so unburden memory | ||||
if not next_set and not delayed_matches and not next_to_scan: | if not next_set and not delayed_matches and not next_to_scan: | ||||
considered_rules = list(sorted(to_scan, key=lambda key: key.rule.origin.name)) | |||||
raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect.name for item in to_scan}, | raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect.name for item in to_scan}, | ||||
set(to_scan), state=frozenset(i.s for i in to_scan)) | |||||
set(to_scan), state=frozenset(i.s for i in to_scan), | |||||
considered_rules=considered_rules | |||||
) | |||||
return next_to_scan | return next_to_scan | ||||