def hwdb_grammar(): ParserElement.setDefaultWhitespaceChars('') prefix = Or(category + ':' + Or(conn) + ':' for category, conn in TYPES.items()) matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL propertyline = ( White(' ', exact=1).suppress() + Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.! ') - Optional(pythonStyleComment)) + EOL ) propertycomment = White(' ', exact=1) + pythonStyleComment + EOL group = ( OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) - OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) - (EMPTYLINE ^ stringEnd()).suppress() ) commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress() grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd() return grammar
def hwdb_grammar(): ParserElement.setDefaultWhitespaceChars('') prefix = Or(category + ':' + Or(conn) + ':' for category, conn in TYPES.items()) matchline_typed = Combine(prefix + Word(printables + ' ' + '®')) matchline_general = Combine( Or(GENERAL_MATCHES) + ':' + Word(printables + ' ' + '®')) matchline = (matchline_typed | matchline_general) + EOL propertyline = ( White(' ', exact=1).suppress() + Combine(UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/')) - Optional(pythonStyleComment)) + EOL) propertycomment = White(' ', exact=1) + pythonStyleComment + EOL group = ( OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) - OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) - (EMPTYLINE ^ stringEnd()).suppress()) commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress() grammar = OneOrMore(Group(group)('GROUPS*') ^ commentgroup) + stringEnd() return grammar
def oui_grammar(type): prefix_line = ( Combine(NUM2 - Suppress('-') - NUM2 - Suppress('-') - NUM2)('prefix') - Literal('(hex)') - text_eol('text')) if type == 'small': vendor_line = (NUM3('start') - '000-' - NUM3('end') - 'FFF' - Literal('(base 16)') - text_eol('text2')) elif type == 'medium': vendor_line = (NUM1('start') - '00000-' - NUM1('end') - 'FFFFF' - Literal('(base 16)') - text_eol('text2')) else: assert type == 'large' vendor_line = (NUM6('start') - Literal('(base 16)') - text_eol('text2')) extra_line = TAB - TAB - TAB - TAB - SkipTo(EOL) vendor = prefix_line + vendor_line + ZeroOrMore(extra_line) + Optional( EMPTYLINE) grammar = (Literal('OUI') + text_eol('header') + text_eol('header') + text_eol('header') + EMPTYLINE + OneOrMore(vendor('VENDORS*')) + stringEnd()) grammar.parseWithTabs() return grammar
def usb_ids_grammar(): vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') interface_line = TAB + TAB + NUM4('interface') + NUM4( 'interface2') + text_eol('text') device = (device_line + ZeroOrMore(Group(interface_line) ^ COMMENTLINE.suppress())) vendor = ( vendor_line('VENDOR') + ZeroOrMore(Group(device)('VENDOR_DEV*') ^ COMMENTLINE.suppress())) klass = klass_grammar() other_line = (Literal('AT ') ^ Literal('HID ') ^ Literal('R ') ^ Literal('PHY ') ^ Literal('BIAS ') ^ Literal('HUT ') ^ Literal('L ') ^ Literal('VT ') ^ Literal('HCC ')) + text_eol('text') other_group = (other_line - ZeroOrMore(TAB + text_eol('text'))) commentgroup = OneOrMore(COMMENTLINE).suppress() ^ EMPTYLINE.suppress() grammar = OneOrMore( Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ other_group.suppress() ^ commentgroup) + stringEnd() grammar.parseWithTabs() return grammar
def __init__(self): """ Create a parser that parse arithmetic expressions. They can contains variable identifiers or raw numbers. The meaning for the identifiers is left to the """ number = p.Regex(r'\d+(\.\d*)?([eE]\d+)?') identifier = p.Word(p.alphas) terminal = identifier | number self._expr = p.infixNotation( terminal, [(p.oneOf('* /'), 2, p.opAssoc.LEFT), (p.oneOf('+ -'), 2, p.opAssoc.LEFT)]) + p.stringEnd()
def __init__(self): """ Create a parser that parse arithmetic expressions. They can contains variable identifiers or raw numbers. The meaning for the identifiers is left to the """ number = p.Regex(r'\d+(\.\d*)?([eE]\d+)?') identifier = p.Word(p.alphas) terminal = identifier | number self._expr = p.infixNotation(terminal, [ (p.oneOf('* /'), 2, p.opAssoc.LEFT), (p.oneOf('+ -'), 2, p.opAssoc.LEFT) ]) + p.stringEnd()
def sdio_ids_grammar(): vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') vendor = (vendor_line('VENDOR') + ZeroOrMore(device_line('DEVICES*') ^ COMMENTLINE.suppress())) klass = klass_grammar() commentgroup = OneOrMore(COMMENTLINE).suppress() ^ EMPTYLINE.suppress() grammar = OneOrMore(vendor('VENDORS*') ^ klass('CLASSES*') ^ commentgroup) + stringEnd() grammar.parseWithTabs() return grammar
def create_parser(): """Creates the parser using PyParsing functions.""" # Day details (day number, superscript and day name) daynum = Word(nums, max=2) superscript = oneOf("th rd st nd", caseless=True) day = oneOf( "Mon Monday Tue Tues Tuesday Wed Weds Wednesday " "Thu Thur Thurs Thursday Fri Friday Sat Saturday Sun Sunday", caseless=True) full_day_string = daynum + Optional(superscript).suppress() full_day_string.setParseAction(check_day) full_day_string.leaveWhitespace() # Month names, with abbreviations, with action to convert to equivalent month number month = oneOf(list(MONTHS.keys()), caseless=True) + \ Optional(Literal(".").suppress()) month.setParseAction(month_to_number) # Year year = Word(nums, exact=4) year.setParseAction(lambda tokens: int(tokens[0])) time_sep = oneOf(": .") am_pm = oneOf("am pm", caseless=True) hours = Word(nums, max=2) mins = Word(nums, max=2) time = hours("hour") + time_sep.suppress() + \ mins("mins") + Optional(am_pm)("meridian") # date pattern date = (Optional(time).suppress() & Optional(full_day_string("day")) & Optional(day).suppress() & Optional(month("month")) & Optional(year("year"))) # Possible separators separator = oneOf("- -- to until through till untill \u2013 \u2014 ->", caseless=True) # Strings to completely ignore (whitespace ignored by default) ignoreable_chars = oneOf(", from starting beginning of", caseless=True) # Final putting together of everything daterange = (Optional( date("start") + Optional(time).suppress() + separator.suppress()) + date("end") + Optional(time).suppress() + stringEnd()) daterange.ignore(ignoreable_chars) return daterange
def create_parser(): """Creates the parser using PyParsing functions.""" # Day details (day number, superscript and day name) daynum = Word(nums, max=2) superscript = oneOf("th rd st nd", caseless=True) day = oneOf("Mon Monday Tue Tues Tuesday Wed Weds Wednesday " "Thu Thur Thurs Thursday Fri Friday Sat Saturday Sun Sunday", caseless=True) full_day_string = daynum + Optional(superscript).suppress() full_day_string.setParseAction(check_day) full_day_string.leaveWhitespace() # Month names, with abbreviations, with action to convert to equivalent month number month = oneOf(list(MONTHS.keys()), caseless=True) + \ Optional(Literal(".").suppress()) month.setParseAction(month_to_number) # Year year = Word(nums, exact=4) year.setParseAction(lambda tokens: int(tokens[0])) time_sep = oneOf(": .") am_pm = oneOf("am pm", caseless=True) hours = Word(nums, max=2) mins = Word(nums, max=2) time = hours("hour") + time_sep.suppress() + \ mins("mins") + Optional(am_pm)("meridian") # date pattern date = ( Optional(time).suppress() & Optional(full_day_string("day")) & Optional(day).suppress() & Optional(month("month")) & Optional(year("year")) ) # Possible separators separator = oneOf("- -- to until through till untill \u2013 \u2014 ->", caseless=True) # Strings to completely ignore (whitespace ignored by default) ignoreable_chars = oneOf(", from starting beginning of", caseless=True) # Final putting together of everything daterange = ( Optional(date("start") + Optional(time).suppress() + separator.suppress()) + date("end") + Optional(time).suppress() + stringEnd() ) daterange.ignore(ignoreable_chars) return daterange
def pci_ids_grammar(): vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') subvendor_line = TAB + TAB + NUM4('a') + White(' ') + NUM4('b') + text_eol('name') device = (device_line('DEVICE') + ZeroOrMore(Group(subvendor_line)('SUBVENDORS*') ^ COMMENTLINE.suppress())) vendor = (vendor_line('VENDOR') + ZeroOrMore(Group(device)('DEVICES*') ^ COMMENTLINE.suppress())) klass = klass_grammar() commentgroup = OneOrMore(COMMENTLINE).suppress() ^ EMPTYLINE.suppress() grammar = OneOrMore(Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ commentgroup) + stringEnd() grammar.parseWithTabs() return grammar
def usb_ids_grammar(): vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') vendor = (vendor_line('VENDOR') + ZeroOrMore(device_line('VENDOR_DEV*') ^ COMMENTLINE.suppress())) klass = klass_grammar() other_line = (Literal('AT ') ^ Literal('HID ') ^ Literal('R ') ^ Literal('PHY ') ^ Literal('BIAS ') ^ Literal('HUT ') ^ Literal('L ') ^ Literal('VT ') ^ Literal('HCC ')) + text_eol('text') other_group = (other_line - ZeroOrMore(TAB + text_eol('text'))) commentgroup = OneOrMore(COMMENTLINE).suppress() ^ EMPTYLINE.suppress() grammar = OneOrMore(vendor('VENDORS*') ^ klass('CLASSES*') ^ other_group.suppress() ^ commentgroup) + stringEnd() grammar.parseWithTabs() return grammar
def oui_grammar(type): prefix_line = (Combine(NUM2 - Suppress('-') - NUM2 - Suppress('-') - NUM2)('prefix') - Literal('(hex)') - text_eol('text')) if type == 'small': vendor_line = (NUM3('start') - '000-' - NUM3('end') - 'FFF' - Literal('(base 16)') - text_eol('text2')) elif type == 'medium': vendor_line = (NUM1('start') - '00000-' - NUM1('end') - 'FFFFF' - Literal('(base 16)') - text_eol('text2')) else: assert type == 'large' vendor_line = (NUM6('start') - Literal('(base 16)') - text_eol('text2')) extra_line = TAB - TAB - TAB - TAB - SkipTo(EOL) vendor = prefix_line + vendor_line + ZeroOrMore(extra_line) + Optional(EMPTYLINE) grammar = (Literal('OUI') + text_eol('header') + text_eol('header') + text_eol('header') + EMPTYLINE + OneOrMore(vendor('VENDORS*')) + stringEnd()) grammar.parseWithTabs() return grammar
def dockerfile_instruction_grammar(self): """dockerfile_instruction_grammar""" # # Fail Action - Error template - Line / Col / Instruction # def error(s, loc, expr, error): """Main error template""" raise ParseFatalException(DOCKERFILE_ERROR[202].format( ligne=self.line_counter, colonne=error.loc, inst=self.currentInstructionName, erreur=error.msg)) # # Parse Action (Basic verification) # def arg_validate(strng, loc, toks): """Do some verfications for the instruction arguments""" if not self.validator.validate_instruction(toks): raise ParseFatalException(self.validator.get_errors(), loc=loc) def instructions_parse(strng, loc, toks): """Check if the instruction exist in the config file""" self.currentInstructionName = toks[0] if toks[0] not in INSTRUCTION_CONFIG_LIST: raise ParseFatalException(DOCKERFILE_ERROR[211], loc=loc) self.currentInstruction = INSTRUCTION_CONFIG_LIST[toks[0]] def args_table_parse(strng, loc, toks): """Check if the table form is correct for the current instruction arguments""" if (self.currentInstruction[0] == 1): raise ParseFatalException(DOCKERFILE_ERROR[213], loc=loc) def args_list_parse(strng, loc, toks): """Check if the list form is correct for the current instruction arguments""" if (self.currentInstruction[0] == 2): raise ParseFatalException(DOCKERFILE_ERROR[214], loc=loc) def args_num_parse(strng, loc, toks): """Check if the number of arguments is correct""" minArg = self.currentInstruction[1] maxArg = self.currentInstruction[2] nbArgs = len(toks) if (not minArg <= nbArgs <= maxArg): raise ParseFatalException(DOCKERFILE_ERROR[215].format( nombre=nbArgs, min=minArg, max=maxArg), loc=loc) def opt_parse(strng, loc, toks): """Check if the option exist and if she's correct for the current instruction""" if toks[0] not in OPTIONAL_OPTION_CONFIG: raise ParseFatalException( DOCKERFILE_ERROR[216].format(opt=toks[0]), loc=loc) if self.currentInstructionName not in OPTIONAL_OPTION_CONFIG[ toks[0]]: raise ParseFatalException( DOCKERFILE_ERROR[217].format(opt=toks[0]), loc=loc) #INIT ParserElement.setDefaultWhitespaceChars(" \t") # # TERMINALS # INST = Regex(r'([A-Z]+)(?<!\s)').setName('Instruction').setParseAction( instructions_parse) OPT = Regex(r'--[a-z]+=').setName('Option').setParseAction(opt_parse) STR = Regex(r'\"((.|\s)+?)\"').setName("chaîne de caractère") ARG = Regex(r'\S+').setName("argument") EOL = lineEnd().setName("fin de ligne").suppress() COM = Regex(r'#.*').suppress() OH = Literal('[').suppress() CH = Literal(']').suppress() CO = Literal(',').suppress() # # NO TERMINALS # #Arguments t_args_table = OH - STR - ZeroOrMore(CO - STR) - CH t_args_table.setName('["argument1", "argument2" …]') t_args_table.setParseAction(args_table_parse) t_args_list = ARG - ZeroOrMore(ARG) t_args_list.setName('argument1 argument2 …') t_args_list.setParseAction(args_list_parse) t_args = (t_args_table | t_args_list) t_args.setParseAction(args_num_parse) #Multiple lines separator continuation = '\\' - EOL #Optional elements t_opt = OneOrMore(OPT - Group(ARG)) t_opt.setParseAction(opt_parse) #instruction instruction = (INST - Group(Optional(t_opt)) - Group(t_args)).setParseAction(arg_validate) #line grammar line = (stringStart - (COM | Optional(instruction)) - EOL - stringEnd()).setFailAction(error) line.ignore(continuation) return line
def __init__(self): # Bibtex keywords string_def_start = pp.CaselessKeyword("@string") preamble_start = pp.CaselessKeyword("@preamble") comment_line_start = pp.CaselessKeyword('@comment') # String names string_name = pp.Word(pp.alphanums + '_')('StringName') self.set_string_name_parse_action(lambda s, l, t: None) string_name.addParseAction(self._string_name_parse_action) # Values inside bibtex fields # Values can be integer or string expressions. The latter may use # quoted or braced values. # Integer values integer = pp.Word(pp.nums)('Integer') # Braced values: braced values can contain nested (but balanced) braces braced_value_content = pp.CharsNotIn('{}') braced_value = pp.Forward() # Recursive definition for nested braces braced_value <<= pp.originalTextFor( '{' + pp.ZeroOrMore(braced_value | braced_value_content) + '}' )('BracedValue') braced_value.setParseAction(remove_braces) # TODO add ignore for "\}" and "\{" ? # TODO @ are not parsed by bibtex in braces # Quoted values: may contain braced content with balanced braces brace_in_quoted = pp.nestedExpr('{', '}') text_in_quoted = pp.CharsNotIn('"{}') # (quotes should be escaped in quoted value) quoted_value = pp.originalTextFor( '"' + pp.ZeroOrMore(text_in_quoted | brace_in_quoted) + '"')('QuotedValue') quoted_value.addParseAction(pp.removeQuotes) # String expressions string_expr = pp.delimitedList( (quoted_value | braced_value | string_name), delim='#' )('StringExpression') self.set_string_expression_parse_action(lambda s, l, t: None) string_expr.addParseAction(self._string_expr_parse_action) value = (integer | string_expr)('Value') # Entries # @EntryType { ... entry_type = (pp.Suppress('@') + pp.Word(pp.alphas))('EntryType') entry_type.setParseAction(first_token) # Entry key: any character up to a ',' without leading and trailing # spaces. key = pp.SkipTo(',')('Key') # Exclude @',\#}{~% key.setParseAction(lambda s, l, t: first_token(s, l, t).strip()) # Field name: word of letters and underscores field_name = pp.Word(pp.alphas + '_')('FieldName') field_name.setParseAction(first_token) # Field: field_name = value field = pp.Group(field_name + pp.Suppress('=') + value)('Field') field.setParseAction(field_to_pair) # List of fields: comma separeted fields field_list = (pp.delimitedList(field) + pp.Suppress(pp.Optional(',')) )('Fields') field_list.setParseAction( lambda s, l, t: {k: v for (k, v) in reversed(t.get('Fields'))}) # Entry: type, key, and fields self.entry = (entry_type + in_braces_or_pars(key + pp.Suppress(',') + field_list) )('Entry') # Other stuff: comments, string definitions, and preamble declarations # Explicit comments: @comment + everything up to next valid declaration # starting on new line. not_an_implicit_comment = (pp.LineStart() + pp.Literal('@') ) | pp.stringEnd() self.explicit_comment = ( pp.Suppress(comment_line_start) + pp.originalTextFor(pp.SkipTo(not_an_implicit_comment), asString=True))('ExplicitComment') self.explicit_comment.addParseAction(remove_trailing_newlines) self.explicit_comment.addParseAction(remove_braces) # Previous implementation included comment until next '}'. # This is however not inline with bibtex behavior that is to only # ignore until EOL. Brace stipping is arbitrary here but avoids # duplication on bibtex write. # Empty implicit_comments lead to infinite loop of zeroOrMore def mustNotBeEmpty(t): if not t[0]: raise pp.ParseException("Match must not be empty.") # Implicit comments: not anything else self.implicit_comment = pp.originalTextFor( pp.SkipTo(not_an_implicit_comment).setParseAction(mustNotBeEmpty), asString=True)('ImplicitComment') self.implicit_comment.addParseAction(remove_trailing_newlines) # String definition self.string_def = (pp.Suppress(string_def_start) + in_braces_or_pars( string_name + pp.Suppress('=') + string_expr('StringValue') ))('StringDefinition') # Preamble declaration self.preamble_decl = (pp.Suppress(preamble_start) + in_braces_or_pars(value))('PreambleDeclaration') # Main bibtex expression self.main_expression = pp.ZeroOrMore( self.string_def | self.preamble_decl | self.explicit_comment | self.entry | self.implicit_comment)
pp.ZeroOrMore(pp.Literal(",").suppress() + value_or_property_name) + pp.Literal(")").suppress()) redirection << (live_calculation_property | stored_property ) + pp.Literal(".").suppress() + property_identifier parameters = pp.Literal("(").suppress() + pp.Optional( value_or_property_name + pp.ZeroOrMore(pp.Literal(",").suppress() + value_or_property_name)) + pp.Literal(")").suppress() live_calculation_property << property_name + parameters array_element << ( (live_calculation_property | stored_property) + element_identifier) property_complete = pp.stringStart() + value_or_property_name + pp.stringEnd() def parse_property_name(name): with _parsing_lock: return property_complete.parseString(name)[0] def parse_property_name_if_required(name): if isinstance(name, Calculation): return name else: return parse_property_name(name) def parse_property_names(*names):
atom_vLevels = ("v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9") integer = pp.Word(pp.nums) atom_first_vLevel = pp.oneOf(atom_vLevels).setResultsName("firstVLevel") atom_first_vLevel_int = integer.setResultsName("firstVLevelInt") atom_second_vLevel = pp.oneOf(atom_vLevels).setResultsName("secondVLevel") atom_second_vLevel_int = integer.setResultsName("secondVLevelInt") atom_term = ( pp.Optional(atom_first_vLevel_int) + atom_first_vLevel + "+" + pp.Optional(atom_second_vLevel_int) + atom_second_vLevel + pp.stringEnd() ) atom_exc = integer.setResultsName("excLevel") atom_term_exc = atom_exc + pp.StringEnd() class VibrationalState(State): def parse_state(self, state_str): self.state_str = state_str.replace(" ", "") if "+" in state_str: try: components = atom_term.parseString(state_str) except pp.ParseException: raise StateParseError("Invalid atomic term symbol syntax: {0}".format(state_str))
def __init__(self): # Bibtex keywords string_def_start = pp.CaselessKeyword("@string") preamble_start = pp.CaselessKeyword("@preamble") comment_line_start = pp.CaselessKeyword('@comment') # String names string_name = pp.Word(pp.alphanums + '_')('StringName') self.set_string_name_parse_action(lambda s, l, t: None) string_name.addParseAction(self._string_name_parse_action) # Values inside bibtex fields # Values can be integer or string expressions. The latter may use # quoted or braced values. # Integer values integer = pp.Word(pp.nums)('Integer') # Braced values: braced values can contain nested (but balanced) braces braced_value_content = pp.CharsNotIn('{}') braced_value = pp.Forward() # Recursive definition for nested braces braced_value <<= pp.originalTextFor( '{' + pp.ZeroOrMore(braced_value | braced_value_content) + '}' )('BracedValue') braced_value.setParseAction(remove_braces) # TODO add ignore for "\}" and "\{" ? # TODO @ are not parsed by bibtex in braces # Quoted values: may contain braced content with balanced braces brace_in_quoted = pp.nestedExpr('{', '}', ignoreExpr=None) text_in_quoted = pp.CharsNotIn('"{}') # (quotes should be escaped by braces in quoted value) quoted_value = pp.originalTextFor( '"' + pp.ZeroOrMore(text_in_quoted | brace_in_quoted) + '"' )('QuotedValue') quoted_value.addParseAction(pp.removeQuotes) # String expressions string_expr = pp.delimitedList( (quoted_value | braced_value | string_name), delim='#' )('StringExpression') self.set_string_expression_parse_action(lambda s, l, t: None) string_expr.addParseAction(self._string_expr_parse_action) value = (integer | string_expr)('Value') # Entries # @EntryType { ... entry_type = (pp.Suppress('@') + pp.Word(pp.alphas))('EntryType') entry_type.setParseAction(first_token) # Entry key: any character up to a ',' without leading and trailing # spaces. key = pp.SkipTo(',')('Key') # Exclude @',\#}{~% key.setParseAction(lambda s, l, t: first_token(s, l, t).strip()) # Field name: word of letters, digits, dashes and underscores field_name = pp.Word(pp.alphanums + '_-()')('FieldName') field_name.setParseAction(first_token) # Field: field_name = value field = pp.Group(field_name + pp.Suppress('=') + value)('Field') field.setParseAction(field_to_pair) # List of fields: comma separeted fields field_list = (pp.delimitedList(field) + pp.Suppress(pp.Optional(',')) )('Fields') field_list.setParseAction( lambda s, l, t: {k: v for (k, v) in reversed(t.get('Fields'))}) # Entry: type, key, and fields self.entry = (entry_type + in_braces_or_pars(key + pp.Suppress(',') + field_list) )('Entry') # Other stuff: comments, string definitions, and preamble declarations # Explicit comments: @comment + everything up to next valid declaration # starting on new line. not_an_implicit_comment = (pp.LineStart() + pp.Literal('@') ) | pp.stringEnd() self.explicit_comment = ( pp.Suppress(comment_line_start) + pp.originalTextFor(pp.SkipTo(not_an_implicit_comment), asString=True))('ExplicitComment') self.explicit_comment.addParseAction(remove_trailing_newlines) self.explicit_comment.addParseAction(remove_braces) # Previous implementation included comment until next '}'. # This is however not inline with bibtex behavior that is to only # ignore until EOL. Brace stipping is arbitrary here but avoids # duplication on bibtex write. # Empty implicit_comments lead to infinite loop of zeroOrMore def mustNotBeEmpty(t): if not t[0]: raise pp.ParseException("Match must not be empty.") # Implicit comments: not anything else self.implicit_comment = pp.originalTextFor( pp.SkipTo(not_an_implicit_comment).setParseAction(mustNotBeEmpty), asString=True)('ImplicitComment') self.implicit_comment.addParseAction(remove_trailing_newlines) # String definition self.string_def = (pp.Suppress(string_def_start) + in_braces_or_pars( string_name + pp.Suppress('=') + string_expr('StringValue') ))('StringDefinition') # Preamble declaration self.preamble_decl = (pp.Suppress(preamble_start) + in_braces_or_pars(value))('PreambleDeclaration') # Main bibtex expression self.main_expression = pp.ZeroOrMore( self.string_def | self.preamble_decl | self.explicit_comment | self.entry | self.implicit_comment)