def test_004__n1_eq_t1__with_eq(self): if False: from prettyprinter import cpprint as pp import p print(f": test_004__n1_eq_t1__with_eq") print(f": n1 :") pp(self.n1) print(f": t1 :") pp(self.t1) NonTerminal_enable_structural_eq() with self.assertRaises(AssertionError) as context: assert self.n1 == self.t1 # now it fails self.assertTrue('Internal error, AssertionError not raised !!!') assert self.n1 != self.t1 assert self.n1 == deepcopy(self.n1) t2 = Terminal(self.dot, 0, 'one') n2 = NonTerminal(self.dot, [t2]) assert self.n1 == n2 t2 = Terminal(self.dot, 0, 'one') n2 = NonTerminal(self.dot, [t2]) assert self.n1 == n2 bang = StrMatch('!', rule_name='bang') t3 = Terminal(bang, 0, 'one') n3 = NonTerminal(bang, [t3]) assert self.n1 != n3
def setUp(self): self.dot = StrMatch('.', rule_name='self.dot') self.s1 = 's1 : string' self.s2 = 's2 : string' self.s3 = 's3 : string' # rule, position, value self.t1 = Terminal(self.dot, 0, 'one') self.t2 = Terminal(self.dot, 0, 'two') self.t3 = Terminal(self.dot, 0, 'three') assert not isinstance(self.t1, list) assert not isinstance(self.t2, list) assert not isinstance(self.t3, list) # rule, value : a list where the first element is a node # self.n1 = NonTerminal(self.dot, self.t1) # TypeError: 'Terminal' object is not subscriptable self.n2 = NonTerminal(self.dot, [self.t1]) self.n3 = NonTerminal(self.dot, self.n2) self.n4 = NonTerminal(self.dot, [self.n2]) assert isinstance(self.n2, list) assert isinstance(self.n3, list) assert isinstance(self.n4, list) self.v0 = self.n2 self.v1 = [self.s1, self.s2] self.v2 = self.t1 self.v3s = self.s3 self.v3t = (self.s1, self.s2)
def as_lines(self, node, children, depth=0): if len(children): # print(f": as_lines() : {node.rule_name} : collecting {len(children)} children" # f": {repr(children)}") value = '\n'.join([c.value for c in children]) else: # print(f": as_lines() : {node.rule_name} : single value '{node.value}'") value = node.value return Terminal(StrMatch('', node.rule_name), 0, value)
def str_match_SA(parser, node, children): to_match = children[0] # Support for autokwd metamodel param. if parser.metamodel.autokwd: match = parser.keyword_regex.match(to_match) if match and match.span() == (0, len(to_match)): regex_match = RegExMatch(r'{}\b'.format(to_match), ignore_case=parser.metamodel.ignore_case, str_repr=to_match) regex_match.compile() return regex_match return StrMatch(to_match, ignore_case=parser.metamodel.ignore_case)
def visit_str_match(self, node, children): match_str = node.value[1:-1] # Scan the string literal, and sequentially match those escape # sequences which are syntactically valid Python. Attempt to convert # those, raising ``GrammarError`` for any semantically invalid ones. def decode_escape(match): try: return codecs.decode(match.group(0), "unicode_escape") except UnicodeDecodeError: raise GrammarError("Invalid escape sequence '%s'." % match.group(0)) match_str = PEG_ESCAPE_SEQUENCES_RE.sub(decode_escape, match_str) return StrMatch(match_str, ignore_case=self.ignore_case)
def visit_str_match(self, node, children): try: to_match = children[0][1:-1] except IndexError: to_match = '' # Support for autokwd metamodel param. if self.metamodel.autokwd: match = self.keyword_regex.match(to_match) if match and match.span() == (0, len(to_match)): regex_match = RegExMatch( r'{}\b'.format(to_match), ignore_case=self.metamodel.ignore_case, str_repr=to_match) regex_match.compile() return regex_match return StrMatch(to_match, ignore_case=self.metamodel.ignore_case)
def empty_string_is_accepted(name_prefix, rule_f, ch): """Add test that empty string is accepted """ def create_method(rule): def the_method(self): nonlocal rule parsed = ParserPython(rule, skipws=False).parse('') return the_method rule = rule_f() if isinstance(rule, str): rule = StrMatch(ch, rule_f.__name__) test_name = "empty_string_is_accepted" method_name = f"test_{name_prefix}_rule_{rule.rule_name}_{test_name}" method = create_method(rule) setattr(Test_Common, method_name, method)
def empty_string_raises_NoMatch(name_prefix, rule_f, ch): """Add test that empty string does not match """ def create_method(rule): def the_method(self): nonlocal rule with self.assertRaises(NoMatch): parsed = ParserPython(rule, skipws=False).parse('') return the_method rule = rule_f() if isinstance(rule, str): rule = StrMatch(ch, rule_f.__name__) test_name = "empty_string_raises_NoMatch" method_name = f"test_{name_prefix}_rule_{rule.rule_name}_{test_name}" method = create_method(rule) setattr(Test_Common, method_name, method)
def visit_repeatable(self, node, children, depth=0): if False and str(children) in [ "[command 'turn' [0], command 'rise' [0]]", "[command 'move' [0]]" ]: print(f": repeatable : {node.name} : {children}") pp(NonTerminal(node.rule, children)) print('') n_children = len(children) assert n_children in [1, 2] term = children[0] if n_children == 1: return term assert children[1] == self.REPEATING return NonTerminal(StrMatch(':repeating:', 'repeating'), [term])
def grammar(): return UnorderedGroup("a", "b", "c", sep=StrMatch(",")), EOF
def r_literal(rule_f, s): rule = rule_f() if isinstance(rule, str): return StrMatch(s, rule_name=rule_f.__name__) return rule
def setUp(self): NonTerminal_restore_original_eq() self.dot = StrMatch('.', rule_name='dot') self.t1 = Terminal(self.dot, 0, 'one') self.n1 = NonTerminal(self.dot, [self.t1])
def or_more_from_charset(rule_f, charset, suffix, levels, empty_ok=False): for ch in charset: s = ch + suffix string_test('2_class', rule_f, s, empty_ok=empty_ok) if levels > 1: or_more_from_charset(rule_f, charset, s, levels - 1, empty_ok) #------------------------------------------------------------------------------ if tst_individual_characters: for name, ch in common.CHARACTER_NAME_TO_CHAR.items(): (ch_hexes, ch_names) = character_lookup(ch) rule_f = lambda: StrMatch( ch, rule_name=f"common_character_{ch_hexes[0]}_{ch_names[0]}") string_test("1_individual", rule_f, ch) if tst_debug_first_char: break if tst_whitespace_chars: for ch in common.WHITESPACE_CHARS: (ch_hexes, ch_names) = character_lookup(ch) rule_f = lambda: StrMatch( ch, rule_name=f"whitespace_character_{ch_hexes[0]}_{ch_names[0]}") string_test("1_individual", rule_f, ch, ch_names) if tst_debug_first_char: break #------------------------------------------------------------------------------
class DocOptSimplifyVisitor_Pass2(object): classes = \ ( ' divulge_list ' ' divulge_single ' ' divulge_terminal ' ' text ' ' empty ' ) . split() UNUSED_unwrap = \ ( ' other_sections other ' ' required ' # explicit no differnet than explicit # ' expression ' # NO, determinant for sides of implicit choice ' term argument ' ' usage_line ' # NOT: usage or usage_pattern ' option short long long_with_eq_all_caps long_with_eq_angle ' ' option_line option_list option_single ' ' operand_line ' # operand_section ' # ' short_no_arg short_stacked ' -- necessary for semantics # ' long_no_arg long_with_eq_arg ' -- necessary for semantics ) # A 'list' being a NonTerminal with one or more children _divulge_list = \ ( ' other_sections ' ' required ' ' term ' # NOT why working on option.list # ' list ' # !o # ' option_list ' ' option_list_comma ' ' option_list_bar ' ' option_list_space ' ) # A 'single' being a NonTerminal with only one child _divulge_single = \ ( ' other ' ' usage_line ' # NOT usage or usage_pattern ' argument ' # !o # ' option ' ' short ' ' long ' ' option_line option_single ' # ' long_with_eq_arg ' ) # A 'terminal' being, obviously, a Terminal node, contents in node.value _divulge_terminal = \ ( ' short_no_arg_ ' ' ' # TERMINALS: short_no_arg short_stacked long_with_eq_all_caps long_with_eq_angle ) _text = \ ( ' intro ' ' operand_help ' ' option_help ' ' short_no_arg ' ' long_no_arg ' ' operand_no_space ' # ' operand_intro operand_help ' # ' option_intro option_help ' ) _empty = \ ( ' intro_line ' ' usage_entry ' ' operand_all_caps operand_angle ' ' EQ LPAREN RPAREN LBRACKET RBRACKET OR COMMA EOF blankline newline ' ) # Used to make BAR directly searchable within the choice list BAR = Terminal(StrMatch('|', rule_name='BAR'), 0, '|') # '--long= <argument>' => '--long' '=' '<argument>' # FIXME: revamp grammar to explicitly handle all whitespace, see issues.txt EQ = Terminal(StrMatch('=', rule_name='repeated'), 0, '=') #-------------------------------------------------------------------------- def __init__(self, *args, **kwargs): dprint.debug = False dprint(": pass 2 : init : ENTER") super().__init__(*args, **kwargs) # 'unwrap' -- did way too much, break into judiciously defined # groupings that can be handled easily and explicitly. for _class in self.classes: dprint(f" : handler '{_class}'") method = getattr(self, _class) for rule_name in getattr(self, f"_{_class}").split(): alias = f"visit_{rule_name}" dprint(f" - rule '{rule_name}'") setattr(self, alias, method) dprint(": pass 2 : init : LEAVE") #-------------------------------------------------------------------------- def visit(self, node, depth=0, path=[]): i = ' ' * 3 * depth dprint('') dprint(f"{i} : visit : {node.name} -- START") if not isinstance(node, (NonTerminal, Terminal, SemanticActionResults)): dprint(f"{i} ** Invalid type '{str(type(node))}'") dprint(f"{i} => {_res(node)}") dprint(f"{i} : visit : {node.name} -- DONE") return node #---------------------------------------------------------------------- dprint('') dprint(f"{i} Process Children -- START") dprint(f"{i} # essentially, thus :") dprint(f"{i} children = []") dprint(f"{i} for child in node :") dprint(f"{i} response = visit(child)") dprint(f"{i} if response is not None :") dprint( f"{i} children.append(response) # generally reformed") dprint('') children = [] if isinstance(node, (NonTerminal, SemanticActionResults)): # these object types are lists for child in node: # NonTerminal IS the list #print(f"{i} Process Children -- START") if hasattr(child, 'name'): dprint(f"{i} - '{child.name}'") else: if hasattr(child, '__name__'): dprint(f"{i} - '{child.__name__}'") else: dprint( f"{i} - id = {id(child)} : {str(type(child))}") response = self.visit(child, depth=1 + depth, path=path + [node.name]) dprint(f"{i} - '{child.name}'") dprint(f"{i} : response = {_res(response)}") if response is not None: value = unwrap(response) dprint(f"{i} : unwrapped = {_res(value)}") children.append(value) dprint('') dprint(f"[ children : final ]\n{_res(children)}") dprint(f"{i} Process Children -- Done\n") dprint('') #---------------------------------------------------------------------- # In extreme circumstances, rule_name may be list. Note, that # such probably means unwrapping has gone too far and your node # is merely an empty list. rule_name = str(node.rule_name) # print(f": visit : {rule_name}") method = f"visit_{rule_name}" if hasattr(self, method): dprint(f"\n*** VISIT_{node.name} -- START") out = getattr(self, method)(node, children) dprint(f" => {_res(out)}\n") dprint("*** VISIT_{node.name} -- DONE\n") dprint('') return out if isinstance(node, Terminal): dprint( f"{i} Terminal without a visit method. Return unchanged.") dprint(f"{i} => {_res(node)}") dprint(f"{i} : visit : {node.name} -- DONE") dprint('') return node if len(children) > 0: if type(children) is list and len(children) == 1: if type(children[0]) is list: dprint( f": visit : {node.name} : list w/ single child, divulge" ) children = children[0] if isinstance(children, (list, NonTerminal)): which = None if isinstance(children[0], ParseTreeNode): dprint( f": visit : {node.name} : list w/ children => NonTerminal" ) out = NonTerminal(node.rule, children) verb = 'is' # # *NO* : it strips rule info which we need. # was : # out = NonTerminal(node.rule, wrap(None)) # del out[0] # out.extend(children) # else: out = NonTerminal(node.rule, wrap(children)) verb = 'is not' dprint('') dprint(f"{i} : list or NonTerminal and [0] {verb} a node") dprint(f"{i} => {_res(out)}") dprint(f"{i} : visit : {node.name} -- DONE") dprint('') return out internal_error( context, node, "Has children but neither a list nor " "ParseTreeNode. Nothing left to try. ") raise ValueError(f"Visiting {node.name}, nothing left to try.") # - node can't be a terminal node (as they bailed earlier). # - node can't be the result of a visit_* method (bailed earlier). # # - Academically, we should crash. Let's continue in Battle Mode # and wrap it in a Terminal -- complaining first. with redirect_stdout(sys.stderr): print( f"INTERNAL ERROR : Unhandled configuration for children of a NonTerminal" ) print('') print(f" path : ", end='') _path = path + [node.name] prefix = '' for idx in range(len(_path)): i = ' ' * 3 * idx print(f"{prefix}{i}{_path[idx]}") prefix = ' ' * 10 print('') print(f" node = {node.name} : depth {depth}") seq = isinstance(children, Sequence) seq_text = ': is a sequence' if seq else '' print(f" children type = {str(type(children))} {seq_text}") if seq: print(f" children[0] type = {str(type(children[0]))}") print(": children =") pp(children) print(f"Please report this scenario to the maintainer.") out = Terminal(node.rule, 0, wrap(children)) dprint(f": visit : {node.name} => {_res(out)}") dprint('') return out #-------------------------------------------------------------------------- def empty(self, node, children): return None #-------------------------------------------------------------------------- # A 'terminal' being, obviously, a Terminal node, contents in node.value def divulge_terminal(self, node, children): """ Dispense with an unneeded Terminal enclosing a value. Sanity checks may result in a automatci divulge upgrade (i.e. if it is not actually a Terminal node (but not quietly). """ context = 'divulge_terminal' dprint(f": {context} : {node.name} : value = {node.value}") if False: # Academic Research Mode - every step must be perfect assert isinstance(node, Terminal), \ internal_error(context, node, "is not a Terminal node") assert len(children) == 0, \ internal_error(context, node, "Is Terminal with children ?") else: # Battle Mode -- keep going at all cost ! With some complaints ... upgrade = False if not isinstance(node, Terminal): internal_error(context, node, "Is not a Terminal node.") upgrade = True else: n = len(children) if n > 0: internal_error(context, node, f"How does a Terminal have {n} children ?") upgrade = True if upgrade: return self.divulge_single(node, children) dprint(f": {context} : '{node.name}' => {_res(node.value)}") # Intentially breaks the Parse Tree structure so that the parent or # other ancestor may trivially gather it's components. Said gatherer # must have a visitor method of course. text() is use to gather # text fragments into return node.value #-------------------------------------------------------------------------- def divulge_NonTerminal(self, context: str, node: ParseTreeNode, children: list, only_child: bool): """Sanity checks for a NonTerminal being 'divulged'. Automatically upgrades or downgrades the divulge action as necessary (with complaints). """ # Battle Mode -- keep going at all costs -- with noisy complaints for improvement ... if not isinstance(node, NonTerminal): internal_error( context, node, "Is not a NonTerminal node. Perhaps it is a list ?") if not isinstance(node, list): internal_error(context, node, "Also not list.") if isinstance(node, Terminal): internal_error( context, node, "It is a Terminal. Downgrading automatically.") return self.divulge_terminal(node, children) if only_child and len(children) > 1: internal_error(context, node, f"Too many children. Upgrading automatically.") return self.divulge_list(node, children) if len(children) <= 0: internal_error( context, node, f"How does a NonTerminal have ZERO children ? " "Let's try handling it like a Terminal.") w = wrap(node.value) dprint(f": {context} : '{node.name}' => {_res(w)}") return w return None #-------------------------------------------------------------------------- # A 'single' being a NonTerminal with only one child, a ParseTreeNode def divulge_single(self, node, children): """ Dispense with an unneeded NonTerminal enclosing a single node.""" context = 'divulge_single' out = self.divulge_NonTerminal(context, node, children, only_child=True) if out is not None: return out single = children[0] if isinstance(single, ParseTreeNode): internal_error( context, node, f"And it's single child is not ParseTreeNode. " "We'll simply let that slide.") w = wrap(single) dprint(f": divulge single '{node.name}' => {_res(w)}") return w #-------------------------------------------------------------------------- def divulge_list(self, node, children): """ Dispense with an unneeded NonTerminal enclosing a 'list' of one or more nodes. Though, if it can only ever have one child, divulge_single() would be more appropriate. """ context = 'divulge_list' out = self.divulge_NonTerminal(context, node, children, only_child=False) if out is not None: return out if isinstance(children[0], ParseTreeNode): internal_error( context, node, f"And it's first child is not ParseTreeNode. " "We'll simply let that slide.") w = wrap(children) dprint(f": {context} : '{node.name}' => {_res(w)}") return w #-------------------------------------------------------------------------- # operand name is all that is relevant (i.e. FILE or <src>) def visit_operand(self, node, children): return Terminal(node.rule, 0, node.value) #-------------------------------------------------------------------------- # make BAR directly searchable within the choice list def visit_BAR(self, node, children): return self.BAR def visit_choice(self, node, children): # *** Need to maintain valid Parse Tree, up one level ? # *** >>> return sentinel indicating that visit should unwrap as # *** children of choice's parent. # Eliminate fake choice, now must look into expressions # # XXX Test case 12 : 'Usage: copy move\ncopy ( move )' # XXX output : error/name/lost-choice-and-expressions # XXX # XXX Error manifests without this enabled. # # This isn't the cause, it simply magnifies the error. # # Error is caused by the puzzling loss of the 'usage_pattern' enclosure # >>> unwrap() was doing too much unwrapping # if True: if (len(children) == 1 and isinstance(children[0], NonTerminal) and children[0].rule_name == 'expression' and self.BAR not in children[0]): return Unwrap(children[0]) # Elimnate unnecessary expression wrapper when it has a single child # # *** If must be FALSE, document why and which test case # # >>> 'expression' context needful to discriminate between choice factors # if True: # DO NOT ENABLE THIS for now for idx in range(len(children)): child = children[idx] if (isinstance(child, NonTerminal) and child.rule_name == 'expression' and len(child) == 1): children[idx] = child[0] # Unchain cascading choices -- only necessary if recursive # i.e. choice = expression ( BAR choice ) # # NO LONGER NEEDED: choice = expression ( BAR expression )* # if False and (isinstance(children, list) and len(children) == 3 and isinstance(children[-1], list) and children[-1][0]): additional = children[-1] del children[-1] children.extend(additional) return NonTerminal(node.rule, children) #-------------------------------------------------------------------------- #example #EXAMPLE def visit_Non_Terminal(self, node, children): return NonTerminal(node.rule, children) #-------------------------------------------------------------------------- def visit_options_intro(self, node, children): # print(f": visit_options_intro : {node.name}") # pp(children) return Terminal(node.rule, 0, '\n'.join(children)) def visit_operand_intro(self, node, children): # print(f": visit_operand_intro : {node.name}") # pp(children) return Terminal(node.rule, 0, '\n'.join(children)) def _visit_operand_help(self, node, children): while isinstance(children[-1], list): tmp = children[-1] children = children[:-1] children.extend(tmp) return Terminal(node.rule, 0, '\n'.join(children)) def _visit_option_help(self, node, children): print(f": visit_option_help : {node.name}") pp(children) while isinstance(children[-1], list): additional = children[-1] children = children[:-1] children.extend(additional) if isinstance(children[-1], Terminal): children[-1] = children[-1].value return Terminal(node.rule, 0, '\n'.join(children)) #-------------------------------------------------------------------------- def text(self, node, children): # print(f": text : {node.rule_name} : len = {len(children)}") # example: option_help = words ( newline !option_single option_help )* if isinstance(children[-1], Terminal): children[-1] = children[-1].value else: # example: intro = newline* !usage_entry line+ newline while isinstance(children[-1], list): additional = children[-1] children = children[:-1] children.extend(additional) return Terminal(node.rule, 0, '\n'.join(children)) #-------------------------------------------------------------------------- def visit_description(self, node, children): return Terminal(node.rule, 0, '\n'.join(children)) def visit_line(self, node, children): assert len(children) == 1 return children[0] def visit_words(self, node, children): return ' '.join(children) def visit_word(self, node, children): return node.value #-------------------------------------------------------------------------- visit_trailing = text def visit_trailing_line(self, node, children): assert len(children) == 1 return children[0] def visit_trailing_strings(self, node, children): return ' '.join(children) def visit_string_no_whitespace(self, node, children): return node.value
contents=list(value), ) # @register_pretty(Unwrap) def _pretty_Unwrap(value, ctx): return pretty_call( ctx, Unwrap, value=value.value, ) import p #------------------------------------------------------------------------------ x = Terminal(StrMatch('.', 'x'), 0, 'value:x') y = Terminal(StrMatch('.', 'y'), 0, 'value:y') v = NonTerminal(StrMatch('.', 'x_y'), [x, y]) t = NonTerminal(StrMatch('.', 'v_v'), [v, v]) pp(v) pp(t, indent=2) #------------------------------------------------------------------------------
class DocOptSimplifyVisitor_Pass1(object): rule_groups = ' empty as_value as_lines '.split() _empty = (' _ EOF blankline COMMA LBRACKET RBRACKET LPAREN RPAREN EOF ' ' newline ') _as_value = (' string_no_whitespace word words line ' ' short_no_arg_ ' ' short_stacked ') _as_lines = (' description ' ' ') #-------------------------------------------------------------------------- def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for group in self.rule_groups: if not hasattr(self, group): raise ValueError( f"INTERNAL ERROR, rule group '{group}' method " "missing. Please contact the maintainer.") if group not in kwargs: rules = getattr(self, f"_{group}").split() else: rules = kwargs[group] rules = rules.split() if isinstance(rules, str) else rules if not isinstance(rules, list): raise ValueError(f"Invalid type for rule group argument " f"'{group}'. Expected string or list, " f"found {str(type(rules))}") for rule_name in rules: setattr(self, f"visit_{rule_name}", getattr(self, group)) #-------------------------------------------------------------------------- def visit(self, node, depth=0): i = ' ' * 3 * depth dprint(f"{i} [ p2 : node = {node}") if not hasattr(node, 'rule_name'): dprint(f"{i} - not a known container : {str(type(node))}") dprint(f"{i} => itself") dprint(f"{i} ]") if isinstance(node, list) and len(node) == 1: return node[0] return node #---------------------------------------------------------------------- children = [] if isinstance(node, (NonTerminal, SemanticActionResults)): dprint( f"{i} - visiting children of '{node.name}' : len = {len(node)}" ) # each of these object types is a list for child in node: response = self.visit(child, 1 + depth) if response: if not isinstance(response, NonTerminal): children.append(response) continue dprint(f": repeatable : {node.name} : response =") # pp(response) if (response.rule_name == 'repeating' and len(children) > 0 and response[0] == children[-1]): dprint(f": repeatable : {node.name} : children =") # pp(children) children[-1] = response else: children.append(response) dprint(f"{i} - visited children = {children}") #---------------------------------------------------------------------- # rule name specific visitor ? rule_name = str(node.rule_name) method = f"visit_{rule_name}" dprint(f"{i} - {method} ?") if hasattr(self, method): dprint(f"{i} - method found, applying to {node.name}") out = getattr(self, method)(node, children, 1 + depth) dprint(f"{i} => {_res(out,i)}") dprint(f"{i} ]") return out else: dprint(f"{i} - no such method") #---------------------------------------------------------------------- if len(children) <= 0: out = Terminal(node.rule, 0, node.value) dprint(f"{i} => {_res(out,i)}") dprint(f"{i} ]") return out # if len(children) == 1: # children = children[0] # can break the Parse Tree out = NonTerminal(node.rule, children) if False: try: out = NonTerminal(node.rule, children) except: # automatically unwrap children[0] = Unwrap(children[0]) out = NonTerminal(node.rule, children) out[0] = out[0].value # FIXME: this breaks the parse tree dprint(f"{i} => {_res(out,i)}") dprint(f"{i} ]") return out #------------------------------------------------------------------------------ def empty(self, node, children, depth=0): return None #-------------------------------------------------------------------------- # Gather composition elements into a terminal # (i.e. a line from a sequence of words) def as_value(self, node, children, depth=0): if len(children): # print(f"\n: as_value() : {node.rule_name} : collecting {len(children)} children" # f": {repr(children)}") value = ' '.join([c.value for c in children]) else: # print(f"\n: as_value() : {node.rule_name} : single value '{node.value}'") value = node.value return Terminal(StrMatch('', node.rule_name), 0, value) #-------------------------------------------------------------------------- # Gather composition elements into a terminal with line breaks def as_lines(self, node, children, depth=0): if len(children): # print(f": as_lines() : {node.rule_name} : collecting {len(children)} children" # f": {repr(children)}") value = '\n'.join([c.value for c in children]) else: # print(f": as_lines() : {node.rule_name} : single value '{node.value}'") value = node.value return Terminal(StrMatch('', node.rule_name), 0, value) #-------------------------------------------------------------------------- REPEATING = Terminal(StrMatch('...', rule_name='repeating'), 0, '...') # make REPEATED directly searchable within expressions def visit_repeated(self, node, children, depth=0): return self.REPEATED #-------------------------------------------------------------------------- # Repeatable is in Pass1 because in Pass2, the assertions fail due to unwrapping. # # Collapse and encapsulate repeated terms # i.e. expression ( <other> <foo> <foo> '...' )expression ( <other> <foo> <foo> '...' ) # => expression ( <other> repeated(<foo>) ) # # Or improve the grammar: repeatable = term repeating? # def visit_repeatable(self, node, children, depth=0): if False and str(children) in [ "[command 'turn' [0], command 'rise' [0]]", "[command 'move' [0]]" ]: print(f": repeatable : {node.name} : {children}") pp(NonTerminal(node.rule, children)) print('') n_children = len(children) assert n_children in [1, 2] term = children[0] if n_children == 1: return term assert children[1] == self.REPEATING return NonTerminal(StrMatch(':repeating:', 'repeating'), [term])
def string_test(name_prefix, rule_f, s, ch_names=None, empty_ok=False): """Generate test methods whether <rule_f> matches <s> in these scenarios : - by itself, <s> is the entire text string to be parsed. - at start of the parsed text, followed by a phrase - in the middle between twp phrases - at the end of the parsed text, preceeded by a phrase - scattered a various points about a paragraph, at least once two <s>'s directly next to each other. - Test whether or not the specified rule matches an empty string : If empty_ok is False (DEFAULT), a NoMatch exception is expected. If empty_ok is True, a successful match is expected. Each scenario generates an individual test. The test names include the characters being tested against. In each scenario, the 'text' is ensured to not include any characters present in <s>. """ def create_method(name, rule_f, rule, s, grammar, text, expect): grammar_obj = grammar def the_method(self): nonlocal name, rule_f, rule, s, grammar_obj, text, expect def grammar(): grammar_obj.rule_name = 'grammar' return grammar_obj self.verify_grammar(grammar, text, expect, skipws=False) return the_method #-------------------------------------------------------------------------- assert len(s) > 0, "Zero length 's' invalid for string_test()" for ch in s: assert ch not in INUSE_CHARACTERS, \ ( f"argument s '{s}', contains '{ch}' character whuch is a " f"member of INUSE_CHARACTERS, test code must be reconfigured " f"in order to handle this character." ) if not tst_debug_first_scenario: if empty_ok: empty_string_is_accepted(name_prefix, rule_f, ch) else: empty_string_raises_NoMatch(name_prefix, rule_f, ch) rule = rule_f() if isinstance(rule, str): # print(f"- rule '{rule_f.__name__}' : create StrMatch() rule for " # f"literal '{ch}' : {ord(ch)} : {hex(ord(ch))}") rule = StrMatch(ch, rule_f.__name__) # or eval("{rule_f.__name__}_m") (ch_hexes, ch_names) = character_lookup(s, ch_names) if len(ch_hexes) == 1: ch_hexes = ch_hexes[0] ch_names = ch_names[0] for test_name, grammar, text, expect in scenarios(rule, ch): method_name = f"test_{name_prefix}_rule_{rule.rule_name}__AGAINST_{ch_hexes}_{ch_names}_{test_name}" # print(f"Adding {method_name}") method = create_method(method_name, rule_f, rule, ch, grammar, text, expect) setattr(Test_Common, method_name, method) if tst_debug_first_scenario: break