Sesiones de Lenguaje (`gram.sessions.Session`)
En Gram, los catálogos léxicos (lexer.words.MAP_KEYWORDS, lexer.words.groups, lexer.tokens.MAP_SYMBOLS) y las opciones de tiempo de ejecución (gram.config) son registros globales.
Cuando un compilador o herramienta necesita parsear múltiples lenguajes o formatos de archivo dentro del mismo proceso de Python—por ejemplo, un flujo de trabajo que procesa un DSL de configuración (comentarios con ;, docstrings activados), archivos .c (comentarios con //, palabras clave de C) y contratos de cabecera .h—gram.sessions.Session aisla el estado de cada lenguaje y permite alternar entre ellos in-situ.
1. Cómo Funciona Session
Session (gram.sessions.Session) captura y restaura cuatro registros globales:
words.MAP_KEYWORDS: Todas las instancias deKeywordregistradas.words.groups._groups: Todas las instancias deWordGroupregistradas.tokens.MAP_SYMBOLS: El diccionario de búsqueda de símbolos aToken.gram.config: Todas las flags de configuración en mayúsculas (config.save()/config.load()).
Las instantáneas (snapshots) se almacenan dentro de la instancia de Session como tuplas compactas, y session.load() muta los diccionarios globales in-situ para que las referencias existentes de los módulos sigan siendo válidas.
Firma del Constructor
from gram import sessions
session = sessions.Session(
name: str,
mode: Literal["Many", "Some"],
entrypoint: type[RuleItem] | None = None,
)
name: Identificador descriptivo para la sesión (ej.:"Session From C").mode: Modo del parser de nivel superior ("Many"para $0..n$ sentencias o"Some"para $1..n$ sentencias). Pasar cualquier otro valor lanzaValueError.entrypoint: ClaseRuleItemraíz por defecto opcional usada porsession.parse()cuando no se pasaentrypointen la llamada.
2. Ciclo de Vida de una Sesión
Definir una sesión de lenguaje sigue un patrón estricto de inicialización en 4 pasos, seguido de load() / tokenize() / parse() en tiempo de ejecución:
| Método | Fase | Acción |
|---|---|---|
session.clear_global() |
Configuración (Paso 1) | Llama a words.clear_all(), limpiando MAP_KEYWORDS y words.groups para que las palabras clave de lenguajes anteriores no se filtren en esta sesión. |
session.init() |
Configuración (Paso 2) | Guarda una instantánea de gram.config y tokens.MAP_SYMBOLS globales que estaban activos antes de configurar esta sesión. |
| (Configurar lenguaje) | Configuración (Paso 3) | Establece flags de gram.config, registra words.add_group() / words.add_keyword(), registra símbolos personalizados y define/importa clases RuleItem. |
session.migrate() |
Configuración (Paso 4) | Guarda en self los MAP_KEYWORDS, WordGroups, config y MAP_SYMBOLS de la sesión, y restaura la instantánea previa de config y MAP_SYMBOLS capturada por init(). |
session.load() |
Ejecución | Aplica las palabras clave, grupos, símbolos y config guardados de esta sesión a los registros globales. (Debe haberse llamado a migrate() previamente, o lanzará RuntimeError.) |
session.tokenize(source, name) |
Ejecución | Ejecuta Lexer(source, name).process() bajo el estado de la sesión activa y retorna list[TokenType]. |
session.parse(tokens, name, entrypoint=None, conversion_state=None) |
Ejecución | Ejecuta el parser usando entrypoint o self.entrypoint; el estado opcional permite compartir conversiones explícitamente entre archivos. |
3. Ejemplo de Frontend Multilenguaje
El siguiente ejemplo completo y ejecutable demuestra dos sesiones de lenguaje aisladas—un frontend estilo C (comentarios //, palabra clave int) y un frontend para un DSL de scripting (comentarios ;, palabra clave fn)—coexistiendo en el mismo proceso:
from gram import combinators as cmb
from gram import config, lexer, sessions
# ==============================================================================
# 1. DEFINIR SESIÓN A: Frontend estilo C (comentarios "//")
# ==============================================================================
C_SESSION = sessions.Session("C Frontend", "Many")
C_SESSION.clear_global()
C_SESSION.init()
config.LEXER_COMMENT_TOKEN = "//"
config.LEXER_SUPPORT_DOCSTRINGS = False
config.LEXER_IGNORE_EMPTY_LINES = True
lexer.words.add_group("types", color_group="#3B82F6", allow_override=True)
lexer.words.add_keyword("int", group="types", allow_override=True)
class CVarDecl(cmb.RuleItem):
entrypoint = cmb.Seq(
cmb.MatchGroup("types"),
cmb.MatchToken(lexer.Token.IDENT),
cmb.MatchToken(lexer.Token.SEMICOLON),
)
class CProgram(cmb.RuleItem):
entrypoint = cmb.Alt(cmb.Ref(CVarDecl))
C_SESSION.entrypoint = CProgram
C_SESSION.migrate()
# ==============================================================================
# 2. DEFINIR SESIÓN B: Frontend de Script DSL (comentarios ";")
# ==============================================================================
SCRIPT_SESSION = sessions.Session("Script Frontend", "Many")
SCRIPT_SESSION.clear_global()
SCRIPT_SESSION.init()
config.LEXER_COMMENT_TOKEN = ";"
config.LEXER_SUPPORT_DOCSTRINGS = True
config.LEXER_IGNORE_EMPTY_LINES = True
lexer.words.add_keyword("fn", hex_color="#FF79C6", allow_override=True)
class ScriptFuncDecl(cmb.RuleItem):
entrypoint = cmb.Seq(
cmb.MatchKeyword("fn"),
cmb.MatchToken(lexer.Token.IDENT),
cmb.Enclosed(
open=lexer.Token.LPAREN,
close=lexer.Token.RPAREN,
content=cmb.MatchToken(lexer.Token.IDENT),
allow_empty=True,
),
)
class ScriptProgram(cmb.RuleItem):
entrypoint = cmb.Alt(cmb.Ref(ScriptFuncDecl))
SCRIPT_SESSION.entrypoint = ScriptProgram
SCRIPT_SESSION.migrate()
# ==============================================================================
# 3. CAMBIAR SESIONES EN TIEMPO DE EJECUCIÓN
# ==============================================================================
# Parsear código C usando C_SESSION
C_SESSION.load()
c_tokens = C_SESSION.tokenize("int counter; // C comment", "sample.c")
c_ast = C_SESSION.parse(c_tokens, "sample.c")
print("C AST:", c_ast)
# Cambiar a SCRIPT_SESSION y parsear código del Script DSL
SCRIPT_SESSION.load()
script_tokens = SCRIPT_SESSION.tokenize("fn main() ; Script comment", "sample.dsl")
script_ast = SCRIPT_SESSION.parse(script_tokens, "sample.dsl")
print("Script AST:", script_ast)
Salida:
C AST: [AstRef(nodes=3, name=CVarDecl)]
Script AST: [AstRef(nodes=2, name=ScriptFuncDecl)]
Observa cómo en C_SESSION, ; se tokeniza como Token.SEMICOLON mientras que // C comment se descarta como comentario; tan pronto como se llama a SCRIPT_SESSION.load(), ; Script comment pasa a ser el delimitador de comentarios y fn se reconoce como Token.KEYWORD.
Compartir conversiones entre archivos
Cada llamada a session.parse() comienza con conversiones aisladas si no se proporciona un estado. Para conservar adiciones de un archivo y usarlas en otro, cree un estado explícito:
state = C_SESSION.new_conversion_state()
C_SESSION.parse(header_tokens, "header.h", conversion_state=state)
C_SESSION.parse(source_tokens, "main.c", conversion_state=state)
El estado puede copiarse con state.clone() o limpiarse con state.clear(). El estado externo solo se actualiza cuando el parseo termina correctamente.
Registro de delimitadores multilínea
El registro MULTLINE (add_multline() / remove_multline()) es global y no
forma parte de las instantáneas de Session. Registre los delimitadores antes
de crear el Lexer de un archivo y elimínelos o restáurelos explícitamente si
otro lenguaje no debe verlos. Cada Lexer copia el registro al construirse.