"""
Various pyparsing grammar elements and a few useful routines.
"""
from contextlib import contextmanager
import pyparsing as pp
import re
from .decorators import cache
from typing import Optional
[docs]
@contextmanager
def generate_grammar():
""" Set the pyparsing newline handling
and then restore to the original state """
try:
old = None
kchars = None
pe = pp.ParserElement
if hasattr(pe, "DEFAULT_WHITE_CHARS"):
old = pe.DEFAULT_WHITE_CHARS
if hasattr(pp.Keyword, "DEFAULT_KEYWORD_CHARS"):
kchars = pp.Keyword.DEFAULT_KEYWORD_CHARS + '-'
pe.setDefaultWhitespaceChars(' \t\r')
yield
finally:
if old is not None:
pe.setDefaultWhitespaceChars(old)
if kchars is not None:
pp.Keyword.DEFAULT_KEYWORD_CHARS = kchars
[docs]
def replace_whitechars(expr):
expr = expr.copy()
expr.setWhitespaceChars(' \t\r')
return expr
with generate_grammar():
optional_line_end = pp.Suppress(pp.LineEnd() | pp.WordStart() ).setName(' ')
""" Grammar for an optinal newline """
line_end = pp.Suppress(pp.LineEnd()).setName('\n')
""" Grammar for a required newline """
end_of_file = (pp.Regex(r'[\s]*') + pp.StringEnd()).suppress().setName('<EOF>')
""" Grammar for an end of file (ending whitespaces are allowed) """
optional_quote = pp.Optional("'").suppress()
""" Grammar for an optional quote """
[docs]
def separator_pattern(char):
return f'[{char}]' * 11 + '*'
[docs]
@cache
def separator_grammar(char):
""" Pattern for separating sections in an input file """
separator = pp.Regex(separator_pattern(char)).setName(f"{char*10}[{char*4}....]").suppress()
return separator
[docs]
def delimitedList(expr, delim):
""" Delimited list with already suppressed delimiter (or with a in-results-wanted one) """
return expr + pp.ZeroOrMore(delim + expr)
[docs]
def addConditionEx(self, condition, message):
""" Add check condition to the pyparsing ParseElement,
that, if it failed, raise a parse exception with a given message. """
def check_condition(s, loc, tocs):
m = message
if condition(tocs):
return tocs
if not isinstance(m, str):
m = m(tocs)
raise pp.ParseException(s, loc, m)
self.addParseAction(check_condition)
return self
[docs]
def addParseActionEx(self, pa, message = None):
"""
Add parse action to a given pyparsing ParseElemenet,
that, if it raise an exception, fail with a given message
"""
def parse_action(s, loc, x):
try:
return pa(x)
except Exception as e:
if message:
msg = f'{message}\n{e}'
else:
msg = str(e)
raise pp.ParseException(s, loc, msg).with_traceback(e.__traceback__) from e
self.addParseAction(parse_action)
[docs]
class White(pp.White):
""" Fix for whitechars in pp.White
In Python 3.10, pyparsing.White do not respect default_white_chars.
This class fixes this.
"""
[docs]
def __init__(self, white):
super().__init__(white)
self.setWhitespaceChars('')
[docs]
class SkipToRegex(pp.Token):
""" Skip to given regex """
[docs]
def __init__(self, pattern, include_pattern:Optional[bool]=None,
parse_pattern:bool=False):
if not isinstance(pattern, re.Pattern):
pattern = re.compile(pattern)
self.pattern = pattern
if include_pattern is None:
include_pattern = parse_pattern
self.include_pattern = include_pattern
self.parse_pattern = parse_pattern
super().__init__()
@property
def custom_name(self):
self.customName="skipTo{self.pattern}"
[docs]
def parseImpl(self, instr, loc, doActions = True):
result = self.pattern.search(instr,loc)
if result:
start = result.start()
out = instr[loc:start]
if self.parse_pattern:
out = (out, instr[start:result.end()])
if self.include_pattern:
loc = result.end()
else:
loc = result.start()
return loc, pp.ParseResults(out)
raise pp.ParseException(instr, loc, "Pattern {self.pattern} not found", self)
pp.ParserElement.addConditionEx = addConditionEx
pp.ParserElement.addParseActionEx = addParseActionEx