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
|
# Copyright (c) 2017-2026 Juancarlo AƱez (apalala@gmail.com)
# SPDX-License-Identifier: BSD-4-Clause
from __future__ import annotations
import re
import warnings
from collections.abc import Collection
from dataclasses import dataclass, field
from typing import Any, override
from .tokenizing import NullTokenizer, Tokenizer
from .util.configs import Config
from .util.misc import cached_re_compile
from .util.undefined import Undefined
from .util.unicode_characters import C_DERIVE
MEMO_CACHE_SIZE = 4 * 1024
@dataclass
class ParserConfig(Config):
name: str | None = 'Test'
filename: str = ''
start: str | None = None
tokenizercls: type[Tokenizer] = NullTokenizer
semantics: Any = None
comment_recovery: bool = False
memoization: bool = True
memoize_lookaheads: bool = True
memo_cache_size: int = MEMO_CACHE_SIZE
prune_memos_on_cut: bool = True
colorize: bool = True # INFO: requires the colorama library
trace: bool = False
trace_filename: str = ''
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: Collection[str] = field(default_factory=set)
ignorecase: bool = False
namechars: str | None = None
nameguard: bool | None = None # implied by namechars
whitespace: str | None = Undefined # type: ignore
parseinfo: bool = False
# WARNING: DEPRECATED: some old projects use these
owner: Any = None
extra: Any = None
start_rule: str | None = None
rule_name: str | None = None
comments_re: re.Pattern | str | None = None
eol_comments_re: re.Pattern | str | None = None
def __post_init__(self): # pylint: disable=W0235
if self.ignorecase:
self.keywords = {k.upper() for k in self.keywords}
super().__post_init__()
if not self.memoization:
self.left_recursion = False
if self.namechars:
self.nameguard = True
if isinstance(self.semantics, type):
raise TypeError(
f'semantics must be an object instance or None, not class {self.semantics!r}',
)
self._deprecate_and_compile_comments()
def _deprecate_and_compile_comments(self):
# note: handle deprecations gracefully
if self.comments_re:
warnings.warn(
'ParserConfig.comments_re is deprecated: use `comments`',
stacklevel=3,
)
if not self.comments:
self.comments = str(self.comments_re)
del self.comments_re
if self.eol_comments_re:
warnings.warn(
'ParserConfig.eol_comments_re is deprecated: use `eol_comments`',
stacklevel=3,
)
if not self.eol_comments:
self.eol_comments = str(self.eol_comments_re)
del self.eol_comments_re
if self.comments and isinstance(self.comments, str):
cached_re_compile(self.comments)
if self.eol_comments and not isinstance(self.eol_comments, re.Pattern):
cached_re_compile(self.eol_comments)
if self.whitespace and not isinstance(self.whitespace, re.Pattern):
cached_re_compile(self.whitespace)
for name in ('start_rule', 'rule_name'):
if (value := getattr(self, name, None)) is None:
continue
warnings.warn(
message=(
f'\nSetting {name}={value!r} is deprecated.'
f' Use start={value!r} for the name of the start rule.'
),
category=DeprecationWarning,
stacklevel=2,
)
break
self.start = self.effective_start_rule_name()
def effective_start_rule_name(self):
# NOTE: there are legacy reasons for this mess
return (
self.start
or getattr(self, 'start_rule', None)
or getattr(self, 'rule_name', None)
)
@override
def override(self, **settings: Any) -> ParserConfig:
result = super().override(**settings)
if 'grammar' in settings:
result.name = result.grammar
self._deprecate_and_compile_comments()
return result
|