Castle: The best Real-Time/Embedded/HighTech language EVER. Attempt 2
Révision | ddd4d080d1e246c71c9da2b101ada74d9369d230 (tree) |
---|---|
l'heure | 2022-01-04 07:33:12 |
Auteur | Albert Mietus < albert AT mietus DOT nl > |
Commiter | Albert Mietus < albert AT mietus DOT nl > |
Refactored, added some extra test
@@ -1,3 +1,5 @@ | ||
1 | +import logging; logger = logging.getLogger(__name__) | |
2 | + | |
1 | 3 | from ._base import AST_BASE, ID, IDError |
2 | 4 | |
3 | 5 | class PEG (AST_BASE): # abstract |
@@ -6,26 +8,45 @@ | ||
6 | 8 | super().__init__(**kwargs) |
7 | 9 | |
8 | 10 | |
11 | +class MixIn_value_attribute: | |
12 | + """With this MixIn PEG-classes get the ``.value`` property""" | |
13 | + | |
14 | + def __init__(self, *, value=None, **kwargs): | |
15 | + super().__init__(**kwargs) | |
16 | + logger.debug(f'{type(self).__name__}:: value:={value}:{type(value)}') | |
17 | + self._value=value | |
18 | + | |
19 | + @property | |
20 | + def value(self): | |
21 | + logger.debug(f'{type(self).__name__}:: @property={self._value}') | |
22 | + return self._value | |
23 | + | |
24 | + | |
25 | +class MixIn_expr_attribute: | |
26 | + """With this MixIn PEG-classes get the ``.expr`` property""" | |
27 | + | |
28 | + def __init__(self, *, expr=None, **kwargs): | |
29 | + super().__init__(**kwargs) | |
30 | + self._expr = expr | |
31 | + | |
32 | + @property | |
33 | + def expr(self): | |
34 | + return self._expr | |
35 | + | |
9 | 36 | ## |
10 | 37 | ## Note: When using TypeHints with PEG-classes; the clases |
11 | 38 | ## should be defined "above" the typehints uses them |
12 | 39 | ## This defines (largely/partly) the order of classes. |
13 | 40 | ## |
14 | 41 | |
15 | -class Terminal(PEG): # abstract | |
16 | - def __init__(self, *, value=None, **kwargs): | |
17 | - super().__init__(**kwargs) | |
18 | - self.value=value | |
19 | - | |
20 | - | |
21 | -class NonTerminal(PEG): pass # abstract | |
22 | -class Markers(PEG): pass # abstract | |
23 | - | |
42 | +class Terminal(MixIn_value_attribute, PEG): pass # abstract | |
24 | 43 | class StrTerm(Terminal): pass |
25 | 44 | class RegExpTerm(Terminal): pass |
26 | 45 | |
27 | -class EOF(Markers): pass # singleton? | |
46 | +class Markers(PEG): pass # abstract | |
47 | +class EOF(Markers): pass # XXX Todo ## singleton? | |
28 | 48 | |
49 | +class NonTerminal(PEG): pass # abstract | |
29 | 50 | class Expression(NonTerminal): pass # abstract |
30 | 51 | |
31 | 52 |
@@ -42,14 +63,14 @@ | ||
42 | 63 | |
43 | 64 | class Rule(NonTerminal): |
44 | 65 | def __init__(self, *, |
45 | - name: ID, | |
46 | - expr :Expression=None, | |
66 | + name: ID, expr:Expression=None, | |
47 | 67 | **kwargs): |
48 | 68 | ID.validate_or_raise(name) |
49 | 69 | super().__init__(**kwargs) |
50 | 70 | self.name = name |
51 | 71 | self.expr = expr |
52 | - | |
72 | + logger.debug(f'{type(self).__name__}:: expr:={expr}:{type(expr)}') | |
73 | + logger.debug("\t" + "; ".join(f'{c}:{type(c)}' for c in expr)) | |
53 | 74 | |
54 | 75 | class Grammar(NonTerminal): |
55 | 76 | def __init__(self, *, |
@@ -61,29 +82,22 @@ | ||
61 | 82 | self.settings = settings |
62 | 83 | |
63 | 84 | |
64 | -class Group(Expression):pass # abstract -- Do not use for a '(' ...')' group, that's a Sequence!! | |
65 | -class UnorderedGroup(Group): # It looks like a Quantity, but is a group | |
66 | - def __init__(self, *, expr=None, **kwargs): | |
67 | - super().__init__(**kwargs) | |
68 | - self.expr = expr | |
85 | +class Group(Expression): pass # abstract -- Note: Do not Group for '(' ...')'; that's a Sequence!! | |
86 | +class UnorderedGroup(MixIn_expr_attribute, Group): # It looks like a Quantity, but is a group | |
87 | + """See a set (aka "group") of expressions that **all** have to be matched, but the **order** is a don't care. | |
88 | + | |
89 | + Possible an extension of Arpeggio (see: https://textx.github.io/Arpeggio/stable/grammars/), possible a generic one.""" | |
90 | + | |
91 | +class Quantity(MixIn_expr_attribute, Expression): # abstract | |
92 | + """An expression with Quantification; like optional, or repetition. The subclasses defines which Quantification""" | |
69 | 93 | |
70 | 94 | |
71 | -class Quantity(Expression): # abstract | |
72 | - """An expression with Quantification; like optional, or repetition. The subclasses defines which Quantification""" | |
73 | - def __init__(self, *, expr=None, **kwargs): | |
74 | - super().__init__(**kwargs) | |
75 | - self.expr = expr | |
76 | - | |
95 | +class Sequence(MixIn_value_attribute, Expression): | |
96 | + """A _list_ of expressions; can be of length=1""" | |
97 | + # __init__ (see MixIn) sets self._value; assuming it is a list | |
77 | 98 | |
78 | -class Sequence(Expression): | |
79 | - """A _list_ of expressions; can be of length=1""" | |
80 | - def __init__(self, *, value=None, **kwargs): | |
81 | - super().__init__(**kwargs) | |
82 | - self.value=value | |
83 | - def __len__(self): | |
84 | - return len(self.value) | |
85 | - def __getitem__(self, n): | |
86 | - return self.value[n] | |
99 | + def __len__(self): return len(self._value) | |
100 | + def __getitem__(self, n): return self._value[n] | |
87 | 101 | |
88 | 102 | |
89 | 103 | class OrderedChoice(Expression):pass # It a an set of alternatives |
@@ -2,6 +2,7 @@ | ||
2 | 2 | |
3 | 3 | from castle.peg import Rule |
4 | 4 | |
5 | +@pytest.mark.skip("This test is wrong: (1) An ID is not string, (2) a int is not an Expression") | |
5 | 6 | def test_a_ID(): |
6 | 7 | a_name, a_val = 'aName', 42 |
7 | 8 | s=Rule(name=a_name, expr=a_val) |
@@ -0,0 +1,53 @@ | ||
1 | +"""Test that a sequence of expressions is an Expression() | |
2 | + | |
3 | + Note: the value of Expression() is a list-subclass; which is fine. But use it as list!!""" | |
4 | + | |
5 | +import pytest | |
6 | +import logging; logger = logging.getLogger(__name__) | |
7 | + | |
8 | +import grammar | |
9 | +from castle import peg # has the AST classes | |
10 | + | |
11 | +from . import parse, assert_ID | |
12 | + | |
13 | +def test_seq_of_one_as_single_expr(): | |
14 | + txt = "A" | |
15 | + ast = parse(txt, grammar.single_expr) | |
16 | + | |
17 | + assert_ID(ast, 'A'), "An Id as single_expr results in an ID()" | |
18 | + | |
19 | +def test_seq_of_two_is_NOT_a_single_expression(): | |
20 | + txt = "A B" | |
21 | + with pytest.raises(AssertionError): | |
22 | + ast = parse(txt, grammar.single_expr) | |
23 | + | |
24 | + | |
25 | +def test_seq_of_two_as_expressions(): | |
26 | + txt = "A B" | |
27 | + ast = parse(txt, grammar.expressions) | |
28 | + | |
29 | + assert isinstance(ast, peg.Expression), "Two sequence of two expr is an Expression()" | |
30 | + assert isinstance(ast, peg.Sequence), " ... and a Sequence()" | |
31 | + assert len(ast) == 2, " ... of length==2" | |
32 | + | |
33 | + assert isinstance(ast.value, list), "It will be an `arpeggio.SemanticActionResult` which is a subclass of list" | |
34 | + assert_ID(ast[0], 'A'), " ... the first one is ID('A')" | |
35 | + assert_ID(ast[1], 'B'), "... and the 2nd: ID('B')" | |
36 | + | |
37 | + | |
38 | +def test_seq_of_thre_with_quantification(): | |
39 | + txt = "A? B+ C*" | |
40 | + ast = parse(txt, grammar.expressions) | |
41 | + | |
42 | + assert isinstance(ast, peg.Expression), "Two sequence of two expr is an Expression()" | |
43 | + assert isinstance(ast, peg.Sequence), " ... and a Sequence()" | |
44 | + assert len(ast) == 3, " ... of length==3" | |
45 | + | |
46 | + assert isinstance(ast[0], peg.Optional) | |
47 | + assert isinstance(ast[1], peg.OneOrMore) | |
48 | + assert isinstance(ast[2], peg.ZeroOrMore) | |
49 | + | |
50 | + assert_ID(ast[0].expr, 'A'), "The first ID is an 'A'" | |
51 | + assert_ID(ast[1].expr, 'B'), "The 2nd ID is a 'B'" | |
52 | + assert_ID(ast[2].expr, 'C'), "The 3th one is a 'C'" | |
53 | + |
@@ -1,4 +1,5 @@ | ||
1 | 1 | import pytest |
2 | +import logging; logger = logging.getLogger(__name__) | |
2 | 3 | |
3 | 4 | import grammar |
4 | 5 | from castle import peg # has the AST classes |
@@ -17,7 +18,7 @@ | ||
17 | 18 | expr = ast.expr; |
18 | 19 | assert isinstance(expr, peg.Expression), "The expression is an Expression ..." |
19 | 20 | assert isinstance(expr, peg.Sequence), " .. and a Sequence .." |
20 | - assert len(expr) ==1, " .. of length==1" | |
21 | + assert len(expr) == 1, " .. of length==1" | |
21 | 22 | assert_ID(expr[0], txt.split()[2], "The single element of the expression is an ID (the 2nd) -- which name is the 3 part of the txt") |
22 | 23 | |
23 | 24 |
@@ -1,7 +1,7 @@ | ||
1 | 1 | """ Test several optional parts of an expression -- mosty quantity suffixes like '?' '*' and '+' -- also '#' although is different""" |
2 | 2 | |
3 | 3 | import pytest |
4 | -import logging;logger = logging.getLogger(__name__) | |
4 | +import logging; logger = logging.getLogger(__name__) | |
5 | 5 | |
6 | 6 | import grammar |
7 | 7 | from castle import peg # has the AST classes |
@@ -1,5 +1,5 @@ | ||
1 | 1 | import pytest |
2 | -import logging;logger = logging.getLogger(__name__) | |
2 | +import logging; logger = logging.getLogger(__name__) | |
3 | 3 | |
4 | 4 | import grammar |
5 | 5 | from castle import peg # has the AST classes |
@@ -31,19 +31,19 @@ | ||
31 | 31 | def visit_single_expr(self, node, children): # [ rule_crossref, term, group, predicate ], expr_quantity |
32 | 32 | if len(children) == 1: # No Optional part |
33 | 33 | try: |
34 | - n = children[0].name | |
34 | + n = f'name={children[0].name}' | |
35 | 35 | except AttributeError: |
36 | - n = str(children[0]) | |
36 | + n = f'name:{children[0]}' | |
37 | 37 | logger.debug(f'visit_single_expr==1:: {n}:{type(children[0])}') |
38 | 38 | return children[0] |
39 | 39 | elif len(children) == 2: # Optional part |
40 | - logger.debug(f'visit_single_expr==2:: {children[0]}, {children[1]}') | |
40 | + logger.debug(f'visit_single_expr==2::Got: {children[0]}, {children[1]}') | |
41 | 41 | expr = children[0] |
42 | 42 | token = str(children[1]) |
43 | 43 | quantum_cls = self.token_2_class.get(token) |
44 | 44 | if quantum_cls: |
45 | 45 | ast=quantum_cls(expr=expr, parse_tree=node) |
46 | - logger.debug(f'visit_single_expr==2:: {quantum_cls} {expr}') | |
46 | + logger.debug(f'visit_single_expr==2::Pass: {quantum_cls}(expr={expr})') | |
47 | 47 | return ast |
48 | 48 | else: |
49 | 49 | raise QuantityError(f"token '{token}' not recognised") |
@@ -56,6 +56,6 @@ | ||
56 | 56 | |
57 | 57 | |
58 | 58 | def visit_expressions(self, node, children): # OneOrMore(single_expr), Optional( '|' , expressions ) |
59 | - logger.debug(f'>>{node}<< len={len(children)} children={children}') | |
59 | + logger.debug(f'visit_expressions:: >>{node}<< len={len(children)} children={children}:{type(children)}') | |
60 | 60 | return peg.Sequence(value=children, parse_tree=node) |
61 | 61 |