1 Commits

Author SHA1 Message Date
John Alanbrook
f5fad52d47 Rewrite template literals with OP_format_template
Replace complex template literal handling with a simple format-based
approach. Template literals like `hello ${x}` now compile to:
  <push x>
  OP_format_template expr_count=1, cpool_idx=N
where cpool[N] = "hello {0}"

The opcode handler parses the format string, substitutes {N} placeholders
with stringified stack values, and produces the result string.

Key implementation details:
- Uses PPretext (parser pretext) with pjs_malloc to avoid GC issues
- Re-reads b->cpool[cpool_idx] after any GC-triggering operation
- Opcode layout is u16 expr_count followed by u32 cpool_idx - the u16
  must come first because compute_stack_size reads the pop count from
  position 1 for npop_u16 format opcodes

Removed:
- OP_template_concat opcode and handler
- Tagged template literal support (users can use format() directly)
- FuncCallType enum (FUNC_CALL_TEMPLATE case no longer needed)
- Complex template object creation logic in js_parse_template

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 14:18:02 -06:00
4 changed files with 706 additions and 744 deletions

View File

@@ -139,57 +139,36 @@ void script_startup(cell_rt *prt)
cell_rt *crt = JS_GetContextOpaque(js);
JS_FreeValue(js, js_blob_use(js));
/* Use GC refs to protect values during allocations.
Initialize values to JS_NULL before adding to GC list to avoid
scanning uninitialized memory. */
JSGCRef globalThis_ref, cell_ref, hidden_fn_ref;
globalThis_ref.val = JS_NULL;
cell_ref.val = JS_NULL;
hidden_fn_ref.val = JS_NULL;
JS_AddGCRef(js, &globalThis_ref);
JS_AddGCRef(js, &cell_ref);
JS_AddGCRef(js, &hidden_fn_ref);
JSValue globalThis = JS_GetGlobalObject(js);
globalThis_ref.val = JS_GetGlobalObject(js);
cell_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, globalThis_ref.val, "cell", cell_ref.val);
JSValue cell = JS_NewObject(js);
JS_SetPropertyStr(js,globalThis,"cell", cell);
hidden_fn_ref.val = JS_NewObject(js);
JS_SetPropertyStr(js, cell_ref.val, "hidden", hidden_fn_ref.val);
JSValue hidden_fn = JS_NewObject(js);
/* Call functions that allocate BEFORE using their results as arguments,
to avoid GC invalidating already-evaluated arguments. */
JSGCRef tmp_ref;
tmp_ref.val = JS_NULL;
JS_AddGCRef(js, &tmp_ref);
tmp_ref.val = js_os_use(js);
JS_SetPropertyStr(js, hidden_fn_ref.val, "os", tmp_ref.val);
tmp_ref.val = JS_NewObject(js);
crt->actor_sym = tmp_ref.val;
tmp_ref.val = JS_DupValue(js, crt->actor_sym);
JS_SetPropertyStr(js, hidden_fn_ref.val, "actorsym", tmp_ref.val);
JS_SetPropertyStr(js, cell, "hidden", hidden_fn);
JS_SetPropertyStr(js, hidden_fn, "os", js_os_use(js));
crt->actor_sym = JS_NewObject(js);
JS_SetPropertyStr(js, hidden_fn, "actorsym", JS_DupValue(js,crt->actor_sym));
if (crt->init_wota) {
tmp_ref.val = wota2value(js, crt->init_wota);
JS_SetPropertyStr(js, hidden_fn_ref.val, "init", tmp_ref.val);
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, crt->init_wota));
// init wota can now be freed
free(crt->init_wota);
crt->init_wota = NULL;
}
// Store the core path for scripts to use
JSValue js_cell = JS_GetPropertyStr(js, globalThis, "cell");
JSValue hidden = JS_GetPropertyStr(js, js_cell, "hidden");
if (core_path) {
tmp_ref.val = JS_NewString(js, core_path);
JS_SetPropertyStr(js, hidden_fn_ref.val, "core_path", tmp_ref.val);
JS_SetPropertyStr(js, hidden, "core_path", JS_NewString(js, core_path));
}
JS_FreeValue(js, hidden);
JS_FreeValue(js, js_cell);
JS_DeleteGCRef(js, &tmp_ref);
JS_DeleteGCRef(js, &hidden_fn_ref);
JS_DeleteGCRef(js, &cell_ref);
JS_DeleteGCRef(js, &globalThis_ref);
JS_FreeValue(js, globalThis);
// Load engine.cm from the core directory
size_t engine_size;

View File

@@ -250,14 +250,11 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
int scode;
data_ptr = wota_read_sym(&scode, data_ptr);
if (scode == WOTA_PRIVATE) {
JSGCRef inner_ref;
JS_PushGCRef(ctx, &inner_ref);
inner_ref.val = JS_NULL;
data_ptr = decode_wota_value(ctx, data_ptr, &inner_ref.val, holder, JS_NULL, reviver);
JSValue inner = JS_NULL;
data_ptr = decode_wota_value(ctx, data_ptr, &inner, holder, JS_NULL, reviver);
JSValue obj = JS_NewObject(ctx);
cell_rt *crt = JS_GetContextOpaque(ctx);
// JS_SetProperty(ctx, obj, crt->actor_sym, inner_ref.val);
JS_PopGCRef(ctx, &inner_ref);
// JS_SetProperty(ctx, obj, crt->actor_sym, inner);
*out_val = obj;
} else if (scode == WOTA_NULL) *out_val = JS_NULL;
else if (scode == WOTA_FALSE) *out_val = JS_NewBool(ctx, 0);
@@ -282,40 +279,34 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
}
case WOTA_ARR: {
long long c;
JSGCRef arr_ref;
data_ptr = wota_read_array(&c, data_ptr);
JS_PushGCRef(ctx, &arr_ref);
arr_ref.val = JS_NewArrayLen(ctx, c);
JSValue arr = JS_NewArrayLen(ctx, c);
for (long long i = 0; i < c; i++) {
JSValue elem_val = JS_NULL;
JSValue idx_key = JS_NewInt32(ctx, (int32_t)i);
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr_ref.val, idx_key, reviver);
JS_SetPropertyUint32(ctx, arr_ref.val, i, elem_val);
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr, idx_key, reviver);
JS_SetPropertyUint32(ctx, arr, i, elem_val);
}
*out_val = JS_PopGCRef(ctx, &arr_ref);
*out_val = arr;
break;
}
case WOTA_REC: {
long long c;
JSGCRef obj_ref;
data_ptr = wota_read_record(&c, data_ptr);
JS_PushGCRef(ctx, &obj_ref);
obj_ref.val = JS_NewObject(ctx);
JSValue obj = JS_NewObject(ctx);
for (long long i = 0; i < c; i++) {
char *tkey = NULL;
size_t key_len;
data_ptr = wota_read_text_len(&key_len, &tkey, data_ptr);
if (!tkey) continue; // invalid key
JSGCRef key_ref;
JS_PushGCRef(ctx, &key_ref);
key_ref.val = JS_NewStringLen(ctx, tkey, key_len);
JSValue prop_key = JS_NewStringLen(ctx, tkey, key_len);
JSValue sub_val = JS_NULL;
data_ptr = decode_wota_value(ctx, data_ptr, &sub_val, obj_ref.val, key_ref.val, reviver);
JS_SetProperty(ctx, obj_ref.val, key_ref.val, sub_val);
JS_PopGCRef(ctx, &key_ref);
data_ptr = decode_wota_value(ctx, data_ptr, &sub_val, obj, prop_key, reviver);
JS_SetProperty(ctx, obj, prop_key, sub_val);
JS_FreeValue(ctx, prop_key);
free(tkey);
}
*out_val = JS_PopGCRef(ctx, &obj_ref);
*out_val = obj;
break;
}
default:

View File

@@ -193,8 +193,10 @@ DEF( strict_neq, 1, 2, 1, none)
DEF( and, 1, 2, 1, none)
DEF( xor, 1, 2, 1, none)
DEF( or, 1, 2, 1, none)
/* template literal concatenation - pops N parts, pushes concatenated string */
DEF(template_concat, 3, 0, 1, npop_u16)
/* format template - format_string_cpool_idx(u32), expr_count(u16)
Note: n_push=2 ensures stack has room for temp [format_str, arr] pair,
even though we only leave 1 value (the result) on the stack. */
DEF(format_template, 7, 0, 1, npop_u16)
/* Upvalue access (closures via outer_frame chain) */
DEF( get_up, 4, 0, 1, u8_u16) /* depth:u8, slot:u16 -> value */

File diff suppressed because it is too large Load Diff