From f1a5072ff24287098f6a10957c846a6807ec0426 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 10 Feb 2026 17:17:36 -0600 Subject: [PATCH] fix increment operators on objects --- parse.cm | 2 + parse.mach | Bin 52939 -> 53003 bytes source/mach.c | 68 ++++++++++++++++++++++++- vm_suite.ce | 135 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+), 2 deletions(-) diff --git a/parse.cm b/parse.cm index 77262d1f..2553f775 100644 --- a/parse.cm +++ b/parse.cm @@ -1644,6 +1644,8 @@ var parse = function(tokens, src, filename, tokenizer) { operand.level = -1 } } + } else if (operand != null) { + sem_check_assign_target(scope, operand) } return null } diff --git a/parse.mach b/parse.mach index d5dce8b25d314bd59e057066317277fb1e76800a..22336711880abfc57635dbf39928f54e617c6246 100644 GIT binary patch delta 4160 zcmY+{3vg7`8Nl(g1nwaa!Y+hINH)m^7Lrg2S&+EAqAj3NoFN7UXN1Bq14>J4>C_j& zJ0v&*1Yay|ksub4mq;R6@j<``1V@N7lC-o$tqqD(TXi}W!B=Ja|L)zh_r{suz31LN z-*>+6?9E>6csiqTXT}p1R-k`>73hDs^3E*#KUR)9sz<7S=?ure^&n+@p@%EuOWjX> zsdF6vWzJVxBRgdzXV)d2sV?aeX zdF`OvcLe^DXgqK@P?ebRd|_ZhV$t(QipFZC`h=7U1~Wq?!M`46B zS~V=99w4u)g0mx0lgzarB*Ih0*!2*rc#0VtF~+Vn%<&X4cGX#0)mg(4cC6)kE!S(g z77x>xSZ9T7IXLQ>RL`V(CWr>E8@O(8#$mOd>-Ai(=UO~M0%ljE!=}t^{*DQO2&V6tk6Lwo-`rJ^92o z<{MAhRJffAw^N~C6BRa5VUtyeQpXMgjh!SCySQgOORnAI+D$IM=d7T5&KjlmScBDG z^6n+?Uh)ci9|?>DBor@j&p2caRm}u=%2W%Bw9rwDH3sE~_wpityW%hfwKBJrg8Y)? zPg;4xRxhzdo_xk7h#`(yDf2jsC5|H$b%X-M%M|A+V0;ZK9j~yE@fyXvW)-9zWhahW zc9_FBN1HWB9i!@FROWY_z~gM+ak@QjO?JFa0mfT={uYUi6XZEbo|EM9YiC|NdD_X- zP9AZ}Cef*scdRV+jx{>%G!M{e?wuw`oUukYI_O%w%U31dqf6r~i*&N(oou;Z7u|NT zQ(b)LT|7_X&&1hs5a&oF&NDW;xqhGGj1TDS13EQ6q#*Hk5{r*mSoAQj$I8d(vVftgzIr*T9EmM9b|G)$J0UthIk)_bOFX-d`g_ND~ES!4)59=9d-<(NHLP) zja;t9D2g}oC_az%@+dyVR{2!pi7<|+Fhzu^K1}stst;4WUjg?$rHqZ4jPFP*WV=NX zFLR@q+{H9lOoPSR9>bwLWg2Huoff6jD4j;>RE(t~QA(#q8FR{*Q^x0FJfDjR%rVND zQ_h@nJ{J=yqJrn7!lwHQpBN8Hj0Yt~)iJsflSpJ-OW+g&rx4>;Nn9mymBdvNBd#OP zm`dDKJ~yWE&z$Le9rkp##%~4_XXsIgV7y}{-=T2>orzf_H)hk_Y`U9GXJQVC=TOKT zJr;5Ntd|>E<0hK8N$1*fNUNrcYO1W}!K>Dj9XGR(F_(qrQrujM6Sq=`n8zHWhQu`# zQA3UzU5OidxsAnZnN!QUwc3`0Yd(w5XYu*E7%|7~BsLbX_yQJRz`|l7*9+--A&ZGS zDMZ{wjIoHFTf~F5NJnuUe%i}on`#!bsPS{2da;CsJQEqamNI`SpDiV!v5b4mxVMaZ zVmXB^r?BM|DOPa3g6kE|IHK-m{ypr#Jv461!C1)-tfZKgdNd|EenB_JDiWi%77r66 z))8ma6IV~1UjuOs#5E8j9wA0-AZ~-^_g@AZ*^*6c$tEWFH4@xN!bYCvMtX674@ zkw`qrJ!1=9Z=vfgbSx5lF8;tAccVchaDr{R~w-!*7826IFZi8J8f2IA*2H<1Cgqc2U$W z3J}jyoTq^CHK=s#W*uV>JG6)O_UNcB2ep^#_EFtFs`J~=_U|X@e*Pr)Q;0Y~BI6)& z2l?|dUf@^l5ZxXkxnDE$nt5=W*`;QFS40bix9BQ43{|Z>IITSOMv@AX+)EN9UgCuz zj?kreneWe${Nl_lee5=BY$LplU)?so!8Sdmmt)lRx(=#02z!%FK0#k6xjsoszji)v z*ZGc9l;z1~9K|HWaeYcH=UQ71>TNwZ?KBIVVSzI&;MYN49Td>PMs$!&yi1(%9&u-h zJ4>8jCvly`brRRfmms=`bJ>H{IsUKr96t%hU#Z^s8%fWT^gK!Zx=Gs2CU>)Y-E=43 lC#mrv-=z46Cqw*$0z?n_ddTN0P(34f1bT3R$%f*;{{zsj(dqyI delta 4117 zcmY+{3s6*57{KvE);)>}iONG@-4#)M5Qq}4=A)!2ZNkT3Q(EebrH#!*lQo6ST+kU6 ztqG;lHBC*$QK1BE0_`O>BAG&CiM_CLvdrmW+S|$g-(Ak$OEbT_=iEKtcfRlJW!Kie z+U@w3ZWW`fYr4C-rfc)=>t+9B^-~QxQGKtwIeyT+mGPsFSH?wsgSx2uIeuZyuUaFf zQzEZnmvj$xNhd`7#@froUDnB6Tw&c69W#VW!1258ue|;uNLS}#%@1o67A62g1 zQyZ06IbrX>sLaX-_P$x4voBjODz;*jc*5%A;b&}QF*YVL4qM84m!Gkb#n_m{*il(t zH#BpSdXkxVifC7Wu`!CVqw+%Cr2d1{)0UCL*ikv5KFKbzv?{UUk?L5+d>Qj)%*C_x zBy4svN?B9Nno`~uWz5T%mpS89wVe5K=F6Fj6~v2`BK6jVqn8Ethn)o@&-p@p_68l_V4|6JTth zz>O5RkpjI!6d0nw5Cw)PP;4U3*i0U=m3ziEa&0HqcAI3|JzleJRIgbB)DA0F?IiI| z67M9j*hKs~kdWY!tkTu5fHU${(@&0?{HQu-E-s&)k4wJ~MiG@uhY9dh+ ziNp~S8Aq*N>Zp|#(aiJH%)Mq}#4#(u(L&MULp~~Tf+mfV?9pm-n2Y6yH+$w-g;Vl-ICxJhbPy?@D1jJmNcYi1Wms=Ts1_E>O9nendkKFbfe&xQ)$JnPXasM~!yiC1Ucs74${D;+F{fWQuH}ijp`Pa(vm;VDZDITPi8lNs_tT-D z4*hiK*IAv$^D6F&NTbsL{E^)cUh?|KEv-_yA1de5a z5#%2)EEZ}1)&|@5TvXL>BjiyrEREiVRC`8=D8l#ZBg%nXpjzXP> zTa_&rV>+A9V9^XV_nOJ(GueEm9)ujnED{?;{l#}@gN(yvKfcXWBokdn@2ul0rwVgZvpqjLJC_*VGAiz zJfv;SB_1*fsD}wy#0f0o1iTh=0*fhTu};Hi$0KxOEFtj{Hd;as@hIJj$5`V^XB<|{ z`eODiwmF_+JkDaVlmO#N=HhAY8PDim>KO|0DxtU%jeGCxKiT0%80Ye z2rMH=tRP6NB+yvJiLB;CR`b4BIl<*5Eaz!1=X*k|A zk+Fj#+QEi9=veHexLp*ti{iYhIQ}YPH@*cAecwjg5pi z@~zv*C)lWO?&2VIy{&Il?-2Gbhx|T$9cF%*lwM7|-=tF_}UaiEn64y#xD<6VrBQDI2 zRj2r0*;9Na7@ttR@hM49lk_x6z1m6I&LOvRdhK*4&XCmjoKI4G$&(?zq5#oBz7Fz* R`BX;&k3a{`ve;1k_dkc^%*g-% diff --git a/source/mach.c b/source/mach.c index 7a510381..2336cf30 100644 --- a/source/mach.c +++ b/source/mach.c @@ -843,19 +843,83 @@ static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { int slot = mach_find_var(cs, name); if (slot >= 0) { if (is_postfix) { - /* Return old value, then increment */ mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); mach_emit(cs, MACH_ABC(inc_op, slot, slot, 0)); } else { - /* Increment, then return new value */ mach_emit(cs, MACH_ABC(inc_op, slot, slot, 0)); if (dest != slot) mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); } return dest; } + } else if (level > 0 && name) { + /* Closure variable */ + int save = cs->freereg; + MachCompState *target = cs; + for (int i = 0; i < level; i++) target = target->parent; + int slot = mach_find_var(target, name); + int val_r = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABC(MACH_GETUP, val_r, level, slot)); + if (is_postfix) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, val_r, 0)); + mach_emit(cs, MACH_ABC(inc_op, val_r, val_r, 0)); + if (!is_postfix) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, val_r, 0)); + mach_emit(cs, MACH_ABC(MACH_SETUP, val_r, level, slot)); + mach_free_reg_to(cs, save); + return dest; } } + /* Property access: obj.prop++ */ + if (op_kind && strcmp(op_kind, ".") == 0) { + int save = cs->freereg; + cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(operand, "expression"); + if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(operand, "left"); + cJSON *prop = cJSON_GetObjectItemCaseSensitive(operand, "name"); + if (!prop) prop = cJSON_GetObjectItemCaseSensitive(operand, "right"); + const char *prop_name = NULL; + if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); + else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "value")); + if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "name")); + if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(operand, "value")); + if (prop_name) { + int obj_r = mach_compile_expr(cs, obj_expr, -1); + if (cs->freereg <= obj_r) cs->freereg = obj_r + 1; + int ki = mach_cpool_add_str(cs, prop_name); + int val_r = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABC(MACH_GETFIELD, val_r, obj_r, ki)); + if (is_postfix) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, val_r, 0)); + mach_emit(cs, MACH_ABC(inc_op, val_r, val_r, 0)); + if (!is_postfix) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, val_r, 0)); + mach_emit(cs, MACH_ABC(MACH_SETFIELD, obj_r, ki, val_r)); + mach_free_reg_to(cs, save); + return dest; + } + } + /* Computed property access: obj[idx]++ */ + if (op_kind && strcmp(op_kind, "[") == 0) { + int save = cs->freereg; + cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(operand, "expression"); + if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(operand, "left"); + cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive(operand, "index"); + if (!idx_expr) idx_expr = cJSON_GetObjectItemCaseSensitive(operand, "right"); + int obj_r = mach_compile_expr(cs, obj_expr, -1); + if (cs->freereg <= obj_r) cs->freereg = obj_r + 1; + int idx_r = mach_compile_expr(cs, idx_expr, -1); + if (cs->freereg <= idx_r) cs->freereg = idx_r + 1; + int val_r = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABC(MACH_GETINDEX, val_r, obj_r, idx_r)); + if (is_postfix) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, val_r, 0)); + mach_emit(cs, MACH_ABC(inc_op, val_r, val_r, 0)); + if (!is_postfix) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, val_r, 0)); + mach_emit(cs, MACH_ABC(MACH_SETINDEX, obj_r, idx_r, val_r)); + mach_free_reg_to(cs, save); + return dest; + } /* Fallback: just compile operand */ return mach_compile_expr(cs, operand, dest); } diff --git a/vm_suite.ce b/vm_suite.ce index 09a42edf..495ae752 100644 --- a/vm_suite.ce +++ b/vm_suite.ce @@ -1271,6 +1271,141 @@ run("prefix decrement on array element", function() { if (arr[0] != 9) fail("side effect") }) +// ============================================================================ +// POSTFIX INCREMENT/DECREMENT ON PROPERTIES (Bug: never worked) +// ============================================================================ + +run("postfix increment on property", function() { + var obj = {x: 5} + obj.x++ + assert_eq(obj.x, 6, "obj.x should be 6 after obj.x++") +}) + +run("postfix decrement on property", function() { + var obj = {x: 5} + obj.x-- + assert_eq(obj.x, 4, "obj.x should be 4 after obj.x--") +}) + +run("postfix increment on property return value", function() { + var obj = {x: 5} + var y = obj.x++ + assert_eq(y, 5, "return value should be old value") + assert_eq(obj.x, 6, "property should be incremented") +}) + +run("postfix decrement on property return value", function() { + var obj = {x: 5} + var y = obj.x-- + assert_eq(y, 5, "return value should be old value") + assert_eq(obj.x, 4, "property should be decremented") +}) + +run("postfix increment on array element", function() { + var arr = [10, 20, 30] + arr[1]++ + assert_eq(arr[1], 21, "arr[1] should be 21 after arr[1]++") +}) + +run("postfix decrement on array element", function() { + var arr = [10, 20, 30] + arr[1]-- + assert_eq(arr[1], 19, "arr[1] should be 19 after arr[1]--") +}) + +run("postfix increment on array element return value", function() { + var arr = [10] + var y = arr[0]++ + assert_eq(y, 10, "return value should be old value") + assert_eq(arr[0], 11, "array element should be incremented") +}) + +run("postfix increment on computed property", function() { + var obj = {a: 10} + var key = "a" + obj[key]++ + assert_eq(obj.a, 11, "computed property should be incremented") +}) + +run("postfix increment on nested property", function() { + var obj = {inner: {val: 10}} + obj.inner.val++ + assert_eq(obj.inner.val, 11, "nested property should be incremented") +}) + +run("postfix increment property in loop", function() { + var obj = {count: 0} + var i = 0 + for (i = 0; i < 5; i++) { + obj.count++ + } + assert_eq(obj.count, 5, "property should be 5 after 5 increments") +}) + +// ============================================================================ +// POSTFIX INCREMENT ON CLOSURE PROPERTIES (Original reported bug) +// ============================================================================ + +run("postfix increment closure property", function() { + var obj = {x: 0} + var fn = function() { + obj.x++ + } + fn() + assert_eq(obj.x, 1, "closure property should be incremented") +}) + +run("postfix decrement closure property", function() { + var obj = {x: 5} + var fn = function() { + obj.x-- + } + fn() + assert_eq(obj.x, 4, "closure property should be decremented") +}) + +run("postfix increment closure counter pattern", function() { + var counter = {passed: 0, failed: 0} + var pass = function() { counter.passed++ } + var fail_fn = function() { counter.failed++ } + pass() + pass() + pass() + fail_fn() + assert_eq(counter.passed, 3, "passed count") + assert_eq(counter.failed, 1, "failed count") +}) + +run("postfix increment deep closure property", function() { + var obj = {x: 0} + var fn = function() { + var inner = function() { + obj.x++ + } + inner() + } + fn() + assert_eq(obj.x, 1, "deep closure property should be incremented") +}) + +run("postfix increment closure array element", function() { + var arr = [10] + var fn = function() { + arr[0]++ + } + fn() + assert_eq(arr[0], 11, "closure array element should be incremented") +}) + +run("postfix increment on closure variable", function() { + var x = 5 + var fn = function() { + x++ + } + fn() + assert_eq(x, 6, "closure variable should be incremented by postfix ++") +}) + // ============================================================================ // INCREMENT/DECREMENT IN LOOPS AND EXPRESSIONS // ============================================================================