From 446476f039acd5d2d75f50f036ada35657f9c5d6 Mon Sep 17 00:00:00 2001 From: jucasoliveira Date: Fri, 6 Feb 2026 21:04:27 +0000 Subject: [PATCH 1/2] Fix ++ argument , all handled Identifier arguments (like x++) and skipped MemberExpression arguments entirely. --- bootstrap/emitter.ot | 155 ++++++++++++++++++++++++++++++++++++ compiler/codegen/emitter.ot | 103 ++++++++++++++++++++++++ compiler/ir/builder.ot | 36 +++++++++ src/compiler/mod.rs | 96 ++++++++++++++++++++++ 4 files changed, 390 insertions(+) diff --git a/bootstrap/emitter.ot b/bootstrap/emitter.ot index 79e5497..be7d1f0 100644 --- a/bootstrap/emitter.ot +++ b/bootstrap/emitter.ot @@ -206,6 +206,10 @@ function emit(emitter: Emitter, node: Node | null): void { emitUnaryExpression(emitter, node); return; } + if (typeVal == "UpdateExpression") { + emitUpdateExpression(emitter, node); + return; + } if (typeVal == "AssignmentExpression") { emitAssignmentExpression(emitter, node); return; @@ -537,6 +541,157 @@ function emitUnaryExpression(emitter: Emitter, node: UnaryExpression): void { return; } +function emitUpdateExpression(emitter: Emitter, node: UpdateExpression): void { + // ++x or x++, and obj.prop++ / ++obj.prop + let addOpcode: number = 2; // binary ADD + let subOpcode: number = 3; // binary SUB + + if (node.argument.type == "Identifier") { + let isLocal: boolean = false; + let slot: number = -1; + if (emitter.scope != null) { + slot = scopeResolve(emitter.scope, node.argument.name); + if (slot != -1) { + isLocal = true; + } + } + + if (isLocal) { + emitLoadLocal(emitter, slot); + } else { + emitU8(emitter, OP.LOAD); + emitString(emitter, node.argument.name); + } + + if (!node.prefix) { + emitU8(emitter, OP.DUP); + } + + emitU8(emitter, OP.PUSH); + emitU8(emitter, TYPE.NUMBER); + emitF64(emitter, 1); + + if (node.operator == "++") { + emitU8(emitter, addOpcode); + } else { + emitU8(emitter, subOpcode); + } + + if (node.prefix) { + emitU8(emitter, OP.DUP); + } + + if (isLocal) { + emitStoreLocal(emitter, slot); + } else { + emitU8(emitter, OP.STORE); + emitString(emitter, node.argument.name); + } + } else if (node.argument.type == "MemberExpression") { + let memberNode: MemberExpression = node.argument; + + if (node.prefix) { + // ++obj.prop + emit(emitter, memberNode.object); + emitU8(emitter, OP.DUP); + if (memberNode.computed) { + emit(emitter, memberNode.property); + emitU8(emitter, OP.LOAD_ELEMENT); + } else { + emitU8(emitter, OP.GET_PROP); + let propName: string = ""; + if (memberNode.property.type == "Identifier") { + propName = memberNode.property.name; + } + emitString(emitter, propName); + } + emitU8(emitter, OP.PUSH); + emitU8(emitter, TYPE.NUMBER); + emitF64(emitter, 1); + if (node.operator == "++") { + emitU8(emitter, addOpcode); + } else { + emitU8(emitter, subOpcode); + } + // Stack: [obj, new_val] -> SET_PROP -> [] + if (memberNode.computed) { + emit(emitter, memberNode.property); + emitU8(emitter, OP.STORE_ELEMENT); + } else { + emitU8(emitter, OP.SET_PROP); + let propName2: string = ""; + if (memberNode.property.type == "Identifier") { + propName2 = memberNode.property.name; + } + emitString(emitter, propName2); + } + // Reload the new value + emit(emitter, memberNode.object); + if (memberNode.computed) { + emit(emitter, memberNode.property); + emitU8(emitter, OP.LOAD_ELEMENT); + } else { + emitU8(emitter, OP.GET_PROP); + let propName3: string = ""; + if (memberNode.property.type == "Identifier") { + propName3 = memberNode.property.name; + } + emitString(emitter, propName3); + } + } else { + // obj.prop++ + emit(emitter, memberNode.object); + if (memberNode.computed) { + emit(emitter, memberNode.property); + emitU8(emitter, OP.LOAD_ELEMENT); + } else { + emitU8(emitter, OP.GET_PROP); + let propName4: string = ""; + if (memberNode.property.type == "Identifier") { + propName4 = memberNode.property.name; + } + emitString(emitter, propName4); + } + // Stack: [old_val] - this is the return value + // Now store new value back + emit(emitter, memberNode.object); + emitU8(emitter, OP.DUP); + if (memberNode.computed) { + emit(emitter, memberNode.property); + emitU8(emitter, OP.LOAD_ELEMENT); + } else { + emitU8(emitter, OP.GET_PROP); + let propName5: string = ""; + if (memberNode.property.type == "Identifier") { + propName5 = memberNode.property.name; + } + emitString(emitter, propName5); + } + emitU8(emitter, OP.PUSH); + emitU8(emitter, TYPE.NUMBER); + emitF64(emitter, 1); + if (node.operator == "++") { + emitU8(emitter, addOpcode); + } else { + emitU8(emitter, subOpcode); + } + // Stack: [old_val, obj, new_val] -> SET_PROP -> [old_val] + if (memberNode.computed) { + emit(emitter, memberNode.property); + emitU8(emitter, OP.STORE_ELEMENT); + } else { + emitU8(emitter, OP.SET_PROP); + let propName6: string = ""; + if (memberNode.property.type == "Identifier") { + propName6 = memberNode.property.name; + } + emitString(emitter, propName6); + } + } + } + return; +} + function emitAssignmentExpression(emitter: Emitter, node: AssignmentExpression): void { let leftNode: Expression = node.left; let rightNode: Expression = node.right; diff --git a/compiler/codegen/emitter.ot b/compiler/codegen/emitter.ot index fc4a798..0c5a4ee 100644 --- a/compiler/codegen/emitter.ot +++ b/compiler/codegen/emitter.ot @@ -1107,6 +1107,109 @@ function emitUpdateExpression(emitter, node) { emitU8(emitter, OP.STORE); emitString(emitter, node.argument.name); } + } else if (node.argument.type == "MemberExpression") { + // obj.prop++ / ++obj.prop / obj[key]++ / ++obj[key] + let memberNode = node.argument; + + if (node.prefix) { + // ++obj.prop: compute new value, store, reload new value + emitNode(emitter, memberNode.object); + emitU8(emitter, OP.DUP); + if (memberNode.computed) { + emitNode(emitter, memberNode.property); + emitU8(emitter, OP.GET_PROP_COMPUTED); + } else { + emitU8(emitter, OP.GET_PROP); + let propName = ""; + if (memberNode.property.type == "Identifier") { + propName = memberNode.property.name; + } + emitString(emitter, propName); + } + // Stack: [obj, old_val] + emitU8(emitter, OP.PUSH); + emitU8(emitter, TYPE.NUMBER); + emitF64(emitter, 1); + if (node.operator == "++") { + emitU8(emitter, OP.ADD); + } else { + emitU8(emitter, OP.SUB); + } + // Stack: [obj, new_val] -> SetProp -> [] + if (memberNode.computed) { + emitNode(emitter, memberNode.property); + emitU8(emitter, OP.SET_PROP_COMPUTED); + } else { + emitU8(emitter, OP.SET_PROP); + let propName2 = ""; + if (memberNode.property.type == "Identifier") { + propName2 = memberNode.property.name; + } + emitString(emitter, propName2); + } + // Reload the new value + emitNode(emitter, memberNode.object); + if (memberNode.computed) { + emitNode(emitter, memberNode.property); + emitU8(emitter, OP.GET_PROP_COMPUTED); + } else { + emitU8(emitter, OP.GET_PROP); + let propName3 = ""; + if (memberNode.property.type == "Identifier") { + propName3 = memberNode.property.name; + } + emitString(emitter, propName3); + } + } else { + // obj.prop++: save old value, compute new, store back + emitNode(emitter, memberNode.object); + emitU8(emitter, OP.DUP); + if (memberNode.computed) { + emitNode(emitter, memberNode.property); + emitU8(emitter, OP.GET_PROP_COMPUTED); + } else { + emitU8(emitter, OP.GET_PROP); + let propName4 = ""; + if (memberNode.property.type == "Identifier") { + propName4 = memberNode.property.name; + } + emitString(emitter, propName4); + } + // Stack: [obj, old_val] -> Swap -> [old_val, obj] + emitU8(emitter, OP.SWAP); + emitU8(emitter, OP.DUP); + if (memberNode.computed) { + emitNode(emitter, memberNode.property); + emitU8(emitter, OP.GET_PROP_COMPUTED); + } else { + emitU8(emitter, OP.GET_PROP); + let propName5 = ""; + if (memberNode.property.type == "Identifier") { + propName5 = memberNode.property.name; + } + emitString(emitter, propName5); + } + emitU8(emitter, OP.PUSH); + emitU8(emitter, TYPE.NUMBER); + emitF64(emitter, 1); + if (node.operator == "++") { + emitU8(emitter, OP.ADD); + } else { + emitU8(emitter, OP.SUB); + } + // Stack: [old_val, obj, new_val] -> SetProp -> [old_val] + if (memberNode.computed) { + emitNode(emitter, memberNode.property); + emitU8(emitter, OP.SET_PROP_COMPUTED); + } else { + emitU8(emitter, OP.SET_PROP); + let propName6 = ""; + if (memberNode.property.type == "Identifier") { + propName6 = memberNode.property.name; + } + emitString(emitter, propName6); + } + } } } diff --git a/compiler/ir/builder.ot b/compiler/ir/builder.ot index c7749ab..366860b 100644 --- a/compiler/ir/builder.ot +++ b/compiler/ir/builder.ot @@ -914,6 +914,42 @@ function lowerExpression(ctx, node) { return currentVal; } } + } else if (node.argument.type == "MemberExpression") { + // obj.prop++ / ++obj.prop / obj[key]++ / ++obj[key] + let objReg = lowerExpression(ctx, node.argument.object); + let currentVal; + if (node.argument.computed) { + let indexReg = lowerExpression(ctx, node.argument.property); + currentVal = irBuilderEmitLoadElement(builder, objReg, indexReg); + } else { + let propName = node.argument.property.name; + if (propName == null) { + propName = String(node.argument.property); + } + currentVal = irBuilderEmitLoadProp(builder, objReg, propName); + } + let one = irBuilderEmitConst(builder, 1); + let newVal; + if (node.operator == "++") { + newVal = irBuilderEmitAdd(builder, currentVal, one); + } else { + newVal = irBuilderEmitSub(builder, currentVal, one); + } + if (node.argument.computed) { + let indexReg2 = lowerExpression(ctx, node.argument.property); + irBuilderEmitStoreElement(builder, objReg, indexReg2, newVal); + } else { + let propName2 = node.argument.property.name; + if (propName2 == null) { + propName2 = String(node.argument.property); + } + irBuilderEmitStoreProp(builder, objReg, propName2, newVal); + } + if (node.prefix) { + return newVal; + } else { + return currentVal; + } } return irBuilderEmitConst(builder, 0); } diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 50be688..608e2c7 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -2163,6 +2163,102 @@ impl Codegen { } self.instructions.push(OpCode::Store(name)); } + } else if let Expr::Member(member) = update_expr.arg.as_ref() { + // Member expression: obj.prop++ / ++obj.prop / obj[key]++ / ++obj[key] + let op = update_expr.op; + if update_expr.prefix { + // ++obj.prop: compute new value, store it, leave new value on stack + // Stack: [obj, obj] -> GetProp -> [obj, old] -> +1 -> [obj, new] -> SetProp -> [] + // Then reload to get new value on stack + self.gen_expr(&member.obj); + self.instructions.push(OpCode::Dup); + match &member.prop { + MemberProp::Ident(id) => { + self.instructions.push(OpCode::GetProp(id.sym.to_string())); + } + MemberProp::Computed(c) => { + self.gen_expr(&c.expr); + self.instructions.push(OpCode::GetPropComputed); + } + _ => {} + } + self.instructions.push(OpCode::Push(JsValue::Number(1.0))); + if op == UpdateOp::PlusPlus { + self.instructions.push(OpCode::Add); + } else { + self.instructions.push(OpCode::Sub); + } + // Stack: [obj, new_val] -> SetProp -> [] + match &member.prop { + MemberProp::Ident(id) => { + self.instructions.push(OpCode::SetProp(id.sym.to_string())); + } + MemberProp::Computed(c) => { + self.gen_expr(&c.expr); + self.instructions.push(OpCode::SetPropComputed); + } + _ => {} + } + // Reload the new value + self.gen_expr(&member.obj); + match &member.prop { + MemberProp::Ident(id) => { + self.instructions.push(OpCode::GetProp(id.sym.to_string())); + } + MemberProp::Computed(c) => { + self.gen_expr(&c.expr); + self.instructions.push(OpCode::GetPropComputed); + } + _ => {} + } + } else { + // obj.prop++: get old value, save it, compute new, store back + // Stack: [obj, obj] -> GetProp -> [obj, old] -> Swap -> [old, obj] + // -> Dup -> [old, obj, obj] -> GetProp -> [old, obj, old2] + // -> +1 -> [old, obj, new] -> SetProp -> [old] + self.gen_expr(&member.obj); + self.instructions.push(OpCode::Dup); + match &member.prop { + MemberProp::Ident(id) => { + self.instructions.push(OpCode::GetProp(id.sym.to_string())); + } + MemberProp::Computed(c) => { + self.gen_expr(&c.expr); + self.instructions.push(OpCode::GetPropComputed); + } + _ => {} + } + // Stack: [obj, old_val] -> Swap -> [old_val, obj] + self.instructions.push(OpCode::Swap); + self.instructions.push(OpCode::Dup); + match &member.prop { + MemberProp::Ident(id) => { + self.instructions.push(OpCode::GetProp(id.sym.to_string())); + } + MemberProp::Computed(c) => { + self.gen_expr(&c.expr); + self.instructions.push(OpCode::GetPropComputed); + } + _ => {} + } + self.instructions.push(OpCode::Push(JsValue::Number(1.0))); + if op == UpdateOp::PlusPlus { + self.instructions.push(OpCode::Add); + } else { + self.instructions.push(OpCode::Sub); + } + // Stack: [old_val, obj, new_val] -> SetProp -> [old_val] + match &member.prop { + MemberProp::Ident(id) => { + self.instructions.push(OpCode::SetProp(id.sym.to_string())); + } + MemberProp::Computed(c) => { + self.gen_expr(&c.expr); + self.instructions.push(OpCode::SetPropComputed); + } + _ => {} + } + } } } Expr::TsAs(ts_as) => { From 29587cfb73c91a7fda956deaf17e232ac3c09902 Mon Sep 17 00:00:00 2001 From: jucasoliveira Date: Fri, 6 Feb 2026 23:07:37 +0000 Subject: [PATCH 2/2] fix bare return and array detection --- src/compiler/mod.rs | 21 ++++---------- src/vm/mod.rs | 69 +++++++++++++++++++++++++++++++++++++++------ 2 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 608e2c7..55ea51f 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -632,11 +632,8 @@ impl Codegen { let before = self.instructions.len(); self.gen_stmt(s); // Check if the last instruction emitted was a Return - if self.instructions.len() > before - && let Some(OpCode::Return) = self.instructions.last() - { - last_instr_was_return = true; - } + last_instr_was_return = self.instructions.len() > before + && matches!(self.instructions.last(), Some(OpCode::Return)); } self.in_function = false; @@ -1460,11 +1457,8 @@ impl Codegen { for s in stmts { let before = self.instructions.len(); self.gen_stmt(s); - if self.instructions.len() > before - && let Some(OpCode::Return) = self.instructions.last() - { - last_instr_was_return = true; - } + last_instr_was_return = self.instructions.len() > before + && matches!(self.instructions.last(), Some(OpCode::Return)); } // For async functions with no return statement at the end, wrap the result @@ -1618,11 +1612,8 @@ impl Codegen { for s in stmts { let before = self.instructions.len(); self.gen_stmt(s); - if self.instructions.len() > before - && let Some(OpCode::Return) = self.instructions.last() - { - last_instr_was_return = true; - } + last_instr_was_return = self.instructions.len() > before + && matches!(self.instructions.last(), Some(OpCode::Return)); } if stmts.is_empty() { diff --git a/src/vm/mod.rs b/src/vm/mod.rs index b6155a5..5add6f8 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -928,6 +928,35 @@ impl VM { self.stack.push(val); } else if key_name == "length" { self.stack.push(JsValue::Number(arr.len() as f64)); + } else if matches!( + key_name.as_str(), + "push" + | "pop" + | "shift" + | "unshift" + | "splice" + | "slice" + | "concat" + | "join" + | "indexOf" + | "lastIndexOf" + | "includes" + | "reverse" + | "fill" + | "at" + | "map" + | "filter" + | "forEach" + | "reduce" + | "find" + | "findIndex" + | "some" + | "every" + | "flat" + | "flatMap" + | "sort" + ) { + self.stack.push(JsValue::NativeFunction(0)); } else { self.stack.push(JsValue::Undefined); } @@ -1014,6 +1043,37 @@ impl VM { HeapData::Array(arr) => { if name == "length" { self.stack.push(JsValue::Number(arr.len() as f64)); + } else if matches!( + name.as_str(), + "push" + | "pop" + | "shift" + | "unshift" + | "splice" + | "slice" + | "concat" + | "join" + | "indexOf" + | "lastIndexOf" + | "includes" + | "reverse" + | "fill" + | "at" + | "map" + | "filter" + | "forEach" + | "reduce" + | "find" + | "findIndex" + | "some" + | "every" + | "flat" + | "flatMap" + | "sort" + ) { + // Array methods are handled by CallMethod; + // return a function-typed value so typeof reports "function" + self.stack.push(JsValue::NativeFunction(0)); } else { self.stack.push(JsValue::Undefined); } @@ -1256,14 +1316,7 @@ impl VM { } OpCode::Drop(name) => { - if let Some(JsValue::Object(ptr)) = - self.call_stack.last_mut().unwrap().locals.remove(&name) - && let Some(HeapObject { - data: HeapData::Object(props), - }) = self.heap.get_mut(ptr) - { - props.clear(); - } + self.call_stack.last_mut().unwrap().locals.remove(&name); } OpCode::Add => {