SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
fc3e70a verified
Raw History Blame Contribute Delete
5.13 kB
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
"""Phase 10: Regex Latin lexer β€” token recognition and field extraction.
The lexer recognizes structure. It does NOT determine semantic meaning.
Invalid Latin blocks fail closed.
"""
from __future__ import annotations
import re
from dataclasses import dataclass, field
from typing import Optional
from .latin import LatinCanonicalForm, LatinToken, vocabulary
from .types import FSMState, PipelineError
# ── Regex patterns ────────────────────────────────────────────────────────────
# Block delimiters
_RE_BLOCK_OPEN = re.compile(r"\bINITIUM\b")
_RE_BLOCK_CLOSE = re.compile(r"\bFINIS\b")
# Operators
_RE_OPERATORS = re.compile(
r"\b(ET|VEL|NON|AUT|SI|IFF|VERUM|FALSUM)\b"
)
# Operands with optional payload: OPUS[varname]
_RE_OPERAND = re.compile(r"\b(OPUS|FORMA|COMPUTA|MULTIPLICA|ADDE|SUBTRAHE|PARSE|"
r"AEDIFICA|GENERA|VERIFICA|CONVERTE|RESTITUE|"
r"MATRIX|VECTOR|PARSER|SERVITIUM|FORMA_BINARIA|TEMPUS)"
r"(?:\[([^\]]*)\])?")
# Instruction fields: KEYWORD:VALUE
_RE_FIELD = re.compile(r"\b([A-Z_]+):(\S+)")
# Delimiters β€” semicolons, pipes
_RE_DELIM = re.compile(r"[;|]")
@dataclass(frozen=True)
class LexedToken:
kind: str # "operator", "operand", "open", "close", "field", "delim", "unknown"
lexeme: str
payload: str = ""
position: int = 0
@dataclass
class LexResult:
tokens: list[LexedToken] = field(default_factory=list)
errors: list[str] = field(default_factory=list)
valid: bool = True
# ── Lexer ─────────────────────────────────────────────────────────────────────
class LatinLexer:
"""
Regex-based lexer for Latin canonical form text.
Produces LexResult with typed tokens.
"""
def lex(self, text: str) -> LexResult:
result = LexResult()
vocab = vocabulary()
pos = 0
tpos = 0
# Tokenize by scanning left to right
while pos < len(text):
# Skip whitespace
ws = re.match(r"\s+", text[pos:])
if ws:
pos += ws.end()
continue
# Block open/close
m = _RE_BLOCK_OPEN.match(text, pos)
if m:
result.tokens.append(LexedToken("open", "INITIUM", position=tpos))
pos = m.end(); tpos += 1; continue
m = _RE_BLOCK_CLOSE.match(text, pos)
if m:
result.tokens.append(LexedToken("close", "FINIS", position=tpos))
pos = m.end(); tpos += 1; continue
# Operands (with optional [payload])
m = _RE_OPERAND.match(text, pos)
if m:
lexeme = m.group(1)
payload = m.group(2) or ""
if lexeme not in vocab:
result.errors.append(f"Unknown lexeme '{lexeme}' at pos {pos}")
result.valid = False
result.tokens.append(LexedToken("operand", lexeme, payload, tpos))
pos = m.end(); tpos += 1; continue
# Operators
m = _RE_OPERATORS.match(text, pos)
if m:
lexeme = m.group(1)
if lexeme not in vocab:
result.errors.append(f"Unknown operator '{lexeme}' at pos {pos}")
result.valid = False
result.tokens.append(LexedToken("operator", lexeme, position=tpos))
pos = m.end(); tpos += 1; continue
# Field annotations
m = _RE_FIELD.match(text, pos)
if m:
result.tokens.append(LexedToken("field", m.group(1), m.group(2), tpos))
pos = m.end(); tpos += 1; continue
# Delimiters
m = _RE_DELIM.match(text, pos)
if m:
result.tokens.append(LexedToken("delim", m.group(0), position=tpos))
pos = m.end(); tpos += 1; continue
# Unknown character/word β€” fail closed
word_m = re.match(r"\S+", text[pos:])
unknown = word_m.group(0) if word_m else text[pos]
result.errors.append(f"Unrecognized token '{unknown}' at position {pos}")
result.valid = False
pos += len(unknown); tpos += 1
if not result.valid:
raise PipelineError(
FSMState.LATIN_LEXING,
"Latin lexer errors: " + "; ".join(result.errors),
)
return result
def lex_canonical(self, form: LatinCanonicalForm) -> LexResult:
"""Lex from a LatinCanonicalForm directly."""
return self.lex(form.text())