377 lines
8.5 KiB
Python
377 lines
8.5 KiB
Python
"""µ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
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 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
|
||
theme_name: str = ""
|
||
|
||
|
||
@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 1–3)."""
|
||
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)
|