Step 17b: bytecode-compiled text-layout, WASM library import fix
- text-layout.sx added to WASM bytecode pipeline (9K compiled) - Fix multi-list map calls (map-indexed + nth instead of map fn list1 list2) - pretext-layout-lines and pretext-position-line moved to library exports - Browser load-sxbc: handle VmSuspended for import, copy library exports to global_env after module load (define-library export fix) - compile-modules.js: text-layout in SOURCE_MAP, FILES, and entry deps - Island uses library functions (break-lines, pretext-layout-lines) instead of inlining — runs on bytecode VM when exports resolve Known issue: define-library exports don't propagate to browser global env yet. The load-sxbc import suspension handler resumes correctly but bind_import_set doesn't fire. Needs deeper investigation into how the WASM kernel's define-library registers exports vs how other libraries (adapter-html, tw) make their exports available. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -198,23 +198,54 @@ def parse_checks(check):
|
||||
|
||||
all_checks.append(('skip', part[:60], None, None))
|
||||
|
||||
# Deduplicate: keep last per (type, name, key)
|
||||
# Deduplicate: keep last per (element, property).
|
||||
# Pre-action and post-action assertions for the same property get the same key,
|
||||
# so only the post-action assertion (the last one) survives.
|
||||
seen = {}
|
||||
for c in all_checks:
|
||||
key = (c[0], c[1], c[2] if c[0] == 'class' else None)
|
||||
typ, name = c[0], c[1]
|
||||
if typ in ('class',):
|
||||
key = (name, 'class', c[2])
|
||||
elif typ in ('innerHTML', 'textContent'):
|
||||
key = (name, 'content')
|
||||
elif typ in ('style', 'computedStyle'):
|
||||
key = (name, 'style', c[2])
|
||||
elif typ in ('attr', 'hasAttr'):
|
||||
key = (name, 'attr', c[2])
|
||||
elif typ in ('noParent', 'hasParent'):
|
||||
key = (name, 'parent')
|
||||
elif typ in ('value',):
|
||||
key = (name, 'value')
|
||||
else:
|
||||
key = (typ, name, c[2])
|
||||
seen[key] = c
|
||||
|
||||
return list(seen.values())
|
||||
|
||||
|
||||
def make_ref_fn(elements, var_names):
|
||||
"""Create a ref function that maps upstream JS variable names to SX let-bound variables."""
|
||||
tag_to_var = {}
|
||||
"""Create a ref function that maps upstream JS variable names to SX let-bound variables.
|
||||
|
||||
Upstream naming conventions:
|
||||
- div, form, button, select — first element of that tag type
|
||||
- d1, d2, d3 — elements by position (1-indexed)
|
||||
- div1, div2, div3 — divs by position among same tag (1-indexed)
|
||||
- bar, btn, A, B — elements by ID
|
||||
"""
|
||||
# Map tag → first UNNAMED element of that tag (no id)
|
||||
tag_to_unnamed = {}
|
||||
# Map tag → list of vars for elements of that tag (ordered)
|
||||
tag_to_all = {}
|
||||
id_to_var = {}
|
||||
last_var = var_names[-1] if var_names else '_el-div'
|
||||
first_var = var_names[0] if var_names else '_el-div'
|
||||
|
||||
for i, el in enumerate(elements):
|
||||
tag_to_var[el['tag']] = var_names[i]
|
||||
tag = el['tag']
|
||||
if tag not in tag_to_unnamed and not el['id']:
|
||||
tag_to_unnamed[tag] = var_names[i]
|
||||
if tag not in tag_to_all:
|
||||
tag_to_all[tag] = []
|
||||
tag_to_all[tag].append(var_names[i])
|
||||
if el['id']:
|
||||
id_to_var[el['id']] = var_names[i]
|
||||
|
||||
@@ -223,12 +254,42 @@ def make_ref_fn(elements, var_names):
|
||||
'output'}
|
||||
|
||||
def ref(name):
|
||||
if name in tags:
|
||||
return tag_to_var.get(name, last_var)
|
||||
# Exact ID match first
|
||||
if name in id_to_var:
|
||||
return id_to_var[name]
|
||||
if re.match(r'^[a-z]+\d*$', name) and len(elements) > 0:
|
||||
return last_var
|
||||
|
||||
# Bare tag name → first UNNAMED element of that tag (upstream convention:
|
||||
# named elements use their ID, unnamed use their tag)
|
||||
if name in tags:
|
||||
if name in tag_to_unnamed:
|
||||
return tag_to_unnamed[name]
|
||||
# Fallback: first element of that tag (even if named)
|
||||
return tag_to_all.get(name, [first_var])[0]
|
||||
|
||||
# Tag + number: div1→1st div, div2→2nd div, form1→1st form, etc.
|
||||
m = re.match(r'^([a-z]+)(\d+)$', name)
|
||||
if m:
|
||||
tag_part, num = m.group(1), int(m.group(2))
|
||||
if tag_part in tag_to_all:
|
||||
idx = num - 1 # 1-indexed
|
||||
if 0 <= idx < len(tag_to_all[tag_part]):
|
||||
return tag_to_all[tag_part][idx]
|
||||
|
||||
# Positional: d1→1st element, d2→2nd, d3→3rd, etc.
|
||||
m = re.match(r'^d(\d+)$', name)
|
||||
if m:
|
||||
idx = int(m.group(1)) - 1 # 1-indexed
|
||||
if 0 <= idx < len(var_names):
|
||||
return var_names[idx]
|
||||
|
||||
# Short aliases: btn → look up as ID
|
||||
if name == 'btn':
|
||||
return id_to_var.get('btn', tag_to_unnamed.get('button', first_var))
|
||||
|
||||
# Single-letter or short lowercase → try as ID, fallback to first element
|
||||
if re.match(r'^[a-z]+$', name) and len(elements) > 0:
|
||||
return first_var
|
||||
|
||||
return f'(dom-query-by-id "{name}")'
|
||||
|
||||
return ref
|
||||
@@ -442,6 +503,8 @@ def emit_element_setup(lines, elements, var_names):
|
||||
# Clean up: collapse spaces, dedupe then
|
||||
hs_val = re.sub(r'\s+', ' ', hs_val)
|
||||
hs_val = re.sub(r'(then\s*)+then', 'then', hs_val)
|
||||
# Don't insert 'then' between event name and first command in 'on' handlers
|
||||
hs_val = re.sub(r'\bon (\w[\w.:+-]*) then\b', r'on \1 ', hs_val)
|
||||
hs_val = hs_val.strip()
|
||||
if not hs_val:
|
||||
lines.append(f' (dom-append (dom-body) {var})')
|
||||
|
||||
Reference in New Issue
Block a user