feat: dynamic mode

This commit is contained in:
2026-04-01 08:28:44 +02:00
parent df11705875
commit 619d3ec538
16 changed files with 1184 additions and 38 deletions

View File

@@ -17,6 +17,10 @@ from uframe.borders import merge_borders
from uframe.grid import CharGrid
from uframe.emit_ascii import emit_ascii
from uframe.emit_micron import emit_micron
from uframe.ir import (
IRNode, Field, Password, Radio, Checkbox, FormButton,
Source, IfBlock, ForLoop, OnSubmit, StateDecl, CacheControl,
)
@dataclass
@@ -24,9 +28,57 @@ class CompileResult:
"""Result of compiling a .uf source."""
ascii: str = ""
micron: str = ""
script: str = "" # generated Python script (dynamic mode only)
is_dynamic: bool = False
warnings: list[CompileWarning] = field(default_factory=list)
def _has_dynamic_nodes(node: IRNode) -> bool:
"""Check if the IR tree contains any dynamic nodes."""
if isinstance(node, (Source, IfBlock, ForLoop, OnSubmit, StateDecl, CacheControl)):
return True
return any(_has_dynamic_nodes(child) for child in node.children)
def _collect_form_nodes(node: IRNode) -> dict[int, IRNode]:
"""Walk the IR tree and collect form nodes keyed by their y position."""
result: dict[int, IRNode] = {}
if isinstance(node, (Field, Password, Radio, Checkbox, FormButton)):
result[node.rect.y] = node
for child in node.children:
result.update(_collect_form_nodes(child))
return result
def _micron_form_line(node: IRNode) -> str:
"""Generate the Micron form tag for a form node."""
if isinstance(node, Field):
w = node.field_width
name = node.field_name
ph = node.placeholder or name
return f"{name}: `<{w}|{name}`{ph}>"
elif isinstance(node, Password):
w = node.field_width
name = node.field_name
ph = node.placeholder or name
return f"{name}: `<!{w}|{name}`{ph}>"
elif isinstance(node, Radio):
parts = []
for i, opt in enumerate(node.options):
val = opt.lower().replace(" ", "_")
checked = "|*" if i == 0 else ""
parts.append(f"`<^|{node.group}|{val}{checked}`{opt}>")
return f"{node.group}: {' '.join(parts)}"
elif isinstance(node, Checkbox):
name = node.field_name
label = node.checkbox_label
checked = "|*" if node.checked else ""
return f"`<?|{name}|yes{checked}`{label}>"
elif isinstance(node, FormButton):
return f"`[`!{node.button_label}`!`:{node.dest}]"
return ""
def compile(source: str, width: int = 64) -> CompileResult:
"""Compile a µFrame .uf source string into ASCII and Micron output.
@@ -67,8 +119,26 @@ def compile(source: str, width: int = 64) -> CompileResult:
ascii_out = emit_ascii(grid)
micron_out = emit_micron(grid, page_title=page.title)
# 7. Post-pass: replace form element lines in Micron output
form_nodes = _collect_form_nodes(page)
if form_nodes:
micron_lines = micron_out.split("\n")
for row_y, form_node in form_nodes.items():
if 0 <= row_y < len(micron_lines):
micron_lines[row_y] = _micron_form_line(form_node)
micron_out = "\n".join(micron_lines)
# 8. Check if this page has dynamic features
is_dynamic = _has_dynamic_nodes(page)
script = ""
if is_dynamic:
from uframe.codegen import compile_dynamic
script = compile_dynamic(page)
return CompileResult(
ascii=ascii_out,
micron=micron_out,
script=script,
is_dynamic=is_dynamic,
warnings=warnings,
)