diff --git a/mcode.cm b/mcode.cm index ab9f62ca..d28f0c61 100644 --- a/mcode.cm +++ b/mcode.cm @@ -2135,6 +2135,14 @@ var mcode = function(ast) { emit_label(guard_done) return a1 } + // apply(fn, arr) → direct opcode + if (nargs == 2 && fname == "apply") { + a0 = gen_expr(args_list[0], -1) + a1 = gen_expr(args_list[1], -1) + d = alloc_slot() + emit_3("apply", d, a0, a1) + return d + } // Callback intrinsics → inline mcode loops if (fname == "arrfor" && nargs >= 2 && nargs <= 4 && inline_arrfor) { a0 = gen_expr(args_list[0], -1) diff --git a/source/mach.c b/source/mach.c index d9efe270..59d06174 100644 --- a/source/mach.c +++ b/source/mach.c @@ -281,6 +281,7 @@ typedef enum MachOpcode { MACH_IS_UPPER, /* R(A) = is_upper(R(B)) */ MACH_IS_WS, /* R(A) = is_whitespace(R(B)) */ MACH_IS_ACTOR, /* R(A) = is_actor(R(B)) — has actor_sym property */ + MACH_APPLY, /* R(A) = apply(R(B), R(C)) — call fn with args from array (ABC) */ MACH_OP_COUNT } MachOpcode; @@ -405,6 +406,7 @@ static const char *mach_opcode_names[MACH_OP_COUNT] = { [MACH_IS_UPPER] = "is_upper", [MACH_IS_WS] = "is_ws", [MACH_IS_ACTOR] = "is_actor", + [MACH_APPLY] = "apply", }; /* ---- Compile-time constant pool entry ---- */ @@ -1427,6 +1429,7 @@ vm_dispatch: DT(MACH_IS_DIGIT), DT(MACH_IS_LETTER), DT(MACH_IS_LOWER), DT(MACH_IS_UPPER), DT(MACH_IS_WS), DT(MACH_IS_ACTOR), + DT(MACH_APPLY), }; #pragma GCC diagnostic pop #undef DT @@ -2511,6 +2514,49 @@ vm_dispatch: frame->slots[a] = JS_NewBool(ctx, result); VM_BREAK(); } + VM_CASE(MACH_APPLY): { + /* A=dest, B=fn, C=arr_or_val */ + JSValue fn_val = frame->slots[b]; + JSValue arg_val = frame->slots[c]; + if (!mist_is_function(fn_val)) { + frame->slots[a] = fn_val; + VM_BREAK(); + } + JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val); + JSValue ret; + ctx->reg_current_frame = frame_ref.val; + ctx->current_register_pc = pc > 0 ? pc - 1 : 0; + ctx->vm_call_depth++; + if (!mist_is_array(arg_val)) { + /* Non-array: use as single argument */ + if (!mach_check_call_arity(ctx, fn, 1)) { + ctx->vm_call_depth--; + goto disrupt; + } + ret = JS_CallInternal(ctx, fn_val, JS_NULL, 1, &arg_val, 0); + } else { + JSArray *arr = JS_VALUE_GET_ARRAY(arg_val); + int len = arr->len; + if (!mach_check_call_arity(ctx, fn, len)) { + ctx->vm_call_depth--; + goto disrupt; + } + if (len == 0) { + ret = JS_CallInternal(ctx, fn_val, JS_NULL, 0, NULL, 0); + } else { + JSValue *args = alloca(sizeof(JSValue) * len); + for (int i = 0; i < len; i++) + args[i] = arr->values[i]; + ret = JS_CallInternal(ctx, fn_val, JS_NULL, len, args, 0); + } + } + ctx->vm_call_depth--; + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + ctx->reg_current_frame = JS_NULL; + if (JS_IsException(ret)) goto disrupt; + frame->slots[a] = ret; + VM_BREAK(); + } /* Logical */ VM_CASE(MACH_NOT): { int bval = JS_ToBool(ctx, frame->slots[b]); @@ -3168,6 +3214,7 @@ static MachCode *mcode_lower_func(cJSON *fobj, const char *filename) { else if (strcmp(op, "is_upper") == 0) { AB2(MACH_IS_UPPER); } else if (strcmp(op, "is_ws") == 0) { AB2(MACH_IS_WS); } else if (strcmp(op, "is_actor") == 0) { AB2(MACH_IS_ACTOR); } + else if (strcmp(op, "apply") == 0) { ABC3(MACH_APPLY); } /* Logical */ else if (strcmp(op, "not") == 0) { AB2(MACH_NOT); } else if (strcmp(op, "and") == 0) { ABC3(MACH_AND); } diff --git a/source/runtime.c b/source/runtime.c index b749bc0e..65d34188 100644 --- a/source/runtime.c +++ b/source/runtime.c @@ -9432,15 +9432,23 @@ static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSV if (argc < 1) return JS_NULL; if (!JS_IsFunction (argv[0])) return argv[0]; + JSFunction *fn = JS_VALUE_GET_FUNCTION (argv[0]); + if (argc < 2) return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0); - if (!JS_IsArray (argv[1])) + if (!JS_IsArray (argv[1])) { + if (fn->length >= 0 && 1 > fn->length) + return JS_RaiseDisrupt (ctx, "too many arguments for apply: expected %d, got 1", fn->length); return JS_CallInternal (ctx, argv[0], JS_NULL, 1, &argv[1], 0); + } JSArray *arr = JS_VALUE_GET_ARRAY (argv[1]); int len = arr->len; + if (fn->length >= 0 && len > fn->length) + return JS_RaiseDisrupt (ctx, "too many arguments for apply: expected %d, got %d", fn->length, len); + if (len == 0) return JS_CallInternal (ctx, argv[0], JS_NULL, 0, NULL, 0);