|
|
from typing import List, Dict, Any |
|
|
from .glyph_dictionary_loader import get_dictionary |
|
|
from .glyph_validator import validate_sequence, validate_roles |
|
|
|
|
|
|
|
|
class GlyphSyntaxError(Exception): |
|
|
pass |
|
|
|
|
|
|
|
|
ROLE_ORDER = [ |
|
|
"actor", |
|
|
"action", |
|
|
"object", |
|
|
"modifier", |
|
|
"context" |
|
|
] |
|
|
|
|
|
CONTEXT_ORDER = [ |
|
|
"place", |
|
|
"time", |
|
|
"emotion", |
|
|
"sensory", |
|
|
"social" |
|
|
] |
|
|
|
|
|
|
|
|
def _get_role(entry: Dict[str, Any]) -> str: |
|
|
"""Return the highest-priority role for a glyph.""" |
|
|
roles = entry.get("roles", []) |
|
|
for r in ROLE_ORDER: |
|
|
if r in roles: |
|
|
return r |
|
|
return "context" |
|
|
|
|
|
|
|
|
def _get_context_type(entry: Dict[str, Any]) -> str: |
|
|
"""Return the context subtype.""" |
|
|
category = entry.get("category", "") |
|
|
|
|
|
if category.startswith("context_place"): |
|
|
return "place" |
|
|
if category.startswith("context_time"): |
|
|
return "time" |
|
|
if category.startswith("context_emotion") or category.startswith("emotion_context"): |
|
|
return "emotion" |
|
|
if category.startswith("context_sensory") or category.startswith("sensory_context"): |
|
|
return "sensory" |
|
|
if category.startswith("context_social") or category.startswith("social_context"): |
|
|
return "social" |
|
|
|
|
|
return "unknown" |
|
|
|
|
|
|
|
|
def parse_syntax(glyphs: List[str]) -> Dict[str, Any]: |
|
|
""" |
|
|
Strict syntax parser enforcing: |
|
|
- required roles |
|
|
- strict ordering |
|
|
- single actor/action/object |
|
|
- context last |
|
|
- deterministic structure |
|
|
""" |
|
|
validate_sequence(glyphs) |
|
|
validate_roles(glyphs) |
|
|
|
|
|
dictionary = get_dictionary() |
|
|
|
|
|
syntax_tree = { |
|
|
"actor": None, |
|
|
"action": None, |
|
|
"object": None, |
|
|
"modifiers": [], |
|
|
"context": { |
|
|
"place": [], |
|
|
"time": [], |
|
|
"emotion": [], |
|
|
"sensory": [], |
|
|
"social": [] |
|
|
}, |
|
|
"raw": glyphs |
|
|
} |
|
|
|
|
|
last_role_index = -1 |
|
|
last_context_index = -1 |
|
|
|
|
|
for g in glyphs: |
|
|
entries = dictionary.get_entry_by_glyph(g) |
|
|
entry = entries[0] |
|
|
role = _get_role(entry) |
|
|
|
|
|
|
|
|
role_index = ROLE_ORDER.index(role) |
|
|
if role_index < last_role_index: |
|
|
raise GlyphSyntaxError( |
|
|
f"Invalid ordering: {g} ({role}) appears after a later role." |
|
|
) |
|
|
last_role_index = role_index |
|
|
|
|
|
|
|
|
if role == "actor": |
|
|
if syntax_tree["actor"] is not None: |
|
|
raise GlyphSyntaxError("Multiple actors not allowed.") |
|
|
syntax_tree["actor"] = entry |
|
|
|
|
|
elif role == "action": |
|
|
if syntax_tree["action"] is not None: |
|
|
raise GlyphSyntaxError("Multiple actions not allowed.") |
|
|
syntax_tree["action"] = entry |
|
|
|
|
|
elif role == "object": |
|
|
if syntax_tree["object"] is not None: |
|
|
raise GlyphSyntaxError("Multiple objects not allowed.") |
|
|
syntax_tree["object"] = entry |
|
|
|
|
|
elif role == "modifier": |
|
|
syntax_tree["modifiers"].append(entry) |
|
|
|
|
|
elif role == "context": |
|
|
ctx_type = _get_context_type(entry) |
|
|
if ctx_type == "unknown": |
|
|
raise GlyphSyntaxError(f"Unknown context type for glyph {g}") |
|
|
|
|
|
|
|
|
ctx_index = CONTEXT_ORDER.index(ctx_type) |
|
|
if ctx_index < last_context_index: |
|
|
raise GlyphSyntaxError( |
|
|
f"Context out of order: {ctx_type} appears after later context." |
|
|
) |
|
|
last_context_index = ctx_index |
|
|
|
|
|
syntax_tree["context"][ctx_type].append(entry) |
|
|
|
|
|
return syntax_tree |
|
|
|
|
|
|
|
|
def get_syntax_tree(glyphs: List[str]) -> Dict[str, Any]: |
|
|
return parse_syntax(glyphs) |
|
|
|
|
|
|
|
|
def is_valid_order(glyphs: List[str]) -> bool: |
|
|
try: |
|
|
parse_syntax(glyphs) |
|
|
return True |
|
|
except Exception: |
|
|
return False |
|
|
|
|
|
|