Browse Source

Merge branch 'master' into 0.7c

Update from latest 0.6
tags/gm/2021-09-23T00Z/github.com--lark-parser-lark/0.6.6
Erez Shinan 6 years ago
parent
commit
085eba3b8e
13 changed files with 234 additions and 32 deletions
  1. +1
    -0
      .gitignore
  2. +5
    -1
      docs/classes.md
  3. +2
    -2
      docs/grammar.md
  4. +16
    -0
      docs/how_to_use.md
  5. +1
    -1
      examples/python3.lark
  6. +8
    -0
      lark/grammar.py
  7. +8
    -3
      lark/lark.py
  8. +32
    -8
      lark/load_grammar.py
  9. +74
    -13
      lark/parse_tree_builder.py
  10. +12
    -0
      lark/tree.py
  11. +5
    -0
      lark/visitors.py
  12. +43
    -4
      tests/test_parser.py
  13. +27
    -0
      tests/test_trees.py

+ 1
- 0
.gitignore View File

@@ -1,4 +1,5 @@
*.pyc *.pyc
*.pyo
/lark_parser.egg-info/** /lark_parser.egg-info/**
tags tags
.vscode .vscode


+ 5
- 1
docs/classes.md View File

@@ -76,7 +76,11 @@ Returns all nodes of the tree whose data equals the given data.


#### iter_subtrees(self) #### iter_subtrees(self)


Iterates over all the subtrees, never returning to the same node twice (Lark's parse-tree is actually a DAG)
Iterates over all the subtrees, never returning to the same node twice (Lark's parse-tree is actually a DAG).

#### iter_subtrees_topdown(self)

Iterates over all the subtrees, return nodes in order like pretty() does.


#### \_\_eq\_\_, \_\_hash\_\_ #### \_\_eq\_\_, \_\_hash\_\_




+ 2
- 2
docs/grammar.md View File

@@ -43,7 +43,7 @@ Literals can be one of:
* `/regular expression+/` * `/regular expression+/`
* `"case-insensitive string"i` * `"case-insensitive string"i`
* `/re with flags/imulx` * `/re with flags/imulx`
* Literal range: `"a".."z"`, `"1..9"`, etc.
* Literal range: `"a".."z"`, `"1".."9"`, etc.


#### Notes for when using a lexer: #### Notes for when using a lexer:


@@ -145,4 +145,4 @@ If the module path is relative, such as `.path.to.file`, Lark will attempt to lo


### %declare ### %declare


Declare a terminal without defining it. Useful for plugins.
Declare a terminal without defining it. Useful for plugins.

+ 16
- 0
docs/how_to_use.md View File

@@ -52,3 +52,19 @@ class MyTransformer(Transformer):
new_tree = MyTransformer().transform(tree) new_tree = MyTransformer().transform(tree)
``` ```


## LALR usage

By default Lark silently resolves Shift/Reduce conflicts as Shift. To enable warnings pass `debug=True`. To get the messages printed you have to configure `logging` framework beforehand. For example:

```python
from lark import Lark
import logging
logging.basicConfig(level=logging.DEBUG)

collision_grammar = '''
start: as as
as: a*
a: 'a'
'''
p = Lark(collision_grammar, parser='lalr', debug=True)
```

+ 1
- 1
examples/python3.lark View File

@@ -161,7 +161,7 @@ yield_expr: "yield" [yield_arg]
yield_arg: "from" test | testlist yield_arg: "from" test | testlist




number: DEC_NUMBER | HEX_NUMBER | OCT_NUMBER | FLOAT_NUMBER | IMAG_NUMBER
number: DEC_NUMBER | HEX_NUMBER | BIN_NUMBER | OCT_NUMBER | FLOAT_NUMBER | IMAG_NUMBER
string: STRING | LONG_STRING string: STRING | LONG_STRING
// Tokens // Tokens




+ 8
- 0
lark/grammar.py View File

@@ -51,12 +51,20 @@ class Rule(object):
def __repr__(self): def __repr__(self):
return 'Rule(%r, %r, %r, %r)' % (self.origin, self.expansion, self.alias, self.options) return 'Rule(%r, %r, %r, %r)' % (self.origin, self.expansion, self.alias, self.options)


def __hash__(self):
return hash((self.origin, tuple(self.expansion)))
def __eq__(self, other):
if not isinstance(other, Rule):
return False
return self.origin == other.origin and self.expansion == other.expansion



class RuleOptions: class RuleOptions:
def __init__(self, keep_all_tokens=False, expand1=False, priority=None): def __init__(self, keep_all_tokens=False, expand1=False, priority=None):
self.keep_all_tokens = keep_all_tokens self.keep_all_tokens = keep_all_tokens
self.expand1 = expand1 self.expand1 = expand1
self.priority = priority self.priority = priority
self.empty_indices = ()


def __repr__(self): def __repr__(self):
return 'RuleOptions(%r, %r, %r)' % ( return 'RuleOptions(%r, %r, %r)' % (


+ 8
- 3
lark/lark.py View File

@@ -45,8 +45,11 @@ class LarkOptions(object):
profile - Measure run-time usage in Lark. Read results from the profiler proprety (Default: False) profile - Measure run-time usage in Lark. Read results from the profiler proprety (Default: False)
propagate_positions - Propagates [line, column, end_line, end_column] attributes into all tree branches. propagate_positions - Propagates [line, column, end_line, end_column] attributes into all tree branches.
lexer_callbacks - Dictionary of callbacks for the lexer. May alter tokens during lexing. Use with caution. lexer_callbacks - Dictionary of callbacks for the lexer. May alter tokens during lexing. Use with caution.
maybe_placeholders - Experimental feature. Instead of omitting optional rules (i.e. rule?), replace them with None
""" """
__doc__ += OPTIONS_DOC
if __doc__:
__doc__ += OPTIONS_DOC

def __init__(self, options_dict): def __init__(self, options_dict):
o = dict(options_dict) o = dict(options_dict)


@@ -63,6 +66,7 @@ class LarkOptions(object):
self.ambiguity = o.pop('ambiguity', 'auto') self.ambiguity = o.pop('ambiguity', 'auto')
self.propagate_positions = o.pop('propagate_positions', False) self.propagate_positions = o.pop('propagate_positions', False)
self.lexer_callbacks = o.pop('lexer_callbacks', {}) self.lexer_callbacks = o.pop('lexer_callbacks', {})
self.maybe_placeholders = o.pop('maybe_placeholders', False)


assert self.parser in ('earley', 'lalr', 'cyk', None) assert self.parser in ('earley', 'lalr', 'cyk', None)


@@ -167,7 +171,8 @@ class Lark:


if self.profiler: self.profiler.enter_section('outside_lark') if self.profiler: self.profiler.enter_section('outside_lark')


__init__.__doc__ += "\nOPTIONS:" + LarkOptions.OPTIONS_DOC
if __init__.__doc__:
__init__.__doc__ += "\nOPTIONS:" + LarkOptions.OPTIONS_DOC


def _build_lexer(self): def _build_lexer(self):
return TraditionalLexer(self.lexer_conf.tokens, ignore=self.lexer_conf.ignore, user_callbacks=self.lexer_conf.callbacks) return TraditionalLexer(self.lexer_conf.tokens, ignore=self.lexer_conf.ignore, user_callbacks=self.lexer_conf.callbacks)
@@ -175,7 +180,7 @@ class Lark:
def _build_parser(self): def _build_parser(self):
self.parser_class = get_frontend(self.options.parser, self.options.lexer) self.parser_class = get_frontend(self.options.parser, self.options.lexer)


self._parse_tree_builder = ParseTreeBuilder(self.rules, self.options.tree_class, self.options.propagate_positions, self.options.keep_all_tokens, self.options.parser!='lalr' and self.options.ambiguity=='explicit')
self._parse_tree_builder = ParseTreeBuilder(self.rules, self.options.tree_class, self.options.propagate_positions, self.options.keep_all_tokens, self.options.parser!='lalr' and self.options.ambiguity=='explicit', self.options.maybe_placeholders)
callback = self._parse_tree_builder.create_callback(self.options.transformer) callback = self._parse_tree_builder.create_callback(self.options.transformer)
if self.profiler: if self.profiler:
for f in dir(callback): for f in dir(callback):


+ 32
- 8
lark/load_grammar.py View File

@@ -3,7 +3,7 @@
import os.path import os.path
import sys import sys
from ast import literal_eval from ast import literal_eval
from copy import deepcopy
from copy import copy, deepcopy


from .utils import bfs from .utils import bfs
from .lexer import Token, TerminalDef, PatternStr, PatternRE from .lexer import Token, TerminalDef, PatternStr, PatternRE
@@ -26,6 +26,8 @@ EXT = '.lark'


_RE_FLAGS = 'imslux' _RE_FLAGS = 'imslux'


_EMPTY = Symbol('__empty__')

_TERMINAL_NAMES = { _TERMINAL_NAMES = {
'.' : 'DOT', '.' : 'DOT',
',' : 'COMMA', ',' : 'COMMA',
@@ -151,7 +153,6 @@ RULES = {
'literal': ['REGEXP', 'STRING'], 'literal': ['REGEXP', 'STRING'],
} }



@inline_args @inline_args
class EBNF_to_BNF(Transformer_InPlace): class EBNF_to_BNF(Transformer_InPlace):
def __init__(self): def __init__(self):
@@ -175,7 +176,14 @@ class EBNF_to_BNF(Transformer_InPlace):


def expr(self, rule, op, *args): def expr(self, rule, op, *args):
if op.value == '?': if op.value == '?':
return ST('expansions', [rule, ST('expansion', [])])
if isinstance(rule, Terminal) and rule.filter_out and not (
self.rule_options and self.rule_options.keep_all_tokens):
empty = ST('expansion', [])
elif isinstance(rule, NonTerminal) and rule.name.startswith('_'):
empty = ST('expansion', [])
else:
empty = _EMPTY
return ST('expansions', [rule, empty])
elif op.value == '+': elif op.value == '+':
# a : b c+ d # a : b c+ d
# --> # -->
@@ -481,7 +489,8 @@ class Grammar:
for name, rule_tree, options in rule_defs: for name, rule_tree, options in rule_defs:
ebnf_to_bnf.rule_options = RuleOptions(keep_all_tokens=True) if options and options.keep_all_tokens else None ebnf_to_bnf.rule_options = RuleOptions(keep_all_tokens=True) if options and options.keep_all_tokens else None
tree = transformer.transform(rule_tree) tree = transformer.transform(rule_tree)
rules.append((name, ebnf_to_bnf.transform(tree), options))
res = ebnf_to_bnf.transform(tree)
rules.append((name, res, options))
rules += ebnf_to_bnf.new_rules rules += ebnf_to_bnf.new_rules


assert len(rules) == len({name for name, _t, _o in rules}), "Whoops, name collision" assert len(rules) == len({name for name, _t, _o in rules}), "Whoops, name collision"
@@ -499,9 +508,17 @@ class Grammar:
if alias and name.startswith('_'): if alias and name.startswith('_'):
raise GrammarError("Rule %s is marked for expansion (it starts with an underscore) and isn't allowed to have aliases (alias=%s)" % (name, alias)) raise GrammarError("Rule %s is marked for expansion (it starts with an underscore) and isn't allowed to have aliases (alias=%s)" % (name, alias))


assert all(isinstance(x, Symbol) for x in expansion), expansion
empty_indices = [x==_EMPTY for i, x in enumerate(expansion)]
if any(empty_indices):
assert options
exp_options = copy(options)
exp_options.empty_indices = empty_indices
expansion = [x for x in expansion if x!=_EMPTY]
else:
exp_options = options


rule = Rule(NonTerminal(name), expansion, alias, options)
assert all(isinstance(x, Symbol) for x in expansion), expansion
rule = Rule(NonTerminal(name), expansion, alias, exp_options)
compiled_rules.append(rule) compiled_rules.append(rule)


return terminals, compiled_rules, self.ignore return terminals, compiled_rules, self.ignore
@@ -526,8 +543,12 @@ def import_grammar(grammar_path, base_paths=[]):
return _imported_grammars[grammar_path] return _imported_grammars[grammar_path]


def import_from_grammar_into_namespace(grammar, namespace, aliases): def import_from_grammar_into_namespace(grammar, namespace, aliases):
"""Returns all rules and terminals of grammar, prepended
with a 'namespace' prefix, except for those which are aliased.
"""

imported_terms = dict(grammar.term_defs) imported_terms = dict(grammar.term_defs)
imported_rules = {n:(n,t,o) for n,t,o in grammar.rule_defs}
imported_rules = {n:(n,deepcopy(t),o) for n,t,o in grammar.rule_defs}
term_defs = [] term_defs = []
rule_defs = [] rule_defs = []
@@ -535,7 +556,10 @@ def import_from_grammar_into_namespace(grammar, namespace, aliases):
def rule_dependencies(symbol): def rule_dependencies(symbol):
if symbol.type != 'RULE': if symbol.type != 'RULE':
return [] return []
_, tree, _ = imported_rules[symbol]
try:
_, tree, _ = imported_rules[symbol]
except KeyError:
raise GrammarError("Missing symbol '%s' in grammar %s" % (symbol, namespace))
return tree.scan_values(lambda x: x.type in ('RULE', 'TERMINAL')) return tree.scan_values(lambda x: x.type in ('RULE', 'TERMINAL'))


def get_namespace_name(name): def get_namespace_name(name):


+ 74
- 13
lark/parse_tree_builder.py View File

@@ -1,7 +1,5 @@
from .exceptions import GrammarError from .exceptions import GrammarError
from .utils import suppress
from .lexer import Token from .lexer import Token
from .grammar import Rule
from .tree import Tree from .tree import Tree
from .visitors import InlineTransformer # XXX Deprecated from .visitors import InlineTransformer # XXX Deprecated


@@ -20,7 +18,6 @@ class ExpandSingleChild:
else: else:
return self.node_builder(children) return self.node_builder(children)



class PropagatePositions: class PropagatePositions:
def __init__(self, node_builder): def __init__(self, node_builder):
self.node_builder = node_builder self.node_builder = node_builder
@@ -63,7 +60,50 @@ class PropagatePositions:




class ChildFilter: class ChildFilter:
"Optimized childfilter (assumes no duplication in parse tree, so it's safe to change it)"
def __init__(self, to_include, append_none, node_builder):
self.node_builder = node_builder
self.to_include = to_include
self.append_none = append_none

def __call__(self, children):
filtered = []

for i, to_expand, add_none in self.to_include:
if add_none:
filtered += [None] * add_none
if to_expand:
filtered += children[i].children
else:
filtered.append(children[i])

if self.append_none:
filtered += [None] * self.append_none

return self.node_builder(filtered)

class ChildFilterLALR(ChildFilter):
"Optimized childfilter for LALR (assumes no duplication in parse tree, so it's safe to change it)"

def __call__(self, children):
filtered = []
for i, to_expand, add_none in self.to_include:
if add_none:
filtered += [None] * add_none
if to_expand:
if filtered:
filtered += children[i].children
else: # Optimize for left-recursion
filtered = children[i].children
else:
filtered.append(children[i])

if self.append_none:
filtered += [None] * self.append_none

return self.node_builder(filtered)

class ChildFilterLALR_NoPlaceholders(ChildFilter):
"Optimized childfilter for LALR (assumes no duplication in parse tree, so it's safe to change it)"
def __init__(self, to_include, node_builder): def __init__(self, to_include, node_builder):
self.node_builder = node_builder self.node_builder = node_builder
self.to_include = to_include self.to_include = to_include
@@ -83,13 +123,6 @@ class ChildFilter:
def _should_expand(sym): def _should_expand(sym):
return not sym.is_term and sym.name.startswith('_') return not sym.is_term and sym.name.startswith('_')


def maybe_create_child_filter(expansion, keep_all_tokens):
to_include = [(i, _should_expand(sym)) for i, sym in enumerate(expansion)
if keep_all_tokens or not (sym.is_term and sym.filter_out)]

if len(to_include) < len(expansion) or any(to_expand for i, to_expand in to_include):
return partial(ChildFilter, to_include)

class AmbiguousExpander: class AmbiguousExpander:
"""Deal with the case where we're expanding children ('_rule') into a parent but the children """Deal with the case where we're expanding children ('_rule') into a parent but the children
are ambiguous. i.e. (parent->_ambig->_expand_this_rule). In this case, make the parent itself are ambiguous. i.e. (parent->_ambig->_expand_this_rule). In this case, make the parent itself
@@ -100,6 +133,33 @@ class AmbiguousExpander:
self.tree_class = tree_class self.tree_class = tree_class
self.to_expand = to_expand self.to_expand = to_expand


def maybe_create_child_filter(expansion, keep_all_tokens, ambiguous, _empty_indices):
# Prepare empty_indices as: How many Nones to insert at each index?
if _empty_indices:
assert _empty_indices.count(False) == len(expansion)
s = ''.join(str(int(b)) for b in _empty_indices)
empty_indices = [len(ones) for ones in s.split('0')]
assert len(empty_indices) == len(expansion)+1, (empty_indices, len(expansion))
else:
empty_indices = [0] * (len(expansion)+1)

to_include = []
nones_to_add = 0
for i, sym in enumerate(expansion):
nones_to_add += empty_indices[i]
if keep_all_tokens or not (sym.is_term and sym.filter_out):
to_include.append((i, _should_expand(sym), nones_to_add))
nones_to_add = 0

nones_to_add += empty_indices[len(expansion)]

if _empty_indices or len(to_include) < len(expansion) or any(to_expand for i, to_expand,_ in to_include):
if _empty_indices or ambiguous:
return partial(ChildFilter if ambiguous else ChildFilterLALR, to_include, nones_to_add)
else:
# LALR without placeholders
return partial(ChildFilterLALR_NoPlaceholders, [(i, x) for i,x,_ in to_include])

def __call__(self, children): def __call__(self, children):
def _is_ambig_tree(child): def _is_ambig_tree(child):
return hasattr(child, 'data') and child.data == '_ambig' return hasattr(child, 'data') and child.data == '_ambig'
@@ -127,11 +187,12 @@ def ptb_inline_args(func):
return f return f


class ParseTreeBuilder: class ParseTreeBuilder:
def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False):
def __init__(self, rules, tree_class, propagate_positions=False, keep_all_tokens=False, ambiguous=False, maybe_placeholders=False):
self.tree_class = tree_class self.tree_class = tree_class
self.propagate_positions = propagate_positions self.propagate_positions = propagate_positions
self.always_keep_all_tokens = keep_all_tokens self.always_keep_all_tokens = keep_all_tokens
self.ambiguous = ambiguous self.ambiguous = ambiguous
self.maybe_placeholders = maybe_placeholders


self.rule_builders = list(self._init_builders(rules)) self.rule_builders = list(self._init_builders(rules))


@@ -145,7 +206,7 @@ class ParseTreeBuilder:


wrapper_chain = filter(None, [ wrapper_chain = filter(None, [
(expand_single_child and not rule.alias) and ExpandSingleChild, (expand_single_child and not rule.alias) and ExpandSingleChild,
maybe_create_child_filter(rule.expansion, keep_all_tokens),
maybe_create_child_filter(rule.expansion, keep_all_tokens, self.ambiguous, options.empty_indices if self.maybe_placeholders and options else None),
self.propagate_positions and PropagatePositions, self.propagate_positions and PropagatePositions,
self.ambiguous and maybe_create_ambiguous_expander(self.tree_class, rule.expansion, keep_all_tokens), self.ambiguous and maybe_create_ambiguous_expander(self.tree_class, rule.expansion, keep_all_tokens),
]) ])


+ 12
- 0
lark/tree.py View File

@@ -5,6 +5,7 @@ except ImportError:


from copy import deepcopy from copy import deepcopy



###{standalone ###{standalone
class Meta: class Meta:
pass pass
@@ -42,6 +43,7 @@ class Tree(object):


def pretty(self, indent_str=' '): def pretty(self, indent_str=' '):
return ''.join(self._pretty(0, indent_str)) return ''.join(self._pretty(0, indent_str))

def __eq__(self, other): def __eq__(self, other):
try: try:
return self.data == other.data and self.children == other.children return self.data == other.data and self.children == other.children
@@ -99,12 +101,22 @@ class Tree(object):
yield x yield x
seen.add(id(x)) seen.add(id(x))


def iter_subtrees_topdown(self):
stack = [self]
while stack:
node = stack.pop()
if not isinstance(node, Tree):
continue
yield node
for n in reversed(node.children):
stack.append(n)


def __deepcopy__(self, memo): def __deepcopy__(self, memo):
return type(self)(self.data, deepcopy(self.children, memo)) return type(self)(self.data, deepcopy(self.children, memo))


def copy(self): def copy(self):
return type(self)(self.data, self.children) return type(self)(self.data, self.children)

def set(self, data, children): def set(self, data, children):
self.data = data self.data = data
self.children = children self.children = children


+ 5
- 0
lark/visitors.py View File

@@ -69,6 +69,10 @@ class Transformer:
if name.startswith('_') or name in libmembers: if name.startswith('_') or name in libmembers:
continue continue


# Skip if v_args already applied (at the function level)
if hasattr(cls.__dict__[name], 'vargs_applied'):
continue

static = isinstance(cls.__dict__[name], (staticmethod, classmethod)) static = isinstance(cls.__dict__[name], (staticmethod, classmethod))
setattr(cls, name, decorator(value, static=static, **kwargs)) setattr(cls, name, decorator(value, static=static, **kwargs))
return cls return cls
@@ -241,6 +245,7 @@ def _visitor_args_func_dec(func, inline=False, meta=False, whole_tree=False, sta
f = wraps(func)(create_decorator(func, False)) f = wraps(func)(create_decorator(func, False))
else: else:
f = smart_decorator(func, create_decorator) f = smart_decorator(func, create_decorator)
f.vargs_applied = True
f.inline = inline f.inline = inline
f.meta = meta f.meta = meta
f.whole_tree = whole_tree f.whole_tree = whole_tree


+ 43
- 4
tests/test_parser.py View File

@@ -1069,7 +1069,7 @@ def _make_parser_test(LEXER, PARSER):
bb_.1: "bb" bb_.1: "bb"
""" """


l = _Lark(grammar, ambiguity='resolve__antiscore_sum')
l = Lark(grammar, ambiguity='resolve__antiscore_sum')
res = l.parse('abba') res = l.parse('abba')
self.assertEqual(''.join(child.data for child in res.children), 'ab_b_a_') self.assertEqual(''.join(child.data for child in res.children), 'ab_b_a_')


@@ -1082,7 +1082,7 @@ def _make_parser_test(LEXER, PARSER):
bb_: "bb" bb_: "bb"
""" """


l = Lark(grammar, parser='earley', ambiguity='resolve__antiscore_sum')
l = Lark(grammar, ambiguity='resolve__antiscore_sum')
res = l.parse('abba') res = l.parse('abba')
self.assertEqual(''.join(child.data for child in res.children), 'indirection') self.assertEqual(''.join(child.data for child in res.children), 'indirection')


@@ -1095,7 +1095,7 @@ def _make_parser_test(LEXER, PARSER):
bb_.3: "bb" bb_.3: "bb"
""" """


l = Lark(grammar, parser='earley', ambiguity='resolve__antiscore_sum')
l = Lark(grammar, ambiguity='resolve__antiscore_sum')
res = l.parse('abba') res = l.parse('abba')
self.assertEqual(''.join(child.data for child in res.children), 'ab_b_a_') self.assertEqual(''.join(child.data for child in res.children), 'ab_b_a_')


@@ -1108,7 +1108,7 @@ def _make_parser_test(LEXER, PARSER):
bb_.3: "bb" bb_.3: "bb"
""" """


l = Lark(grammar, parser='earley', ambiguity='resolve__antiscore_sum')
l = Lark(grammar, ambiguity='resolve__antiscore_sum')
res = l.parse('abba') res = l.parse('abba')
self.assertEqual(''.join(child.data for child in res.children), 'indirection') self.assertEqual(''.join(child.data for child in res.children), 'indirection')


@@ -1282,6 +1282,45 @@ def _make_parser_test(LEXER, PARSER):
res = p.parse('B') res = p.parse('B')
self.assertEqual(len(res.children), 3) self.assertEqual(len(res.children), 3)


@unittest.skipIf(PARSER=='cyk', "Empty rules")
def test_maybe_placeholders(self):
# Anonymous tokens shouldn't count
p = _Lark("""start: "a"? "b"? "c"? """, maybe_placeholders=True)
self.assertEqual(p.parse("").children, [])

# Anonymous tokens shouldn't count, other constructs should
p = _Lark("""start: A? "b"? _c?
A: "a"
_c: "c" """, maybe_placeholders=True)
self.assertEqual(p.parse("").children, [None])

p = _Lark("""!start: "a"? "b"? "c"? """, maybe_placeholders=True)
self.assertEqual(p.parse("").children, [None, None, None])
self.assertEqual(p.parse("a").children, ['a', None, None])
self.assertEqual(p.parse("b").children, [None, 'b', None])
self.assertEqual(p.parse("c").children, [None, None, 'c'])
self.assertEqual(p.parse("ab").children, ['a', 'b', None])
self.assertEqual(p.parse("ac").children, ['a', None, 'c'])
self.assertEqual(p.parse("bc").children, [None, 'b', 'c'])
self.assertEqual(p.parse("abc").children, ['a', 'b', 'c'])

p = _Lark("""!start: ("a"? "b" "c"?)+ """, maybe_placeholders=True)
self.assertEqual(p.parse("b").children, [None, 'b', None])
self.assertEqual(p.parse("bb").children, [None, 'b', None, None, 'b', None])
self.assertEqual(p.parse("abbc").children, ['a', 'b', None, None, 'b', 'c'])
self.assertEqual(p.parse("babbcabcb").children,
[None, 'b', None,
'a', 'b', None,
None, 'b', 'c',
'a', 'b', 'c',
None, 'b', None])

p = _Lark("""!start: "a"? "c"? "b"+ "a"? "d"? """, maybe_placeholders=True)
self.assertEqual(p.parse("bb").children, [None, None, 'b', 'b', None, None])
self.assertEqual(p.parse("bd").children, [None, None, 'b', None, 'd'])
self.assertEqual(p.parse("abba").children, ['a', None, 'b', 'b', 'a', None])
self.assertEqual(p.parse("cbbbb").children, [None, 'c', 'b', 'b', 'b', 'b', None, None])





_NAME = "Test" + PARSER.capitalize() + LEXER.capitalize() _NAME = "Test" + PARSER.capitalize() + LEXER.capitalize()


+ 27
- 0
tests/test_trees.py View File

@@ -21,6 +21,17 @@ class TestTrees(TestCase):
data = pickle.dumps(s) data = pickle.dumps(s)
assert pickle.loads(data) == s assert pickle.loads(data) == s


def test_iter_subtrees(self):
expected = [Tree('b', 'x'), Tree('c', 'y'), Tree('d', 'z'),
Tree('a', [Tree('b', 'x'), Tree('c', 'y'), Tree('d', 'z')])]
nodes = list(self.tree1.iter_subtrees())
self.assertEqual(nodes, expected)

def test_iter_subtrees_topdown(self):
expected = [Tree('a', [Tree('b', 'x'), Tree('c', 'y'), Tree('d', 'z')]),
Tree('b', 'x'), Tree('c', 'y'), Tree('d', 'z')]
nodes = list(self.tree1.iter_subtrees_topdown())
self.assertEqual(nodes, expected)


def test_interp(self): def test_interp(self):
t = Tree('a', [Tree('b', []), Tree('c', []), 'd']) t = Tree('a', [Tree('b', []), Tree('c', []), 'd'])
@@ -117,6 +128,22 @@ class TestTrees(TestCase):
x = MyTransformer().transform( Tree('hello', [2])) x = MyTransformer().transform( Tree('hello', [2]))
self.assertEqual(x, 'hello') self.assertEqual(x, 'hello')


def test_vargs_override(self):
t = Tree('add', [Tree('sub', [Tree('i', ['3']), Tree('f', ['1.1'])]), Tree('i', ['1'])])

@v_args(inline=True)
class T(Transformer):
i = int
f = float
sub = lambda self, a, b: a-b

@v_args(inline=False)
def add(self, values):
return sum(values)

res = T().transform(t)
self.assertEqual(res, 2.9)



if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()


Loading…
Cancel
Save