def _parse_error_catcher(self): pos = self._pos() args = [] etype = self._tok(expected="error").value if etype.isnumeric(): args.append(ErrorCatcherArg('int', etype, self._pos())) etype = 'generated' self._tok() while not (self._cur().name == 'keyword' and self._cur().value == 'in') and self._cur().name not in [ 'newline', 'rarrow', 'keyword' ]: args.append( ErrorCatcherArg(self._cur().name, self._cur().value, self._pos())) self._tok() ctx_rules = [self._tok(expected='ident').value] while self._tok().name == 'coma': ctx_rules.append(self._tok(expected='ident').value) self._cur(expected='rarrow') msg = self._tok(expected="string").value self._tok() if self._cur().name == "coma": self._tok() if not (self._cur().name == 'keyword' and self._cur().value == "continue"): raise KombuError( "{} : ',' not expected. Or do you mean ', continue' ?". format(self._pos(formatted=True))) if not etype == "generated": raise KombuError( "{} : ', continue' create a warning catcher, but you seem to need an " "error catcher".format(self._pos(formatted=True))) warn = True self._tok() else: warn = False if warn: if len(args) > 1: raise KombuError( "{} : 'catch !{} {}' : Warning does not need any arguments." .format(self._pos(formatted=True), args[0].value, " ".join([a.value for a in args[1:]]))) return [ WarningCatcher(msg, self.filename[:-3] + '__' + ctx_rule, args[0].value, pos) for ctx_rule in ctx_rules ] else: return [ ErrorCatcher(msg, etype, self.filename[:-3] + '__' + ctx_rule, args, pos) for ctx_rule in ctx_rules ]
def _parse_init_or_end_block(self, block_type): if block_type == 'init': block = BeforeBlock(self._pos()) elif block_type == 'end': block = AfterBlock(self._pos()) else: block = None self._tok(expected='LBlock') self._tok(expected='newline') imbrication_level = 1 while self._cur() and imbrication_level > 0: added_code, imbrication_add = self._parse_init_or_end_block_part() imbrication_level += imbrication_add block.code.append(added_code) if not self._cur(): if block_type == 'init': raise KombuError( "{} : Unexpected end of file while parsing 'before' block." .format(self._pos(formatted=True))) elif block_type == 'end': raise KombuError( "{} : Unexpected end of file while parsing 'after' block.". format(self._pos(formatted=True))) block.code.pop() block.code.pop() return block
def _cur(self, expected=None): if len(self.tokenlist) > self.i: cur = self.tokenlist[self.i] if not expected or cur.name == expected: return cur else: raise KombuError("{} : '{}' : Expected {}.".format( self._pos(formatted=True), self._cur().value, expected)) else: if expected: raise KombuError("{} : Expected {}, got end of file.".format( self._pos(formatted=True), expected))
def _next(self, expected=None, failmsg="Expected {expected}, got {got}."): failmsg = "Line {line} : '{gotvalue}' : " + failmsg if len(self.tokenlist) > self.i + 1: pcur = self.tokenlist[self.i + 1] if not expected or pcur.name == expected: return pcur else: raise KombuError( replaces(failmsg, '{line}', self._pos(), '{gotvalue}', self.tokenlist[self.i].value, '{expected}', expected, '{gottype}', pcur.name)) else: if expected: raise KombuError("{} : Expected {}, got end of file.".format( self._pos(formatted=True), expected))
def _check_left_recursive_loop(self, node): """Check that this rule definition won't provoke an endless recursive loop by rule calls. Raises an KomBuGrammarError if so.""" def find_left_recursive_rule_call(rule): for node in rule.definition: if type(node) is Match: return None elif type(node) is Choices: for option in node.options: for subnode in option.definition: if type(subnode) is Match: return None elif type(node) is RuleCall: if not [s for s in rule.definition if type(s) is Match]: return get_rule(self.code, node.whole_name) return None left_recursive_call = find_left_recursive_rule_call(node) while left_recursive_call: if left_recursive_call.whole_name == node.whole_name: raise KombuError( "{} : This rule definition'll provoke an endless recursive loop." .format(self.formatted_pos(node.pos))) else: left_recursive_call = find_left_recursive_rule_call( left_recursive_call)
def _tok(self, expected=None): if len(self.tokenlist) > self.i + 1: self.i += 1 return self._cur(expected=expected) else: self.i += 1 if expected: raise KombuError("{} : Expected {}, got end of file.".format( self._pos(formatted=True), expected))
def _check_multiline_inspection(self, node): assert type(node) is MultilineInspection, "Error in the checker code." self._current_inspected_rule = get_rule(self.code, node.whole_name) if not self._current_inspected_rule: raise KombuError( "{} : 'inspect {}' : Cannot define an inspection on a not-existing rule." .format(self.formatted_pos(node.pos), node.name)) inspection_definition = split(node.definition, NewLine(), include_at_beginning=True)[1:] base_indentation = len(inspection_definition[0][0].indentation.replace( '\t', ' ' * 16)) for i, line in enumerate(inspection_definition): indentation = len(line[0].indentation.replace('\t', ' ' * 16)) if indentation < base_indentation and len(line) > 1: raise KombuError("{} : This line is under-indented.".format( self.formatted_pos(line[0].pos) + i + 1))
def _check_uniline_inspection(self, node): assert type(node) is UnilineInspection, "Error in the checker code." self._current_inspected_rule = get_rule(self.code, node.whole_name) if not get_rule(self.code, node.whole_name): raise KombuError( "{} : 'inspect {}' : Cannot define an inspection on a not-existing rule." .format(self.formatted_pos(node.pos), node.name)) for node in [n for n in node.definition if type(n) in [NodeCall]]: self._check_node(node)
def _parse_optional_part(self): definition = [] self._tok() while self._cur().name != 'ROptional': if self._cur().name == 'EOF' or self._cur().name == 'newline': raise KombuError( "{} : Missing ']' to close the optional group.".format( self._pos(formatted=True))) definition.append(self._parse_rule_definition_part()) self._tok() return OptionalGroup(definition, self._pos())
def _parse_block(self): if self._cur().name == 'constantname': return self._parse_constant_declaration() elif self._cur().name == 'ident': return self._parse_rule_definition() elif self._cur().name == 'uni_line_comment': return self._parse_comment() elif self._cur().name == 'multi_lines_comment': return self._parse_comment() elif self._cur().name == 'keyword' and self._cur().value == 'with': return self._parse_import() elif self._cur().name == 'keyword' and self._cur().value == 'block': return self._parse_block_definition() elif self._cur().name == 'keyword' and self._cur().value == 'group': return self._parse_group() elif self._cur().name == 'keyword' and self._cur().value == 'inspect': return self._parse_inspection() elif self._cur().name == 'keyword' and self._cur().value == 'before': return self._parse_init_or_end_block(block_type='init') elif self._cur().name == 'keyword' and self._cur().value == 'after': return self._parse_init_or_end_block(block_type='end') elif self._cur().name == 'keyword' and self._cur().value == 'catch': return self._parse_error_catcher() elif self._cur().name == 'newline': return NewLine(self._pos(), self._cur().value) elif self._cur().name == 'EOF': self._tok() return EOF(self._pos()) elif re.match('R[A-Z]', self._cur().name): raise KombuError( "{} : '{}' : Got end char but there wasn't any begin char as (, or [." .format(self._pos(formatted=True), self._cur().value)) else: raise KombuError("{} : '{}' : Unexpected line syntax.".format( self._pos(formatted=True), self._cur().value))
def _parse_rule_definition_part(self): if self._cur().name == 'rulecall': part = RuleCall(self._cur().value, self.namespace, self._pos()) self._tok() elif self._cur().name == 'LOptional': part = self._parse_optional_part() elif self._cur().name == 'LGroup': part = self._parse_group_or_choices_group() elif self._cur().name == 'string': part = Match(self._cur().value, self._pos()) self._tok() elif self._cur().name == 'regex': regex = self._cur().value self._tok() part = RegexMatch(regex, self._pos()) else: errortoken = self._cur().value if errortoken == '|': errormsg = "{} : '|' used outside of a choices group.".format( self._pos(formatted=True)) elif errortoken == ']': errormsg = "{} : Found ']' to close an optional group but no '[' to begin it.".format( self._pos(formatted=True)) elif errortoken == ')': errormsg = "{} : Found ')' to close a choices group but no '(' to begin it.".format( self._pos(formatted=True)) else: errormsg = "{} : '{}' : Syntax error in the rule definition.".format( self._pos(formatted=True), self._cur().value) raise KombuError(errormsg) name = self._parse_name() if name: self._tok() part.group_name = name if type(part) is Choices: part = self._apply_choices_groupname_to_options(part) error_trigger = self._parse_error_trigger() if error_trigger: self._tok() if type(part) is OptionalGroup: part = WarningTrigger(error_trigger, part, self._pos()) else: part = ErrorTrigger(error_trigger, part, self._pos()) return part
def _parse_choices_group(self): choices = Choices(self._pos()) while self._cur().name != 'RGroup': self._tok() option = Option(self._pos()) if self._cur().name == 'OptionsSeparator' or self._cur( ).name == 'RGroup': raise KombuError("{} : An option cannot be empty.".format( self._pos(formatted=True))) option.add_code(self._parse_rule_definition_part()) while self._cur().name != 'OptionsSeparator' and self._cur( ).name != 'RGroup': option.add_code(self._parse_rule_definition_part()) choices += option self._tok() return choices
def _parse_group_or_choices_group(self): is_choices_group = False i = self.i while not (self.tokenlist[i].name == 'RGroup' or self.tokenlist[i - 1].name == 'OptionsSeparator'): if self.tokenlist[i].name == 'OptionsSeparator': is_choices_group = True if self.tokenlist[i].name in ["newline", 'EOF']: raise KombuError( "{} : Missing ')' to close the choices group".format( self._pos(formatted=True))) i += 1 if is_choices_group: return self._parse_choices_group() else: return self._parse_group()
def _check_node(self, node): if type(node) is RuleDefinition: self._check_rule_definition(node) elif type(node) is SetConstant: self._check_constant_declaration(node) elif type(node) is OptionalGroup: self._check_optional_group(node) elif type(node) is Choices: self._check_choices(node) elif type(node) is RuleCall: self._check_rulecall(node) elif type(node) is UnilineInspection: self._check_uniline_inspection(node) elif type(node) is MultilineInspection: self._check_multiline_inspection(node) elif type(node) is NodeCall: self._check_node_call(node) elif type(node) is Import or type(node) is FromImport: self._check_import(node) elif type(node) is Group: self._check_group(node) elif type(node) in [WarningTrigger, ErrorTrigger]: self._check_error_or_warning_trigger(node) elif type(node) is WarningCatcher: self._check_warning_catcher(node) elif type(node) is ErrorCatcher: self._check_error_catcher(node) elif type(node) is RawPythonCode: print(node.code.replace(' ', '[s]')) elif type(node) is Match: pass elif type(node) is NewLine: pass elif type(node) is RegexMatch: pass elif type(node) is BeforeBlock: pass elif type(node) is AfterBlock: pass else: raise KombuError("Error in the checker code.")
def _parse_node_keyword(self): node_to_get = NodeCall(self._pos()) if self._next().name == 'ident' and self._next( ).value == 'ast' and self.tokenlist[self.i + 2:self.i + 3][ 0] and self.tokenlist[self.i + 2:self.i + 3][0].name != 'point': raise KombuError( "{} : 'node' keyword needs to be applied to a subnode of the ast." .format(self._pos(formatted=True))) self._tok(expected='ident') # subnode while self._next().name == 'point': node_to_get.node_path.append(self._cur(expected='ident').value) self._tok(expected='point') self._tok(expected='ident') node_to_get.node_path.append(self._cur(expected='ident').value) self._tok() return node_to_get
def _check_constant_declaration(self, node): assert type(node) is SetConstant, "Error in the Checker code." if node.constantname == 'axiom': self._check_option_value(node, True) found = get_rule(self.code, node.value) assert found, "{} : Axiom is defined by {}, but that rule doesn't exist.".format( self.formatted_pos(node.pos), node.value) elif node.constantname == 'libspath': self._check_option_value(node, True) elif node.constantname == 'name': self._check_option_value(node, True) elif node.constantname == 'keep_whitespaces': self._check_option_value(node, False) elif node.constantname == 'cut_end': self._check_option_value(node, False) elif node.constantname == 'get_time': self._check_option_value(node, False) elif node.constantname == 'show_ast': self._check_option_value(node, False) else: raise KombuError("{} : '{}' isn't an option name.".format( self.formatted_pos(node.pos), node.constantname))
def _parse_uniline_inspection(self): inspection_name = self._cur().value self._tok(expected='keyword') self._tok() inspection_code = [] inspection_code_part = self._parse_inspection_code_part()[1] if inspection_code_part == NewLine(): raise KombuError( "{} : Unilines inspections need a return value. Use None if you don't want to " "return anything.".format(self._pos(formatted=True))) inspection_code.append(inspection_code_part) while inspection_code_part != NewLine(): inspection_code_part = self._parse_inspection_code_part()[1] inspection_code.append(inspection_code_part) self._bef() return UnilineInspection(inspection_name, self.namespace, inspection_code, self._pos())
def _parse_multiline_inspection(self): inspection = MultilineInspection(name=self._cur().value, namespace=self.namespace, definition=[], pos=self._pos()) self._tok(expected='LBlock') self._tok() imbrication_level = 1 while imbrication_level > 0 and self._cur(): imbrication_add, inspection_code_part = self._parse_inspection_code_part( ) imbrication_level += imbrication_add inspection.definition.append(inspection_code_part) if not self._cur(): raise KombuError( "{} : Unexpected end of file while parsing multiline inspection." .format(self.tokenlist[-1].pos)) self._cur(expected='newline') inspection.definition = inspection.definition[:-1] return inspection
def t_double_underscored_ident(self, t): raise KombuError("Line {} : '{}' : Cannot use '__' in an ident name.".format(t.pos, t.value))
def t_unexpected_option_name(self, t): raise KombuError("Line {} : '{}' : Option name should only be composed of alphanumerics characters.".format( t.pos, t.value))
def t_missing_option_name(self, t): raise KombuError("Line {} : '$' : Expected an option name.".format(t.pos))
def _check_rule_exist(self, rule): if rule not in [ r.name for r in self.code.ast if type(r) is RuleDefinition ]: raise KombuError( "'<{}>' : Trying to invoke a non-existing rule.".format(rule))
def _parse_block_definition(self): raise KombuError("{} : Blocks option isn't implemented.".format( self._pos(formatted=True)))
def _parse_test(self): raise KombuError("{} : Tests option isn't implemented.".format( self._pos(formatted=True)))
def _check_error_catcher(self, node): def check_args(e_catcher, expected): if len(e_catcher.args) > expected: raise ValueError( "{} : '{}' : To many arguments for the error ; type !{} expects {} arguments" .format(self.formatted_pos(e_catcher.args[expected].pos), e_catcher.args[expected].value, e_catcher.etype, expected)) if len(e_catcher.args) < expected: raise ValueError( '{} : To few arguments for the error : type !{} expects {} arguments' .format(self.formatted_pos(e_catcher.pos), e_catcher.etype, expected)) if node.etype == "generated": check_args(node, 1) if not self._find_node(lambda n: type(n) is ErrorTrigger and n.nb == node.arg(0).value, rule=node.ctx_rule): raise ValueError( "{} : catch !{} in {} : Cannot try to catch an error that won't be " "raised.".format(self.formatted_pos(node.pos), node.arg(0).value, node.ctx_rule.split('__')[-1])) elif node.etype == "missing": check_args(node, 1) if node.arg(0).type == "string": found = self._find_node(lambda n: type(n) is Match and n.string == node.arg(0).value, rule=node.ctx_rule) elif node.arg(0).type == 'regex': found = self._find_node(lambda n: type(n) is RegexMatch and n. pattern == node.arg(0).value, rule=node.ctx_rule) else: raise KombuError( "{} : Pattern or string expected after !missing, not {}". format(self.formatted_pos(node.arg(0).pos), node.arg(0).type)) if not found: raise KombuError( "{} : Define a catcher on a !missing error that cannot be raised." .format(self.formatted_pos(node.pos))) elif node.etype == "failed": check_args(node, 1) if not node.arg(0).type == 'ident': raise KombuError( "{} : Rule name expected for error !failed, not {}".format( self.formatted_pos(node.pos), node.arg(0).type)) if not self._find_node(lambda n: type(n) is RuleCall and n.name == node.arg(0).value, rule=node.ctx_rule): raise KombuError( "{} : catch !failed {} : Cannot find a call to that rule in {}" .format(self.formatted_pos(node.pos), node.arg(0).value, node.ctx_rule)) else: raise NameError("{} : catch !{} : Invalid error name.".format( self.formatted_pos(node.pos), node.etype))