diff --git a/src/zcl_mjs.clas.abap b/src/zcl_mjs.clas.abap index 92d0520..007581d 100644 --- a/src/zcl_mjs.clas.abap +++ b/src/zcl_mjs.clas.abap @@ -149,6 +149,8 @@ CLASS zcl_mjs DEFINITION PUBLIC. RAISING zcx_mjs_runtime. CLASS-METHODS compile_function IMPORTING ir_fn TYPE REF TO data. + CLASS-METHODS materialize_function + IMPORTING ir_fn TYPE REF TO data. CLASS-METHODS collect_slots IMPORTING ir_node TYPE REF TO data CHANGING ct_map TYPE zif_mjs=>tt_slot_map @@ -169,12 +171,13 @@ ENDCLASS. CLASS zcl_mjs IMPLEMENTATION. METHOD eval. - DATA lt_tokens TYPE zif_mjs=>tt_tokens. - lt_tokens = zcl_mjs_tokenizer=>tokenize( iv_source ). + DATA lr_tokens TYPE REF TO zif_mjs=>tt_tokens. + CREATE DATA lr_tokens. + lr_tokens->* = zcl_mjs_tokenizer=>tokenize( iv_source ). DATA lo_parser TYPE REF TO zcl_mjs_parser. CREATE OBJECT lo_parser - EXPORTING it_tokens = lt_tokens. + EXPORTING ir_tokens = lr_tokens. DATA lt_stmts TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY. lt_stmts = lo_parser->parse_program( ). @@ -242,6 +245,10 @@ CLASS zcl_mjs IMPLEMENTATION. ASSIGN ir_fn->* TO . + IF -body_lazy = abap_true. + materialize_function( ir_fn ). + ENDIF. + " Lazy compile: assign variable slots on first call IF -compiled = abap_false. compile_function( ir_fn ). @@ -353,6 +360,28 @@ CLASS zcl_mjs IMPLEMENTATION. ENDIF. ENDMETHOD. + METHOD materialize_function. + FIELD-SYMBOLS TYPE zif_mjs=>ty_function. + ASSIGN ir_fn->* TO . + IF sy-subrc <> 0 OR -body_lazy = abap_false. + RETURN. + ENDIF. + + IF -body_tokens IS NOT BOUND. + -body_lazy = abap_false. + RETURN. + ENDIF. + + DATA lo_parser TYPE REF TO zcl_mjs_parser. + CREATE OBJECT lo_parser + EXPORTING ir_tokens = -body_tokens. + lo_parser->pos = -body_pos. + -body = lo_parser->parse_block( ). + -body_lazy = abap_false. + CLEAR -body_pos. + CLEAR -body_tokens. + ENDMETHOD. + METHOD compile_function. FIELD-SYMBOLS TYPE zif_mjs=>ty_function. ASSIGN ir_fn->* TO . @@ -1324,6 +1353,9 @@ CLASS zcl_mjs IMPLEMENTATION. -params = -params. -default_params = -default_params. -body = -body. + -body_lazy = -body_lazy. + -body_pos = -body_pos. + -body_tokens = -body_tokens. -closure = io_env. DATA ls_fnval TYPE zif_mjs=>ty_value. ls_fnval-type = zif_mjs=>c_type_function. @@ -1666,6 +1698,9 @@ CLASS zcl_mjs IMPLEMENTATION. -name = ls_cm-name. -params = ls_cm-params. -body = ls_cm-body. + -body_lazy = ls_cm-body_lazy. + -body_pos = ls_cm-body_pos. + -body_tokens = ls_cm-body_tokens. -closure = lo_cls_env. DATA ls_mfnval TYPE zif_mjs=>ty_value. ls_mfnval-type = zif_mjs=>c_type_function. diff --git a/src/zcl_mjs.clas.testclasses.abap b/src/zcl_mjs.clas.testclasses.abap index f67cfa2..f102570 100644 --- a/src/zcl_mjs.clas.testclasses.abap +++ b/src/zcl_mjs.clas.testclasses.abap @@ -108,6 +108,7 @@ CLASS ltcl_test DEFINITION FOR TESTING METHODS test_replace_regex_anchor FOR TESTING RAISING zcx_mjs_runtime. METHODS test_replace_empty_regex FOR TESTING RAISING zcx_mjs_runtime. METHODS test_replace_empty_regex_g FOR TESTING RAISING zcx_mjs_runtime. + METHODS test_lazy_bodies FOR TESTING RAISING zcx_mjs_runtime. METHODS test262 FOR TESTING RAISING zcx_mjs_runtime. @@ -928,6 +929,33 @@ CLASS ltcl_test IMPLEMENTATION. exp = |-a-b-c-| ). ENDMETHOD. + METHOD test_lazy_bodies. + DATA(lv_nl) = cl_abap_char_utilities=>newline. + DATA(lv_js) = + `function unused() { let brace = "{"; console.log("BAD" + brace); }` && lv_nl && + `function outer(x) { let brace = "}"; return (y) => { return x + y; }; }` && lv_nl && + `class C { value() { return 40; } unused() { console.log("BAD"); } }` && lv_nl && + `let add = outer(1);` && lv_nl && + `let c = new C();` && lv_nl && + `console.log(add(1));` && lv_nl && + `console.log(add(2));` && lv_nl && + `console.log(c.value());` && lv_nl && + `console.log(c.value());`. + + DATA(lt_tokens) = zcl_mjs_tokenizer=>tokenize( lv_js ). + DATA(lo_parser) = NEW zcl_mjs_parser( it_tokens = lt_tokens ). + DATA(lt_nodes) = lo_parser->parse_program( ). + READ TABLE lt_nodes INDEX 1 INTO DATA(lr_unused). + FIELD-SYMBOLS TYPE zif_mjs=>ty_node. + ASSIGN lr_unused->* TO . + cl_abap_unit_assert=>assert_equals( act = -body_lazy exp = abap_true ). + cl_abap_unit_assert=>assert_initial( -body ). + + cl_abap_unit_assert=>assert_equals( + act = trim( zcl_mjs=>eval( lv_js ) ) + exp = |2 3 40 40| ). + ENDMETHOD. + METHOD test262. DATA(lv_nl) = cl_abap_char_utilities=>newline. DATA(lv_js) = diff --git a/src/zcl_mjs_parser.clas.abap b/src/zcl_mjs_parser.clas.abap index 92b7ca6..4e188ae 100644 --- a/src/zcl_mjs_parser.clas.abap +++ b/src/zcl_mjs_parser.clas.abap @@ -1,10 +1,11 @@ CLASS zcl_mjs_parser DEFINITION PUBLIC. PUBLIC SECTION. - DATA tokens TYPE zif_mjs=>tt_tokens. + DATA tokens TYPE REF TO zif_mjs=>tt_tokens. DATA pos TYPE i. METHODS constructor - IMPORTING it_tokens TYPE zif_mjs=>tt_tokens. + IMPORTING it_tokens TYPE zif_mjs=>tt_tokens OPTIONAL + ir_tokens TYPE REF TO zif_mjs=>tt_tokens OPTIONAL. METHODS peek IMPORTING iv_offset TYPE i DEFAULT 0 RETURNING VALUE(rs_tok) TYPE zif_mjs=>ty_token. @@ -78,20 +79,27 @@ CLASS zcl_mjs_parser DEFINITION PUBLIC. RETURNING VALUE(rr_node) TYPE REF TO data. METHODS parse_member RETURNING VALUE(rr_node) TYPE REF TO data. + METHODS defer_block + RETURNING VALUE(rv_pos) TYPE i. ENDCLASS. CLASS zcl_mjs_parser IMPLEMENTATION. METHOD constructor. - tokens = it_tokens. + IF ir_tokens IS BOUND. + tokens = ir_tokens. + ELSE. + CREATE DATA tokens. + tokens->* = it_tokens. + ENDIF. pos = 0. ENDMETHOD. METHOD peek. - IF pos + iv_offset >= lines( tokens ). + IF tokens IS NOT BOUND OR pos + iv_offset >= lines( tokens->* ). rs_tok-kind = 5. rs_tok-val = ``. ELSE. - READ TABLE tokens INDEX pos + iv_offset + 1 INTO rs_tok. + READ TABLE tokens->* INDEX pos + iv_offset + 1 INTO rs_tok. ENDIF. ENDMETHOD. @@ -105,6 +113,28 @@ CLASS zcl_mjs_parser IMPLEMENTATION. next( ). ENDMETHOD. + METHOD defer_block. + rv_pos = pos. + IF peek( )-val <> `{`. + RETURN. + ENDIF. + + DATA lv_depth TYPE i. + WHILE peek( )-kind <> 5. + DATA(ls_token) = next( ). + " Only operator/punctuation tokens affect nesting. String and regex + " token values can themselves be "{" or "}". + IF ls_token-kind = 3 AND ls_token-val = `{`. + lv_depth = lv_depth + 1. + ELSEIF ls_token-kind = 3 AND ls_token-val = `}`. + lv_depth = lv_depth - 1. + IF lv_depth = 0. + RETURN. + ENDIF. + ENDIF. + ENDWHILE. + ENDMETHOD. + METHOD parse_program. WHILE peek( )-kind <> 5. DATA(lr_s) = parse_statement( ). @@ -510,12 +540,15 @@ CLASS zcl_mjs_parser IMPLEMENTATION. ENDIF. ENDWHILE. expect( `)` ). - DATA(lt_mbody) = parse_block( ). + DATA(lv_mbody_pos) = defer_block( ). DATA ls_m TYPE zif_mjs=>ty_class_method. + CLEAR ls_m. ls_m-name = lv_mname. ls_m-params = lt_params. - ls_m-body = lt_mbody. ls_m-is_ctor = boolc( lv_mname = `constructor` ). + ls_m-body_lazy = abap_true. + ls_m-body_pos = lv_mbody_pos. + ls_m-body_tokens = tokens. APPEND ls_m TO lt_methods. ENDWHILE. expect( `}` ). @@ -562,7 +595,15 @@ CLASS zcl_mjs_parser IMPLEMENTATION. ENDIF. ENDWHILE. expect( `)` ). - DATA(lt_body) = parse_body( ). + DATA lt_body TYPE zif_mjs=>tt_nodes. + DATA lv_body_pos TYPE i. + DATA lv_body_lazy TYPE abap_bool. + IF peek( )-val = `{`. + lv_body_pos = defer_block( ). + lv_body_lazy = abap_true. + ELSE. + lt_body = parse_body( ). + ENDIF. DATA lr_n TYPE REF TO zif_mjs=>ty_node. CREATE DATA lr_n. lr_n->kind = zif_mjs=>c_node_func_decl. @@ -570,6 +611,9 @@ CLASS zcl_mjs_parser IMPLEMENTATION. lr_n->params = lt_params. lr_n->default_params = lt_default_params. lr_n->body = lt_body. + lr_n->body_lazy = lv_body_lazy. + lr_n->body_pos = lv_body_pos. + lr_n->body_tokens = tokens. rr_node = lr_n. ENDMETHOD. @@ -662,8 +706,11 @@ CLASS zcl_mjs_parser IMPLEMENTATION. IF -kind = zif_mjs=>c_node_ident AND peek( )-val = `=>`. next( ). DATA lt_arrow_body TYPE zif_mjs=>tt_nodes. + DATA lv_arrow_pos TYPE i. + DATA lv_arrow_lazy TYPE abap_bool. IF peek( )-val = `{`. - lt_arrow_body = parse_block( ). + lv_arrow_pos = defer_block( ). + lv_arrow_lazy = abap_true. ELSE. DATA(lr_arrow_expr) = parse_assign( ). DATA lr_arrow_ret TYPE REF TO zif_mjs=>ty_node. @@ -678,6 +725,9 @@ CLASS zcl_mjs_parser IMPLEMENTATION. lr_arrow->str = ``. lr_arrow->params = VALUE #( ( -str ) ). lr_arrow->body = lt_arrow_body. + lr_arrow->body_lazy = lv_arrow_lazy. + lr_arrow->body_pos = lv_arrow_pos. + lr_arrow->body_tokens = tokens. rr_node = lr_arrow. RETURN. ENDIF. @@ -1162,8 +1212,11 @@ CLASS zcl_mjs_parser IMPLEMENTATION. IF peek( )-val = `=>`. next( ). DATA lt_ab TYPE zif_mjs=>tt_nodes. + DATA lv_ab_pos TYPE i. + DATA lv_ab_lazy TYPE abap_bool. IF peek( )-val = `{`. - lt_ab = parse_block( ). + lv_ab_pos = defer_block( ). + lv_ab_lazy = abap_true. ELSE. DATA(lr_ae) = parse_assign( ). DATA lr_ar TYPE REF TO zif_mjs=>ty_node. @@ -1178,6 +1231,9 @@ CLASS zcl_mjs_parser IMPLEMENTATION. lr_af->str = ``. lr_af->params = lt_arrow_params. lr_af->body = lt_ab. + lr_af->body_lazy = lv_ab_lazy. + lr_af->body_pos = lv_ab_pos. + lr_af->body_tokens = tokens. rr_node = lr_af. RETURN. ENDIF. diff --git a/src/zcl_mjs_tokenizer.clas.abap b/src/zcl_mjs_tokenizer.clas.abap index f7cd382..a836e30 100644 --- a/src/zcl_mjs_tokenizer.clas.abap +++ b/src/zcl_mjs_tokenizer.clas.abap @@ -1,21 +1,46 @@ CLASS zcl_mjs_tokenizer DEFINITION PUBLIC. PUBLIC SECTION. + CLASS-METHODS class_constructor. CLASS-METHODS tokenize IMPORTING iv_src TYPE string RETURNING VALUE(rt_tokens) TYPE zif_mjs=>tt_tokens. + PRIVATE SECTION. + " Escape characters and Unicode line terminators, built once + CLASS-DATA gv_cr TYPE c LENGTH 1. " \r U+000D + CLASS-DATA gv_backspace TYPE c LENGTH 1. " \b U+0008 + CLASS-DATA gv_vtab TYPE c LENGTH 1. " \v U+000B + CLASS-DATA gv_formfeed TYPE c LENGTH 1. " \f U+000C + CLASS-DATA gv_nul TYPE c LENGTH 1. " \0 U+0000 + CLASS-DATA gv_linterm_ls TYPE c LENGTH 1. " U+2028 LINE SEPARATOR + CLASS-DATA gv_linterm_ps TYPE c LENGTH 1. " U+2029 PARAGRAPH SEPARATOR ENDCLASS. CLASS zcl_mjs_tokenizer IMPLEMENTATION. + METHOD class_constructor. + gv_cr = cl_abap_char_utilities=>cr_lf(1). + TRY. + gv_backspace = cl_abap_conv_in_ce=>uccpi( 8 ). + gv_vtab = cl_abap_conv_in_ce=>uccpi( 11 ). + gv_formfeed = cl_abap_conv_in_ce=>uccpi( 12 ). + gv_nul = cl_abap_conv_in_ce=>uccpi( 0 ). + gv_linterm_ls = cl_abap_conv_in_ce=>uccpi( 8232 ). + gv_linterm_ps = cl_abap_conv_in_ce=>uccpi( 8233 ). + CATCH cx_sy_conversion_codepage. + ENDTRY. + ENDMETHOD. + METHOD tokenize. DATA lv_i TYPE i VALUE 0. DATA lv_len TYPE i. DATA lv_ch TYPE c LENGTH 1. DATA lv_j TYPE i. - FIELD-SYMBOLS TYPE zif_mjs=>ty_token. + FIELD-SYMBOLS TYPE zif_mjs=>ty_token. + FIELD-SYMBOLS TYPE zif_mjs=>ty_token. DATA lv_ni TYPE i. - DATA lv_bt TYPE c LENGTH 1. + DATA lv_nc TYPE c LENGTH 1. + DATA lv_n2 TYPE c LENGTH 1. DATA lv_d TYPE c LENGTH 1. DATA lv_numlen TYPE i. DATA lv_echar TYPE c LENGTH 1. @@ -32,88 +57,101 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. DATA lv_chunk_len TYPE i. DATA lv_esc TYPE c LENGTH 1. DATA lv_esc_cp TYPE i. - DATA lv_esc_xb TYPE x LENGTH 1. - DATA lv_esc_xs TYPE xstring. DATA lv_xh TYPE i. - DATA lv_ub1 TYPE x LENGTH 1. - DATA lv_ub2 TYPE x LENGTH 1. - DATA lv_ub3 TYPE x LENGTH 1. - DATA lv_utf8x TYPE xstring. - DATA lv_ndot TYPE c LENGTH 1. - DATA lv_two TYPE string. - DATA lv_three TYPE string. DATA lv_idlen TYPE i. DATA lv_ic TYPE c LENGTH 1. DATA lv_sc TYPE c LENGTH 1. - DATA lv_linterm_ls TYPE c LENGTH 1. - DATA lv_linterm_ps TYPE c LENGTH 1. - DATA lv_ls_bytes TYPE xstring. + DATA lv_has_non_octal TYPE abap_bool. + DATA lv_rxis TYPE abap_bool. + DATA lv_nptok TYPE i. + DATA lv_rxpat TYPE string. + DATA lv_rxflg TYPE string. + DATA lv_rxch TYPE c LENGTH 1. + DATA lv_rxfc TYPE c LENGTH 1. + DATA lv_rxclass TYPE abap_bool. + + " Hot characters as locals: class-static attribute access per character is + " slow, and in the transpiled runtime every literal comparison allocates + DATA lv_sp TYPE c LENGTH 1 VALUE ' '. + DATA lv_tab TYPE c LENGTH 1. + DATA lv_lf TYPE c LENGTH 1. + DATA lv_cr TYPE c LENGTH 1. + DATA lv_c0 TYPE c LENGTH 1 VALUE '0'. + DATA lv_c9 TYPE c LENGTH 1 VALUE '9'. + DATA lv_ca TYPE c LENGTH 1 VALUE 'a'. + DATA lv_cz TYPE c LENGTH 1 VALUE 'z'. + DATA lv_cua TYPE c LENGTH 1 VALUE 'A'. + DATA lv_cuz TYPE c LENGTH 1 VALUE 'Z'. + DATA lv_cus TYPE c LENGTH 1 VALUE '_'. + DATA lv_sq TYPE c LENGTH 1 VALUE ''''. + DATA lv_dq TYPE c LENGTH 1 VALUE '"'. + DATA lv_bt TYPE c LENGTH 1 VALUE '`'. + DATA lv_bsl TYPE c LENGTH 1 VALUE '\'. + DATA lv_dot TYPE c LENGTH 1 VALUE '.'. + DATA lv_slash TYPE c LENGTH 1 VALUE '/'. + DATA lv_star TYPE c LENGTH 1 VALUE '*'. + DATA lv_ceq TYPE c LENGTH 1 VALUE '='. - lv_bt = |`|. + lv_tab = cl_abap_char_utilities=>horizontal_tab. + lv_lf = cl_abap_char_utilities=>newline. + lv_cr = gv_cr. lv_len = strlen( iv_src ). - lv_hexdig = `0123456789abcdef`. - " Build U+2028 (LS) and U+2029 (PS) for line-continuation detection - lv_ub1 = 226. - lv_ub2 = 128. - lv_ub3 = 168. - CONCATENATE lv_ub1 lv_ub2 lv_ub3 INTO lv_ls_bytes IN BYTE MODE. - TRY. - lv_linterm_ls = cl_abap_codepage=>convert_from( source = lv_ls_bytes ). - CATCH cx_sy_conversion_codepage. - ENDTRY. - lv_ub3 = 169. - CONCATENATE lv_ub1 lv_ub2 lv_ub3 INTO lv_ls_bytes IN BYTE MODE. - TRY. - lv_linterm_ps = cl_abap_codepage=>convert_from( source = lv_ls_bytes ). - CATCH cx_sy_conversion_codepage. - ENDTRY. + " Case-complete hex digits: offsets 16-21 (A-F) map back via -6 + lv_hexdig = `0123456789abcdefABCDEF`. WHILE lv_i < lv_len. lv_ch = iv_src+lv_i(1). - " Skip whitespace, // and /* comments - IF lv_ch = ` ` OR lv_ch = cl_abap_char_utilities=>horizontal_tab - OR lv_ch = cl_abap_char_utilities=>newline - OR lv_ch = cl_abap_char_utilities=>cr_lf(1). + " Whitespace: consume the whole run in a tight loop + IF lv_ch = lv_sp OR lv_ch = lv_tab OR lv_ch = lv_lf OR lv_ch = lv_cr. lv_i = lv_i + 1. + WHILE lv_i < lv_len. + lv_ch = iv_src+lv_i(1). + IF lv_ch = lv_sp OR lv_ch = lv_tab OR lv_ch = lv_lf OR lv_ch = lv_cr. + lv_i = lv_i + 1. + ELSE. + EXIT. + ENDIF. + ENDWHILE. CONTINUE. ENDIF. - IF lv_i + 1 < lv_len. - IF iv_src+lv_i(2) = `//`. - lv_i = lv_i + 2. - WHILE lv_i < lv_len. - IF iv_src+lv_i(1) = cl_abap_char_utilities=>newline. - EXIT. - ENDIF. - lv_i = lv_i + 1. - ENDWHILE. - CONTINUE. - ELSEIF iv_src+lv_i(2) = `/*`. - lv_j = lv_i + 2. - WHILE lv_j + 1 < lv_len. - IF iv_src+lv_j(2) = `*/`. - lv_j = lv_j + 2. - EXIT. - ENDIF. + " Identifier / keyword + IF lv_ch = lv_cus OR ( lv_ch >= lv_ca AND lv_ch <= lv_cz ) + OR ( lv_ch >= lv_cua AND lv_ch <= lv_cuz ). + lv_j = lv_i + 1. + WHILE lv_j < lv_len. + lv_ic = iv_src+lv_j(1). + IF lv_ic = lv_cus OR ( lv_ic >= lv_ca AND lv_ic <= lv_cz ) + OR ( lv_ic >= lv_cua AND lv_ic <= lv_cuz ) + OR ( lv_ic >= lv_c0 AND lv_ic <= lv_c9 ). lv_j = lv_j + 1. - ENDWHILE. - lv_i = lv_j. - CONTINUE. + ELSE. + EXIT. + ENDIF. + ENDWHILE. + lv_idlen = lv_j - lv_i. + APPEND INITIAL LINE TO rt_tokens ASSIGNING . + -kind = 2. + -val = iv_src+lv_i(lv_idlen). + IF -val = `instanceof`. + -kind = 3. ENDIF. + lv_i = lv_j. + CONTINUE. ENDIF. " Number: decimal, hex (0x/0X), scientific (1e5) - IF lv_ch >= `0` AND lv_ch <= `9`. + IF lv_ch >= lv_c0 AND lv_ch <= lv_c9. " Hex literal: 0x... or 0X... - IF lv_ch = `0` AND lv_i + 1 < lv_len. + IF lv_ch = lv_c0 AND lv_i + 1 < lv_len. lv_ni = lv_i + 1. lv_nhc = iv_src+lv_ni(1). IF lv_nhc = `x` OR lv_nhc = `X`. lv_j = lv_i + 2. WHILE lv_j < lv_len. lv_d = iv_src+lv_j(1). - IF ( lv_d >= `0` AND lv_d <= `9` ) + IF ( lv_d >= lv_c0 AND lv_d <= lv_c9 ) OR ( lv_d >= `a` AND lv_d <= `f` ) OR ( lv_d >= `A` AND lv_d <= `F` ). lv_j = lv_j + 1. @@ -121,13 +159,14 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. EXIT. ENDIF. ENDWHILE. - lv_hexdig = `0123456789abcdef`. lv_hexval = 0. DO lv_j - lv_i - 2 TIMES. lv_hk = lv_i + 1 + sy-index. lv_hc = iv_src+lv_hk(1). - TRANSLATE lv_hc TO LOWER CASE. FIND FIRST OCCURRENCE OF lv_hc IN lv_hexdig MATCH OFFSET lv_hpos. + IF lv_hpos > 15. + lv_hpos = lv_hpos - 6. + ENDIF. lv_hexval = lv_hexval * 16 + lv_hpos. ENDDO. APPEND INITIAL LINE TO rt_tokens ASSIGNING . @@ -146,7 +185,6 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. EXIT. ENDIF. ENDWHILE. - lv_hexdig = `0123456789abcdef`. lv_hexval = 0. DO lv_j - lv_i - 2 TIMES. lv_hk = lv_i + 1 + sy-index. @@ -164,13 +202,12 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. lv_j = lv_i + 2. WHILE lv_j < lv_len. lv_d = iv_src+lv_j(1). - IF lv_d >= `0` AND lv_d <= `7`. + IF lv_d >= lv_c0 AND lv_d <= `7`. lv_j = lv_j + 1. ELSE. EXIT. ENDIF. ENDWHILE. - lv_hexdig = `0123456789abcdef`. lv_hexval = 0. DO lv_j - lv_i - 2 TIMES. lv_hk = lv_i + 1 + sy-index. @@ -183,14 +220,14 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. -val = |{ lv_hexval }|. lv_i = lv_j. CONTINUE. - ELSEIF lv_nhc >= `0` AND lv_nhc <= `7`. + ELSEIF lv_nhc >= lv_c0 AND lv_nhc <= `7`. " Legacy octal literal: 0NNN (non-strict mode ECMAScript) " Scan all consecutive decimal digits first; if any 8/9 found → decimal lv_j = lv_i + 1. - DATA lv_has_non_octal TYPE abap_bool VALUE abap_false. + lv_has_non_octal = abap_false. WHILE lv_j < lv_len. lv_d = iv_src+lv_j(1). - IF lv_d >= `0` AND lv_d <= `9`. + IF lv_d >= lv_c0 AND lv_d <= lv_c9. IF lv_d = `8` OR lv_d = `9`. lv_has_non_octal = abap_true. ENDIF. @@ -221,7 +258,7 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. lv_j = lv_i. WHILE lv_j < lv_len. lv_d = iv_src+lv_j(1). - IF ( lv_d >= `0` AND lv_d <= `9` ) OR lv_d = `.`. + IF ( lv_d >= lv_c0 AND lv_d <= lv_c9 ) OR lv_d = lv_dot. lv_j = lv_j + 1. ELSE. EXIT. @@ -239,7 +276,7 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. ENDIF. WHILE lv_j < lv_len. lv_d = iv_src+lv_j(1). - IF lv_d >= `0` AND lv_d <= `9`. + IF lv_d >= lv_c0 AND lv_d <= lv_c9. lv_j = lv_j + 1. ELSE. EXIT. @@ -256,7 +293,7 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. ENDIF. " String (single-quote, double-quote, or backtick template literal) - IF lv_ch = `'` OR lv_ch = `"` OR lv_ch = lv_bt. + IF lv_ch = lv_sq OR lv_ch = lv_dq OR lv_ch = lv_bt. lv_quote = lv_ch. lv_j = lv_i + 1. lv_chunk_start = lv_j. @@ -270,7 +307,7 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. ENDIF. EXIT. ENDIF. - IF lv_sc = `\` AND lv_j + 1 < lv_len. + IF lv_sc = lv_bsl AND lv_j + 1 < lv_len. lv_chunk_len = lv_j - lv_chunk_start. IF lv_chunk_len > 0. lv_sbuf = lv_sbuf && iv_src+lv_chunk_start(lv_chunk_len). @@ -279,114 +316,82 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. lv_esc = iv_src+lv_j(1). CASE lv_esc. WHEN `n`. - lv_sbuf = lv_sbuf && cl_abap_char_utilities=>newline. + lv_sbuf = lv_sbuf && lv_lf. WHEN `t`. - lv_sbuf = lv_sbuf && cl_abap_char_utilities=>horizontal_tab. + lv_sbuf = lv_sbuf && lv_tab. WHEN `r`. " CR = 0x0D - lv_esc_xb = 13. - lv_esc_xs = lv_esc_xb. - TRY. - lv_sbuf = lv_sbuf && cl_abap_codepage=>convert_from( source = lv_esc_xs ). - CATCH cx_sy_conversion_codepage. - ENDTRY. + lv_sbuf = lv_sbuf && lv_cr. WHEN `b`. " backspace = 0x08 - lv_esc_xb = 8. - lv_esc_xs = lv_esc_xb. - TRY. - lv_sbuf = lv_sbuf && cl_abap_codepage=>convert_from( source = lv_esc_xs ). - CATCH cx_sy_conversion_codepage. - ENDTRY. + lv_sbuf = lv_sbuf && gv_backspace. WHEN `f`. " form feed = 0x0C - lv_esc_xb = 12. - lv_esc_xs = lv_esc_xb. - TRY. - lv_sbuf = lv_sbuf && cl_abap_codepage=>convert_from( source = lv_esc_xs ). - CATCH cx_sy_conversion_codepage. - ENDTRY. + lv_sbuf = lv_sbuf && gv_formfeed. WHEN `v`. " vertical tab = 0x0B - lv_esc_xb = 11. - lv_esc_xs = lv_esc_xb. - TRY. - lv_sbuf = lv_sbuf && cl_abap_codepage=>convert_from( source = lv_esc_xs ). - CATCH cx_sy_conversion_codepage. - ENDTRY. + lv_sbuf = lv_sbuf && gv_vtab. WHEN `0`. " null = 0x00 - lv_esc_xb = 0. - lv_esc_xs = lv_esc_xb. - TRY. - lv_sbuf = lv_sbuf && cl_abap_codepage=>convert_from( source = lv_esc_xs ). - CATCH cx_sy_conversion_codepage. - ENDTRY. - WHEN `x`. " \xNN - 2 hex digits (0x00-0xFF, build proper UTF-8) + lv_sbuf = lv_sbuf && gv_nul. + WHEN `x`. " \xNN - 2 hex digits (0x00-0xFF) lv_esc_cp = 0. lv_xh = lv_j + 1. DO 2 TIMES. lv_hc = iv_src+lv_xh(1). - TRANSLATE lv_hc TO LOWER CASE. FIND FIRST OCCURRENCE OF lv_hc IN lv_hexdig MATCH OFFSET lv_hpos. + IF lv_hpos > 15. + lv_hpos = lv_hpos - 6. + ENDIF. lv_esc_cp = lv_esc_cp * 16 + lv_hpos. lv_xh = lv_xh + 1. ENDDO. lv_j = lv_j + 2. - IF lv_esc_cp < 128. - lv_ub1 = lv_esc_cp. - lv_utf8x = lv_ub1. + IF lv_esc_cp = 32. + " uccpi returns fixed-length char, && would trim a trailing blank + lv_sbuf = lv_sbuf && ` `. ELSE. - lv_ub1 = 192 + lv_esc_cp DIV 64. - lv_ub2 = 128 + lv_esc_cp MOD 64. - CONCATENATE lv_ub1 lv_ub2 INTO lv_utf8x IN BYTE MODE. + TRY. + lv_sbuf = lv_sbuf && cl_abap_conv_in_ce=>uccpi( lv_esc_cp ). + CATCH cx_sy_conversion_codepage. + ENDTRY. ENDIF. - TRY. - lv_sbuf = lv_sbuf && cl_abap_codepage=>convert_from( source = lv_utf8x ). - CATCH cx_sy_conversion_codepage. - ENDTRY. - WHEN `u`. " \uNNNN - 4 hex digits, BMP (U+0000-U+FFFF), build proper UTF-8 + WHEN `u`. " \uNNNN - 4 hex digits, BMP (U+0000-U+FFFF) lv_esc_cp = 0. lv_xh = lv_j + 1. DO 4 TIMES. lv_hc = iv_src+lv_xh(1). - TRANSLATE lv_hc TO LOWER CASE. FIND FIRST OCCURRENCE OF lv_hc IN lv_hexdig MATCH OFFSET lv_hpos. + IF lv_hpos > 15. + lv_hpos = lv_hpos - 6. + ENDIF. lv_esc_cp = lv_esc_cp * 16 + lv_hpos. lv_xh = lv_xh + 1. ENDDO. lv_j = lv_j + 4. - IF lv_esc_cp < 128. - lv_ub1 = lv_esc_cp. - lv_utf8x = lv_ub1. - ELSEIF lv_esc_cp < 2048. - lv_ub1 = 192 + lv_esc_cp DIV 64. - lv_ub2 = 128 + lv_esc_cp MOD 64. - CONCATENATE lv_ub1 lv_ub2 INTO lv_utf8x IN BYTE MODE. + IF lv_esc_cp = 32. + " uccpi returns fixed-length char, && would trim a trailing blank + lv_sbuf = lv_sbuf && ` `. ELSE. - lv_ub1 = 224 + lv_esc_cp DIV 4096. - lv_ub2 = 128 + ( lv_esc_cp DIV 64 ) MOD 64. - lv_ub3 = 128 + lv_esc_cp MOD 64. - CONCATENATE lv_ub1 lv_ub2 lv_ub3 INTO lv_utf8x IN BYTE MODE. + TRY. + lv_sbuf = lv_sbuf && cl_abap_conv_in_ce=>uccpi( lv_esc_cp ). + CATCH cx_sy_conversion_codepage. + ENDTRY. ENDIF. - TRY. - lv_sbuf = lv_sbuf && cl_abap_codepage=>convert_from( source = lv_utf8x ). - CATCH cx_sy_conversion_codepage. - ENDTRY. WHEN `\`. lv_sbuf = lv_sbuf && `\`. WHEN `'`. lv_sbuf = lv_sbuf && `'`. WHEN `"`. lv_sbuf = lv_sbuf && `"`. - WHEN cl_abap_char_utilities=>newline. " \ line continuation + WHEN lv_lf. " \ line continuation " skip – produce nothing - WHEN cl_abap_char_utilities=>cr_lf(1). " \ line continuation + WHEN lv_cr. " \ line continuation " skip; also consume following if present (CRLF line ending) IF lv_j + 1 < lv_len. lv_ni = lv_j + 1. - IF iv_src+lv_ni(1) = cl_abap_char_utilities=>newline. + IF iv_src+lv_ni(1) = lv_lf. lv_j = lv_ni. ENDIF. ENDIF. - WHEN lv_linterm_ls. " \ line continuation + WHEN gv_linterm_ls. " \ line continuation " skip – produce nothing - WHEN lv_linterm_ps. " \ line continuation + WHEN gv_linterm_ps. " \ line continuation " skip – produce nothing WHEN OTHERS. lv_sbuf = lv_sbuf && lv_esc. @@ -409,203 +414,320 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. CONTINUE. ENDIF. - " Identifier / keyword - IF lv_ch = `_` OR ( lv_ch >= `a` AND lv_ch <= `z` ) - OR ( lv_ch >= `A` AND lv_ch <= `Z` ). - lv_j = lv_i. - WHILE lv_j < lv_len. - lv_ic = iv_src+lv_j(1). - IF lv_ic = `_` OR ( lv_ic >= `a` AND lv_ic <= `z` ) - OR ( lv_ic >= `A` AND lv_ic <= `Z` ) - OR ( lv_ic >= `0` AND lv_ic <= `9` ). - lv_j = lv_j + 1. - ELSE. - EXIT. - ENDIF. - ENDWHILE. - lv_idlen = lv_j - lv_i. - APPEND INITIAL LINE TO rt_tokens ASSIGNING . - -kind = 2. - -val = iv_src+lv_i(lv_idlen). - IF -val = `instanceof`. + " Operators and punctuation: dispatch on the first character, then peek + " single characters - no 2/3-char substring comparisons + CASE lv_ch. + WHEN '(' OR ')' OR '{' OR '}' OR '[' OR ']' OR ';' OR ',' OR ':' OR '*' OR '%'. + APPEND INITIAL LINE TO rt_tokens ASSIGNING . -kind = 3. - ENDIF. - lv_i = lv_j. - CONTINUE. - ENDIF. + -val = lv_ch. + lv_i = lv_i + 1. - " Multi-char operators - IF lv_i + 1 < lv_len. - lv_two = iv_src+lv_i(2). - IF lv_two = `?.` OR lv_two = `??`. + WHEN '='. + CLEAR lv_nc. + lv_ni = lv_i + 1. + IF lv_ni < lv_len. + lv_nc = iv_src+lv_ni(1). + ENDIF. APPEND INITIAL LINE TO rt_tokens ASSIGNING . -kind = 3. - -val = lv_two. - lv_i = lv_i + 2. - CONTINUE. - ENDIF. - IF lv_two = `==` OR lv_two = `!=` OR lv_two = `<=` - OR lv_two = `>=` OR lv_two = `&&` OR lv_two = `||` - OR lv_two = `=>` OR lv_two = `+=` OR lv_two = `-=` - OR lv_two = `++` OR lv_two = `--`. - IF lv_i + 2 < lv_len AND - ( lv_two = `==` OR lv_two = `!=` ). - lv_three = iv_src+lv_i(3). - IF lv_three = `===` OR lv_three = `!==`. - APPEND INITIAL LINE TO rt_tokens ASSIGNING . - -kind = 3. - -val = lv_three. + IF lv_nc = lv_ceq. + CLEAR lv_n2. + lv_ni = lv_i + 2. + IF lv_ni < lv_len. + lv_n2 = iv_src+lv_ni(1). + ENDIF. + IF lv_n2 = lv_ceq. + -val = `===`. lv_i = lv_i + 3. - CONTINUE. + ELSE. + -val = `==`. + lv_i = lv_i + 2. ENDIF. + ELSEIF lv_nc = `>`. + -val = `=>`. + lv_i = lv_i + 2. + ELSE. + -val = `=`. + lv_i = lv_i + 1. + ENDIF. + + WHEN '!'. + CLEAR lv_nc. + lv_ni = lv_i + 1. + IF lv_ni < lv_len. + lv_nc = iv_src+lv_ni(1). ENDIF. APPEND INITIAL LINE TO rt_tokens ASSIGNING . -kind = 3. - -val = lv_two. - lv_i = lv_i + 2. - CONTINUE. - ENDIF. - ENDIF. - IF lv_ch = `.` AND lv_i + 2 < lv_len AND iv_src+lv_i(3) = `...`. - APPEND INITIAL LINE TO rt_tokens ASSIGNING . - -kind = 3. - -val = `...`. - lv_i = lv_i + 3. - CONTINUE. - ENDIF. - - " Dot-prefixed number: .5, .0e1 - IF lv_ch = `.` AND lv_i + 1 < lv_len. - lv_ni = lv_i + 1. - lv_ndot = iv_src+lv_ni(1). - IF lv_ndot >= `0` AND lv_ndot <= `9`. - lv_j = lv_i. - WHILE lv_j < lv_len. - lv_d = iv_src+lv_j(1). - IF ( lv_d >= `0` AND lv_d <= `9` ) OR lv_d = `.`. - lv_j = lv_j + 1. - ELSE. - EXIT. + IF lv_nc = lv_ceq. + CLEAR lv_n2. + lv_ni = lv_i + 2. + IF lv_ni < lv_len. + lv_n2 = iv_src+lv_ni(1). ENDIF. - ENDWHILE. - IF lv_j < lv_len. - lv_echar = iv_src+lv_j(1). - IF lv_echar = `e` OR lv_echar = `E`. - lv_j = lv_j + 1. - IF lv_j < lv_len. - lv_esign = iv_src+lv_j(1). - IF lv_esign = `+` OR lv_esign = `-`. - lv_j = lv_j + 1. - ENDIF. - ENDIF. - WHILE lv_j < lv_len. - lv_d = iv_src+lv_j(1). - IF lv_d >= `0` AND lv_d <= `9`. - lv_j = lv_j + 1. - ELSE. - EXIT. - ENDIF. - ENDWHILE. + IF lv_n2 = lv_ceq. + -val = `!==`. + lv_i = lv_i + 3. + ELSE. + -val = `!=`. + lv_i = lv_i + 2. ENDIF. + ELSE. + -val = `!`. + lv_i = lv_i + 1. + ENDIF. + + WHEN '<' OR '>'. + CLEAR lv_nc. + lv_ni = lv_i + 1. + IF lv_ni < lv_len. + lv_nc = iv_src+lv_ni(1). ENDIF. - lv_numlen = lv_j - lv_i. APPEND INITIAL LINE TO rt_tokens ASSIGNING . - -kind = 0. - -val = iv_src+lv_i(lv_numlen). - lv_i = lv_j. - CONTINUE. - ENDIF. - ENDIF. + -kind = 3. + IF lv_nc = lv_ceq. + -val = lv_ch && lv_nc. + lv_i = lv_i + 2. + ELSE. + -val = lv_ch. + lv_i = lv_i + 1. + ENDIF. - " Regex literal: /pattern/flags — when context indicates start of expression - " (not after number, string, identifier, or closing bracket which would mean division) - IF lv_ch = `/`. - DATA lv_rxis TYPE abap_bool VALUE abap_true. - DATA ls_ptok TYPE zif_mjs=>ty_token. - DATA lv_nptok TYPE i. - lv_nptok = lines( rt_tokens ). - IF lv_nptok > 0. - READ TABLE rt_tokens INDEX lv_nptok INTO ls_ptok. - IF ls_ptok-kind = 0 " number - OR ls_ptok-kind = 1 " string - OR ( ls_ptok-kind = 2 " identifier (but not expression-starting keywords) - AND ls_ptok-val <> `return` - AND ls_ptok-val <> `typeof` - AND ls_ptok-val <> `instanceof` - AND ls_ptok-val <> `in` - AND ls_ptok-val <> `of` - AND ls_ptok-val <> `throw` - AND ls_ptok-val <> `case` - AND ls_ptok-val <> `delete` - AND ls_ptok-val <> `new` - AND ls_ptok-val <> `void` ) - OR ls_ptok-val = `)` OR ls_ptok-val = `]` OR ls_ptok-val = `}`. - lv_rxis = abap_false. + WHEN '+' OR '-'. + CLEAR lv_nc. + lv_ni = lv_i + 1. + IF lv_ni < lv_len. + lv_nc = iv_src+lv_ni(1). ENDIF. - ENDIF. - IF lv_rxis = abap_true. - DATA lv_rxpat TYPE string. - DATA lv_rxflg TYPE string. - DATA lv_rxch TYPE c LENGTH 1. - DATA lv_rxfc TYPE c LENGTH 1. - DATA lv_rxclass TYPE abap_bool VALUE abap_false. - CLEAR lv_rxpat. - CLEAR lv_rxflg. - lv_j = lv_i + 1. - WHILE lv_j < lv_len. - lv_rxch = iv_src+lv_j(1). - IF lv_rxch = `/` AND lv_rxclass = abap_false. - EXIT. - ENDIF. - IF lv_rxch = `[` AND lv_rxclass = abap_false. - lv_rxclass = abap_true. - ELSEIF lv_rxch = `]` AND lv_rxclass = abap_true. - lv_rxclass = abap_false. + APPEND INITIAL LINE TO rt_tokens ASSIGNING . + -kind = 3. + IF lv_nc = lv_ch OR lv_nc = lv_ceq. + -val = lv_ch && lv_nc. + lv_i = lv_i + 2. + ELSE. + -val = lv_ch. + lv_i = lv_i + 1. + ENDIF. + + WHEN '?'. + CLEAR lv_nc. + lv_ni = lv_i + 1. + IF lv_ni < lv_len. + lv_nc = iv_src+lv_ni(1). + ENDIF. + APPEND INITIAL LINE TO rt_tokens ASSIGNING . + -kind = 3. + IF lv_nc = lv_dot OR lv_nc = `?`. + -val = lv_ch && lv_nc. + lv_i = lv_i + 2. + ELSE. + -val = `?`. + lv_i = lv_i + 1. + ENDIF. + + WHEN '&'. + CLEAR lv_nc. + lv_ni = lv_i + 1. + IF lv_ni < lv_len. + lv_nc = iv_src+lv_ni(1). + ENDIF. + IF lv_nc = `&`. + APPEND INITIAL LINE TO rt_tokens ASSIGNING . + -kind = 3. + -val = `&&`. + lv_i = lv_i + 2. + ELSE. + " single & not supported: skip (pre-existing behavior) + lv_i = lv_i + 1. + ENDIF. + + WHEN '|'. + CLEAR lv_nc. + lv_ni = lv_i + 1. + IF lv_ni < lv_len. + lv_nc = iv_src+lv_ni(1). + ENDIF. + IF lv_nc = `|`. + APPEND INITIAL LINE TO rt_tokens ASSIGNING . + -kind = 3. + -val = `||`. + lv_i = lv_i + 2. + ELSE. + " single | not supported: skip (pre-existing behavior) + lv_i = lv_i + 1. + ENDIF. + + WHEN '.'. + CLEAR lv_nc. + lv_ni = lv_i + 1. + IF lv_ni < lv_len. + lv_nc = iv_src+lv_ni(1). + ENDIF. + IF lv_nc = lv_dot. + CLEAR lv_n2. + lv_ni = lv_i + 2. + IF lv_ni < lv_len. + lv_n2 = iv_src+lv_ni(1). ENDIF. - IF lv_rxch = `\` AND lv_j + 1 < lv_len. - lv_rxpat = lv_rxpat && `\`. - lv_j = lv_j + 1. - lv_rxpat = lv_rxpat && substring( val = iv_src off = lv_j len = 1 ). + APPEND INITIAL LINE TO rt_tokens ASSIGNING . + -kind = 3. + IF lv_n2 = lv_dot. + -val = `...`. + lv_i = lv_i + 3. ELSE. - lv_rxpat = lv_rxpat && substring( val = iv_src off = lv_j len = 1 ). + -val = `.`. + lv_i = lv_i + 1. ENDIF. - lv_j = lv_j + 1. - ENDWHILE. - IF lv_j < lv_len. - lv_j = lv_j + 1. " skip closing / + ELSEIF lv_nc >= lv_c0 AND lv_nc <= lv_c9. + " Dot-prefixed number: .5, .0e1 + lv_j = lv_i. WHILE lv_j < lv_len. - lv_rxfc = iv_src+lv_j(1). - IF lv_rxfc >= `a` AND lv_rxfc <= `z`. - lv_rxflg = lv_rxflg && lv_rxfc. + lv_d = iv_src+lv_j(1). + IF ( lv_d >= lv_c0 AND lv_d <= lv_c9 ) OR lv_d = lv_dot. lv_j = lv_j + 1. ELSE. EXIT. ENDIF. ENDWHILE. + IF lv_j < lv_len. + lv_echar = iv_src+lv_j(1). + IF lv_echar = `e` OR lv_echar = `E`. + lv_j = lv_j + 1. + IF lv_j < lv_len. + lv_esign = iv_src+lv_j(1). + IF lv_esign = `+` OR lv_esign = `-`. + lv_j = lv_j + 1. + ENDIF. + ENDIF. + WHILE lv_j < lv_len. + lv_d = iv_src+lv_j(1). + IF lv_d >= lv_c0 AND lv_d <= lv_c9. + lv_j = lv_j + 1. + ELSE. + EXIT. + ENDIF. + ENDWHILE. + ENDIF. + ENDIF. + lv_numlen = lv_j - lv_i. APPEND INITIAL LINE TO rt_tokens ASSIGNING . - -kind = 6. - -val = lv_rxpat && cl_abap_char_utilities=>newline && lv_rxflg. + -kind = 0. + -val = iv_src+lv_i(lv_numlen). lv_i = lv_j. - CONTINUE. + ELSE. + APPEND INITIAL LINE TO rt_tokens ASSIGNING . + -kind = 3. + -val = `.`. + lv_i = lv_i + 1. ENDIF. - ENDIF. - ENDIF. - " Single char op/punc - IF lv_ch = `+` OR lv_ch = `-` OR lv_ch = `*` OR lv_ch = `/` - OR lv_ch = `%` OR lv_ch = `=` OR lv_ch = `<` OR lv_ch = `>` - OR lv_ch = `!` OR lv_ch = `(` OR lv_ch = `)` OR lv_ch = `,` - OR lv_ch = `{` OR lv_ch = `}` OR lv_ch = `;` OR lv_ch = `:` - OR lv_ch = `.` OR lv_ch = `[` OR lv_ch = `]` - OR lv_ch = `?`. - APPEND INITIAL LINE TO rt_tokens ASSIGNING . - -kind = 3. - -val = lv_ch. - lv_i = lv_i + 1. - CONTINUE. - ENDIF. + WHEN '/'. + " Comment? + lv_ni = lv_i + 1. + IF lv_ni < lv_len. + lv_nc = iv_src+lv_ni(1). + IF lv_nc = lv_slash. + " Line comment: skip to end of line + lv_i = lv_i + 2. + WHILE lv_i < lv_len. + IF iv_src+lv_i(1) = lv_lf. + EXIT. + ENDIF. + lv_i = lv_i + 1. + ENDWHILE. + CONTINUE. + ELSEIF lv_nc = lv_star. + " Block comment: skip to */ + lv_j = lv_i + 2. + WHILE lv_j < lv_len. + IF iv_src+lv_j(1) = lv_star. + lv_ni = lv_j + 1. + IF lv_ni < lv_len AND iv_src+lv_ni(1) = lv_slash. + lv_j = lv_ni + 1. + EXIT. + ENDIF. + ENDIF. + lv_j = lv_j + 1. + ENDWHILE. + lv_i = lv_j. + CONTINUE. + ENDIF. + ENDIF. + " Regex literal: /pattern/flags — when context indicates start of expression + " (not after number, string, identifier, or closing bracket which would mean division) + lv_rxis = abap_true. + lv_nptok = lines( rt_tokens ). + IF lv_nptok > 0. + READ TABLE rt_tokens INDEX lv_nptok ASSIGNING . + IF -kind = 0 " number + OR -kind = 1 " string + OR ( -kind = 2 " identifier (but not expression-starting keywords) + AND -val <> `return` + AND -val <> `typeof` + AND -val <> `instanceof` + AND -val <> `in` + AND -val <> `of` + AND -val <> `throw` + AND -val <> `case` + AND -val <> `delete` + AND -val <> `new` + AND -val <> `void` ) + OR -val = `)` OR -val = `]` OR -val = `}`. + lv_rxis = abap_false. + ENDIF. + ENDIF. + IF lv_rxis = abap_true. + CLEAR lv_rxpat. + CLEAR lv_rxflg. + lv_rxclass = abap_false. + lv_j = lv_i + 1. + WHILE lv_j < lv_len. + lv_rxch = iv_src+lv_j(1). + IF lv_rxch = lv_slash AND lv_rxclass = abap_false. + EXIT. + ENDIF. + IF lv_rxch = `[` AND lv_rxclass = abap_false. + lv_rxclass = abap_true. + ELSEIF lv_rxch = `]` AND lv_rxclass = abap_true. + lv_rxclass = abap_false. + ENDIF. + IF lv_rxch = lv_bsl AND lv_j + 1 < lv_len. + lv_rxpat = lv_rxpat && lv_bsl. + lv_j = lv_j + 1. + lv_rxpat = lv_rxpat && iv_src+lv_j(1). + ELSE. + lv_rxpat = lv_rxpat && iv_src+lv_j(1). + ENDIF. + lv_j = lv_j + 1. + ENDWHILE. + IF lv_j < lv_len. + lv_j = lv_j + 1. " skip closing / + WHILE lv_j < lv_len. + lv_rxfc = iv_src+lv_j(1). + IF lv_rxfc >= lv_ca AND lv_rxfc <= lv_cz. + lv_rxflg = lv_rxflg && lv_rxfc. + lv_j = lv_j + 1. + ELSE. + EXIT. + ENDIF. + ENDWHILE. + APPEND INITIAL LINE TO rt_tokens ASSIGNING . + -kind = 6. + -val = lv_rxpat && lv_lf && lv_rxflg. + lv_i = lv_j. + CONTINUE. + ENDIF. + ENDIF. + " Division or lone slash + APPEND INITIAL LINE TO rt_tokens ASSIGNING . + -kind = 3. + -val = `/`. + lv_i = lv_i + 1. - lv_i = lv_i + 1. + WHEN OTHERS. + lv_i = lv_i + 1. + ENDCASE. ENDWHILE. APPEND INITIAL LINE TO rt_tokens ASSIGNING . diff --git a/src/zif_mjs.intf.abap b/src/zif_mjs.intf.abap index d3205a2..40a90c2 100644 --- a/src/zif_mjs.intf.abap +++ b/src/zif_mjs.intf.abap @@ -47,10 +47,13 @@ INTERFACE zif_mjs PUBLIC. " Class method TYPES: BEGIN OF ty_class_method, - name TYPE string, - params TYPE STANDARD TABLE OF string WITH DEFAULT KEY, - body TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY, - is_ctor TYPE abap_bool, + name TYPE string, + params TYPE STANDARD TABLE OF string WITH DEFAULT KEY, + body TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY, + is_ctor TYPE abap_bool, + body_lazy TYPE abap_bool, + body_pos TYPE i, + body_tokens TYPE REF TO tt_tokens, END OF ty_class_method, tt_class_methods TYPE STANDARD TABLE OF ty_class_method WITH DEFAULT KEY. @@ -124,6 +127,11 @@ INTERFACE zif_mjs PUBLIC. prop_expr TYPE REF TO data, cases TYPE tt_switch_cases, methods TYPE tt_class_methods, + " Deferred function/arrow body. body_pos is the zero-based token index + " of its opening brace in body_tokens. + body_lazy TYPE abap_bool, + body_pos TYPE i, + body_tokens TYPE REF TO tt_tokens, " Slot optimization: pre-assigned local variable index (1-based) slot TYPE i, " 0 = not assigned slot_ok TYPE abap_bool, " X = use slot, ' ' = use name-based lookup @@ -136,6 +144,9 @@ INTERFACE zif_mjs PUBLIC. params TYPE STANDARD TABLE OF string WITH DEFAULT KEY, default_params TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY, body TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY, + body_lazy TYPE abap_bool, + body_pos TYPE i, + body_tokens TYPE REF TO tt_tokens, closure TYPE REF TO object, " Slot optimization: populated once on first call (lazy compile) compiled TYPE abap_bool, " X = slots already assigned