Castle: The best Real-Time/Embedded/HighTech language EVER. Attempt 2
Révision | f0bc39d34f56083b4ca3345023730d8ded559f50 (tree) |
---|---|
l'heure | 2022-01-03 05:43:48 |
Auteur | Albert Mietus < albert AT mietus DOT nl > |
Commiter | Albert Mietus < albert AT mietus DOT nl > |
Added Quantification (former: 'many') suffix as '*','+','?'. --Needed to move the optional part to a sub-rule, to be able to visit it
@@ -61,12 +61,19 @@ | ||
61 | 61 | self.settings = settings |
62 | 62 | |
63 | 63 | |
64 | +class Group(Expression):pass # abstract | |
65 | +class UnorderedGroup(Group):pass # It looks like a Quantity, but is a group | |
64 | 66 | |
65 | -class ManyExpression(Expression): pass # abstract | |
66 | -class Group(Expression):pass | |
67 | + | |
68 | +class Quantity(Expression): # abstract | |
69 | + """An expression with Quantification; like optional, or repetition. The subclasses defines which Quantification""" | |
70 | + def __init__(self, *, expr=None, **kwargs): | |
71 | + super().__init__(**kwargs) | |
72 | + self.expr = expr | |
73 | + | |
67 | 74 | |
68 | 75 | class Sequence(Expression): |
69 | - """A "list of expressions; can be of length=1""" | |
76 | + """A _list_ of expressions; can be of length=1""" | |
70 | 77 | def __init__(self, *, value=None, **kwargs): |
71 | 78 | super().__init__(**kwargs) |
72 | 79 | self.value=value |
@@ -76,12 +83,14 @@ | ||
76 | 83 | return self.value[n] |
77 | 84 | |
78 | 85 | |
79 | -class OrderedChoice(Expression):pass | |
80 | -class Predicate(Expression): pass # abstract | |
86 | +class OrderedChoice(Expression):pass # It a an set of alternatives | |
81 | 87 | |
82 | -class Optional(ManyExpression):pass | |
83 | -class OneOrMore(ManyExpression):pass | |
84 | -class ZeroOrMore(ManyExpression):pass | |
85 | 88 | |
89 | +class Optional(Quantity):pass | |
90 | +class ZeroOrMore(Quantity):pass | |
91 | +class OneOrMore(Quantity):pass | |
92 | + | |
93 | + | |
94 | +class Predicate(Expression): pass # abstract | |
86 | 95 | class AndPredicate(Predicate): pass |
87 | 96 | class NotPredicate(Predicate): pass |
@@ -10,7 +10,7 @@ | ||
10 | 10 | |
11 | 11 | def test_abstracts_2(): |
12 | 12 | assert isinstance(peg.Expression(), peg.NonTerminal) |
13 | - assert isinstance(peg.ManyExpression(), peg.Expression) | |
13 | + assert isinstance(peg.Quantity(), peg.Expression) | |
14 | 14 | assert isinstance(peg.Predicate(), peg.Expression) |
15 | 15 | |
16 | 16 | def test_eof(): |
@@ -21,6 +21,6 @@ | ||
21 | 21 | assert isinstance(peg.NotPredicate(), peg.Predicate) |
22 | 22 | |
23 | 23 | def test_manys(): |
24 | - assert isinstance(peg.Optional(), peg.ManyExpression) | |
25 | - assert isinstance(peg.OneOrMore(), peg.ManyExpression) | |
26 | - assert isinstance(peg.ZeroOrMore(), peg.ManyExpression) | |
24 | + assert isinstance(peg.Optional(), peg.Quantity) | |
25 | + assert isinstance(peg.OneOrMore(), peg.Quantity) | |
26 | + assert isinstance(peg.ZeroOrMore(), peg.Quantity) |
@@ -1,4 +1,5 @@ | ||
1 | 1 | import pytest |
2 | +import logging;logger = logging.getLogger(__name__) | |
2 | 3 | |
3 | 4 | import grammar |
4 | 5 | import arpeggio |
@@ -13,7 +14,7 @@ | ||
13 | 14 | |
14 | 15 | def show_visited(label="VISIT_", node=None, children=None): |
15 | 16 | nl= "\n" if label[-1] != '_' else "" |
16 | - print(f'XXX {label}{nl}{format_node(node)}{format_children(children)}') | |
17 | + logger.info(f'{label}{nl}{format_node(node)}{format_children(children)}') | |
17 | 18 | |
18 | 19 | |
19 | 20 |
@@ -7,8 +7,9 @@ | ||
7 | 7 | def rule(): return rule_name, '<-', expressions, ";" |
8 | 8 | |
9 | 9 | def expressions(): return ( OneOrMore(single_expr), Optional( '|' , expressions ) ) |
10 | -def single_expr(): return ( [ rule_crossref, term, group, predicate ], Optional([ '?' , '*' , '+' , '#' ])) | |
10 | +def single_expr(): return ( [ rule_crossref, term, group, predicate ], expr_quantity) | |
11 | 11 | |
12 | +def expr_quantity(): return Optional([ '?' , '*' , '+' , '#' ]) | |
12 | 13 | def term(): return [ str_term, regex_term ] |
13 | 14 | def group(): return '(', expressions, ')' |
14 | 15 | def predicate(): return ['&','!'], single_expr |
@@ -1,11 +1,13 @@ | ||
1 | 1 | import pytest |
2 | +import logging;logger = logging.getLogger(__name__) | |
3 | + | |
2 | 4 | from grammar import * |
3 | 5 | |
4 | 6 | import arpeggio |
5 | 7 | RE, S = arpeggio.RegExMatch, arpeggio.StrMatch # shortcut |
6 | 8 | |
7 | 9 | def parse_regex(txt, pattern=None): |
8 | - #print(f'\nXXX >>{txt}<<') | |
10 | + logger.debug(f'>>{txt}<<') | |
9 | 11 | parser = ParserPython(regex_term, comment) # DoNot use debug/dot_exporter as it will re-use-same-file |
10 | 12 | tree = parser.parse(txt) |
11 | 13 | assert tree.position_end == len(txt) , f"Not parsed whole input; Only: >>{txt[tree.position: tree.position_end]}<<; Not: >>{txt[tree.position_end:]}<<." |
@@ -1,4 +1,5 @@ | ||
1 | 1 | import pytest |
2 | +import logging;logger = logging.getLogger(__name__) | |
2 | 3 | |
3 | 4 | from grammar import * |
4 | 5 | import arpeggio |
@@ -6,7 +7,7 @@ | ||
6 | 7 | |
7 | 8 | |
8 | 9 | def parse_str(str, pattern=[S, RE, S]): |
9 | - #print(f'\nXXX >>{str}<<') | |
10 | + logger.debug(f'>>{str}<<') | |
10 | 11 | parser = ParserPython(str_term, comment) |
11 | 12 | tree = parser.parse(str) |
12 | 13 | assert tree.position_end == len(str) , f"Not parsed whole input; Only: >>{str[tree.position: tree.position_end]}<<; Not: >>{str[tree.position_end:]}<<." |
@@ -1,6 +1,7 @@ | ||
1 | 1 | import pytest |
2 | +import logging;logger = logging.getLogger(__name__) | |
3 | + | |
2 | 4 | import grammar |
3 | - | |
4 | 5 | import arpeggio |
5 | 6 | |
6 | 7 | R, S, X = grammar.regex_term.__name__, grammar.str_term.__name__, grammar.rule_crossref.__name__ # shortcut in grammar |
@@ -10,7 +11,7 @@ | ||
10 | 11 | def parse_expressions(txt, pattern=None): |
11 | 12 | parser = arpeggio.ParserPython(grammar.expressions) |
12 | 13 | parse_tree = parser.parse(txt) |
13 | - print("\nPARSE-TREE\n" + parse_tree.tree_str()+'\n') | |
14 | + logger.info("\nPARSE-TREE\n" + parse_tree.tree_str()+'\n') | |
14 | 15 | |
15 | 16 | assert parse_tree.position_end == len(txt) , f"Not parsed whole input; Only: >>{txt[parse_tree.position: parse_tree.position_end]}<<; Not: >>{txt[parse_tree.position_end:]}<<." |
16 | 17 | assert parse_tree.rule_name == "expressions" |
@@ -20,7 +21,6 @@ | ||
20 | 21 | return parse_tree |
21 | 22 | |
22 | 23 | def validate_pattern(pt, pattern=None): |
23 | - print('VVV', pt, type(pt)) | |
24 | 24 | assert len(pt) == len(pattern), f"Not correct number-of-element" |
25 | 25 | |
26 | 26 | for p, s in zip(pattern, pt): # E <- S* (| E)? |
@@ -31,14 +31,11 @@ | ||
31 | 31 | assert s[0][0].rule_name == p # S => T => str/regex |
32 | 32 | elif isinstance(p, tuple): # Group: '(' ... ')' |
33 | 33 | assert s[0].rule_name == G |
34 | - print("\nVVV:G\n" + s.tree_str()) | |
35 | - # XXXXX | |
36 | 34 | validate_pattern(s[0][1:-1][0], pattern=p) # G=>E=> |
37 | 35 | elif p == P: |
38 | 36 | assert False, "To Do: Predicate" |
39 | 37 | else: |
40 | 38 | assert False, "To Do: More" |
41 | - print('VVV OKE') | |
42 | 39 | |
43 | 40 | |
44 | 41 | def test_simple_1(): parse_expressions(r"abc", pattern=[X]) |
@@ -1,4 +1,6 @@ | ||
1 | 1 | import pytest |
2 | +import logging;logger = logging.getLogger(__name__) | |
3 | + | |
2 | 4 | import grammar |
3 | 5 | |
4 | 6 | import arpeggio |
@@ -7,7 +9,7 @@ | ||
7 | 9 | def parse_rule(txt, pattern=None): |
8 | 10 | parser = arpeggio.ParserPython(grammar.rule) |
9 | 11 | tree = parser.parse(txt) |
10 | - print(f'\nTREE\n{tree.tree_str()}') | |
12 | + logger.info(f'\nTREE\n{tree.tree_str()}') | |
11 | 13 | |
12 | 14 | assert tree.position_end == len(txt) , f"Not parsed whole input; Only: >>{txt[tree.position: tree.position_end]}<<; Not: >>{txt[tree.position_end:]}<<." |
13 | 15 | assert len(tree) == 4, "A rule should have length=4; ..." |
@@ -4,11 +4,15 @@ | ||
4 | 4 | import sys; sys.path.append("./../AST/") ; sys.path.append("./../../AST/") |
5 | 5 | from castle import peg # has the AST clases |
6 | 6 | |
7 | -def parse(txt, rule): | |
7 | +def parse(txt, rule, | |
8 | + print_tree_debug=False, | |
9 | + visitor_debug=False): | |
8 | 10 | parser = arpeggio.ParserPython(rule) |
9 | 11 | pt = parser.parse(txt) |
12 | + if print_tree_debug: | |
13 | + print('\n'+pt.tree_str()) | |
10 | 14 | assert pt.position_end == len(txt), f"Did not parse all input txt=>>{txt}<<len={len(txt)} ==> parse_tree: >>{pt}<<_end={pt.position_end}" |
11 | - ast = arpeggio.visit_parse_tree(pt, visitor.PegVisitor()) | |
15 | + ast = arpeggio.visit_parse_tree(pt, visitor.PegVisitor(debug=visitor_debug)) | |
12 | 16 | assert ast.position == 0 and ast.position_end == len(txt), f"Also the AST (type={type(ast)}) should include all input" |
13 | 17 | return ast |
14 | 18 |
@@ -18,3 +22,5 @@ | ||
18 | 22 | assert isinstance(id, peg.ID), "The id should be an ID" |
19 | 23 | peg.ID.validate_or_raise(id) # with correct syntax |
20 | 24 | assert id.name == name, err_message if err_message else f"Note correct name, expected {name}" |
25 | + | |
26 | + |
@@ -1,7 +1,7 @@ | ||
1 | 1 | import pytest |
2 | 2 | |
3 | 3 | import grammar |
4 | -from castle import peg # has the AST clases | |
4 | +from castle import peg # has the AST classes | |
5 | 5 | |
6 | 6 | from . import parse |
7 | 7 |
@@ -1,7 +1,7 @@ | ||
1 | 1 | import pytest |
2 | 2 | |
3 | 3 | import grammar |
4 | -from castle import peg # has the AST clases | |
4 | +from castle import peg # has the AST classes | |
5 | 5 | |
6 | 6 | from . import parse, assert_ID |
7 | 7 |
@@ -1,7 +1,7 @@ | ||
1 | 1 | import pytest |
2 | 2 | |
3 | 3 | import grammar |
4 | -from castle import peg # has the AST clases | |
4 | +from castle import peg # has the AST classes | |
5 | 5 | |
6 | 6 | from . import parse, assert_ID |
7 | 7 |
@@ -0,0 +1,35 @@ | ||
1 | +""" Test several optional parts of an expression -- mosty quantity suffixes like '?' '*' and '+' -- also '#' although is different""" | |
2 | + | |
3 | +import pytest | |
4 | +import logging;logger = logging.getLogger(__name__) | |
5 | + | |
6 | +import grammar | |
7 | +from castle import peg # has the AST classes | |
8 | + | |
9 | +from . import parse, assert_ID | |
10 | + | |
11 | +def assert_Quantification(token:str, kind:type(peg.Quantity)): | |
12 | + txt = f"R <- X {token} ;" | |
13 | + | |
14 | + ast = parse(txt, grammar.rule) | |
15 | + assert_ID(ast.name, 'R') | |
16 | + | |
17 | + expr = ast.expr | |
18 | + assert len(expr)==1, "There should be only one expr" | |
19 | + | |
20 | + assert isinstance(expr[0], peg.Quantity), "It should be a (sub of) Quantity .." | |
21 | + assert isinstance(expr[0], kind), f"... namely the specified one: {kind}" | |
22 | + | |
23 | + opt_ex = expr[0].expr | |
24 | + assert_ID(opt_ex, 'X', "should be same a the expr; without the Quantification") | |
25 | + | |
26 | + | |
27 | +def test_Optional(): | |
28 | + assert_Quantification('?', peg.Optional) | |
29 | + | |
30 | +def test_ZeroOrMore(): | |
31 | + assert_Quantification('*', peg.ZeroOrMore) | |
32 | + | |
33 | +def test_OneOrMore(): | |
34 | + assert_Quantification('+', peg.OneOrMore) | |
35 | + |
@@ -3,7 +3,15 @@ | ||
3 | 3 | import sys; sys.path.append("./../AST/") ; sys.path.append("./../../AST/") |
4 | 4 | from castle import peg # has the AST clases |
5 | 5 | |
6 | +import logging;logger = logging.getLogger(__name__) | |
7 | + | |
8 | +class QuantityError(ValueError): pass | |
9 | + | |
6 | 10 | class PegVisitor(arpeggio.PTNodeVisitor): |
11 | + token_2_class = {'?': peg.Optional, | |
12 | + '*': peg.ZeroOrMore, | |
13 | + '+': peg.OneOrMore, | |
14 | + '#': peg.UnorderedGroup} | |
7 | 15 | |
8 | 16 | def visit_str_term(self, node, children): |
9 | 17 | return peg.StrTerm(value=node[1], parse_tree=node) |
@@ -20,15 +28,34 @@ | ||
20 | 28 | def visit_rule(self, node, children): # Name '<-' expressions ';' |
21 | 29 | return peg.Rule(name=children[0],expr=children[1], parse_tree=node) |
22 | 30 | |
31 | + def visit_single_expr(self, node, children): # [ rule_crossref, term, group, predicate ], expr_quantity | |
32 | + if len(children) == 1: # No Optional part | |
33 | + try: | |
34 | + n = children[0].name | |
35 | + except AttributeError: | |
36 | + n = str(children[0]) | |
37 | + logger.warning(f'visit_single_expr==1:: {n}:{type(children[0])}') | |
38 | + return children[0] | |
39 | + elif len(children) == 2: # Optional part | |
40 | + logger.debug(f'visit_single_expr==2:: {children[0]}, {children[1]}') | |
41 | + expr = children[0] | |
42 | + token = str(children[1]) | |
43 | + quantum_cls = self.token_2_class.get(token) | |
44 | + if quantum_cls: | |
45 | + ast=quantum_cls(expr=expr, parse_tree=node) | |
46 | + logger.debug(f'visit_single_expr==2:: {quantum_cls} {expr}') | |
47 | + return ast | |
48 | + else: | |
49 | + raise QuantityError(f"token '{token}' not recognised") | |
50 | + else: | |
51 | + raise NotImplementedError("visit_single_expr, len>2") # XXX -- Is this possible? | |
23 | 52 | |
24 | -# def visit_single_expr(self, node, children): # [ rule_crossref, term, group, predicate ] Optional([ '?' , '*' , '+' , '#' ])) | |
25 | -# if len(children) == 1: # No optional part | |
26 | -# #ast = peg.Sequence(value=children, parse_tree=node) | |
27 | -# return super().visit__default__(node, children) | |
28 | -# else: | |
29 | -# assert NotImplementedError("To Do: visit_single_expr with optional part") # XXX | |
30 | -# return ast | |
53 | + def visit_expr_quantity(self, node, children): # Optional([ '?' , '*' , '+' , '#' ]) | |
54 | + logger.debug(f'visit_expr_quantity [{len(children)}] node={node}:{type(node)}') | |
55 | + return node | |
31 | 56 | |
32 | - def visit_expressions(self, node, children): # OneOrMore(single_expr), Optional( '|' , expressions ) | |
57 | + | |
58 | + def visit_expressions(self, node, children): # OneOrMore(single_expr), Optional( '|' , expressions ) | |
59 | + logger.debug(f'>>{node}<< len={len(children)} children={children}') | |
33 | 60 | return peg.Sequence(value=children, parse_tree=node) |
34 | 61 |