@@ -78,6 +78,8 @@ pub struct Compiler<'a> {
7878 function_stack : Vec < FunctionContext > ,
7979 /// Whether the current function being compiled is async.
8080 is_async : bool ,
81+ /// Tracks the last identifier compiled in compile_primary, for postfix ++/--.
82+ last_primary_name : Option < String > ,
8183}
8284
8385impl < ' a > Compiler < ' a > {
@@ -96,6 +98,7 @@ impl<'a> Compiler<'a> {
9698 upvalues : Vec :: new ( ) ,
9799 function_stack : Vec :: new ( ) ,
98100 is_async : false ,
101+ last_primary_name : None ,
99102 }
100103 }
101104
@@ -1344,14 +1347,35 @@ impl<'a> Compiler<'a> {
13441347 }
13451348
13461349 fn compile_or ( & mut self ) -> JsResult < ( ) > {
1347- self . compile_and ( ) ?;
1350+ self . compile_nullish_coalesce ( ) ?;
13481351
13491352 while self . lexer . peek ( ) . kind == TokenKind :: Or {
13501353 self . lexer . next_token ( ) ;
13511354 // Short-circuit: if truthy, skip RHS
13521355 self . code . emit_op ( Op :: Dup ) ;
13531356 let skip = self . code . emit_jump ( Op :: JumpIfTrue ) ;
13541357 self . code . emit_op ( Op :: Pop ) ;
1358+ self . compile_nullish_coalesce ( ) ?;
1359+ let end = self . code . current_offset ( ) ;
1360+ self . code . patch_jump ( skip, end) ;
1361+ }
1362+
1363+ Ok ( ( ) )
1364+ }
1365+
1366+ fn compile_nullish_coalesce ( & mut self ) -> JsResult < ( ) > {
1367+ self . compile_and ( ) ?;
1368+
1369+ while self . lexer . peek ( ) . kind == TokenKind :: NullishCoalesce {
1370+ self . lexer . next_token ( ) ;
1371+ // Nullish coalescing: if left is NOT nullish, keep it; otherwise use right.
1372+ // Stack: [left]
1373+ // Dup left, check IsNullish, if false (not nullish) jump over right side
1374+ self . code . emit_op ( Op :: Dup ) ;
1375+ self . code . emit_op ( Op :: IsNullish ) ;
1376+ let skip = self . code . emit_jump ( Op :: JumpIfFalse ) ;
1377+ // Left is nullish: pop the duplicated left, compile right
1378+ self . code . emit_op ( Op :: Pop ) ;
13551379 self . compile_and ( ) ?;
13561380 let end = self . code . current_offset ( ) ;
13571381 self . code . patch_jump ( skip, end) ;
@@ -1556,13 +1580,30 @@ impl<'a> Compiler<'a> {
15561580 fn compile_postfix ( & mut self ) -> JsResult < ( ) > {
15571581 self . compile_call ( ) ?;
15581582
1559- // Postfix ++ and -- (Phase 1: handle simple identifier case)
1583+ // Postfix ++ and -- for simple identifiers
15601584 if matches ! (
15611585 self . lexer. peek( ) . kind,
15621586 TokenKind :: PlusPlus | TokenKind :: MinusMinus
15631587 ) {
1564- // For Phase 1, just consume and ignore postfix ops on non-identifiers
1565- self . lexer . next_token ( ) ;
1588+ let is_increment = self . lexer . peek ( ) . kind == TokenKind :: PlusPlus ;
1589+ self . lexer . next_token ( ) ; // consume ++ or --
1590+
1591+ if let Some ( name) = self . last_primary_name . take ( ) {
1592+ // Stack currently has: [old_value]
1593+ // For postfix: the expression result is the OLD value.
1594+ // We need to: dup (keep old for expression result), add/sub 1, store back.
1595+ // Stack: [old_value]
1596+ self . code . emit_op ( Op :: Dup ) ; // [old_value, old_value]
1597+ let one = self . code . add_number ( 1.0 ) ;
1598+ self . code . emit_op_u16 ( Op :: LoadConst , one) ; // [old_value, old_value, 1]
1599+ if is_increment {
1600+ self . code . emit_op ( Op :: Add ) ; // [old_value, new_value]
1601+ } else {
1602+ self . code . emit_op ( Op :: Sub ) ; // [old_value, new_value]
1603+ }
1604+ self . emit_store_var ( & name) ; // [old_value] (new_value stored)
1605+ }
1606+ // If not a simple identifier, postfix op is silently ignored (like before)
15661607 }
15671608
15681609 Ok ( ( ) )
@@ -1573,6 +1614,13 @@ impl<'a> Compiler<'a> {
15731614
15741615 // Handle postfix operations: calls, property access, indexing
15751616 loop {
1617+ match & self . lexer . peek ( ) . kind {
1618+ TokenKind :: LeftParen | TokenKind :: Dot | TokenKind :: LeftBracket => {
1619+ // Any postfix operation invalidates the simple identifier tracking
1620+ self . last_primary_name = None ;
1621+ }
1622+ _ => { }
1623+ }
15761624 match & self . lexer . peek ( ) . kind {
15771625 TokenKind :: LeftParen => {
15781626 self . lexer . next_token ( ) ; // consume '('
@@ -1675,6 +1723,7 @@ impl<'a> Compiler<'a> {
16751723 }
16761724
16771725 fn compile_primary ( & mut self ) -> JsResult < ( ) > {
1726+ self . last_primary_name = None ;
16781727 let tok = self . lexer . peek ( ) . clone ( ) ;
16791728 match & tok. kind {
16801729 TokenKind :: Number ( n) => {
@@ -1782,6 +1831,7 @@ impl<'a> Compiler<'a> {
17821831 let idx = self . code . add_string ( name_id. 0 ) ;
17831832 self . code . emit_op_u16 ( Op :: LoadGlobal , idx) ;
17841833 }
1834+ self . last_primary_name = Some ( name) ;
17851835 Ok ( ( ) )
17861836 }
17871837
@@ -2099,6 +2149,19 @@ impl<'a> Compiler<'a> {
20992149 }
21002150 }
21012151
2152+ /// Emit code to store the top of stack into a variable (pops the value).
2153+ fn emit_store_var ( & mut self , name : & str ) {
2154+ if let Some ( ( slot, _) ) = self . resolve_local ( name) {
2155+ self . code . emit_op_u16 ( Op :: StoreLocal , slot) ;
2156+ } else if let Some ( upval_idx) = self . resolve_upvalue ( name) {
2157+ self . code . emit_op_u16 ( Op :: StoreUpvalue , upval_idx) ;
2158+ } else {
2159+ let name_id = self . strings . intern ( name) ;
2160+ let idx = self . code . add_string ( name_id. 0 ) ;
2161+ self . code . emit_op_u16 ( Op :: StoreGlobal , idx) ;
2162+ }
2163+ }
2164+
21022165 /// Emit code to store a value and leave it on the stack (for assignment expressions).
21032166 fn emit_store_and_load_var ( & mut self , name : & str ) {
21042167 if let Some ( ( slot, _) ) = self . resolve_local ( name) {
0 commit comments