Added the experimental "propagate_positions" feature (only for standard lexer for now).

This commit is contained in:
Erez Shinan 2017-05-27 01:55:49 +03:00
parent f1aede9acd
commit a588a70a7a
4 changed files with 62 additions and 5 deletions

View File

@ -39,6 +39,7 @@ class LarkOptions(object):
postlex - Lexer post-processing (Default: None)
start - The start symbol (Default: start)
profile - Measure run-time usage in Lark. Read results from the profiler proprety (Default: False)
propagate_positions - Experimental. Don't use yet.
"""
__doc__ += OPTIONS_DOC
def __init__(self, options_dict):
@ -55,6 +56,7 @@ class LarkOptions(object):
self.start = o.pop('start', 'start')
self.profile = o.pop('profile', False)
self.ambiguity = o.pop('ambiguity', 'auto')
self.propagate_positions = o.pop('propagate_positions', False)
assert self.parser in ('earley', 'lalr', None)
@ -160,7 +162,7 @@ class Lark:
def _build_parser(self):
self.parser_class = get_frontend(self.options.parser, self.options.lexer)
self.parse_tree_builder = ParseTreeBuilder(self.options.tree_class)
self.parse_tree_builder = ParseTreeBuilder(self.options.tree_class, self.options.propagate_positions)
rules, callback = self.parse_tree_builder.create_tree_builder(self.rules, self.options.transformer)
if self.profiler:
for f in dir(callback):

View File

@ -155,17 +155,27 @@ class Lexer(object):
if m:
value = m.group(0)
type_ = type_from_index[m.lastindex]
if type_ not in ignore_types:
to_yield = type_ not in ignore_types
if to_yield:
t = Token(type_, value, lex_pos, line, lex_pos - col_start_pos)
if t.type in self.callback:
t = self.callback[t.type](t)
yield t
end_col = t.column + len(value)
if type_ in newline_types:
newlines = value.count(self.newline_char)
if newlines:
line += newlines
col_start_pos = lex_pos + value.rindex(self.newline_char)
last_newline_index = value.rindex(self.newline_char) + 1
col_start_pos = lex_pos + last_newline_index
end_col = len(value) - last_newline_index
if to_yield:
t.end_line = line
t.end_col = end_col
yield t
lex_pos += len(value)
break
else:

View File

@ -1,4 +1,5 @@
from .common import is_terminal, GrammarError
from .utils import suppress
from .lexer import Token
class Callback(object):
@ -42,10 +43,31 @@ def create_rule_handler(expansion, usermethod, keep_all_tokens, filter_out):
# else, if no filtering required..
return usermethod
def propagate_positions_wrapper(f):
def _f(args):
res = f(args)
if args:
for a in args:
with suppress(AttributeError):
res.line = a.line
res.column = a.column
break
for a in reversed(args):
with suppress(AttributeError):
res.end_line = a.end_line
res.end_col = a.end_col
break
return res
return _f
class ParseTreeBuilder:
def __init__(self, tree_class):
def __init__(self, tree_class, propagate_positions=False):
self.tree_class = tree_class
self.propagate_positions = propagate_positions
def _create_tree_builder_function(self, name):
tree_class = self.tree_class
@ -92,6 +114,9 @@ class ParseTreeBuilder:
alias_handler = create_rule_handler(expansion, f, keep_all_tokens, filter_out)
if self.propagate_positions:
alias_handler = propagate_positions_wrapper(alias_handler)
callback_name = 'autoalias_%s_%s' % (_origin, '_'.join(expansion))
if hasattr(callback, callback_name):
raise GrammarError("Rule expansion '%s' already exists in rule %s" % (' '.join(expansion), origin))

View File

@ -1,6 +1,7 @@
import functools
import types
from collections import deque
from contextlib import contextmanager
class fzset(frozenset):
def __repr__(self):
@ -88,5 +89,24 @@ except NameError:
return -1
try:
from contextlib import suppress # Python 3
except ImportError:
@contextmanager
def suppress(*excs):
'''Catch and dismiss the provided exception
>>> x = 'hello'
>>> with suppress(IndexError):
... x = x[10]
>>> x
'hello'
'''
try:
yield
except excs:
pass