File size: 7,123 Bytes
ed6bec6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# interpreter/decoder.py




from typing import Dict, Any, List, Optional
from .glyph_syntax import parse_syntax




def simplify_entry(entry: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:

    """
    Normalize a single syntax entry into a stable internal form.
    - For normal glyphs: keep id, glyph, primary, category, roles.
    - For fallback: surface the raw term as `primary` and `id`,
      drop glyph, keep category="fallback".
    """

    if entry is None:
        return None

    category = entry.get("category")
    primary = entry.get("primary")

    # Fallback entries: we only care about the surfaced term
    if category == "fallback":
        return {
            "id": primary,
            "glyph": None,
            "primary": primary,
            "category": "fallback",
            "roles": entry.get("roles", []),
        }

    return {
        "id": entry.get("id"),
        "glyph": entry.get("glyph"),
        "primary": primary,
        "category": category,
        "roles": entry.get("roles", []),
    }


def simplify_list(entries: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    return [simplify_entry(e) for e in entries if e is not None]


def decode_glyphs(glyphs: List[str]) -> Dict[str, Any]:
    """
    Strict decoding:
    - parse syntax tree
    - convert entries into a clean semantic structure
    - deterministic output
    """
    tree = parse_syntax(glyphs)

    actor = simplify_entry(tree.get("actor"))
    action = simplify_entry(tree.get("action"))
    obj = simplify_entry(tree.get("object"))

    context_tree = tree.get("context", {})

    context = {
        "place": simplify_list(context_tree.get("place", [])),
        "time": simplify_list(context_tree.get("time", [])),
        "emotion": simplify_list(context_tree.get("emotion", [])),
        "sensory": simplify_list(context_tree.get("sensory", [])),
        "social": simplify_list(context_tree.get("social", [])),
    }

    struct = {
        "actor": actor,
        "action": action,
        "object": obj,
        "modifiers": simplify_list(tree.get("modifiers", [])),
        "context": context,
        "raw": "".join(glyphs),
    }

    # Attach a realized natural-language sentence as a convenience
    struct["text"] = realize_sentence(struct)

    return struct


# -------------------------
# Natural-language realization
# -------------------------
def realize_sentence(struct: Dict[str, Any]) -> str:

    """
    Very simple, deterministic surface realizer.
    for now: (subject to change)
    - actor (noun phrase)
    - verb phrase (action + object)
    - context (time/place)
    """

    actor = struct.get("actor")
    action = struct.get("action")
    obj = struct.get("object")
    ctx = struct.get("context", {})

    chunks: List[str] = []

    # 1. Actor
    if actor:
        chunks.append(realize_noun_phrase(actor))

    # 2. Verb phrase
    if action:
        chunks.append(realize_verb_phrase(action, obj))

    # 3. Context
    ctx_chunk = realize_context(ctx)
    if ctx_chunk:
        chunks.append(ctx_chunk)

    sentence = " ".join(chunks).strip()
    if sentence and not sentence.endswith("."):
        sentence += "."
    return sentence


def realize_noun_phrase(entry: Dict[str, Any]) -> str:

    """
    For now:
    - Just return the primary term.
    - Later: add articles, pluralization, pronouns, etc.
    """

    return entry.get("primary", "")


def realize_verb_phrase(action: Dict[str, Any],
                        obj: Optional[Dict[str, Any]]) -> str:
    """
    Simple past-tense realization:
    - action.primary is assumed to be a base verb ("walk", "go", "eat").
    - We inflect to past tense in a naive way for now.
    """

    base = action.get("primary", "")
    verb = inflect_past(base)

    if obj:
        return f"{verb} {realize_noun_phrase(obj)}"
    return verb


def realize_context(ctx: Dict[str, Any]) -> str:

    """
    Realize time/place context into a simple phrase.
    This is intentionally minimal and deterministic.
    """

    chunks: List[str] = []

    times = ctx.get("time", [])
    if times:
        t_phrase = realize_time_phrase(times[0])
        if t_phrase:
            chunks.append(t_phrase)

    places = ctx.get("place", [])
    if places:
        p_phrase = realize_place_phrase(places[0])
        if p_phrase:
            chunks.append(p_phrase)

    # emotion/sensory/social can be added later as needed

    return " ".join(chunks)


# -------------------------
# Tiny inflection + mapping helpers
# -------------------------
def inflect_past(verb: str) -> str:

    """
    Naive English past-tense inflection.
    """

    if not verb:
        return ""

    lower = verb.lower()

    # Very small irregular set for now
    irregular = {
        "go": "went",
        "come": "came",
        "run": "ran",
        "eat": "ate",
        "drink": "drank",
        "be": "was",
        "have": "had",
        "do": "did",
        "get": "got",
        "make": "made",
        "walk": "walked",
    }
    if lower in irregular:
        # Preserve capitalization of first letter
        form = irregular[lower]
        if verb[0].isupper():
            form = form.capitalize()
        return form

    # Regular verbs
    if lower.endswith("e"):
        form = lower + "d"
    elif lower.endswith("y") and len(lower) > 1 and lower[-2] not in "aeiou":
        form = lower[:-1] + "ied"
    else:
        form = lower + "ed"

    if verb[0].isupper():
        form = form.capitalize()
    return form


def realize_time_phrase(entry: Dict[str, Any]) -> str:

    """
    Map time context IDs to simple English phrases.
    This is a seed mapping; extend as needed.
    """

    cid = entry.get("id") or entry.get("primary", "")

    mapping = {
        "context.time.day.night": "tonight",
        "context.time.day.morning": "this morning",
        "context.time.day.afternoon": "this afternoon",
        "context.time.day.evening": "this evening",
        "context.time.cycle.daily": "every day",
        "context.time.cycle.weekly": "every week",
        "context.time.cycle.monthly": "every month",
        "context.time.duration.short": "for a short time",
        "context.time.duration.medium": "for a while",
        "context.time.duration.long": "for a long time",
    }

    phrase = mapping.get(cid)
    if phrase:
        return phrase

    # Fallback: just return primary if present
    primary = entry.get("primary")
    if primary and primary != cid:
        return primary

    return ""


def realize_place_phrase(entry: Dict[str, Any]) -> str:

    """
    Map place context IDs to simple English phrases.
    Seed mapping; extend as needed.
    """

    cid = entry.get("id") or entry.get("primary", "")

    mapping = {
        "context.place.home": "at home",
        "context.place.work": "at work",
        "context.place.outside": "outside",
        "context.place.store": "at the store",
    }

    phrase = mapping.get(cid)
    if phrase:
        return phrase

    primary = entry.get("primary")
    if primary and primary != cid:
        return primary

    return ""