File size: 5,406 Bytes
fc3e70a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# 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 9: Latin canonicalizer β€” controlled vocabulary mapping.

Boolean tokens β†’ Semantic IDs β†’ Latin lexemes.
Every Latin lexeme maps to a stable semantic_id. No uncontrolled synonyms.
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

from .token_engine import (
    BoolToken, BooleanTokenStream,
    TTYPE_AND, TTYPE_OR, TTYPE_NOT, TTYPE_XOR,
    TTYPE_IMPLIES, TTYPE_EQUIVALENCE, TTYPE_TRUE, TTYPE_FALSE,
    TTYPE_OPEN, TTYPE_CLOSE, TTYPE_VAR, TTYPE_VAR_NEG,
)
from .types import FSMState, PipelineError

# Map from token type β†’ canonical Latin lexeme
_TTYPE_TO_LATIN: dict[int, str] = {
    TTYPE_AND:         "ET",
    TTYPE_OR:          "VEL",
    TTYPE_NOT:         "NON",
    TTYPE_XOR:         "AUT",
    TTYPE_IMPLIES:     "SI",
    TTYPE_EQUIVALENCE: "IFF",
    TTYPE_TRUE:        "VERUM",
    TTYPE_FALSE:       "FALSUM",
    TTYPE_OPEN:        "INITIUM",
    TTYPE_CLOSE:       "FINIS",
}

_LATIN_TO_TTYPE: dict[str, int] = {v: k for k, v in _TTYPE_TO_LATIN.items()}


# ── Latin token ───────────────────────────────────────────────────────────────

@dataclass(frozen=True)
class LatinToken:
    lexeme: str
    semantic_id: str
    position: int
    payload: str = ""       # variable name for VAR tokens


@dataclass
class LatinLexeme:
    word: str
    semantic_id: str
    operator: Optional[str]
    arity: int


@dataclass
class LatinCanonicalForm:
    tokens: list[LatinToken] = field(default_factory=list)
    source_hash: str = ""

    def text(self) -> str:
        return " ".join(t.lexeme if not t.payload else f"{t.lexeme}[{t.payload}]" for t in self.tokens)


# ── Vocabulary loader ─────────────────────────────────────────────────────────

def _load_vocabulary() -> dict[str, LatinLexeme]:
    vocab_path = Path(__file__).parent.parent.parent / "lexicon" / "latin_vocabulary.json"
    if not vocab_path.exists():
        return {}
    data = json.loads(vocab_path.read_text())
    return {
        word: LatinLexeme(
            word=word,
            semantic_id=entry["semantic_id"],
            operator=entry.get("operator"),
            arity=entry.get("arity", 0),
        )
        for word, entry in data.get("lexemes", {}).items()
    }


_VOCABULARY: dict[str, LatinLexeme] = {}


def vocabulary() -> dict[str, LatinLexeme]:
    global _VOCABULARY
    if not _VOCABULARY:
        _VOCABULARY = _load_vocabulary()
    return _VOCABULARY


# ── Canonicalizer ─────────────────────────────────────────────────────────────

class LatinCanonicalizer:
    """Transform a BooleanTokenStream into a LatinCanonicalForm."""

    def canonicalize(self, stream: BooleanTokenStream) -> LatinCanonicalForm:
        vocab = vocabulary()
        tokens: list[LatinToken] = []

        for i, tok in enumerate(stream.tokens):
            if tok.token_type in (TTYPE_VAR, TTYPE_VAR_NEG):
                lexeme = "NON" if tok.token_type == TTYPE_VAR_NEG else "OPUS"
                sem_id = "BOOL_NOT" if tok.token_type == TTYPE_VAR_NEG else "SEMANTIC_OP"
                var_name = tok.payload.decode("utf-8") if tok.payload else ""
                if tok.token_type == TTYPE_VAR_NEG:
                    tokens.append(LatinToken(lexeme="NON", semantic_id="BOOL_NOT", position=i))
                tokens.append(LatinToken(
                    lexeme="OPUS",
                    semantic_id="SEMANTIC_OP",
                    position=i,
                    payload=var_name,
                ))
                continue

            latin = _TTYPE_TO_LATIN.get(tok.token_type)
            if latin is None:
                raise PipelineError(
                    FSMState.LATIN_CANONICALIZATION,
                    f"No Latin mapping for token type 0x{tok.token_type:02x} at position {i}",
                )

            lexeme_def = vocab.get(latin)
            if lexeme_def is None:
                raise PipelineError(
                    FSMState.LATIN_CANONICALIZATION,
                    f"Latin lexeme '{latin}' not in controlled vocabulary",
                )

            tokens.append(LatinToken(
                lexeme=latin,
                semantic_id=lexeme_def.semantic_id,
                position=i,
            ))

        import hashlib
        src_hash = hashlib.sha256(" ".join(t.lexeme for t in tokens).encode()).hexdigest()[:16]
        return LatinCanonicalForm(tokens=tokens, source_hash=src_hash)


# ── Inverse: Latin β†’ semantic IDs ─────────────────────────────────────────────

def latin_to_semantic_ids(form: LatinCanonicalForm) -> list[tuple[str, str]]:
    """Return list of (lexeme, semantic_id) pairs."""
    return [(t.lexeme, t.semantic_id) for t in form.tokens]