def test_optional(self): parser = ~Char('A') self.assertEqual(parser('X').result, []) self.assertEqual(parser('A').result, ['A']) parser = ~Char('A') + Char('B') self.assertEqual(parser('B').result, [['B']]) self.assertEqual(parser('AB').result, [['A'], ['B']])
def test_seq(self): parser = Seq([Char('A'), Char('B'), Char('C')]) result = parser('ABC').result self.assertEqual(result, [['A'], ['B'], ['C']]) parser = Seq('ABC') result = parser('ABC').result self.assertEqual(result, [['A'], ['B'], ['C']])
def test_any(self): parser = Any([Char('A'), Char('B'), Char('C')]) result = parser('BC').result self.assertEqual(result, ['B']) parser = Any('ABC') result = parser('BC').result self.assertEqual(result, ['B'])
def test_ZeroOrMore(self): parser = ZeroOrMore(Char('A')) self.assertEqual(parser('X').result, []) self.assertEqual(parser('A').result, [['A']]) self.assertEqual(parser('AAAA').result, [['A'], ['A'], ['A'], ['A']]) parser = ZeroOrMore(Any('12345')) self.assertEqual(parser('X').result, []) self.assertEqual(parser('1').result, [['1']]) self.assertEqual(parser('1234').result, [['1'], ['2'], ['3'], ['4']])
def test_OneOrMore(self): parser = OneOrMore(Char('A')) self.assertEqual(parser('X').success, False) self.assertEqual(parser('A').result, [['A']]) self.assertEqual(parser('AAAA').result, [['A'], ['A'], ['A'], ['A']]) parser = OneOrMore(Any('12345')) self.assertEqual(parser('X').success, False) self.assertEqual(parser('1').result, [['1']]) self.assertEqual(parser('1234').result, [['1'], ['2'], ['3'], ['4']])
def exp_parser(): number = number_parser() # exponent -> ( exp ) | NUMBER exponent = Between(Char('('), Ref('exp'), Char(')')) | number # a reference to 'exp' parser is used since its not defined yet # factor -> exponent factor' # factor' -> ^ exponent factor' | NULL factor = (exponent + ZeroOrMore(Char('^') + exponent)).map(eval_exp) # term -> factor term' # term' -> * factor term' | / factor term' | NULL term = (factor + ZeroOrMore(Any('*/') + factor)).map(eval_exp) # exp -> term exp' # exp' -> + term exp' | - term exp' | NULL exp = (term + ZeroOrMore(Any('+-') + term)).map(eval_exp) # linking reference to actual parser Ref.link({'exp': exp}) return StripWhitespace() + exp + End()
def number_parser(): # based on JSON's specs for number # digit: any char from 0 - 9 digit = Any('0123456789') # non zero numbers: digits without a leading 0 non_zero = Any('123456789') + ZeroOrMore(digit) # int: optional minus sign, followed by a 0 OR non zero numbers integer = ~Char('-') + (Char('0') | non_zero) # decimal: point followed by digits decimal = Char('.') + OneOrMore(digit) # exponent: e or E with a optional sign followed by digits exp = Seq([Any('eE'), ~Any('+-'), OneOrMore(digit)]) # all combinations of integer, decimal and exponent number = ((integer + decimal + exp) | (integer + decimal) | (integer + exp) | integer).map(lambda result: float(''.join(x[0] for x in result))) # ^ map the result list to float return number
def json_parser(): # NULL, TRUE, FALSE parser null = Seq('null').map(lambda r: None) true = Seq('true').map(lambda r: True) false = Seq('false').map(lambda r: False) # NUMBER and STRING parser number = number_parser() # json string: QUOTE string QUOTE string = Between(Char('"'), string_parser(), Char('"')) # value -> STRING | NUMBER | TRUE | FALSE | NULL | list | object value = Any([string, number, null, true, false, Ref('lst'), Ref('obj')]) # references to 'lst' and 'obj' are use since they are not defined yet # pair -> STRING COLON value to_dict = lambda result: {result[0][0]: result[2][0]} pair = (string + Char(':') + value).map(to_dict) # pairs_list -> pair COMMA pairs_list | pair pairs_list = (pair + ZeroOrMore(Char(',') + pair)).map(concat_dicts) # object -> OPEN_CURLYBRACE pairs_list CLOSE_CURLYBRACE obj = Between(Char('{'), pairs_list, Char('}')) # values_list -> value COMMA values_list | value values_list = (value + ZeroOrMore(Char(',') + value)).map(concat_lists) # list -> OPEN_SQBRACE values_list CLOSE_BRACE lst = Between(Char('['), values_list, Char(']')) # linking references to actual parsers Ref.link({'lst': lst, 'obj': obj}) # json -> value parser = StripWhitespace() + value + End() parser.map(lambda result: result[0][0]) return parser
def test_between(self): parser = Between(Char('A'), Char('B'), Char('C')) self.assertEqual(parser('ABC').result, ['B']) parser = Between(Char('A'), (Char('B') + Char('C')), Char('D')) self.assertEqual(parser('ABCD').result, [['B'], ['C']])
def test_ref(self): parser = Ref('A_and_B') + Char('C') A_and_B = Char('A') + Char('B') Ref.link({'A_and_B': A_and_B}) self.assertEqual(parser('ABC').result, [['A'], ['B'], ['C']])
def test_and_multiple(self): parser = Char('A') + Char('B') + Char('C') + Char('D') + Char('E') result = parser('ABCDE').result self.assertEqual(result, [['A'], ['B'], ['C'], ['D'], ['E']])
def test_and(self): parser = Char('A') + Char('B') result = parser('ABC').result self.assertEqual(result, [['A'], ['B']])
def test_single_char(self): parse_A = Char('A') self.assertEqual(parse_A('ABC').result, ['A']) self.assertEqual(parse_A('DBC').success, False)
def test_and_complex(self): parser_AB = Char('A') + Char('B') parser_CD = Char('C') + Char('D') parser = parser_AB + parser_CD result = parser('ABCDE').result self.assertEqual(result, [['A'], ['B'], ['C'], ['D']])
def test_or(self): parser = Char('A') | Char('B') result = parser('ABC').result self.assertEqual(result, ['A'])
def test_end(self): parser = Char('A') + End() self.assertEqual(parser('A').result, [['A']]) self.assertEqual(parser('ABCD').success, False)
def test_or_multiple(self): parser = Char('A') | Char('B') | Char('C') | Char('D') | Char('E') result = parser('CDE').result self.assertEqual(result, ['C'])