def test_safety(self): matcher3 = Delayed() matcher4 = Delayed() matcher1 = Any()[::'b', ...] & Eos() with Separator(Drop(Any('a')[:])): matcher2 = Any()[::'b', ...] & Eos() # pylint: disable-msg=W0613 def target(matcher3=matcher3, matcher4=matcher4): matcher3 += Any()[::'b', ...] & Eos() with Separator(Drop(Any('b')[:])): matcher4 += Any()[::'b', ...] & Eos() t = Thread(target=target) t.start() t.join() matcher5 = Any()[::'b', ...] & Eos() matcher6 = Any()[::'b', ...] & Eos() text = 'cababab' assert text == matcher1.parse_string(text)[0], matcher1.parse_string( text) assert 'cbbb' == matcher2.parse_string(text)[0], matcher2.parse_string( text) assert text == matcher3.parse_string(text)[0], matcher3.parse_string( text) assert 'caaa' == matcher4.parse_string(text)[0], matcher4.parse_string( text) assert 'cbbb' == matcher5.parse_string(text)[0], matcher5.parse_string( text) assert text == matcher6.parse_string(text)[0], matcher6.parse_string( text)
def test_ambiguity(self): ''' A (signed) integer will consume a - sign. ''' tokens = (Token(Integer()) | Token(r'\-'))[:] & Eos() self.examples([(lambda: list(tokens.parse_all('1-2')), "[['1', '-2']]")]) matchers = (Integer() | Literal('-'))[:] & Eos() self.examples([(lambda: list(matchers.parse_all('1-2')), "[['1', '-2'], ['1', '-', '2']]")])
def test_error(self): #basicConfig(level=INFO) class Term(Node): pass class Factor(Node): pass class Expression(Node): pass expression = Delayed() number = Digit()[1:, ...] > 'number' term = Or( AnyBut(Space() | Digit() | '(')[1:, ...] ^ 'unexpected text: {results[0]}', number > Term, number**make_error("no ( before '{stream_out}'") / ')' >> node_throw, '(' / expression / ')' > Term, ('(' / expression / Eos())**make_error("no ) for '{stream_in}'") >> node_throw) muldiv = Any('*/') > 'operator' factor = (term / (muldiv / term)[0:, r'\s*']) > Factor addsub = Any('+-') > 'operator' expression += (factor / (addsub / factor)[0:, r'\s*']) > Expression line = expression / Eos() parser = line.get_parse_string() try: parser('1 + 2 * 3 + 4 - 5)')[0] assert False, 'expected error' except SyntaxError as e: assert e.msg == "no ( before ')'", e.msg try: parser('1 + 2 * (3 + 4 - 5') assert False, 'expected error' except SyntaxError as e: assert e.msg == "no ) for '(3 + 4 - 5'", e.msg try: parser('1 + 2 * foo') assert False, 'expected error' except SyntaxError as e: assert e.msg == "unexpected text: foo", e.msg
def test_location(self): matcher = FullFirstMatch(Any('a')[:] & Eos()) matcher.config.clear() try: list(matcher.match_string('aab')) assert False, 'expected error' except FullFirstMatchException as e: assert str(e) == """The match failed at 'b', Line 1, character 2 of str: 'aab'.""", str(e)
def test_expr_with_functions(self): ''' Expression with function calls and appropriate binding. ''' #basicConfig(level=DEBUG) # pylint: disable-msg=C0111, C0321 class Call(Node): pass class Term(Node): pass class Factor(Node): pass class Expression(Node): pass value = Token(Float()) > 'value' name = Token('[a-z]+') symbol = Token('[^a-zA-Z0-9\\. ]') expr = Delayed() open_ = ~symbol('(') close = ~symbol(')') funcn = name > 'name' call = funcn & open_ & expr & close > Call term = call | value | open_ & expr & close > Term muldiv = symbol(Any('*/')) > 'operator' factor = term & (muldiv & term)[:] > Factor addsub = symbol(Any('+-')) > 'operator' expr += factor & (addsub & factor)[:] > Expression line = expr & Eos() line.config.trace(True).lexer() parser = line.get_parse_string() results = str26(parser('1 + 2*sin(3+ 4) - 5')[0]) assert results == """Expression +- Factor | `- Term | `- value '1' +- operator '+' +- Factor | +- Term | | `- value '2' | +- operator '*' | `- Term | `- Call | +- name 'sin' | `- Expression | +- Factor | | `- Term | | `- value '3' | +- operator '+' | `- Factor | `- Term | `- value '4' +- operator '-' `- Factor `- Term `- value '5'""", '[' + results + ']'
def _assert_string(self, separator, expecteds, streams=STREAMS_3): with separator: parser = And(Optional('a') & Optional('b') & 'c', Eos()) ok = True parser.config.no_full_first_match() for (stream, expected) in zip(streams, expecteds): parsed = parser.parse_string(stream) is not None if PRINT: print('{0!r:9} : {1!r:5} {2!r:5}' .format(stream, parsed, parsed == expected)) ok = ok and (parsed == expected) assert ok
def test_smart_spaces(self): with SmartSeparator1(Space()): parser = 'a' & Optional('b') & 'c' & Eos() parser.config.no_full_first_match() assert parser.parse('a b c') assert parser.parse('a c') assert not parser.parse('a b c ') assert not parser.parse('a c ') assert not parser.parse('a bc') assert not parser.parse('ab c') assert not parser.parse('abc') assert not parser.parse('ac') assert not parser.parse('a c')
def test_complex(self): #basicConfig(level=DEBUG) class VerbPhrase(Node): pass class DetPhrase(Node): pass class SimpleTp(Node): pass class TermPhrase(Node): pass class Sentence(Node): pass verb = Literals('knows', 'respects', 'loves') > 'verb' join = Literals('and', 'or') > 'join' proper_noun = Literals('helen', 'john', 'pat') > 'proper_noun' determiner = Literals('every', 'some') > 'determiner' noun = Literals('boy', 'girl', 'man', 'woman') > 'noun' verbphrase = Delayed() verbphrase += verb | (verbphrase // join // verbphrase) > VerbPhrase det_phrase = determiner // noun > DetPhrase simple_tp = proper_noun | det_phrase > SimpleTp termphrase = Delayed() termphrase += simple_tp | (termphrase // join // termphrase) > TermPhrase sentence = termphrase // verbphrase // termphrase & Eos() > Sentence sentence.config.clear().left_memoize() p = sentence.get_match_string() print(p.matcher.tree()) text = 'every boy or some girl and helen and john or pat knows ' \ 'and respects or loves every boy or some girl and pat or ' \ 'john and helen' # text = 'every boy loves helen' count = 0 for _meaning in p(text): count += 1 if count < 3: #print(_meaning[0][0]) pass #print(count) assert count == 392, count
class ExtensionParser(object): """ A class that parses extensions. """ class ExtensionCall(Node): """ An extension call. """ _name = None _args = None _kwargs = None @property def name(self): return self._name[0] if self._name else None @property def args(self): return tuple(self._args) if self._args else tuple() @property def kwargs(self): return dict(self._kwargs) if self._kwargs else {} COMMA = Drop(',') NONE = Literal('None') >> (lambda x: None) BOOL = (Literal('True') | Literal('False')) >> (lambda x: x == 'True') IDENTIFIER = Word(Letter() | '_', Letter() | '_' | Digit()) FLOAT = Real() >> float INTEGER = Integer() >> int STRING = String() | String("'") ITEM = STRING | INTEGER | FLOAT | NONE | BOOL | IDENTIFIER with Separator(~Regexp(r'\s*')): VALUE = Delayed() LIST = Drop('[') & VALUE[:, COMMA] & Drop(']') > list TUPLE = Drop('(') & VALUE[:, COMMA] & Drop(')') > tuple VALUE += LIST | TUPLE | ITEM ARGUMENT = VALUE >> '_args' KWARGUMENT = (IDENTIFIER & Drop('=') & VALUE > tuple) >> '_kwargs' ARGUMENTS = (KWARGUMENT | ARGUMENT)[:, COMMA] NAME = IDENTIFIER > '_name' EXTENSION = ((NAME & Drop('(') & ARGUMENTS & Drop(')')) | NAME) & Eos() > ExtensionCall @property def parser(self): return self.EXTENSION.get_parse_string()
def create_parser(delimiter): space = Space() comma = Drop(',') | Drop(',') + space if delimiter == ',': # by comma seperator = Separator(~Regexp(r'\s*')) delimiter = comma else: assert delimiter == ' ', 'delimiter "%s" not supported' % delimiter seperator = DroppedSpace() delimiter = space none = Literal('None') >> (lambda x: None) bool = (Literal('True') | Literal('False')) >> (lambda x: x == 'True') ident = Word(Letter() | '_', Letter() | '_' | Digit()) float_ = Float() >> float int_ = Integer() >> int str_ = String() | String("'") dict_key = str_ | int_ | float_ | Word() dict_spaces = ~Whitespace()[:] dict_value = dict_key item = str_ | int_ | float_ | none | bool | ident | Word() with seperator: value = Delayed() list_ = Drop('[') & value[:, comma] & Drop(']') > list tuple_ = Drop('(') & value[:, comma] & Drop(')') > tuple dict_el = dict_key & Drop(':') & value > tuple dict_ = Drop('{') & dict_el[1:, Drop(',')] & Drop('}') > dict value += list_ | tuple_ | dict_ | item | space arg = value >> 'arg' karg = (ident & Drop('=') & value > tuple) >> 'karg' expr = (karg | arg)[:, delimiter] & Drop(Eos()) > Node return expr.get_parse()
# # If you wish to allow use of your version of this file only under the # terms of the LGPL License and not to allow others to use your version # of this file under the MPL, indicate your decision by deleting the # provisions above and replace them with the notice and other provisions # required by the LGPL License. If you do not delete the provisions # above, a recipient may use your version of this file under either the # MPL or the LGPL License. ''' Performance related tests. ''' from lepl import Eos from lepl.apps.rfc3696 import _HttpUrl http = _HttpUrl() & Eos() # values are before/after adding IP addresses # value in pares is char count; other values are timing in secs # (89041/115471) 1.17/1.20 #http.config.clear() # (89441/115951) 1.02/1.08 #http.config.clear().direct_eval() # (3089/7835) 0.48/0.53 #http.config.clear().compile_to_nfa() # (3089/7835) 0.45/0.51 #http.config.clear().compile_to_dfa() # (3352/10148) 0.23/0.29
def test_expression2(self): ''' As before, but with evaluation. ''' #basicConfig(level=DEBUG) # we could do evaluation directly in the parser actions. but by # using the nodes instead we allow future expansion into a full # interpreter # pylint: disable-msg=C0111, C0321 class BinaryExpression(Node): op = lambda x, y: None def __float__(self): return self.op(float(self[0]), float(self[1])) class Sum(BinaryExpression): op = add class Difference(BinaryExpression): op = sub class Product(BinaryExpression): op = mul class Ratio(BinaryExpression): op = truediv class Call(Node): funs = {'sin': sin, 'cos': cos} def __float__(self): return self.funs[self[0]](self[1]) # we use unsigned float then handle negative values explicitly; # this lets us handle the ambiguity between subtraction and # negation which requires context (not available to the the lexer) # to resolve correctly. number = Token(UnsignedFloat()) name = Token('[a-z]+') symbol = Token('[^a-zA-Z0-9\\. ]') expr = Delayed() factor = Delayed() float_ = Or(number >> float, ~symbol('-') & number >> (lambda x: -float(x))) open_ = ~symbol('(') close = ~symbol(')') trig = name(Or('sin', 'cos')) call = trig & open_ & expr & close > Call parens = open_ & expr & close value = parens | call | float_ ratio = value & ~symbol('/') & factor > Ratio prod = value & ~symbol('*') & factor > Product factor += prod | ratio | value diff = factor & ~symbol('-') & expr > Difference sum_ = factor & ~symbol('+') & expr > Sum expr += sum_ | diff | factor | value line = expr & Eos() parser = line.get_parse() def myeval(text): return float(parser(text)[0]) self.assertAlmostEqual(myeval('1'), 1) self.assertAlmostEqual(myeval('1 + 2*3'), 7) self.assertAlmostEqual(myeval('1 - 4 / (3 - 1)'), -1) self.assertAlmostEqual(myeval('1 -4 / (3 -1)'), -1) self.assertAlmostEqual(myeval('1 + 2*sin(3+ 4) - 5'), -2.68602680256)
def test_calculation(self): ''' We could do evaluation directly in the parser actions. but by using the nodes instead we allow future expansion into a full interpreter. ''' # pylint: disable-msg=C0111, C0321 class BinaryExpression(Node): op = lambda x, y: None def __float__(self): return self.op(float(self[0]), float(self[1])) class Sum(BinaryExpression): op = add class Difference(BinaryExpression): op = sub class Product(BinaryExpression): op = mul class Ratio(BinaryExpression): op = truediv class Call(Node): funs = {'sin': sin, 'cos': cos} def __float__(self): return self.funs[self[0]](self[1]) # we use unsigned float then handle negative values explicitly; # this lets us handle the ambiguity between subtraction and # negation which requires context (not available to the the lexer) # to resolve correctly. number = Token(UnsignedReal()) name = Token('[a-z]+') symbol = Token('[^a-zA-Z0-9\\. ]') expr = Delayed() factor = Delayed() real_ = Or(number >> float, ~symbol('-') & number >> (lambda x: -float(x))) open_ = ~symbol('(') close = ~symbol(')') trig = name(Or('sin', 'cos')) call = trig & open_ & expr & close > Call parens = open_ & expr & close value = parens | call | real_ ratio = value & ~symbol('/') & factor > Ratio prod = value & ~symbol('*') & factor > Product factor += prod | ratio | value diff = factor & ~symbol('-') & expr > Difference sum_ = factor & ~symbol('+') & expr > Sum expr += sum_ | diff | factor | value line = expr & Eos() parser = line.get_parse() def calculate(text): return float(parser(text)[0]) self.examples([(lambda: calculate('1'), '1.0'), (lambda: calculate('1 + 2*3'), '7.0'), (lambda: calculate('-1 - 4 / (3 - 1)'), '-3.0'), (lambda: calculate('1 -4 / (3 -1)'), '-1.0'), (lambda: str(calculate('1 + 2*sin(3+ 4) - 5'))[:5], '-2.68')])
def target(matcher3=matcher3, matcher4=matcher4): matcher3 += Any()[::'b', ...] & Eos() with Separator(Drop(Any('b')[:])): matcher4 += Any()[::'b', ...] & Eos()