feat: dynamic mode
This commit is contained in:
@@ -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,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user