Files
micronomicon/backend/uframe/ir.py
2026-04-01 08:40:17 +02:00

377 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""µFrame Intermediate Representation — node types for the IR tree.
Every .uf source parses into a tree of IRNode subclasses. The layout
engine measures, positions, and paints these nodes into a CharGrid.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum, auto
from typing import Any
# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------
class BorderWeight(Enum):
LIGHT = auto()
HEAVY = auto()
DOUBLE = auto()
ROUNDED = auto()
class HeadingLevel(Enum):
H1 = 1
H2 = 2
H3 = 3
class DividerStyle(Enum):
LIGHT = auto()
HEAVY = auto()
DOUBLE = auto()
DASH = auto()
DOT = auto()
class ListStyle(Enum):
BULLET = auto()
DASH = auto()
NUMBER = auto()
ARROW = auto()
class Align(Enum):
LEFT = auto()
CENTER = auto()
RIGHT = auto()
# ---------------------------------------------------------------------------
# Style
# ---------------------------------------------------------------------------
@dataclass
class Style:
"""Visual style attached to any node."""
fg: str | None = None # 3-digit hex
bg: str | None = None # 3-digit hex
bold: bool = False
italic: bool = False
underline: bool = False
align: Align = Align.LEFT
# ---------------------------------------------------------------------------
# Inline text spans (parsed from @modifier{} syntax in text content)
# ---------------------------------------------------------------------------
@dataclass
class TextSpan:
"""A run of text with optional inline styling."""
text: str
bold: bool = False
italic: bool = False
underline: bool = False
fg: str | None = None
bg: str | None = None
# ---------------------------------------------------------------------------
# Layout rect — assigned by the layout engine
# ---------------------------------------------------------------------------
@dataclass
class Rect:
x: int = 0
y: int = 0
w: int = 0
h: int = 0
# ---------------------------------------------------------------------------
# Base node
# ---------------------------------------------------------------------------
@dataclass
class IRNode:
"""Base class for all IR nodes."""
children: list[IRNode] = field(default_factory=list)
style: Style = field(default_factory=Style)
rect: Rect = field(default_factory=Rect)
source_line: int | None = None
# Set by measure pass
min_width: int = 0
min_height: int = 0
pref_width: int = 0
pref_height: int = 0
# ---------------------------------------------------------------------------
# Layout nodes
# ---------------------------------------------------------------------------
@dataclass
class Page(IRNode):
"""Root container. One per .uf file."""
title: str = ""
width: int = 64
@dataclass
class Box(IRNode):
"""Bordered panel with optional title."""
title: str = ""
weight: BorderWeight = BorderWeight.LIGHT
@dataclass
class Row(IRNode):
"""Horizontal layout — children split available width."""
gap: int = 1
@dataclass
class Col(IRNode):
"""Explicit column in a row. Width in chars or None (auto)."""
col_width: int | None = None
@dataclass
class Spacer(IRNode):
"""Vertical whitespace."""
lines: int = 1
@dataclass
class Pad(IRNode):
"""Inner margin for a container."""
top: int = 0
right: int = 0
bottom: int = 0
left: int = 0
# ---------------------------------------------------------------------------
# Content nodes
# ---------------------------------------------------------------------------
@dataclass
class Heading(IRNode):
"""Styled heading (levels 13)."""
level: HeadingLevel = HeadingLevel.H1
text: str = ""
@dataclass
class Text(IRNode):
"""Text content with optional @modifier{} inline styles."""
content: str = ""
spans: list[TextSpan] = field(default_factory=list)
@dataclass
class Label(IRNode):
"""Aligned key-value pair."""
key: str = ""
value: str = ""
@dataclass
class Divider(IRNode):
"""Full-width horizontal rule."""
divider_style: DividerStyle = DividerStyle.LIGHT
@dataclass
class Link(IRNode):
"""Clickable link — visual in ASCII, interactive in Micron."""
display: str = ""
dest: str = ""
@dataclass
class ListNode(IRNode):
"""Bulleted or numbered list."""
list_style: ListStyle = ListStyle.BULLET
@dataclass
class ListItem(IRNode):
"""Single entry in a ListNode."""
content: str = ""
# ---------------------------------------------------------------------------
# Placeholder nodes for future phases
# ---------------------------------------------------------------------------
@dataclass
class Gauge(IRNode):
"""Horizontal bar chart (Phase 4)."""
label: str = ""
value: float = 0
max_val: float = 100
bar_width: int = 28
warn: float | None = None
crit: float | None = None
@dataclass
class Sparkline(IRNode):
"""Braille sparkline (Phase 4)."""
label: str = ""
values: list[float] = field(default_factory=list)
spark_width: int = 20
@dataclass
class Status(IRNode):
"""Status indicator (Phase 4)."""
label: str = ""
state: str = "unknown"
@dataclass
class Table(IRNode):
"""Box-drawn table."""
title: str = ""
columns: list[tuple[str, int]] = field(default_factory=list) # (name, width)
rows: list[list[str]] = field(default_factory=list)
# ---------------------------------------------------------------------------
# Form nodes
# ---------------------------------------------------------------------------
@dataclass
class Form(IRNode):
"""Form container grouping interactive fields."""
form_name: str = ""
@dataclass
class Field(IRNode):
"""Text input field."""
field_name: str = ""
field_width: int = 24
placeholder: str = ""
@dataclass
class Password(IRNode):
"""Masked password field."""
field_name: str = ""
field_width: int = 24
placeholder: str = ""
@dataclass
class Radio(IRNode):
"""Radio button group — options separated by |."""
group: str = ""
options: list[str] = field(default_factory=list)
@dataclass
class Checkbox(IRNode):
"""Checkbox field."""
field_name: str = ""
checkbox_label: str = ""
checked: bool = False
@dataclass
class FormButton(IRNode):
"""Submit button — clickable link in Micron."""
button_label: str = ""
dest: str = ""
# ---------------------------------------------------------------------------
# Dynamic nodes (Phase 7)
# ---------------------------------------------------------------------------
class SourceType(Enum):
SHELL = auto()
FILE = auto()
JSON = auto()
PYTHON = auto()
RNS = auto()
PARAM = auto()
@dataclass
class Let(IRNode):
"""Variable assignment: let name = "value" or let name = 1,2,3."""
var_name: str = ""
var_value: str = ""
@dataclass
class Source(IRNode):
"""Data source resolved at render time (dynamic pages only)."""
var_name: str = ""
source_type: SourceType = SourceType.SHELL
command: str = ""
timeout: int = 5
@dataclass
class IfBlock(IRNode):
"""Conditional block: if $var > threshold."""
condition: str = ""
# children = the "then" branch
elif_branches: list[tuple[str, list[IRNode]]] = field(default_factory=list)
else_children: list[IRNode] = field(default_factory=list)
@dataclass
class ForLoop(IRNode):
"""Iteration: for item in $collection."""
var_name: str = ""
iterable: str = ""
# children = loop body
@dataclass
class CacheControl(IRNode):
"""Cache header: cache 0 (never cache) or cache 300 (5 min)."""
seconds: int = 0
@dataclass
class OnSubmit(IRNode):
"""Form submission handler: on_submit "form_name"."""
form_name: str = ""
# children = handler body
@dataclass
class StateDecl(IRNode):
"""State persistence: state "name" "/path.json"."""
state_name: str = ""
path: str = ""
# ---------------------------------------------------------------------------
# Components (Phase 8)
# ---------------------------------------------------------------------------
@dataclass
class ComponentDef(IRNode):
"""Component definition: component name(arg1, arg2)."""
comp_name: str = ""
params: list[str] = field(default_factory=list)
# children = the component body template
@dataclass
class ComponentUse(IRNode):
"""Component instantiation: name "arg1" "arg2"."""
comp_name: str = ""
args: list[str] = field(default_factory=list)