1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
from __future__ import annotations
import pathlib
import typing as ty
import flexcache as fc
import flexparser as fp
from ..base_defparser import ParserConfig
from . import block, common, context, defaults, group, plain, system
class PintRootBlock(
fp.RootBlock[
ty.Union[
plain.CommentDefinition,
common.ImportDefinition,
context.ContextDefinition,
defaults.DefaultsDefinition,
system.SystemDefinition,
group.GroupDefinition,
plain.AliasDefinition,
plain.DerivedDimensionDefinition,
plain.DimensionDefinition,
plain.PrefixDefinition,
plain.UnitDefinition,
],
ParserConfig,
]
):
pass
class _PintParser(fp.Parser[PintRootBlock, ParserConfig]):
"""Parser for the original Pint definition file, with cache."""
_delimiters = {
"#": (
fp.DelimiterInclude.SPLIT_BEFORE,
fp.DelimiterAction.CAPTURE_NEXT_TIL_EOL,
),
**fp.SPLIT_EOL,
}
_root_block_class = PintRootBlock
_strip_spaces = True
_diskcache: fc.DiskCache | None
def __init__(self, config: ParserConfig, *args: ty.Any, **kwargs: ty.Any):
self._diskcache = kwargs.pop("diskcache", None)
super().__init__(config, *args, **kwargs)
def parse_file(
self, path: pathlib.Path
) -> fp.ParsedSource[PintRootBlock, ParserConfig]:
if self._diskcache is None:
return super().parse_file(path)
content, _basename = self._diskcache.load(path, super().parse_file)
return content
class DefParser:
skip_classes: tuple[type, ...] = (
fp.BOF,
fp.BOR,
fp.BOS,
fp.EOS,
plain.CommentDefinition,
)
def __init__(self, default_config: ParserConfig, diskcache: fc.DiskCache):
self._default_config = default_config
self._diskcache = diskcache
def iter_parsed_project(
self, parsed_project: fp.ParsedProject[PintRootBlock, ParserConfig]
) -> ty.Generator[fp.ParsedStatement[ParserConfig], None, None]:
last_location = None
for stmt in parsed_project.iter_blocks():
if isinstance(stmt, fp.BOS):
if isinstance(stmt, fp.BOF):
last_location = str(stmt.path)
continue
elif isinstance(stmt, fp.BOR):
last_location = (
f"[package: {stmt.package}, resource: {stmt.resource_name}]"
)
continue
else:
last_location = "orphan string"
continue
if isinstance(stmt, self.skip_classes):
continue
assert isinstance(last_location, str)
if isinstance(stmt, common.DefinitionSyntaxError):
stmt.set_location(last_location)
raise stmt
elif isinstance(stmt, block.DirectiveBlock):
for exc in stmt.errors:
exc = common.DefinitionSyntaxError(str(exc))
exc.set_position(*stmt.get_position())
exc.set_raw(
(stmt.opening.raw or "") + " [...] " + (stmt.closing.raw or "")
)
exc.set_location(last_location)
raise exc
try:
yield stmt.derive_definition()
except Exception as exc:
exc = common.DefinitionSyntaxError(str(exc))
exc.set_position(*stmt.get_position())
exc.set_raw(stmt.opening.raw + " [...] " + stmt.closing.raw)
exc.set_location(last_location)
raise exc
else:
yield stmt
def parse_file(
self, filename: pathlib.Path | str, cfg: ParserConfig | None = None
) -> fp.ParsedProject[PintRootBlock, ParserConfig]:
return fp.parse(
filename,
_PintParser,
cfg or self._default_config,
diskcache=self._diskcache,
strip_spaces=True,
delimiters=_PintParser._delimiters,
)
def parse_string(
self, content: str, cfg: ParserConfig | None = None
) -> fp.ParsedProject[PintRootBlock, ParserConfig]:
return fp.parse_bytes(
content.encode("utf-8"),
_PintParser,
cfg or self._default_config,
diskcache=self._diskcache,
strip_spaces=True,
delimiters=_PintParser._delimiters,
)
|