add proxy for the first part and try to develop the second part
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import dataclasses
|
||||
import re
|
||||
from collections.abc import Callable, MutableMapping
|
||||
from itertools import starmap
|
||||
from typing import Any, NamedTuple
|
||||
|
||||
from .ast import AST
|
||||
from .tokenizing import Tokenizer
|
||||
from .util.misc import cached_re_compile
|
||||
from .util.unicode_characters import C_DERIVE
|
||||
|
||||
|
||||
class UndefinedStr(str):
|
||||
pass
|
||||
|
||||
|
||||
_undefined_str = UndefinedStr('>>undefined<<')
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ParserConfig:
|
||||
owner: Any = None
|
||||
name: str | None = 'Test'
|
||||
filename: str = ''
|
||||
encoding: str = 'utf-8'
|
||||
|
||||
start: str | None = None # FIXME
|
||||
start_rule: str | None = None # FIXME
|
||||
rule_name: str | None = None # Backward compatibility
|
||||
|
||||
comments_re: re.Pattern | str | None = None
|
||||
eol_comments_re: re.Pattern | str | None = None
|
||||
|
||||
tokenizercls: type[Tokenizer] | None = None # FIXME
|
||||
semantics: type | None = None
|
||||
|
||||
comment_recovery: bool = False
|
||||
memoize_lookaheads: bool = True
|
||||
|
||||
colorize: bool = False
|
||||
trace: bool = False
|
||||
trace_filename: bool = False
|
||||
trace_length: int = 72
|
||||
trace_separator: str = C_DERIVE
|
||||
|
||||
# parser directives
|
||||
grammar: str | None = None
|
||||
left_recursion: bool = True
|
||||
|
||||
comments: str | None = None
|
||||
eol_comments: str | None = None
|
||||
keywords: list[str] | set[str] = dataclasses.field(default_factory=list)
|
||||
|
||||
ignorecase: bool | None = False
|
||||
namechars: str = ''
|
||||
nameguard: bool | None = None # implied by namechars
|
||||
whitespace: str | None = _undefined_str
|
||||
|
||||
parseinfo: bool = False
|
||||
|
||||
def __post_init__(self): # pylint: disable=W0235
|
||||
if self.ignorecase:
|
||||
self.keywords = [k.upper() for k in self.keywords]
|
||||
|
||||
if self.comments_re or self.eol_comments_re:
|
||||
raise AttributeError(
|
||||
"Both `comments_re` and `eol_comments_re` have been removed from parser configurations. " +
|
||||
"Please use `comments` and/or `eol_comments` instead`.",
|
||||
)
|
||||
del self.comments_re
|
||||
del self.eol_comments_re
|
||||
|
||||
if self.comments:
|
||||
cached_re_compile(self.comments)
|
||||
if self.eol_comments:
|
||||
cached_re_compile(self.eol_comments)
|
||||
if self.whitespace:
|
||||
cached_re_compile(self.whitespace)
|
||||
|
||||
@classmethod
|
||||
def new(
|
||||
cls,
|
||||
config: ParserConfig | None = None,
|
||||
owner: Any | None = None,
|
||||
**settings: Any,
|
||||
) -> ParserConfig:
|
||||
result = cls(owner=owner)
|
||||
if config is not None:
|
||||
result = config.replace_config(config)
|
||||
return result.replace(**settings)
|
||||
|
||||
# NOTE:
|
||||
# Using functools.cache directly makes objects of this class unhashable
|
||||
|
||||
def effective_rule_name(self):
|
||||
# note: there are legacy reasons for this mess
|
||||
return self.start_rule or self.rule_name or self.start
|
||||
|
||||
def _find_common(self, **settings: Any) -> MutableMapping[str, Any]:
|
||||
return {
|
||||
name: value
|
||||
for name, value in settings.items()
|
||||
if value is not None and hasattr(self, name)
|
||||
}
|
||||
|
||||
def replace_config(
|
||||
self, other: ParserConfig | None = None,
|
||||
) -> ParserConfig:
|
||||
if other is None:
|
||||
return self
|
||||
elif not isinstance(other, ParserConfig):
|
||||
raise TypeError(f'Unexpected type {type(other).__name__}')
|
||||
else:
|
||||
return self.replace(**vars(other))
|
||||
|
||||
# non-init fields cannot be used as arguments in `replace`, however
|
||||
# they are values returned by `vars` and `dataclass.asdict` so they
|
||||
# must be filtered out.
|
||||
# If the `ParserConfig` dataclass drops these fields, then this filter can be removed
|
||||
def _filter_non_init_fields(self, settings: MutableMapping[str, Any]) -> MutableMapping[str, Any]:
|
||||
for field in [
|
||||
field.name for field in dataclasses.fields(self) if not field.init
|
||||
]:
|
||||
if field in settings:
|
||||
del settings[field]
|
||||
return settings
|
||||
|
||||
def replace(self, **settings: Any) -> ParserConfig:
|
||||
overrides = self._filter_non_init_fields(self._find_common(**settings))
|
||||
result = dataclasses.replace(self, **overrides)
|
||||
if 'grammar' in overrides:
|
||||
result.name = result.grammar
|
||||
return result
|
||||
|
||||
def merge(self, **settings: Any) -> ParserConfig:
|
||||
overrides = self._find_common(**settings)
|
||||
overrides = {
|
||||
name: value
|
||||
for name, value in overrides.items()
|
||||
if getattr(self, name, None) is None
|
||||
}
|
||||
return self.replace(**overrides)
|
||||
|
||||
def asdict(self):
|
||||
# warning: it seems dataclasses.asdict does a deepcopy
|
||||
# result = dataclasses.asdict(self)
|
||||
result = copy.copy(vars(self))
|
||||
result.pop('owner', None)
|
||||
return result
|
||||
|
||||
|
||||
class PosLine(NamedTuple):
|
||||
start: int
|
||||
line: int
|
||||
length: int
|
||||
|
||||
@staticmethod
|
||||
def build_line_cache(lines):
|
||||
cache = []
|
||||
n = 0
|
||||
i = 0
|
||||
for n, line in enumerate(lines):
|
||||
pl = PosLine(i, n, len(line))
|
||||
for _ in line:
|
||||
cache.append(pl) # noqa: PERF401
|
||||
i += len(line)
|
||||
n += 1
|
||||
if lines and lines[-1] and lines[-1][-1] in '\r\n':
|
||||
n += 1
|
||||
cache.append(PosLine(i, n, 0))
|
||||
return cache, n
|
||||
|
||||
|
||||
class LineIndexInfo(NamedTuple):
|
||||
filename: str
|
||||
line: int
|
||||
|
||||
@staticmethod
|
||||
def block_index(name, n):
|
||||
return list(
|
||||
starmap(LineIndexInfo, zip(n * [name], range(n), strict=False)),
|
||||
)
|
||||
|
||||
|
||||
class LineInfo(NamedTuple):
|
||||
filename: str
|
||||
line: int
|
||||
col: int
|
||||
start: int
|
||||
end: int
|
||||
text: int
|
||||
|
||||
|
||||
class CommentInfo(NamedTuple):
|
||||
inline: list
|
||||
eol: list
|
||||
|
||||
@staticmethod
|
||||
def new_comment():
|
||||
return CommentInfo([], [])
|
||||
|
||||
|
||||
class Alert(NamedTuple):
|
||||
level: int = 1
|
||||
message: str = ''
|
||||
|
||||
|
||||
class ParseInfo(NamedTuple):
|
||||
tokenizer: Any
|
||||
rule: str
|
||||
pos: int
|
||||
endpos: int
|
||||
line: int
|
||||
endline: int
|
||||
alerts: list[Alert] = [] # noqa: RUF012
|
||||
|
||||
def text_lines(self):
|
||||
return self.tokenizer.get_lines(self.line, self.endline)
|
||||
|
||||
def line_index(self):
|
||||
return self.tokenizer.line_index(self.line, self.endline)
|
||||
|
||||
@property
|
||||
def buffer(self):
|
||||
return self.tokenizer
|
||||
|
||||
|
||||
class MemoKey(NamedTuple):
|
||||
pos: int
|
||||
rule: str
|
||||
state: Any
|
||||
|
||||
|
||||
class RuleInfo(NamedTuple):
|
||||
name: str
|
||||
impl: Callable
|
||||
is_leftrec: bool
|
||||
is_memoizable: bool
|
||||
is_name: bool
|
||||
params: list
|
||||
kwparams: dict
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, RuleInfo):
|
||||
return self.name == other.name
|
||||
return False
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
class RuleResult(NamedTuple):
|
||||
node: Any
|
||||
newpos: int
|
||||
newstate: Any
|
||||
|
||||
|
||||
@dataclasses.dataclass(slots=True)
|
||||
class ParseState:
|
||||
pos: int = 0
|
||||
ast: AST = dataclasses.field(default_factory=AST)
|
||||
cst: Any = None
|
||||
alerts: list[Alert] = dataclasses.field(default_factory=list)
|
||||
Reference in New Issue
Block a user