My working unpac space for OCaml projects in development
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Parser and interpreter improvements for test262 compliance

- Implement OP_fclosure and OP_fclosure8 opcodes in interpreter
- Fix parameter access to use OP_get_arg for function arguments
- Add regexp context reset after statements (functions, blocks, classes)
- Add HTML-style comment support (<!-- and -->) for Annex B
- Add hashbang comment (#!) support at file start
- Add import.defer parsing for deferred module imports
- Add decorator support (@decorator class {})
- Add BigInt as valid property name in objects, classes, destructuring
- Add class declarations to parse_statement (not just expressions)
- Fix private identifier regexp context (this.#field /= 2 works)
- Add cursor_goto_pos to Source module for re-lexing
- Add Lexer.goto_pos for parser-driven regexp context reset

Test262 pass rate: 48,353/52,896 (91.4%)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+319 -25
+2 -2
lib/quickjs/compiler/bytecode.ml
··· 174 174 builder.vars <- builder.vars @ [var]; 175 175 idx 176 176 177 - (** Find a variable by name *) 177 + (** Find a variable by name - returns (index, kind) *) 178 178 let find_var builder name = 179 179 let rec find idx = function 180 180 | [] -> None 181 181 | v :: rest -> 182 - if v.var_name = name then Some idx 182 + if v.var_name = name then Some (idx, v.var_kind) 183 183 else find (idx + 1) rest 184 184 in 185 185 find 0 builder.vars
+46 -3
lib/quickjs/compiler/compiler.ml
··· 215 215 Bytecode.update_stack comp.builder 0 1 216 216 | _ -> 217 217 match Bytecode.find_var comp.builder id.name with 218 - | Some idx -> 218 + | Some (idx, Bytecode.Var_argument) -> 219 + (* Function argument *) 220 + emit_op comp Opcode.OP_get_arg; 221 + Bytecode.emit_u16 comp.builder idx; 222 + Bytecode.update_stack comp.builder 0 1 223 + | Some (idx, _) -> 219 224 (* Local variable *) 220 225 emit_op comp Opcode.OP_get_loc; 221 226 Bytecode.emit_u16 comp.builder idx; ··· 403 408 match left.pat with 404 409 | Ast.Pat_identifier id -> 405 410 (match Bytecode.find_var comp.builder id.name with 406 - | Some idx -> 411 + | Some (idx, Bytecode.Var_argument) -> 412 + (* Argument assignment *) 413 + if op = Ast.Assign then begin 414 + compile_expr comp right; 415 + emit_op comp Opcode.OP_dup; 416 + Bytecode.update_stack comp.builder 0 1; 417 + emit_op comp Opcode.OP_put_arg; 418 + Bytecode.emit_u16 comp.builder idx 419 + end else begin 420 + emit_op comp Opcode.OP_get_arg; 421 + Bytecode.emit_u16 comp.builder idx; 422 + compile_expr comp right; 423 + compile_compound_op comp op; 424 + emit_op comp Opcode.OP_dup; 425 + Bytecode.update_stack comp.builder 0 1; 426 + emit_op comp Opcode.OP_put_arg; 427 + Bytecode.emit_u16 comp.builder idx 428 + end 429 + | Some (idx, _) -> 430 + (* Local variable assignment *) 407 431 if op = Ast.Assign then begin 408 432 compile_expr comp right; 409 433 emit_op comp Opcode.OP_set_loc; ··· 626 650 match arg.expr with 627 651 | Ast.Identifier id -> 628 652 (match Bytecode.find_var comp.builder id.name with 629 - | Some idx -> 653 + | Some (idx, Bytecode.Var_argument) -> 654 + (* Argument update *) 655 + if prefix then begin 656 + emit_op comp Opcode.OP_get_arg; 657 + Bytecode.emit_u16 comp.builder idx; 658 + emit_op comp (if is_incr then Opcode.OP_inc else Opcode.OP_dec); 659 + emit_op comp Opcode.OP_dup; 660 + Bytecode.update_stack comp.builder 0 1; 661 + emit_op comp Opcode.OP_put_arg; 662 + Bytecode.emit_u16 comp.builder idx 663 + end else begin 664 + emit_op comp Opcode.OP_get_arg; 665 + Bytecode.emit_u16 comp.builder idx; 666 + emit_op comp (if is_incr then Opcode.OP_post_inc else Opcode.OP_post_dec); 667 + emit_op comp Opcode.OP_put_arg; 668 + Bytecode.emit_u16 comp.builder idx 669 + end 670 + | Some (idx, _) -> 671 + (* Local variable update *) 630 672 if prefix then begin 631 673 emit_op comp (if is_incr then Opcode.OP_inc_loc else Opcode.OP_dec_loc); 632 674 Bytecode.emit_u8 comp.builder idx; ··· 1138 1180 Ast.cls_id = Some cd.cd_id; 1139 1181 cls_super = cd.cd_super; 1140 1182 cls_body = cd.cd_body; 1183 + cls_decorators = cd.cd_decorators; 1141 1184 } in 1142 1185 compile_class_expr comp cls_expr; 1143 1186 (* Bind to name *)
+5
lib/quickjs/parser/ast.ml
··· 161 161 | Arrow_expression of expression 162 162 | Arrow_block of function_body 163 163 164 + (** Decorator *) 165 + and decorator = expression 166 + 164 167 (** Class expression *) 165 168 and class_expression = { 166 169 cls_id : identifier option; 167 170 cls_super : expression option; 168 171 cls_body : class_body; 172 + cls_decorators : decorator list; 169 173 } 170 174 171 175 (** Class body *) ··· 309 313 cd_id : identifier; (* Required in declarations *) 310 314 cd_super : expression option; 311 315 cd_body : class_body; 316 + cd_decorators : decorator list; 312 317 cd_loc : loc; 313 318 } 314 319
+4
lib/quickjs/parser/ast.mli
··· 140 140 | Arrow_expression of expression 141 141 | Arrow_block of function_body 142 142 143 + and decorator = expression 144 + 143 145 and class_expression = { 144 146 cls_id : identifier option; 145 147 cls_super : expression option; 146 148 cls_body : class_body; 149 + cls_decorators : decorator list; 147 150 } 148 151 149 152 and class_body = { ··· 277 280 cd_id : identifier; 278 281 cd_super : expression option; 279 282 cd_body : class_body; 283 + cd_decorators : decorator list; 280 284 cd_loc : loc; 281 285 } 282 286
+84 -13
lib/quickjs/parser/lexer.ml
··· 24 24 mutable strict_mode : bool; 25 25 mutable allow_regexp : bool; (* Context-dependent: after certain tokens *) 26 26 mutable newline_before : bool; 27 + mutable first_token : bool; (* For hashbang comment detection *) 27 28 errors : (error * Source.loc) list ref; 28 29 } 29 30 ··· 34 35 strict_mode = false; 35 36 allow_regexp = true; 36 37 newline_before = false; 38 + first_token = true; 37 39 errors = ref []; 38 40 } 39 41 40 42 let set_strict_mode lexer strict = 41 43 lexer.strict_mode <- strict 42 44 45 + let set_allow_regexp lexer allow = 46 + lexer.allow_regexp <- allow 47 + 48 + (** Reposition the lexer to a specific position (for re-scanning) *) 49 + let goto_pos lexer pos = 50 + Source.cursor_goto_pos lexer.cursor pos 51 + 43 52 (* State for save/restore *) 44 53 type state = { 45 54 cursor_state : Source.cursor_state; 46 55 strict_mode : bool; 47 56 allow_regexp : bool; 48 57 newline_before : bool; 58 + first_token : bool; 49 59 } 50 60 51 61 let save lexer = { ··· 53 63 strict_mode = lexer.strict_mode; 54 64 allow_regexp = lexer.allow_regexp; 55 65 newline_before = lexer.newline_before; 66 + first_token = lexer.first_token; 56 67 } 57 68 58 69 let restore lexer state = 59 70 Source.cursor_restore lexer.cursor state.cursor_state; 60 71 lexer.strict_mode <- state.strict_mode; 61 72 lexer.allow_regexp <- state.allow_regexp; 62 - lexer.newline_before <- state.newline_before 73 + lexer.newline_before <- state.newline_before; 74 + lexer.first_token <- state.first_token 63 75 64 76 let error lexer err = 65 77 let loc = Source.cursor_loc lexer.cursor in ··· 177 189 | _ -> -1 178 190 179 191 (* Skip whitespace and comments *) 180 - let rec skip_whitespace_and_comments lexer = 192 + let rec skip_whitespace_and_comments ?(at_line_start=false) lexer = 181 193 let cursor = lexer.cursor in 182 194 match Source.cursor_peek cursor with 183 195 | None -> () 184 196 | Some c when is_whitespace c -> 185 197 Source.cursor_advance cursor; 186 - skip_whitespace_and_comments lexer 198 + skip_whitespace_and_comments ~at_line_start lexer 187 199 | Some '\r' -> 188 200 Source.cursor_advance cursor; 189 201 (* Handle \r\n as single line terminator *) ··· 191 203 | Some '\n' -> Source.cursor_advance cursor 192 204 | _ -> ()); 193 205 lexer.newline_before <- true; 194 - skip_whitespace_and_comments lexer 206 + skip_whitespace_and_comments ~at_line_start:true lexer 195 207 | Some '\n' -> 196 208 Source.cursor_advance cursor; 197 209 lexer.newline_before <- true; 198 - skip_whitespace_and_comments lexer 210 + skip_whitespace_and_comments ~at_line_start:true lexer 199 211 | Some '/' -> 200 212 (match Source.cursor_peek_n cursor 2 with 201 213 | Some "//" -> ··· 203 215 skip_line_comment lexer 204 216 | Some "/*" -> 205 217 Source.cursor_advance_n cursor 2; 206 - skip_block_comment lexer 218 + skip_block_comment ~at_line_start lexer 219 + | _ -> ()) 220 + | Some '<' when not lexer.strict_mode -> 221 + (* HTML-style comment <!-- (Annex B) *) 222 + (match Source.cursor_peek_n cursor 4 with 223 + | Some "<!--" -> 224 + Source.cursor_advance_n cursor 4; 225 + skip_line_comment lexer 226 + | _ -> ()) 227 + | Some '-' when at_line_start && not lexer.strict_mode -> 228 + (* HTML-style close comment --> (Annex B) - only at start of line *) 229 + (match Source.cursor_peek_n cursor 3 with 230 + | Some "-->" -> 231 + Source.cursor_advance_n cursor 3; 232 + skip_line_comment lexer 207 233 | _ -> ()) 208 234 | Some c when Char.code c >= 0x80 -> 209 235 (* Check for multi-byte Unicode whitespace/line terminators *) ··· 212 238 if is_unicode_line_terminator uchar then begin 213 239 Source.cursor_advance_n cursor byte_len; 214 240 lexer.newline_before <- true; 215 - skip_whitespace_and_comments lexer 241 + (* After Unicode line terminator, we're at line start for --> comment *) 242 + skip_whitespace_and_comments ~at_line_start:true lexer 216 243 end else if is_unicode_whitespace uchar then begin 217 244 Source.cursor_advance_n cursor byte_len; 218 - skip_whitespace_and_comments lexer 245 + skip_whitespace_and_comments ~at_line_start lexer 219 246 end 220 247 (* else: not whitespace, stop here *) 221 248 | None -> ()) ··· 242 269 loop () 243 270 in 244 271 loop (); 272 + (* Line comment ends at newline - the newline will be consumed next and set at_line_start *) 245 273 skip_whitespace_and_comments lexer 246 274 247 - and skip_block_comment lexer = 275 + and skip_block_comment ~at_line_start lexer = 248 276 let cursor = lexer.cursor in 277 + let saw_newline = ref false in 249 278 let rec loop () = 250 279 match Source.cursor_peek cursor with 251 280 | None -> error lexer Unterminated_comment ··· 254 283 (match Source.cursor_peek cursor with 255 284 | Some '/' -> 256 285 Source.cursor_advance cursor; 257 - skip_whitespace_and_comments lexer 286 + (* After block comment, continue tracking at_line_start if we saw a newline *) 287 + skip_whitespace_and_comments ~at_line_start:(at_line_start || !saw_newline) lexer 258 288 | _ -> loop ()) 259 289 | Some c when is_line_terminator c -> 260 290 lexer.newline_before <- true; 291 + saw_newline := true; 261 292 Source.cursor_advance cursor; 262 293 loop () 263 294 | Some c when Char.code c >= 0x80 -> 264 295 (* Check for Unicode characters *) 265 296 (match check_unicode_at cursor with 266 297 | Some (uchar, byte_len) -> 267 - if is_unicode_line_terminator uchar then 298 + if is_unicode_line_terminator uchar then begin 268 299 lexer.newline_before <- true; 300 + saw_newline := true 301 + end; 269 302 Source.cursor_advance_n cursor byte_len; 270 303 loop () 271 304 | None -> ··· 802 835 let s = scan_identifier_content lexer in 803 836 Token.Private_identifier s 804 837 838 + (* Skip hashbang comment at the start of source *) 839 + let skip_hashbang lexer = 840 + let cursor = lexer.cursor in 841 + match Source.cursor_peek_n cursor 2 with 842 + | Some "#!" -> 843 + Source.cursor_advance_n cursor 2; 844 + (* Skip to end of line *) 845 + let rec skip_to_eol () = 846 + match Source.cursor_peek cursor with 847 + | None -> () 848 + | Some c when is_line_terminator c -> () 849 + | Some c when Char.code c >= 0x80 -> 850 + (match check_unicode_at cursor with 851 + | Some (uchar, _) when is_unicode_line_terminator uchar -> () 852 + | Some (_, byte_len) -> 853 + Source.cursor_advance_n cursor byte_len; 854 + skip_to_eol () 855 + | None -> 856 + Source.cursor_advance cursor; 857 + skip_to_eol ()) 858 + | Some _ -> 859 + Source.cursor_advance cursor; 860 + skip_to_eol () 861 + in 862 + skip_to_eol () 863 + | _ -> () 864 + 805 865 (* Main lexer function *) 806 866 let next_token lexer : Token.token = 807 867 let cursor = lexer.cursor in 808 868 lexer.newline_before <- false; 809 - skip_whitespace_and_comments lexer; 869 + 870 + (* Handle hashbang comment at the very start *) 871 + let at_line_start = lexer.first_token in 872 + if lexer.first_token then begin 873 + skip_hashbang lexer; 874 + lexer.first_token <- false 875 + end; 876 + 877 + (* At the start of file, treat as if at line start for HTML close comment --> *) 878 + skip_whitespace_and_comments ~at_line_start lexer; 810 879 811 880 Source.cursor_mark cursor; 812 881 let start_pos = Source.cursor_pos cursor in ··· 859 928 | ',' -> Source.cursor_advance cursor; Token.Comma 860 929 | ':' -> Source.cursor_advance cursor; Token.Colon 861 930 | '~' -> Source.cursor_advance cursor; Token.Tilde 931 + | '@' -> Source.cursor_advance cursor; Token.At 862 932 863 933 | '+' -> 864 934 Source.cursor_advance cursor; ··· 1014 1084 1015 1085 (* Update allow_regexp based on token *) 1016 1086 lexer.allow_regexp <- (match tok with 1017 - | Token.Identifier _ | Token.Number _ | Token.BigInt _ | Token.String _ 1087 + | Token.Identifier _ | Token.Private_identifier _ 1088 + | Token.Number _ | Token.BigInt _ | Token.String _ 1018 1089 | Token.Regexp _ | Token.Template (Token.Template_no_sub _) 1019 1090 | Token.Template (Token.Template_tail _) 1020 1091 | Token.Keyword Token.Kw_this | Token.Keyword Token.Kw_true
+6
lib/quickjs/parser/lexer.mli
··· 23 23 (** Set strict mode (affects handling of certain constructs) *) 24 24 val set_strict_mode : t -> bool -> unit 25 25 26 + (** Set whether regexp is allowed as next token (vs division) *) 27 + val set_allow_regexp : t -> bool -> unit 28 + 29 + (** Reposition the lexer to a specific position (for re-scanning) *) 30 + val goto_pos : t -> Source.pos -> unit 31 + 26 32 (** Get next token *) 27 33 val next_token : t -> Token.token 28 34
+124 -4
lib/quickjs/parser/parser.ml
··· 74 74 let is_at_end parser = 75 75 current_token parser = Token.Eof 76 76 77 + (** Reset regexp context after statements where / should be regexp not division. 78 + If the current token is a slash that was incorrectly lexed as division, 79 + we need to re-lex it as a regexp. *) 80 + let reset_regexp_context parser = 81 + Lexer.set_allow_regexp parser.lexer true; 82 + if current_token parser = Token.Slash then begin 83 + (* The slash was incorrectly lexed as division - go back to the start 84 + position of the current token and re-lex it as a regexp *) 85 + Lexer.goto_pos parser.lexer parser.current.loc.start; 86 + parser.current <- Lexer.next_token parser.lexer 87 + end 88 + 77 89 (** Helper to check if current token is the 'async' contextual keyword *) 78 90 let is_async_keyword parser = 79 91 match current_token parser with ··· 95 107 | Token.Keyword Token.Kw_await | Token.Keyword Token.Kw_import 96 108 | Token.Plus | Token.Minus | Token.Bang | Token.Tilde 97 109 | Token.Plus_plus | Token.Minus_minus 110 + | Token.At (* Decorator start *) 98 111 -> true 99 112 | _ -> false 100 113 ··· 222 235 let loc = current_loc parser in 223 236 advance parser; 224 237 Ast.mk_expr ~loc (Ast.Literal (Ast.Lit_number n)) 238 + | Token.BigInt s -> 239 + let loc = current_loc parser in 240 + advance parser; 241 + Ast.mk_expr ~loc (Ast.Literal (Ast.Lit_bigint s)) 225 242 | Token.String (s, _) -> 226 243 let loc = current_loc parser in 227 244 advance parser; ··· 414 431 let loc = current_loc parser in 415 432 advance parser; 416 433 Ast.mk_expr ~loc (Ast.Literal (Ast.Lit_number n)) 434 + | Token.BigInt s -> 435 + let loc = current_loc parser in 436 + advance parser; 437 + Ast.mk_expr ~loc (Ast.Literal (Ast.Lit_bigint s)) 417 438 | Token.String (s, _) -> 418 439 let loc = current_loc parser in 419 440 advance parser; ··· 494 515 let loc = Source.mk_loc ~start:start_loc.start ~end_:end_loc.end_ () in 495 516 Ast.mk_expr ~loc (Ast.Object (List.rev !properties)) 496 517 518 + (** Parse a decorator: @expression *) 519 + and parse_decorator parser : Ast.decorator = 520 + expect parser Token.At; 521 + (* Parse decorator expression - can be member expression or call expression *) 522 + let expr = parse_member_expression parser in 523 + (* Check if it's a call expression *) 524 + if current_token parser = Token.LParen then 525 + parse_call_expression_tail parser expr 526 + else 527 + expr 528 + 529 + (** Parse a list of decorators *) 530 + and parse_decorator_list parser : Ast.decorator list = 531 + let decorators = ref [] in 532 + while current_token parser = Token.At do 533 + decorators := parse_decorator parser :: !decorators 534 + done; 535 + List.rev !decorators 536 + 497 537 and parse_primary_expression parser : Ast.expression = 498 538 let loc = current_loc parser in 499 539 match current_token parser with ··· 674 714 (* import.source(specifier) - source phase import *) 675 715 let source_loc = current_loc parser in 676 716 advance parser; 677 - (* Parse it as a call expression: import.source becomes the callee *) 678 717 let meta = { Ast.name = "import"; loc = start_loc } in 679 718 let property = { Ast.name = "source"; loc = source_loc } in 680 719 let loc = Source.mk_loc ~start:start_loc.start ~end_:source_loc.end_ () in 720 + Ast.mk_expr ~loc (Ast.Meta_property { meta; property }) 721 + | Token.Identifier "defer" -> 722 + (* import.defer(specifier) - deferred import evaluation *) 723 + let defer_loc = current_loc parser in 724 + advance parser; 725 + let meta = { Ast.name = "import"; loc = start_loc } in 726 + let property = { Ast.name = "defer"; loc = defer_loc } in 727 + let loc = Source.mk_loc ~start:start_loc.start ~end_:defer_loc.end_ () in 681 728 Ast.mk_expr ~loc (Ast.Meta_property { meta; property }) 682 729 | _ -> error parser Expected_identifier) 683 730 | Token.LParen -> ··· 700 747 Ast.mk_expr ~loc (Ast.Import arg) 701 748 | _ -> error parser Expected_expression) 702 749 750 + (* Decorator followed by class expression *) 751 + | Token.At -> 752 + let start_loc = current_loc parser in 753 + let decorators = parse_decorator_list parser in 754 + (* After decorators, expect a class *) 755 + if current_token parser <> Token.Keyword Token.Kw_class then 756 + error parser (Expected_token ("class", current_token parser)); 757 + advance parser; 758 + let cls_id = 759 + match current_token parser with 760 + | Token.Identifier _ -> Some (parse_identifier parser) 761 + | _ -> None 762 + in 763 + let cls_super = 764 + if current_token parser = Token.Keyword Token.Kw_extends then begin 765 + advance parser; 766 + Some (parse_call_expression parser) 767 + end else 768 + None 769 + in 770 + let cls_body = parse_class_body parser in 771 + let end_loc = current_loc parser in 772 + let loc = Source.mk_loc ~start:start_loc.start ~end_:end_loc.end_ () in 773 + Ast.mk_expr ~loc (Ast.Class { cls_id; cls_super; cls_body; cls_decorators = decorators }) 774 + 703 775 (* Class expression *) 704 776 | Token.Keyword Token.Kw_class -> 705 777 let start_loc = current_loc parser in ··· 719 791 let cls_body = parse_class_body parser in 720 792 let end_loc = current_loc parser in 721 793 let loc = Source.mk_loc ~start:start_loc.start ~end_:end_loc.end_ () in 722 - Ast.mk_expr ~loc (Ast.Class { cls_id; cls_super; cls_body }) 794 + Ast.mk_expr ~loc (Ast.Class { cls_id; cls_super; cls_body; cls_decorators = [] }) 723 795 724 796 | _ -> error parser Expected_expression 725 797 ··· 835 907 let loc = current_loc parser in 836 908 advance parser; 837 909 Ast.mk_expr ~loc (Ast.Literal (Ast.Lit_number n)) 910 + | Token.BigInt s -> 911 + let loc = current_loc parser in 912 + advance parser; 913 + Ast.mk_expr ~loc (Ast.Literal (Ast.Lit_bigint s)) 838 914 | Token.String (s, _) -> 839 915 let loc = current_loc parser in 840 916 advance parser; ··· 875 951 done; 876 952 let end_loc = current_loc parser in 877 953 expect parser Token.RBrace; 954 + (* After class body, regex is allowed (not division) *) 955 + reset_regexp_context parser; 878 956 let loc = Source.mk_loc ~start:start_loc.start ~end_:end_loc.end_ () in 879 957 { Ast.cls_elements = List.rev !elements; loc } 880 958 ··· 1505 1583 done; 1506 1584 let end_loc = current_loc parser in 1507 1585 expect parser Token.RBrace; 1586 + (* After block statement, regex is allowed (not division) *) 1587 + reset_regexp_context parser; 1508 1588 let loc = Source.mk_loc ~start:start_loc.start ~end_:end_loc.end_ () in 1509 1589 Ast.mk_stmt ~loc (Ast.Block (List.rev !stmts)) 1510 1590 ··· 1901 1981 1902 1982 | Token.Keyword Token.Kw_function -> 1903 1983 let fn = parse_function_declaration parser in 1984 + (* After function declaration, regex is allowed (not division) *) 1985 + reset_regexp_context parser; 1904 1986 Ast.mk_stmt ~loc:fn.fd_loc (Ast.Function fn) 1905 1987 1906 1988 | Token.Identifier "async" when (Lexer.peek parser.lexer).tok = Token.Keyword Token.Kw_function -> ··· 1920 2002 fd_async = true; 1921 2003 fd_loc; 1922 2004 } in 2005 + (* After function declaration, regex is allowed (not division) *) 2006 + reset_regexp_context parser; 1923 2007 Ast.mk_stmt ~loc:fd.fd_loc (Ast.Function fd) 1924 2008 1925 2009 | Token.Keyword Token.Kw_debugger -> ··· 1927 2011 expect_semicolon parser; 1928 2012 Ast.mk_stmt ~loc:start_loc Ast.Debugger 1929 2013 2014 + | Token.Keyword Token.Kw_class -> 2015 + (* Class declaration at statement level - NOT treated as expression *) 2016 + advance parser; 2017 + let cd_id = parse_identifier parser in 2018 + let cd_super = 2019 + if current_token parser = Token.Keyword Token.Kw_extends then begin 2020 + advance parser; 2021 + Some (parse_call_expression parser) 2022 + end else 2023 + None 2024 + in 2025 + let cd_body = parse_class_body parser in 2026 + let cd_loc = Source.mk_loc ~start:start_loc.start ~end_:cd_body.loc.end_ () in 2027 + let cd = { Ast.cd_id; cd_super; cd_body; cd_decorators = []; cd_loc } in 2028 + (* No reset_regexp_context needed here - already called in parse_class_body *) 2029 + Ast.mk_stmt ~loc:cd_loc (Ast.Class cd) 2030 + 2031 + | Token.At -> 2032 + (* Decorated class declaration *) 2033 + let decorators = parse_decorator_list parser in 2034 + if current_token parser <> Token.Keyword Token.Kw_class then 2035 + error parser (Expected_token ("class", current_token parser)); 2036 + advance parser; 2037 + let cd_id = parse_identifier parser in 2038 + let cd_super = 2039 + if current_token parser = Token.Keyword Token.Kw_extends then begin 2040 + advance parser; 2041 + Some (parse_call_expression parser) 2042 + end else 2043 + None 2044 + in 2045 + let cd_body = parse_class_body parser in 2046 + let cd_loc = Source.mk_loc ~start:start_loc.start ~end_:cd_body.loc.end_ () in 2047 + let cd = { Ast.cd_id; cd_super; cd_body; cd_decorators = decorators; cd_loc } in 2048 + Ast.mk_stmt ~loc:cd_loc (Ast.Class cd) 2049 + 1930 2050 | Token.Identifier name when (Lexer.peek parser.lexer).tok = Token.Colon -> 1931 2051 advance parser; 1932 2052 advance parser; ··· 2088 2208 in 2089 2209 let cd_body = parse_class_body parser in 2090 2210 let cd_loc = Source.mk_loc ~start:start.start ~end_:(current_loc parser).end_ () in 2091 - Ast.Export_class { cd_id; cd_super; cd_body; cd_loc } 2211 + Ast.Export_class { cd_id; cd_super; cd_body; cd_decorators = []; cd_loc } 2092 2212 | Token.Identifier "async" when (Lexer.peek parser.lexer).tok = Token.Keyword Token.Kw_function -> 2093 2213 let start = current_loc parser in 2094 2214 advance parser; (* async *) ··· 2252 2372 in 2253 2373 let cd_body = parse_class_body parser in 2254 2374 let cd_loc = Source.mk_loc ~start:start.start ~end_:(current_loc parser).end_ () in 2255 - let cd = { Ast.cd_id; cd_super; cd_body; cd_loc } in 2375 + let cd = { Ast.cd_id; cd_super; cd_body; cd_decorators = []; cd_loc } in 2256 2376 let export_loc = Source.mk_loc ~start:start_loc.start ~end_:cd_loc.end_ () in 2257 2377 Ast.Export_named { specifiers = []; source = None; 2258 2378 declaration = Some (Ast.mk_stmt ~loc:cd_loc (Ast.Class cd));
+6
lib/quickjs/parser/source.ml
··· 198 198 cursor.offset <- state.s_offset; 199 199 cursor.line <- state.s_line; 200 200 cursor.column <- state.s_column 201 + 202 + (** Reposition cursor to a given position *) 203 + let cursor_goto_pos cursor (pos : pos) = 204 + cursor.offset <- pos.offset; 205 + cursor.line <- pos.line; 206 + cursor.column <- pos.column
+3
lib/quickjs/parser/source.mli
··· 55 55 56 56 val cursor_save : cursor -> cursor_state 57 57 val cursor_restore : cursor -> cursor_state -> unit 58 + 59 + (** Reposition cursor to a given position *) 60 + val cursor_goto_pos : cursor -> pos -> unit
+2
lib/quickjs/parser/token.ml
··· 264 264 | Ampersand_ampersand (* && *) 265 265 | Pipe_pipe (* || *) 266 266 | Bang (* ! *) 267 + | At (* @ - for decorators *) 267 268 268 269 (* Assignment operators *) 269 270 | Eq (* = *) ··· 352 353 | Ampersand_ampersand -> Format.pp_print_string fmt "&&" 353 354 | Pipe_pipe -> Format.pp_print_string fmt "||" 354 355 | Bang -> Format.pp_print_string fmt "!" 356 + | At -> Format.pp_print_string fmt "@" 355 357 | Eq -> Format.pp_print_string fmt "=" 356 358 | Plus_eq -> Format.pp_print_string fmt "+=" 357 359 | Minus_eq -> Format.pp_print_string fmt "-="
+1
lib/quickjs/parser/token.mli
··· 135 135 | Ampersand_ampersand 136 136 | Pipe_pipe 137 137 | Bang 138 + | At 138 139 | Eq 139 140 | Plus_eq 140 141 | Minus_eq
+36 -3
lib/quickjs/runtime/interpreter.ml
··· 48 48 | Bytecode.Const_float f -> Float f 49 49 | Bytecode.Const_string s -> String s 50 50 | Bytecode.Const_bigint s -> BigInt (Z.of_string s) 51 - | Bytecode.Const_function _ -> Undefined (* TODO: create closure *) 51 + | Bytecode.Const_function bc -> 52 + (* Create a function closure - this is used by OP_push_const for functions *) 53 + make_function bc [||] 52 54 | Bytecode.Const_regexp { pattern; flags } -> 53 55 let obj = make_object ~class_id:Class_regexp 54 56 ~data:(Data_regexp { pattern; flags; regexp = None }) () in 55 57 Object obj 56 58 else 57 59 Undefined 60 + 61 + (** Create a function closure from bytecode *) 62 + let make_closure _frame bytecode = 63 + (* For now, create an empty var_refs array - true closures will capture variables *) 64 + make_function bytecode [||] 58 65 59 66 (** Binary arithmetic operation *) 60 67 let binary_arith _ctx op a b = ··· 805 812 (* No-op *) 806 813 | Opcode.OP_nop -> () 807 814 815 + (* Function closure - create a function object from bytecode in constant pool *) 816 + | Opcode.OP_fclosure -> 817 + let idx = read_u32 bc frame.pc in 818 + frame.pc <- frame.pc + 4; 819 + let consts = frame.func.Bytecode.constants in 820 + if idx >= 0 && idx < Array.length consts then begin 821 + match consts.(idx) with 822 + | Bytecode.Const_function func_bc -> 823 + (* Create closure with captured variable references *) 824 + let closure = make_closure frame func_bc in 825 + push_value frame closure 826 + | _ -> 827 + push_value frame Undefined 828 + end else 829 + push_value frame Undefined 830 + 808 831 (* Unimplemented - just skip their operands *) 809 - | Opcode.OP_fclosure | Opcode.OP_push_atom_value | Opcode.OP_private_symbol -> 832 + | Opcode.OP_push_atom_value | Opcode.OP_private_symbol -> 810 833 frame.pc <- frame.pc + 4; 811 834 push_value frame Undefined 812 835 ··· 819 842 push_value frame (make_array []) 820 843 821 844 | Opcode.OP_fclosure8 -> 845 + let idx = read_u8 bc frame.pc in 822 846 frame.pc <- frame.pc + 1; 823 - push_value frame Undefined 847 + let consts = frame.func.Bytecode.constants in 848 + if idx >= 0 && idx < Array.length consts then begin 849 + match consts.(idx) with 850 + | Bytecode.Const_function func_bc -> 851 + let closure = make_closure frame func_bc in 852 + push_value frame closure 853 + | _ -> 854 + push_value frame Undefined 855 + end else 856 + push_value frame Undefined 824 857 825 858 | Opcode.OP_get_length -> 826 859 let v = pop_value frame in