Reglas, Átomos y Almacenamiento de Estado
Estas clases y combinadores definen reglas modulares de gramática (RuleItem, Ref), secuencias atómicas de tokens (Item), verificaciones de valores literales (Literal) y almacenamiento de tokens seguro ante checkpoints (Save, Load).
1. RuleItem (Clase de Regla de Producción)
Clase base declarativa (gram.core.combinators.base.RuleItem) para reglas gramaticales. Su metaclase (RuleMeta) asigna automáticamente name (tomando por defecto el nombre de la clase) y asigna un code numérico único a través de AutoCode en code_manager.
Atributos de Clase
class MyRule(RuleItem):
entrypoint: Combinator | None = None
name: str = "" # Por defecto 'MyRule'
code: int = 0 # Auto-asignado >= 21 a menos que se use SetCode(n)
nodeable: bool = True # Si es True, Ref(MyRule) envuelve los resultados en ASTRef
description: str = ""
# Mapas opcionales de metadatos VSIX / LSP (con claves por índice de token o 'this'):
colors: dict[int | str, str] = {}
autocomplete: dict[int | str, str] = {}
suggestions: dict[int | str, Any] = {}
hints: dict[int | str, str] = {}
playhints: dict[int | str, str] = {}
Requisitos del Punto de Entrada Raíz (Entrypoint)
Cuando se pasa una subclase de RuleItem como entrypoint raíz a ASTAnalyzer(parser, RootRule) o Parser.parse(RootRule), ASTState.is_valid_entrypoint() comprueba que:
RootRule.entrypointsea un combinadorAlt(de lo contrario reportaInvalidEntryPointCombinator).- Cada hijo dentro de ese
Altsea un combinadorRef(de lo contrario reportaInvalidEntryPointElement).
2. Ref (Referencia a Regla)
Delega el parseo a rule.entrypoint en tiempo de evaluación, permitiendo recursión mutua y referencias anticipadas (forward references).
Constructor
Ref(
rule: type[RuleItem],
name: str | None = None,
strict: bool = False,
)
name: Etiqueta para el nodoASTRefresultante.strict:- Si es
False(por defecto) yname is None, sigetattr(rule, "nodeable", False)esTrue(que es el valor predeterminado enRuleItem),self.namese establece automáticamente comorule.name. - Si es
Trueyname is None,self.namepermanece comoNoneincluso sirule.nodeableesTrue.
- Si es
Forma de Retorno y Diagnósticos
- Si
rule.entrypointesNone, reportaEmptyRuleReference(GrammarError) y retornaNone. - Si
self.nameno está vacío: envuelve el resultado coincidente enASTRef(name=self.name, nodes=...)(nodeses siempre unalist). - Si
self.nameesNone: retorna el resultado directo derule.entrypointsin envolverlo enASTRef.
Ejemplo
from gram.core.combinators import MatchKeyword, MatchToken, Ref, RuleItem, Seq
from gram.core.lexer import Token
class Expr(RuleItem):
nodeable = True
entrypoint = MatchToken(Token.NUMBER)
class ReturnStmt(RuleItem):
entrypoint = Seq(
MatchKeyword("return"),
Ref(Expr), # produce ASTRef(name='Expr', nodes=[LiteralNumber])
)
3. Item
Evalúa una secuencia fija de combinadores productores de tokens como una unidad atómica y aplana todos los objetos TokenType coincidentes en un ItemNode (subclase de list[TokenType]).
Constructor
Item(*matchs: Combinator)
Ejecución y Forma de Retorno
- Si se invoca sin combinadores (
Item()), retorna unItemNode([])vacío. - Evalúa cada hijo dentro de
analyzer.safe_mode(). - Si algún hijo retorna
None, restaura el checkpoint inicial, reporta/emiteIncompleteItemSequence(GrammarError) y retornaNone. - Extrae objetos
TokenTypedel resultado de cada hijo (TokenType,ASTTokenNodemediante.token,ItemNode,LiteralNode,list,tuple). Si un hijo retorna cualquier otro objeto (como unASTRef), restaura el checkpoint, reportaInvalidItemElementType(GrammarError) y retornaNone. - Valor de Retorno:
ItemNode(tokens)en caso de éxito, oNoneen caso de fallo.
Ejemplo
from gram.core.combinators import Item, MatchToken
from gram.core.lexer import Token
double_colon = Item(
MatchToken(Token.COLON),
MatchToken(Token.COLON),
)
4. Literal
Valida el .value primitivo del token actual contra literal (o acepta cualquier token que no sea una palabra clave cuando se omite literal).
Constructor
Literal(literal: Any = _ANY_LITERAL)
- Los tokens
Token.KEYWORDsiempre son rechazados (reportandoKeywordNotAllowedInLiteral). - Cuando se proporciona
literal, compruebatok.value == literal(incluyendo igualdad numérica entreintyfloat).
Forma de Retorno y Diagnósticos
- Coincidencia: Consume el token y retorna
LiteralNode([tok])(subclase delist[TokenType]). - Discrepancia / EOF: Reporta
LiteralUnexpectedEOF,KeywordNotAllowedInLiteraloLiteralValueMismatch(ParserError) y retornaNone.
Ejemplo
from gram.core.combinators import Literal
match_zero = Literal(0)
match_any_non_keyword = Literal()
5. Save
Almacena el TokenType actualmente en parser.pos en el diccionario seguro ante checkpoints parser.storage[name] o en un almacenamiento externo proporcionado por el usuario.
Constructor
Save(
name: str,
matcher: Combinator | None = None,
*,
consume: bool = True,
storage: MutableMapping[str, TokenType] | None = None,
)
storage es opcional. Si se omite, Save utiliza parser.storage; si se
proporciona un diccionario, guarda allí el token y ese diccionario queda
integrado en los checkpoints del parser.
Modos de Consumo
matcher=None(captura de ancho cero): Almacenaparser.peek()en el almacenamiento seleccionado sin hacer avanzarparser.posy retorna elTokenTypealmacenado.matcherprovisto,consume=True(por defecto): Evalúamatcher(haciendo avanzarparser.pos), almacena el token original en el almacenamiento seleccionado y retorna el resultado AST dematcher. Simatcherfalla, restaura el checkpoint, reportaSaveMatcherFailedy retornaNone.matcherprovisto,consume=False(captura con lookahead validado): Evalúamatcher, luego restaura el checkpoint de modo queparser.posno avanza, almacena el token en el almacenamiento seleccionado y retorna elTokenType.
Ejemplo
from gram.core.combinators import Load, MatchToken, Save
from gram.core.lexer import Token
save_open_tag = Save("open_tag", MatchToken(Token.IDENT), consume=True)
typedef_storage = {}
save_typedef = Save(
"current_type",
MatchToken(Token.IDENT),
storage=typedef_storage,
)
load_typedef = Load(
"current_type",
mode="get",
storage=typedef_storage,
)
6. Load
Lee, compara o intercambia un TokenType guardado previamente en parser.storage[name]
o en un almacenamiento externo proporcionado explícitamente.
Constructor
Load(
name: str,
mode: Literal["get", "match", "unmatch", "replace", "change"] = "get",
matcher: Combinator | None = None,
*,
storage: Mapping[str, TokenType] | None = None,
)
Cuando se proporciona storage, Load utiliza ese diccionario en lugar de
parser.storage. El mismo diccionario debe pasarse a Save y Load para
compartir typedefs o tipos dinámicos aislados.
Modos y Consumo de Tokens
Importante: En los modos
"match","unmatch","replace"y"change",Loadsolo avanzaparser.possi se pasa un combinadormatcher. Cuandomatcher=None, dichos modos operan enparser.possin consumir el token.
| Modo | Comportamiento | Valor de Retorno (matcher=None) |
Valor de Retorno (matcher provisto) |
|---|---|---|---|
"get" |
Retorna el token guardado en el almacenamiento seleccionado sin inspeccionar ni mover el cursor. | TokenType guardado |
TokenType guardado (matcher ignorado) |
"match" |
Requiere current.token == saved.token y current.value == saved.value (de lo contrario reporta LoadMatchFailed). |
current (TokenType), cursor inalterado |
Resultado de matcher (consume token) |
"unmatch" |
Requiere que current difiera en tipo o valor respecto a saved (de lo contrario reporta LoadUnmatchFailed). |
current (TokenType), cursor inalterado |
Resultado de matcher (consume token) |
"replace" |
Reemplaza parser.tokens[parser.pos] in-situ por saved (rastreado para reversión por checkpoint). |
saved (TokenType), cursor inalterado |
Resultado de matcher sobre el token reemplazado |
"change" |
Intercambia parser.tokens[parser.pos] y el token guardado en el almacenamiento seleccionado. |
saved (TokenType), cursor inalterado |
Resultado de matcher sobre el token intercambiado |
Diagnósticos
StorageKeyNotFound(GrammarError) sinameno está en el almacenamiento seleccionado.LoadUnexpectedEOF(ParserError) simode != "get"y el flujo está vacío.LoadMatcherFailed(GrammarError) simatcherfalla (restando el checkpoint, incluyendo cualquier mutación por"replace"o"change").
Ejemplo
from gram.core.combinators import Load, MatchToken, Save, Seq
from gram.core.lexer import Token
# Coincide con un identificador de apertura y exige que el siguiente identificador tenga el mismo token y valor:
paired_ident = Seq(
Save("tag", MatchToken(Token.IDENT)),
Load("tag", mode="match", matcher=MatchToken(Token.IDENT)),
)