"""
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.set_default_whitespace_chars(" \t\r")
yield
finally:
if old is not None:
pe.set_default_whitespace_chars(old)
if kchars is not None:
pp.Keyword.DEFAULT_KEYWORD_CHARS = kchars
[docs]
def replace_whitechars(expr):
expr = expr.copy()
expr.set_whitespace_chars(" \t\r")
return expr
with generate_grammar():
optional_line_end = pp.Suppress(pp.LineEnd() | pp.WordStart()).set_name(" ")
""" Grammar for an optinal newline """
line_end = pp.Suppress(pp.LineEnd()).set_name("\n")
""" Grammar for a required newline """
end_of_file = (pp.Regex(r"[\s]*") + pp.StringEnd()).suppress().set_name("<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)).set_name(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 add_condition_ex(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.add_parse_action(check_condition)
return self
[docs]
def add_parse_action_ex(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.add_parse_action(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.set_whitespace_chars("")
[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.add_condition_ex = add_condition_ex
pp.ParserElement.add_parse_action_ex = add_parse_action_ex