From 36bfa883747d3227d0c1b258ca9674295796dc88 Mon Sep 17 00:00:00 2001 From: Lars HVam Date: Sat, 18 Jul 2026 20:34:29 +0200 Subject: [PATCH 1/5] byte code --- src/zcl_mjs.clas.abap | 762 +++++++++++++++++++++++++++++- src/zcl_mjs.clas.testclasses.abap | 14 + src/zif_mjs.intf.abap | 14 + 3 files changed, 771 insertions(+), 19 deletions(-) diff --git a/src/zcl_mjs.clas.abap b/src/zcl_mjs.clas.abap index 92d0520..3572aac 100644 --- a/src/zcl_mjs.clas.abap +++ b/src/zcl_mjs.clas.abap @@ -14,6 +14,63 @@ CLASS zcl_mjs DEFINITION PUBLIC. RAISING zcx_mjs_runtime. PRIVATE SECTION. + CONSTANTS: + c_bc_push_num TYPE i VALUE 1, + c_bc_push_str TYPE i VALUE 2, + c_bc_push_bool TYPE i VALUE 3, + c_bc_push_undef TYPE i VALUE 4, + c_bc_pop TYPE i VALUE 5, + c_bc_dup TYPE i VALUE 6, + c_bc_load_slot TYPE i VALUE 10, + c_bc_load_name TYPE i VALUE 11, + c_bc_store_slot TYPE i VALUE 12, + c_bc_store_name TYPE i VALUE 13, + c_bc_def_slot TYPE i VALUE 14, + c_bc_def_name TYPE i VALUE 15, + c_bc_binop TYPE i VALUE 20, + c_bc_unary TYPE i VALUE 21, + c_bc_jump TYPE i VALUE 30, + c_bc_jump_false TYPE i VALUE 31, + c_bc_jump_true TYPE i VALUE 32, + c_bc_jump_not_null TYPE i VALUE 33, + c_bc_eval_node TYPE i VALUE 40, + c_bc_return TYPE i VALUE 41, + c_bc_get_prop TYPE i VALUE 50, + c_bc_get_idx TYPE i VALUE 51, + c_bc_set_prop TYPE i VALUE 52, + c_bc_call_name TYPE i VALUE 60, + c_bc_call_method TYPE i VALUE 61, + c_bc_enter_scope TYPE i VALUE 70, + c_bc_exit_scope TYPE i VALUE 71. + + " One-entry compiled-module cache: avoids lexing/parsing/compiling when the + " same source is evaluated repeatedly in one internal session. + CLASS-DATA gv_bc_cached_source TYPE string. + CLASS-DATA gt_bc_cached_stmts TYPE zif_mjs=>tt_nodes. + CLASS-DATA gr_bc_cached_code TYPE REF TO zif_mjs=>tt_bc_code. + CLASS-DATA gv_bc_cache_valid TYPE abap_bool. + + CLASS-METHODS bc_compile_nodes + IMPORTING it_nodes TYPE zif_mjs=>tt_nodes + iv_use_slots TYPE abap_bool DEFAULT abap_false + RETURNING VALUE(rt_code) TYPE zif_mjs=>tt_bc_code. + CLASS-METHODS bc_compile_stmt + IMPORTING ir_node TYPE REF TO data + iv_use_slots TYPE abap_bool + CHANGING ct_code TYPE zif_mjs=>tt_bc_code. + CLASS-METHODS bc_compile_expr + IMPORTING ir_node TYPE REF TO data + iv_use_slots TYPE abap_bool + CHANGING ct_code TYPE zif_mjs=>tt_bc_code. + CLASS-METHODS bc_has_loop_control + IMPORTING ir_node TYPE REF TO data + RETURNING VALUE(rv_found) TYPE abap_bool. + CLASS-METHODS bc_execute + IMPORTING it_code TYPE zif_mjs=>tt_bc_code + io_env TYPE REF TO zcl_mjs_env + RETURNING VALUE(rs_val) TYPE zif_mjs=>ty_value + RAISING zcx_mjs_runtime. + CLASS-METHODS eval_node IMPORTING ir_node TYPE REF TO data io_env TYPE REF TO zcl_mjs_env @@ -169,15 +226,30 @@ ENDCLASS. CLASS zcl_mjs IMPLEMENTATION. METHOD eval. - DATA lt_tokens TYPE zif_mjs=>tt_tokens. - lt_tokens = zcl_mjs_tokenizer=>tokenize( iv_source ). + DATA lt_stmts TYPE zif_mjs=>tt_nodes. + DATA lr_top_code TYPE REF TO zif_mjs=>tt_bc_code. - DATA lo_parser TYPE REF TO zcl_mjs_parser. - CREATE OBJECT lo_parser - EXPORTING it_tokens = lt_tokens. - - DATA lt_stmts TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY. - lt_stmts = lo_parser->parse_program( ). + IF gv_bc_cache_valid = abap_true AND gv_bc_cached_source = iv_source + AND gr_bc_cached_code IS BOUND. + lt_stmts = gt_bc_cached_stmts. + lr_top_code = gr_bc_cached_code. + ELSE. + DATA lt_tokens TYPE zif_mjs=>tt_tokens. + lt_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. + lt_stmts = lo_parser->parse_program( ). + + DATA(lt_top_code) = bc_compile_nodes( it_nodes = lt_stmts iv_use_slots = abap_false ). + CREATE DATA gr_bc_cached_code. + gr_bc_cached_code->* = lt_top_code. + gt_bc_cached_stmts = lt_stmts. + gv_bc_cached_source = iv_source. + gv_bc_cache_valid = abap_true. + lr_top_code = gr_bc_cached_code. + ENDIF. DATA lo_env TYPE REF TO zcl_mjs_env. CREATE OBJECT lo_env. @@ -227,9 +299,7 @@ CLASS zcl_mjs IMPLEMENTATION. ENDIF. ENDLOOP. - LOOP AT lt_stmts INTO DATA(lr_stmt). - eval_node( ir_node = lr_stmt io_env = lo_env ). - ENDLOOP. + bc_execute( it_code = lr_top_code->* io_env = lo_env ). rv_output = lo_env->get_output( ). ENDMETHOD. @@ -331,14 +401,16 @@ CLASS zcl_mjs IMPLEMENTATION. ENDIF. ENDLOOP. - " Execute body - LOOP AT -body INTO DATA(lr_stmt). - rs_val = eval_node( ir_node = lr_stmt io_env = lo_call_env ). - IF lo_call_env->returning = abap_true. - rs_val = lo_call_env->ret_val. - EXIT. - ENDIF. - ENDLOOP. + " Lazily lower the annotated function body to bytecode, then execute it. + IF -bc_code IS NOT BOUND. + DATA(lt_fn_code) = bc_compile_nodes( it_nodes = -body iv_use_slots = abap_true ). + CREATE DATA -bc_code. + -bc_code->* = lt_fn_code. + ENDIF. + rs_val = bc_execute( it_code = -bc_code->* io_env = lo_call_env ). + IF lo_call_env->returning = abap_true. + rs_val = lo_call_env->ret_val. + ENDIF. " Write back 'this' mutations (for constructor use) IF ir_this IS BOUND. @@ -564,6 +636,658 @@ CLASS zcl_mjs IMPLEMENTATION. ENDCASE. ENDMETHOD. + METHOD bc_compile_nodes. + LOOP AT it_nodes INTO DATA(lr_node). + bc_compile_stmt( + EXPORTING ir_node = lr_node + iv_use_slots = iv_use_slots + CHANGING ct_code = rt_code ). + ENDLOOP. + ENDMETHOD. + + METHOD bc_compile_stmt. + IF ir_node IS NOT BOUND. + RETURN. + ENDIF. + FIELD-SYMBOLS TYPE zif_mjs=>ty_node. + ASSIGN ir_node->* TO . + + CASE -kind. + WHEN zif_mjs=>c_node_var. + IF -right IS BOUND. + bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + ELSE. + APPEND VALUE #( opcode = c_bc_push_undef ) TO ct_code. + ENDIF. + IF iv_use_slots = abap_true AND -slot_ok = abap_true. + APPEND VALUE #( opcode = c_bc_def_slot arg = -slot ) TO ct_code. + ELSE. + APPEND VALUE #( opcode = c_bc_def_name text = -str ) TO ct_code. + ENDIF. + APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. + + WHEN zif_mjs=>c_node_if. + bc_compile_expr( EXPORTING ir_node = -cond iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + DATA(lv_if_false) = lines( ct_code ) + 1. + APPEND VALUE #( opcode = c_bc_jump_false ) TO ct_code. + LOOP AT -body INTO DATA(lr_if_body). + bc_compile_stmt( EXPORTING ir_node = lr_if_body iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + ENDLOOP. + IF lines( -els ) > 0. + DATA(lv_if_end) = lines( ct_code ) + 1. + APPEND VALUE #( opcode = c_bc_jump ) TO ct_code. + ct_code[ lv_if_false ]-target = lines( ct_code ) + 1. + LOOP AT -els INTO DATA(lr_else_body). + bc_compile_stmt( EXPORTING ir_node = lr_else_body iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + ENDLOOP. + ct_code[ lv_if_end ]-target = lines( ct_code ) + 1. + ELSE. + ct_code[ lv_if_false ]-target = lines( ct_code ) + 1. + ENDIF. + + WHEN zif_mjs=>c_node_while. + DATA(lv_while_control) = abap_false. + LOOP AT -body INTO DATA(lr_while_check). + IF bc_has_loop_control( lr_while_check ) = abap_true. + lv_while_control = abap_true. + EXIT. + ENDIF. + ENDLOOP. + IF lv_while_control = abap_true. + APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. + APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. + RETURN. + ENDIF. + DATA(lv_while_start) = lines( ct_code ) + 1. + bc_compile_expr( EXPORTING ir_node = -cond iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + DATA(lv_while_exit) = lines( ct_code ) + 1. + APPEND VALUE #( opcode = c_bc_jump_false ) TO ct_code. + LOOP AT -body INTO DATA(lr_while_body). + bc_compile_stmt( EXPORTING ir_node = lr_while_body iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + ENDLOOP. + APPEND VALUE #( opcode = c_bc_jump target = lv_while_start ) TO ct_code. + ct_code[ lv_while_exit ]-target = lines( ct_code ) + 1. + + WHEN zif_mjs=>c_node_for. + " The loop runs in a child lexical environment. Its instructions use + " name lookup so loop locals stay in that child while outer function + " slots are still reached through the parent chain. + DATA(lv_for_control) = abap_false. + LOOP AT -body INTO DATA(lr_for_check). + IF bc_has_loop_control( lr_for_check ) = abap_true. + lv_for_control = abap_true. + EXIT. + ENDIF. + ENDLOOP. + IF lv_for_control = abap_true. + APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. + APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. + RETURN. + ENDIF. + APPEND VALUE #( opcode = c_bc_enter_scope ) TO ct_code. + IF -init IS BOUND. + bc_compile_stmt( EXPORTING ir_node = -init iv_use_slots = abap_false CHANGING ct_code = ct_code ). + ENDIF. + DATA(lv_for_start) = lines( ct_code ) + 1. + DATA lv_for_exit TYPE i. + IF -cond IS BOUND. + bc_compile_expr( EXPORTING ir_node = -cond iv_use_slots = abap_false CHANGING ct_code = ct_code ). + lv_for_exit = lines( ct_code ) + 1. + APPEND VALUE #( opcode = c_bc_jump_false ) TO ct_code. + ENDIF. + LOOP AT -body INTO DATA(lr_for_body). + bc_compile_stmt( EXPORTING ir_node = lr_for_body iv_use_slots = abap_false CHANGING ct_code = ct_code ). + ENDLOOP. + IF -update IS BOUND. + bc_compile_expr( EXPORTING ir_node = -update iv_use_slots = abap_false CHANGING ct_code = ct_code ). + APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. + ENDIF. + APPEND VALUE #( opcode = c_bc_jump target = lv_for_start ) TO ct_code. + IF lv_for_exit > 0. + ct_code[ lv_for_exit ]-target = lines( ct_code ) + 1. + ENDIF. + APPEND VALUE #( opcode = c_bc_exit_scope ) TO ct_code. + + WHEN zif_mjs=>c_node_block. + LOOP AT -body INTO DATA(lr_block_body). + bc_compile_stmt( EXPORTING ir_node = lr_block_body iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + ENDLOOP. + + WHEN zif_mjs=>c_node_return. + IF -left IS BOUND. + bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + ELSE. + APPEND VALUE #( opcode = c_bc_push_undef ) TO ct_code. + ENDIF. + APPEND VALUE #( opcode = c_bc_return ) TO ct_code. + + WHEN OTHERS. + bc_compile_expr( EXPORTING ir_node = ir_node iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. + ENDCASE. + ENDMETHOD. + + METHOD bc_compile_expr. + IF ir_node IS NOT BOUND. + APPEND VALUE #( opcode = c_bc_push_undef ) TO ct_code. + RETURN. + ENDIF. + FIELD-SYMBOLS TYPE zif_mjs=>ty_node. + ASSIGN ir_node->* TO . + + CASE -kind. + WHEN zif_mjs=>c_node_number. + APPEND VALUE #( opcode = c_bc_push_num num = -num ) TO ct_code. + WHEN zif_mjs=>c_node_string. + APPEND VALUE #( opcode = c_bc_push_str text = -str ) TO ct_code. + WHEN zif_mjs=>c_node_bool. + APPEND VALUE #( opcode = c_bc_push_bool arg = CONV i( -num ) ) TO ct_code. + + WHEN zif_mjs=>c_node_ident. + IF iv_use_slots = abap_true AND -slot_ok = abap_true. + APPEND VALUE #( opcode = c_bc_load_slot arg = -slot ) TO ct_code. + ELSE. + APPEND VALUE #( opcode = c_bc_load_name text = -str ) TO ct_code. + ENDIF. + + WHEN zif_mjs=>c_node_binop. + IF -op = `&&` OR -op = `||` OR -op = `??`. + bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + APPEND VALUE #( opcode = c_bc_dup ) TO ct_code. + DATA(lv_short_jump) = lines( ct_code ) + 1. + IF -op = `&&`. + APPEND VALUE #( opcode = c_bc_jump_false ) TO ct_code. + ELSEIF -op = `||`. + APPEND VALUE #( opcode = c_bc_jump_true ) TO ct_code. + ELSE. + APPEND VALUE #( opcode = c_bc_jump_not_null ) TO ct_code. + ENDIF. + APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. + bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + ct_code[ lv_short_jump ]-target = lines( ct_code ) + 1. + ELSE. + bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + APPEND VALUE #( opcode = c_bc_binop text = -op ) TO ct_code. + ENDIF. + + WHEN zif_mjs=>c_node_unaryop. + IF -op = `-` OR -op = `!`. + bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + APPEND VALUE #( opcode = c_bc_unary text = -op ) TO ct_code. + ELSEIF -op = `void`. + bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. + APPEND VALUE #( opcode = c_bc_push_undef ) TO ct_code. + ELSE. + APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. + ENDIF. + + WHEN zif_mjs=>c_node_assign OR zif_mjs=>c_node_assign_add. + IF -kind = zif_mjs=>c_node_assign_add. + IF iv_use_slots = abap_true AND -slot_ok = abap_true. + APPEND VALUE #( opcode = c_bc_load_slot arg = -slot ) TO ct_code. + ELSE. + APPEND VALUE #( opcode = c_bc_load_name text = -str ) TO ct_code. + ENDIF. + ENDIF. + bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + IF -kind = zif_mjs=>c_node_assign_add. + APPEND VALUE #( opcode = c_bc_binop text = `+` ) TO ct_code. + ENDIF. + IF iv_use_slots = abap_true AND -slot_ok = abap_true. + APPEND VALUE #( opcode = c_bc_store_slot arg = -slot ) TO ct_code. + ELSE. + APPEND VALUE #( opcode = c_bc_store_name text = -str ) TO ct_code. + ENDIF. + + WHEN zif_mjs=>c_node_inc OR zif_mjs=>c_node_dec + OR zif_mjs=>c_node_post_inc OR zif_mjs=>c_node_post_dec. + FIELD-SYMBOLS TYPE zif_mjs=>ty_node. + ASSIGN -left->* TO . + IF sy-subrc <> 0 OR -kind <> zif_mjs=>c_node_ident. + APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. + RETURN. + ENDIF. + bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + IF -kind = zif_mjs=>c_node_post_inc OR -kind = zif_mjs=>c_node_post_dec. + APPEND VALUE #( opcode = c_bc_dup ) TO ct_code. + ENDIF. + APPEND VALUE #( opcode = c_bc_push_num num = 1 ) TO ct_code. + IF -kind = zif_mjs=>c_node_inc OR -kind = zif_mjs=>c_node_post_inc. + APPEND VALUE #( opcode = c_bc_binop text = `+` ) TO ct_code. + ELSE. + APPEND VALUE #( opcode = c_bc_binop text = `-` ) TO ct_code. + ENDIF. + IF iv_use_slots = abap_true AND -slot_ok = abap_true. + APPEND VALUE #( opcode = c_bc_store_slot arg = -slot ) TO ct_code. + ELSE. + APPEND VALUE #( opcode = c_bc_store_name text = -str ) TO ct_code. + ENDIF. + IF -kind = zif_mjs=>c_node_post_inc OR -kind = zif_mjs=>c_node_post_dec. + APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. + ENDIF. + + WHEN zif_mjs=>c_node_member_access. + DATA(lv_member_fallback) = boolc( -op = `?.` ). + FIELD-SYMBOLS TYPE zif_mjs=>ty_node. + IF -object IS BOUND. + ASSIGN -object->* TO . + IF sy-subrc = 0 AND -kind = zif_mjs=>c_node_ident + AND -str = `Object`. + lv_member_fallback = abap_true. + ENDIF. + ENDIF. + IF lv_member_fallback = abap_true. + APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. + RETURN. + ENDIF. + bc_compile_expr( EXPORTING ir_node = -object iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + IF -prop_expr IS BOUND. + bc_compile_expr( EXPORTING ir_node = -prop_expr iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + APPEND VALUE #( opcode = c_bc_get_idx ) TO ct_code. + ELSE. + APPEND VALUE #( opcode = c_bc_get_prop text = -property ) TO ct_code. + ENDIF. + + WHEN zif_mjs=>c_node_member_assign. + IF -prop_expr IS BOUND. + APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. + RETURN. + ENDIF. + bc_compile_expr( EXPORTING ir_node = -object iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + APPEND VALUE #( opcode = c_bc_set_prop text = -property node = -object ) TO ct_code. + + WHEN zif_mjs=>c_node_call. + DATA(lv_call_fallback) = boolc( -left IS BOUND OR -str = `super` ). + LOOP AT -args INTO DATA(lr_call_check). + FIELD-SYMBOLS TYPE zif_mjs=>ty_node. + ASSIGN lr_call_check->* TO . + IF sy-subrc = 0 AND -op = `SPREAD`. + lv_call_fallback = abap_true. + EXIT. + ENDIF. + ENDLOOP. + IF lv_call_fallback = abap_true. + APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. + RETURN. + ENDIF. + LOOP AT -args INTO DATA(lr_call_arg). + bc_compile_expr( EXPORTING ir_node = lr_call_arg iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + ENDLOOP. + APPEND VALUE #( opcode = c_bc_call_name arg = lines( -args ) text = -str node = ir_node ) TO ct_code. + + WHEN zif_mjs=>c_node_method_call. + DATA(lv_method_fallback) = boolc( -op = `?.` ). + FIELD-SYMBOLS TYPE zif_mjs=>ty_node. + IF -object IS BOUND. + ASSIGN -object->* TO . + IF sy-subrc = 0 AND -kind = zif_mjs=>c_node_ident + AND ( -str = `Date` OR -str = `Object` OR -str = `super` ). + lv_method_fallback = abap_true. + ENDIF. + ENDIF. + LOOP AT -args INTO DATA(lr_method_check). + FIELD-SYMBOLS TYPE zif_mjs=>ty_node. + ASSIGN lr_method_check->* TO . + IF sy-subrc = 0 AND -op = `SPREAD`. + lv_method_fallback = abap_true. + EXIT. + ENDIF. + ENDLOOP. + IF lv_method_fallback = abap_true. + APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. + RETURN. + ENDIF. + bc_compile_expr( EXPORTING ir_node = -object iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + LOOP AT -args INTO DATA(lr_method_arg). + bc_compile_expr( EXPORTING ir_node = lr_method_arg iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + ENDLOOP. + APPEND VALUE #( opcode = c_bc_call_method arg = lines( -args ) text = -property node = -object ) TO ct_code. + + WHEN zif_mjs=>c_node_ternary. + bc_compile_expr( EXPORTING ir_node = -cond iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + DATA(lv_ternary_false) = lines( ct_code ) + 1. + APPEND VALUE #( opcode = c_bc_jump_false ) TO ct_code. + bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + DATA(lv_ternary_end) = lines( ct_code ) + 1. + APPEND VALUE #( opcode = c_bc_jump ) TO ct_code. + ct_code[ lv_ternary_false ]-target = lines( ct_code ) + 1. + bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). + ct_code[ lv_ternary_end ]-target = lines( ct_code ) + 1. + + WHEN OTHERS. + APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. + ENDCASE. + ENDMETHOD. + + METHOD bc_has_loop_control. + IF ir_node IS NOT BOUND. + RETURN. + ENDIF. + FIELD-SYMBOLS TYPE zif_mjs=>ty_node. + ASSIGN ir_node->* TO . + IF -kind = zif_mjs=>c_node_break OR -kind = zif_mjs=>c_node_continue. + rv_found = abap_true. + RETURN. + ENDIF. + IF -kind = zif_mjs=>c_node_func_decl. + RETURN. + ENDIF. + IF bc_has_loop_control( -left ) = abap_true + OR bc_has_loop_control( -right ) = abap_true + OR bc_has_loop_control( -cond ) = abap_true + OR bc_has_loop_control( -init ) = abap_true + OR bc_has_loop_control( -update ) = abap_true. + rv_found = abap_true. + RETURN. + ENDIF. + LOOP AT -body INTO DATA(lr_body). + IF bc_has_loop_control( lr_body ) = abap_true. + rv_found = abap_true. + RETURN. + ENDIF. + ENDLOOP. + LOOP AT -els INTO DATA(lr_else). + IF bc_has_loop_control( lr_else ) = abap_true. + rv_found = abap_true. + RETURN. + ENDIF. + ENDLOOP. + ENDMETHOD. + + METHOD bc_execute. + DATA lt_stack TYPE zif_mjs=>tt_value_slots. + DATA lv_sp TYPE i VALUE 0. + DATA lv_ip TYPE i VALUE 1. + DATA ls_ins TYPE zif_mjs=>ty_bc_instr. + DATA ls_a TYPE zif_mjs=>ty_value. + DATA ls_b TYPE zif_mjs=>ty_value. + DATA ls_result TYPE zif_mjs=>ty_value. + DATA lv_found TYPE abap_bool. + DATA lo_env TYPE REF TO zcl_mjs_env. + DATA lo_child TYPE REF TO zcl_mjs_env. + DATA lt_env_stack TYPE STANDARD TABLE OF REF TO zcl_mjs_env WITH EMPTY KEY. + DATA lt_args TYPE zif_mjs=>tt_value_slots. + DATA lv_base TYPE i. + DATA lv_arg_index TYPE i. + DATA lv_delete_count TYPE i. + DATA lv_prop TYPE string. + DATA ls_fn TYPE zif_mjs=>ty_value. + DATA lr_box TYPE REF TO data. + FIELD-SYMBOLS TYPE zif_mjs=>ty_node. + + lo_env = io_env. + WHILE lv_ip <= lines( it_code ). + READ TABLE it_code INDEX lv_ip INTO ls_ins. + lv_ip = lv_ip + 1. + + CASE ls_ins-opcode. + WHEN c_bc_push_num. + CLEAR ls_result. + ls_result-type = zif_mjs=>c_type_number. + ls_result-num = ls_ins-num. + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_push_str. + CLEAR ls_result. + ls_result-type = zif_mjs=>c_type_string. + ls_result-str = ls_ins-text. + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_push_bool. + CLEAR ls_result. + ls_result-type = zif_mjs=>c_type_bool. + ls_result-num = ls_ins-arg. + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_push_undef. + CLEAR ls_result. + ls_result-type = zif_mjs=>c_type_undefined. + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_pop. + IF lv_sp > 0. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + ENDIF. + + WHEN c_bc_dup. + READ TABLE lt_stack INDEX lv_sp INTO ls_result. + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_load_slot. + CLEAR ls_result. + READ TABLE io_env->slots INDEX ls_ins-arg INTO ls_result. + IF sy-subrc <> 0. + ls_result-type = zif_mjs=>c_type_undefined. + ENDIF. + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_load_name. + lo_env->resolve( + EXPORTING iv_name = ls_ins-text + IMPORTING ev_found = lv_found es_val = ls_result ). + IF lv_found = abap_false. + raise_ref_error( ls_ins-text ). + ENDIF. + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_store_slot. + READ TABLE lt_stack INDEX lv_sp INTO ls_result. + io_env->set_slot( iv_slot = ls_ins-arg is_val = ls_result ). + + WHEN c_bc_store_name. + READ TABLE lt_stack INDEX lv_sp INTO ls_result. + lo_env->set( iv_name = ls_ins-text is_val = ls_result ). + + WHEN c_bc_def_slot. + READ TABLE lt_stack INDEX lv_sp INTO ls_result. + io_env->set_slot( iv_slot = ls_ins-arg is_val = ls_result ). + + WHEN c_bc_def_name. + READ TABLE lt_stack INDEX lv_sp INTO ls_result. + lo_env->define( iv_name = ls_ins-text is_val = ls_result ). + + WHEN c_bc_binop. + READ TABLE lt_stack INDEX lv_sp INTO ls_b. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + READ TABLE lt_stack INDEX lv_sp INTO ls_a. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + ls_result = eval_bin_op( + iv_op = ls_ins-text is_left = ls_a is_right = ls_b io_env = lo_env ). + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_unary. + READ TABLE lt_stack INDEX lv_sp INTO ls_a. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + CLEAR ls_result. + IF ls_ins-text = `-`. + ls_result-type = zif_mjs=>c_type_number. + ls_result-num = 0 - zcl_mjs_val=>to_number( ls_a ). + ELSE. + ls_result-type = zif_mjs=>c_type_bool. + IF zcl_mjs_val=>is_true( ls_a ) = abap_false. + ls_result-num = 1. + ENDIF. + ENDIF. + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_jump. + lv_ip = ls_ins-target. + + WHEN c_bc_jump_false OR c_bc_jump_true OR c_bc_jump_not_null. + READ TABLE lt_stack INDEX lv_sp INTO ls_a. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + IF ( ls_ins-opcode = c_bc_jump_false AND zcl_mjs_val=>is_true( ls_a ) = abap_false ) + OR ( ls_ins-opcode = c_bc_jump_true AND zcl_mjs_val=>is_true( ls_a ) = abap_true ) + OR ( ls_ins-opcode = c_bc_jump_not_null + AND ls_a-type <> zif_mjs=>c_type_undefined + AND ls_a-type <> zif_mjs=>c_type_null ). + lv_ip = ls_ins-target. + ENDIF. + + WHEN c_bc_eval_node. + ls_result = eval_node( ir_node = ls_ins-node io_env = lo_env ). + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_return. + IF lv_sp > 0. + READ TABLE lt_stack INDEX lv_sp INTO ls_result. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + ELSE. + CLEAR ls_result. + ENDIF. + io_env->returning = abap_true. + io_env->ret_val = ls_result. + rs_val = ls_result. + RETURN. + + WHEN c_bc_get_prop. + READ TABLE lt_stack INDEX lv_sp INTO ls_a. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + ls_result = eval_property_access( + is_obj = ls_a iv_prop = ls_ins-text io_env = lo_env ). + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_get_idx. + READ TABLE lt_stack INDEX lv_sp INTO ls_b. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + READ TABLE lt_stack INDEX lv_sp INTO ls_a. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + lv_prop = zcl_mjs_val=>to_string( ls_b ). + ls_result = eval_property_access( + is_obj = ls_a iv_prop = lv_prop io_env = lo_env ). + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_set_prop. + READ TABLE lt_stack INDEX lv_sp INTO ls_b. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + READ TABLE lt_stack INDEX lv_sp INTO ls_a. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + lr_box = zcl_mjs_val=>box_value( ls_b ). + IF ls_a-type = zif_mjs=>c_type_object AND ls_a-obj IS BOUND. + ls_a-obj->set( iv_key = ls_ins-text ir_val = lr_box ). + ELSEIF ls_a-type = zif_mjs=>c_type_function. + IF ls_a-obj IS NOT BOUND. + CREATE OBJECT ls_a-obj. + ENDIF. + ls_a-obj->set( iv_key = ls_ins-text ir_val = lr_box ). + IF ls_ins-node IS BOUND. + ASSIGN ls_ins-node->* TO . + IF sy-subrc = 0 AND -kind = zif_mjs=>c_node_ident. + lo_env->set( iv_name = -str is_val = ls_a ). + ENDIF. + ENDIF. + ENDIF. + APPEND ls_b TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_call_name. + CLEAR lt_args. + lv_base = lv_sp - ls_ins-arg + 1. + DO ls_ins-arg TIMES. + lv_arg_index = lv_base + sy-index - 1. + READ TABLE lt_stack INDEX lv_arg_index INTO ls_a. + APPEND ls_a TO lt_args. + ENDDO. + lv_delete_count = ls_ins-arg. + DO lv_delete_count TIMES. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + ENDDO. + CLEAR ls_result. + IF ls_ins-text = `Boolean`. + ls_result = zcl_mjs_builtins=>boolean_from_args( lt_args ). + ELSEIF ls_ins-text = `RegExp`. + ls_result = zcl_mjs_builtins=>regexp_from_args( lt_args ). + ELSEIF ls_ins-text = `JSON.stringify`. + ls_result = zcl_mjs_builtins=>json_stringify( lt_args ). + ELSEIF ls_ins-text = `console.log`. + ls_result = zcl_mjs_builtins=>console_log( it_args = lt_args io_env = lo_env ). + ELSE. + lo_env->resolve( + EXPORTING iv_name = ls_ins-text + IMPORTING ev_found = lv_found es_val = ls_fn ). + IF lv_found = abap_true AND ls_fn-type = zif_mjs=>c_type_function AND ls_fn-fn IS BOUND. + ls_result = call_function( ir_fn = ls_fn-fn it_args = lt_args io_env = lo_env ). + ELSE. + ls_result = zcl_mjs_val=>undefined_val( ). + ENDIF. + ENDIF. + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_call_method. + CLEAR lt_args. + lv_base = lv_sp - ls_ins-arg. + READ TABLE lt_stack INDEX lv_base INTO ls_a. + DO ls_ins-arg TIMES. + lv_arg_index = lv_base + sy-index. + READ TABLE lt_stack INDEX lv_arg_index INTO ls_b. + APPEND ls_b TO lt_args. + ENDDO. + lv_delete_count = ls_ins-arg + 1. + DO lv_delete_count TIMES. + DELETE lt_stack INDEX lv_sp. + lv_sp = lv_sp - 1. + ENDDO. + ls_result = eval_method_call( + is_obj = ls_a iv_method = ls_ins-text it_args = lt_args + io_env = lo_env ir_obj_node = ls_ins-node ). + APPEND ls_result TO lt_stack. + lv_sp = lv_sp + 1. + + WHEN c_bc_enter_scope. + APPEND lo_env TO lt_env_stack. + CREATE OBJECT lo_child EXPORTING io_parent = lo_env. + lo_env = lo_child. + + WHEN c_bc_exit_scope. + IF lo_env->parent IS BOUND. + lo_env = lo_env->parent. + ENDIF. + DELETE lt_env_stack INDEX lines( lt_env_stack ). + ENDCASE. + + IF lo_env->returning = abap_true. + io_env->returning = abap_true. + io_env->ret_val = lo_env->ret_val. + rs_val = lo_env->ret_val. + RETURN. + ENDIF. + ENDWHILE. + + IF lv_sp > 0. + READ TABLE lt_stack INDEX lv_sp INTO rs_val. + ELSE. + rs_val = zcl_mjs_val=>undefined_val( ). + ENDIF. + ENDMETHOD. + METHOD eval_node. IF ir_node IS NOT BOUND. CLEAR rs_val. diff --git a/src/zcl_mjs.clas.testclasses.abap b/src/zcl_mjs.clas.testclasses.abap index f67cfa2..0a377fe 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_bytecode_cache FOR TESTING RAISING zcx_mjs_runtime. METHODS test262 FOR TESTING RAISING zcx_mjs_runtime. @@ -1597,4 +1598,17 @@ CLASS ltcl_test IMPLEMENTATION. exp = |12 undefined| ). ENDMETHOD. + METHOD test_bytecode_cache. + DATA(lv_nl) = cl_abap_char_utilities=>newline. + DATA(lv_js) = + `let sum = 0;` && lv_nl && + `for (let i = 0; i < 10; i++) { sum = sum + i; }` && lv_nl && + `function twice(n) { return n + n; }` && lv_nl && + `console.log(twice(sum));`. + DATA(lv_first) = trim( zcl_mjs=>eval( lv_js ) ). + DATA(lv_cached) = trim( zcl_mjs=>eval( lv_js ) ). + cl_abap_unit_assert=>assert_equals( act = lv_first exp = |90| ). + cl_abap_unit_assert=>assert_equals( act = lv_cached exp = |90| ). + ENDMETHOD. + ENDCLASS. diff --git a/src/zif_mjs.intf.abap b/src/zif_mjs.intf.abap index d3205a2..9c7f8d2 100644 --- a/src/zif_mjs.intf.abap +++ b/src/zif_mjs.intf.abap @@ -129,6 +129,19 @@ INTERFACE zif_mjs PUBLIC. slot_ok TYPE abap_bool, " X = use slot, ' ' = use name-based lookup END OF ty_node. + " Flat bytecode instruction. text carries a name/operator, node is used only + " by the compatibility fallback for syntax not yet lowered to bytecode. + TYPES: + BEGIN OF ty_bc_instr, + opcode TYPE i, + arg TYPE i, + target TYPE i, + text TYPE string, + num TYPE f, + node TYPE REF TO data, + END OF ty_bc_instr, + tt_bc_code TYPE STANDARD TABLE OF ty_bc_instr WITH EMPTY KEY. + " Function (closure is REF TO object to break circular dep with zcl_mjs_env) TYPES: BEGIN OF ty_function, @@ -142,6 +155,7 @@ INTERFACE zif_mjs PUBLIC. max_slots TYPE i, " number of slot entries needed slot_map TYPE REF TO tt_slot_map, " shared ref — allocated once, reused across calls needs_arguments TYPE abap_bool, " X = function references 'arguments' pseudo-array + bc_code TYPE REF TO tt_bc_code, " lazily compiled flat instruction stream END OF ty_function. ENDINTERFACE. From 45d63fa23e04c6829193c5c50755d0a9d972669c Mon Sep 17 00:00:00 2001 From: Lars HVam Date: Sat, 18 Jul 2026 21:05:02 +0200 Subject: [PATCH 2/5] Revert "byte code" This reverts commit 36bfa883747d3227d0c1b258ca9674295796dc88. --- src/zcl_mjs.clas.abap | 762 +----------------------------- src/zcl_mjs.clas.testclasses.abap | 14 - src/zif_mjs.intf.abap | 14 - 3 files changed, 19 insertions(+), 771 deletions(-) diff --git a/src/zcl_mjs.clas.abap b/src/zcl_mjs.clas.abap index 3572aac..92d0520 100644 --- a/src/zcl_mjs.clas.abap +++ b/src/zcl_mjs.clas.abap @@ -14,63 +14,6 @@ CLASS zcl_mjs DEFINITION PUBLIC. RAISING zcx_mjs_runtime. PRIVATE SECTION. - CONSTANTS: - c_bc_push_num TYPE i VALUE 1, - c_bc_push_str TYPE i VALUE 2, - c_bc_push_bool TYPE i VALUE 3, - c_bc_push_undef TYPE i VALUE 4, - c_bc_pop TYPE i VALUE 5, - c_bc_dup TYPE i VALUE 6, - c_bc_load_slot TYPE i VALUE 10, - c_bc_load_name TYPE i VALUE 11, - c_bc_store_slot TYPE i VALUE 12, - c_bc_store_name TYPE i VALUE 13, - c_bc_def_slot TYPE i VALUE 14, - c_bc_def_name TYPE i VALUE 15, - c_bc_binop TYPE i VALUE 20, - c_bc_unary TYPE i VALUE 21, - c_bc_jump TYPE i VALUE 30, - c_bc_jump_false TYPE i VALUE 31, - c_bc_jump_true TYPE i VALUE 32, - c_bc_jump_not_null TYPE i VALUE 33, - c_bc_eval_node TYPE i VALUE 40, - c_bc_return TYPE i VALUE 41, - c_bc_get_prop TYPE i VALUE 50, - c_bc_get_idx TYPE i VALUE 51, - c_bc_set_prop TYPE i VALUE 52, - c_bc_call_name TYPE i VALUE 60, - c_bc_call_method TYPE i VALUE 61, - c_bc_enter_scope TYPE i VALUE 70, - c_bc_exit_scope TYPE i VALUE 71. - - " One-entry compiled-module cache: avoids lexing/parsing/compiling when the - " same source is evaluated repeatedly in one internal session. - CLASS-DATA gv_bc_cached_source TYPE string. - CLASS-DATA gt_bc_cached_stmts TYPE zif_mjs=>tt_nodes. - CLASS-DATA gr_bc_cached_code TYPE REF TO zif_mjs=>tt_bc_code. - CLASS-DATA gv_bc_cache_valid TYPE abap_bool. - - CLASS-METHODS bc_compile_nodes - IMPORTING it_nodes TYPE zif_mjs=>tt_nodes - iv_use_slots TYPE abap_bool DEFAULT abap_false - RETURNING VALUE(rt_code) TYPE zif_mjs=>tt_bc_code. - CLASS-METHODS bc_compile_stmt - IMPORTING ir_node TYPE REF TO data - iv_use_slots TYPE abap_bool - CHANGING ct_code TYPE zif_mjs=>tt_bc_code. - CLASS-METHODS bc_compile_expr - IMPORTING ir_node TYPE REF TO data - iv_use_slots TYPE abap_bool - CHANGING ct_code TYPE zif_mjs=>tt_bc_code. - CLASS-METHODS bc_has_loop_control - IMPORTING ir_node TYPE REF TO data - RETURNING VALUE(rv_found) TYPE abap_bool. - CLASS-METHODS bc_execute - IMPORTING it_code TYPE zif_mjs=>tt_bc_code - io_env TYPE REF TO zcl_mjs_env - RETURNING VALUE(rs_val) TYPE zif_mjs=>ty_value - RAISING zcx_mjs_runtime. - CLASS-METHODS eval_node IMPORTING ir_node TYPE REF TO data io_env TYPE REF TO zcl_mjs_env @@ -226,30 +169,15 @@ ENDCLASS. CLASS zcl_mjs IMPLEMENTATION. METHOD eval. - DATA lt_stmts TYPE zif_mjs=>tt_nodes. - DATA lr_top_code TYPE REF TO zif_mjs=>tt_bc_code. + DATA lt_tokens TYPE zif_mjs=>tt_tokens. + lt_tokens = zcl_mjs_tokenizer=>tokenize( iv_source ). - IF gv_bc_cache_valid = abap_true AND gv_bc_cached_source = iv_source - AND gr_bc_cached_code IS BOUND. - lt_stmts = gt_bc_cached_stmts. - lr_top_code = gr_bc_cached_code. - ELSE. - DATA lt_tokens TYPE zif_mjs=>tt_tokens. - lt_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. - lt_stmts = lo_parser->parse_program( ). - - DATA(lt_top_code) = bc_compile_nodes( it_nodes = lt_stmts iv_use_slots = abap_false ). - CREATE DATA gr_bc_cached_code. - gr_bc_cached_code->* = lt_top_code. - gt_bc_cached_stmts = lt_stmts. - gv_bc_cached_source = iv_source. - gv_bc_cache_valid = abap_true. - lr_top_code = gr_bc_cached_code. - ENDIF. + DATA lo_parser TYPE REF TO zcl_mjs_parser. + CREATE OBJECT lo_parser + EXPORTING it_tokens = lt_tokens. + + DATA lt_stmts TYPE STANDARD TABLE OF REF TO data WITH DEFAULT KEY. + lt_stmts = lo_parser->parse_program( ). DATA lo_env TYPE REF TO zcl_mjs_env. CREATE OBJECT lo_env. @@ -299,7 +227,9 @@ CLASS zcl_mjs IMPLEMENTATION. ENDIF. ENDLOOP. - bc_execute( it_code = lr_top_code->* io_env = lo_env ). + LOOP AT lt_stmts INTO DATA(lr_stmt). + eval_node( ir_node = lr_stmt io_env = lo_env ). + ENDLOOP. rv_output = lo_env->get_output( ). ENDMETHOD. @@ -401,16 +331,14 @@ CLASS zcl_mjs IMPLEMENTATION. ENDIF. ENDLOOP. - " Lazily lower the annotated function body to bytecode, then execute it. - IF -bc_code IS NOT BOUND. - DATA(lt_fn_code) = bc_compile_nodes( it_nodes = -body iv_use_slots = abap_true ). - CREATE DATA -bc_code. - -bc_code->* = lt_fn_code. - ENDIF. - rs_val = bc_execute( it_code = -bc_code->* io_env = lo_call_env ). - IF lo_call_env->returning = abap_true. - rs_val = lo_call_env->ret_val. - ENDIF. + " Execute body + LOOP AT -body INTO DATA(lr_stmt). + rs_val = eval_node( ir_node = lr_stmt io_env = lo_call_env ). + IF lo_call_env->returning = abap_true. + rs_val = lo_call_env->ret_val. + EXIT. + ENDIF. + ENDLOOP. " Write back 'this' mutations (for constructor use) IF ir_this IS BOUND. @@ -636,658 +564,6 @@ CLASS zcl_mjs IMPLEMENTATION. ENDCASE. ENDMETHOD. - METHOD bc_compile_nodes. - LOOP AT it_nodes INTO DATA(lr_node). - bc_compile_stmt( - EXPORTING ir_node = lr_node - iv_use_slots = iv_use_slots - CHANGING ct_code = rt_code ). - ENDLOOP. - ENDMETHOD. - - METHOD bc_compile_stmt. - IF ir_node IS NOT BOUND. - RETURN. - ENDIF. - FIELD-SYMBOLS TYPE zif_mjs=>ty_node. - ASSIGN ir_node->* TO . - - CASE -kind. - WHEN zif_mjs=>c_node_var. - IF -right IS BOUND. - bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - ELSE. - APPEND VALUE #( opcode = c_bc_push_undef ) TO ct_code. - ENDIF. - IF iv_use_slots = abap_true AND -slot_ok = abap_true. - APPEND VALUE #( opcode = c_bc_def_slot arg = -slot ) TO ct_code. - ELSE. - APPEND VALUE #( opcode = c_bc_def_name text = -str ) TO ct_code. - ENDIF. - APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. - - WHEN zif_mjs=>c_node_if. - bc_compile_expr( EXPORTING ir_node = -cond iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - DATA(lv_if_false) = lines( ct_code ) + 1. - APPEND VALUE #( opcode = c_bc_jump_false ) TO ct_code. - LOOP AT -body INTO DATA(lr_if_body). - bc_compile_stmt( EXPORTING ir_node = lr_if_body iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - ENDLOOP. - IF lines( -els ) > 0. - DATA(lv_if_end) = lines( ct_code ) + 1. - APPEND VALUE #( opcode = c_bc_jump ) TO ct_code. - ct_code[ lv_if_false ]-target = lines( ct_code ) + 1. - LOOP AT -els INTO DATA(lr_else_body). - bc_compile_stmt( EXPORTING ir_node = lr_else_body iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - ENDLOOP. - ct_code[ lv_if_end ]-target = lines( ct_code ) + 1. - ELSE. - ct_code[ lv_if_false ]-target = lines( ct_code ) + 1. - ENDIF. - - WHEN zif_mjs=>c_node_while. - DATA(lv_while_control) = abap_false. - LOOP AT -body INTO DATA(lr_while_check). - IF bc_has_loop_control( lr_while_check ) = abap_true. - lv_while_control = abap_true. - EXIT. - ENDIF. - ENDLOOP. - IF lv_while_control = abap_true. - APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. - APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. - RETURN. - ENDIF. - DATA(lv_while_start) = lines( ct_code ) + 1. - bc_compile_expr( EXPORTING ir_node = -cond iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - DATA(lv_while_exit) = lines( ct_code ) + 1. - APPEND VALUE #( opcode = c_bc_jump_false ) TO ct_code. - LOOP AT -body INTO DATA(lr_while_body). - bc_compile_stmt( EXPORTING ir_node = lr_while_body iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - ENDLOOP. - APPEND VALUE #( opcode = c_bc_jump target = lv_while_start ) TO ct_code. - ct_code[ lv_while_exit ]-target = lines( ct_code ) + 1. - - WHEN zif_mjs=>c_node_for. - " The loop runs in a child lexical environment. Its instructions use - " name lookup so loop locals stay in that child while outer function - " slots are still reached through the parent chain. - DATA(lv_for_control) = abap_false. - LOOP AT -body INTO DATA(lr_for_check). - IF bc_has_loop_control( lr_for_check ) = abap_true. - lv_for_control = abap_true. - EXIT. - ENDIF. - ENDLOOP. - IF lv_for_control = abap_true. - APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. - APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. - RETURN. - ENDIF. - APPEND VALUE #( opcode = c_bc_enter_scope ) TO ct_code. - IF -init IS BOUND. - bc_compile_stmt( EXPORTING ir_node = -init iv_use_slots = abap_false CHANGING ct_code = ct_code ). - ENDIF. - DATA(lv_for_start) = lines( ct_code ) + 1. - DATA lv_for_exit TYPE i. - IF -cond IS BOUND. - bc_compile_expr( EXPORTING ir_node = -cond iv_use_slots = abap_false CHANGING ct_code = ct_code ). - lv_for_exit = lines( ct_code ) + 1. - APPEND VALUE #( opcode = c_bc_jump_false ) TO ct_code. - ENDIF. - LOOP AT -body INTO DATA(lr_for_body). - bc_compile_stmt( EXPORTING ir_node = lr_for_body iv_use_slots = abap_false CHANGING ct_code = ct_code ). - ENDLOOP. - IF -update IS BOUND. - bc_compile_expr( EXPORTING ir_node = -update iv_use_slots = abap_false CHANGING ct_code = ct_code ). - APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. - ENDIF. - APPEND VALUE #( opcode = c_bc_jump target = lv_for_start ) TO ct_code. - IF lv_for_exit > 0. - ct_code[ lv_for_exit ]-target = lines( ct_code ) + 1. - ENDIF. - APPEND VALUE #( opcode = c_bc_exit_scope ) TO ct_code. - - WHEN zif_mjs=>c_node_block. - LOOP AT -body INTO DATA(lr_block_body). - bc_compile_stmt( EXPORTING ir_node = lr_block_body iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - ENDLOOP. - - WHEN zif_mjs=>c_node_return. - IF -left IS BOUND. - bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - ELSE. - APPEND VALUE #( opcode = c_bc_push_undef ) TO ct_code. - ENDIF. - APPEND VALUE #( opcode = c_bc_return ) TO ct_code. - - WHEN OTHERS. - bc_compile_expr( EXPORTING ir_node = ir_node iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. - ENDCASE. - ENDMETHOD. - - METHOD bc_compile_expr. - IF ir_node IS NOT BOUND. - APPEND VALUE #( opcode = c_bc_push_undef ) TO ct_code. - RETURN. - ENDIF. - FIELD-SYMBOLS TYPE zif_mjs=>ty_node. - ASSIGN ir_node->* TO . - - CASE -kind. - WHEN zif_mjs=>c_node_number. - APPEND VALUE #( opcode = c_bc_push_num num = -num ) TO ct_code. - WHEN zif_mjs=>c_node_string. - APPEND VALUE #( opcode = c_bc_push_str text = -str ) TO ct_code. - WHEN zif_mjs=>c_node_bool. - APPEND VALUE #( opcode = c_bc_push_bool arg = CONV i( -num ) ) TO ct_code. - - WHEN zif_mjs=>c_node_ident. - IF iv_use_slots = abap_true AND -slot_ok = abap_true. - APPEND VALUE #( opcode = c_bc_load_slot arg = -slot ) TO ct_code. - ELSE. - APPEND VALUE #( opcode = c_bc_load_name text = -str ) TO ct_code. - ENDIF. - - WHEN zif_mjs=>c_node_binop. - IF -op = `&&` OR -op = `||` OR -op = `??`. - bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - APPEND VALUE #( opcode = c_bc_dup ) TO ct_code. - DATA(lv_short_jump) = lines( ct_code ) + 1. - IF -op = `&&`. - APPEND VALUE #( opcode = c_bc_jump_false ) TO ct_code. - ELSEIF -op = `||`. - APPEND VALUE #( opcode = c_bc_jump_true ) TO ct_code. - ELSE. - APPEND VALUE #( opcode = c_bc_jump_not_null ) TO ct_code. - ENDIF. - APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. - bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - ct_code[ lv_short_jump ]-target = lines( ct_code ) + 1. - ELSE. - bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - APPEND VALUE #( opcode = c_bc_binop text = -op ) TO ct_code. - ENDIF. - - WHEN zif_mjs=>c_node_unaryop. - IF -op = `-` OR -op = `!`. - bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - APPEND VALUE #( opcode = c_bc_unary text = -op ) TO ct_code. - ELSEIF -op = `void`. - bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. - APPEND VALUE #( opcode = c_bc_push_undef ) TO ct_code. - ELSE. - APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. - ENDIF. - - WHEN zif_mjs=>c_node_assign OR zif_mjs=>c_node_assign_add. - IF -kind = zif_mjs=>c_node_assign_add. - IF iv_use_slots = abap_true AND -slot_ok = abap_true. - APPEND VALUE #( opcode = c_bc_load_slot arg = -slot ) TO ct_code. - ELSE. - APPEND VALUE #( opcode = c_bc_load_name text = -str ) TO ct_code. - ENDIF. - ENDIF. - bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - IF -kind = zif_mjs=>c_node_assign_add. - APPEND VALUE #( opcode = c_bc_binop text = `+` ) TO ct_code. - ENDIF. - IF iv_use_slots = abap_true AND -slot_ok = abap_true. - APPEND VALUE #( opcode = c_bc_store_slot arg = -slot ) TO ct_code. - ELSE. - APPEND VALUE #( opcode = c_bc_store_name text = -str ) TO ct_code. - ENDIF. - - WHEN zif_mjs=>c_node_inc OR zif_mjs=>c_node_dec - OR zif_mjs=>c_node_post_inc OR zif_mjs=>c_node_post_dec. - FIELD-SYMBOLS TYPE zif_mjs=>ty_node. - ASSIGN -left->* TO . - IF sy-subrc <> 0 OR -kind <> zif_mjs=>c_node_ident. - APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. - RETURN. - ENDIF. - bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - IF -kind = zif_mjs=>c_node_post_inc OR -kind = zif_mjs=>c_node_post_dec. - APPEND VALUE #( opcode = c_bc_dup ) TO ct_code. - ENDIF. - APPEND VALUE #( opcode = c_bc_push_num num = 1 ) TO ct_code. - IF -kind = zif_mjs=>c_node_inc OR -kind = zif_mjs=>c_node_post_inc. - APPEND VALUE #( opcode = c_bc_binop text = `+` ) TO ct_code. - ELSE. - APPEND VALUE #( opcode = c_bc_binop text = `-` ) TO ct_code. - ENDIF. - IF iv_use_slots = abap_true AND -slot_ok = abap_true. - APPEND VALUE #( opcode = c_bc_store_slot arg = -slot ) TO ct_code. - ELSE. - APPEND VALUE #( opcode = c_bc_store_name text = -str ) TO ct_code. - ENDIF. - IF -kind = zif_mjs=>c_node_post_inc OR -kind = zif_mjs=>c_node_post_dec. - APPEND VALUE #( opcode = c_bc_pop ) TO ct_code. - ENDIF. - - WHEN zif_mjs=>c_node_member_access. - DATA(lv_member_fallback) = boolc( -op = `?.` ). - FIELD-SYMBOLS TYPE zif_mjs=>ty_node. - IF -object IS BOUND. - ASSIGN -object->* TO . - IF sy-subrc = 0 AND -kind = zif_mjs=>c_node_ident - AND -str = `Object`. - lv_member_fallback = abap_true. - ENDIF. - ENDIF. - IF lv_member_fallback = abap_true. - APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. - RETURN. - ENDIF. - bc_compile_expr( EXPORTING ir_node = -object iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - IF -prop_expr IS BOUND. - bc_compile_expr( EXPORTING ir_node = -prop_expr iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - APPEND VALUE #( opcode = c_bc_get_idx ) TO ct_code. - ELSE. - APPEND VALUE #( opcode = c_bc_get_prop text = -property ) TO ct_code. - ENDIF. - - WHEN zif_mjs=>c_node_member_assign. - IF -prop_expr IS BOUND. - APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. - RETURN. - ENDIF. - bc_compile_expr( EXPORTING ir_node = -object iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - APPEND VALUE #( opcode = c_bc_set_prop text = -property node = -object ) TO ct_code. - - WHEN zif_mjs=>c_node_call. - DATA(lv_call_fallback) = boolc( -left IS BOUND OR -str = `super` ). - LOOP AT -args INTO DATA(lr_call_check). - FIELD-SYMBOLS TYPE zif_mjs=>ty_node. - ASSIGN lr_call_check->* TO . - IF sy-subrc = 0 AND -op = `SPREAD`. - lv_call_fallback = abap_true. - EXIT. - ENDIF. - ENDLOOP. - IF lv_call_fallback = abap_true. - APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. - RETURN. - ENDIF. - LOOP AT -args INTO DATA(lr_call_arg). - bc_compile_expr( EXPORTING ir_node = lr_call_arg iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - ENDLOOP. - APPEND VALUE #( opcode = c_bc_call_name arg = lines( -args ) text = -str node = ir_node ) TO ct_code. - - WHEN zif_mjs=>c_node_method_call. - DATA(lv_method_fallback) = boolc( -op = `?.` ). - FIELD-SYMBOLS TYPE zif_mjs=>ty_node. - IF -object IS BOUND. - ASSIGN -object->* TO . - IF sy-subrc = 0 AND -kind = zif_mjs=>c_node_ident - AND ( -str = `Date` OR -str = `Object` OR -str = `super` ). - lv_method_fallback = abap_true. - ENDIF. - ENDIF. - LOOP AT -args INTO DATA(lr_method_check). - FIELD-SYMBOLS TYPE zif_mjs=>ty_node. - ASSIGN lr_method_check->* TO . - IF sy-subrc = 0 AND -op = `SPREAD`. - lv_method_fallback = abap_true. - EXIT. - ENDIF. - ENDLOOP. - IF lv_method_fallback = abap_true. - APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. - RETURN. - ENDIF. - bc_compile_expr( EXPORTING ir_node = -object iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - LOOP AT -args INTO DATA(lr_method_arg). - bc_compile_expr( EXPORTING ir_node = lr_method_arg iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - ENDLOOP. - APPEND VALUE #( opcode = c_bc_call_method arg = lines( -args ) text = -property node = -object ) TO ct_code. - - WHEN zif_mjs=>c_node_ternary. - bc_compile_expr( EXPORTING ir_node = -cond iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - DATA(lv_ternary_false) = lines( ct_code ) + 1. - APPEND VALUE #( opcode = c_bc_jump_false ) TO ct_code. - bc_compile_expr( EXPORTING ir_node = -left iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - DATA(lv_ternary_end) = lines( ct_code ) + 1. - APPEND VALUE #( opcode = c_bc_jump ) TO ct_code. - ct_code[ lv_ternary_false ]-target = lines( ct_code ) + 1. - bc_compile_expr( EXPORTING ir_node = -right iv_use_slots = iv_use_slots CHANGING ct_code = ct_code ). - ct_code[ lv_ternary_end ]-target = lines( ct_code ) + 1. - - WHEN OTHERS. - APPEND VALUE #( opcode = c_bc_eval_node node = ir_node ) TO ct_code. - ENDCASE. - ENDMETHOD. - - METHOD bc_has_loop_control. - IF ir_node IS NOT BOUND. - RETURN. - ENDIF. - FIELD-SYMBOLS TYPE zif_mjs=>ty_node. - ASSIGN ir_node->* TO . - IF -kind = zif_mjs=>c_node_break OR -kind = zif_mjs=>c_node_continue. - rv_found = abap_true. - RETURN. - ENDIF. - IF -kind = zif_mjs=>c_node_func_decl. - RETURN. - ENDIF. - IF bc_has_loop_control( -left ) = abap_true - OR bc_has_loop_control( -right ) = abap_true - OR bc_has_loop_control( -cond ) = abap_true - OR bc_has_loop_control( -init ) = abap_true - OR bc_has_loop_control( -update ) = abap_true. - rv_found = abap_true. - RETURN. - ENDIF. - LOOP AT -body INTO DATA(lr_body). - IF bc_has_loop_control( lr_body ) = abap_true. - rv_found = abap_true. - RETURN. - ENDIF. - ENDLOOP. - LOOP AT -els INTO DATA(lr_else). - IF bc_has_loop_control( lr_else ) = abap_true. - rv_found = abap_true. - RETURN. - ENDIF. - ENDLOOP. - ENDMETHOD. - - METHOD bc_execute. - DATA lt_stack TYPE zif_mjs=>tt_value_slots. - DATA lv_sp TYPE i VALUE 0. - DATA lv_ip TYPE i VALUE 1. - DATA ls_ins TYPE zif_mjs=>ty_bc_instr. - DATA ls_a TYPE zif_mjs=>ty_value. - DATA ls_b TYPE zif_mjs=>ty_value. - DATA ls_result TYPE zif_mjs=>ty_value. - DATA lv_found TYPE abap_bool. - DATA lo_env TYPE REF TO zcl_mjs_env. - DATA lo_child TYPE REF TO zcl_mjs_env. - DATA lt_env_stack TYPE STANDARD TABLE OF REF TO zcl_mjs_env WITH EMPTY KEY. - DATA lt_args TYPE zif_mjs=>tt_value_slots. - DATA lv_base TYPE i. - DATA lv_arg_index TYPE i. - DATA lv_delete_count TYPE i. - DATA lv_prop TYPE string. - DATA ls_fn TYPE zif_mjs=>ty_value. - DATA lr_box TYPE REF TO data. - FIELD-SYMBOLS TYPE zif_mjs=>ty_node. - - lo_env = io_env. - WHILE lv_ip <= lines( it_code ). - READ TABLE it_code INDEX lv_ip INTO ls_ins. - lv_ip = lv_ip + 1. - - CASE ls_ins-opcode. - WHEN c_bc_push_num. - CLEAR ls_result. - ls_result-type = zif_mjs=>c_type_number. - ls_result-num = ls_ins-num. - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_push_str. - CLEAR ls_result. - ls_result-type = zif_mjs=>c_type_string. - ls_result-str = ls_ins-text. - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_push_bool. - CLEAR ls_result. - ls_result-type = zif_mjs=>c_type_bool. - ls_result-num = ls_ins-arg. - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_push_undef. - CLEAR ls_result. - ls_result-type = zif_mjs=>c_type_undefined. - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_pop. - IF lv_sp > 0. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - ENDIF. - - WHEN c_bc_dup. - READ TABLE lt_stack INDEX lv_sp INTO ls_result. - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_load_slot. - CLEAR ls_result. - READ TABLE io_env->slots INDEX ls_ins-arg INTO ls_result. - IF sy-subrc <> 0. - ls_result-type = zif_mjs=>c_type_undefined. - ENDIF. - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_load_name. - lo_env->resolve( - EXPORTING iv_name = ls_ins-text - IMPORTING ev_found = lv_found es_val = ls_result ). - IF lv_found = abap_false. - raise_ref_error( ls_ins-text ). - ENDIF. - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_store_slot. - READ TABLE lt_stack INDEX lv_sp INTO ls_result. - io_env->set_slot( iv_slot = ls_ins-arg is_val = ls_result ). - - WHEN c_bc_store_name. - READ TABLE lt_stack INDEX lv_sp INTO ls_result. - lo_env->set( iv_name = ls_ins-text is_val = ls_result ). - - WHEN c_bc_def_slot. - READ TABLE lt_stack INDEX lv_sp INTO ls_result. - io_env->set_slot( iv_slot = ls_ins-arg is_val = ls_result ). - - WHEN c_bc_def_name. - READ TABLE lt_stack INDEX lv_sp INTO ls_result. - lo_env->define( iv_name = ls_ins-text is_val = ls_result ). - - WHEN c_bc_binop. - READ TABLE lt_stack INDEX lv_sp INTO ls_b. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - READ TABLE lt_stack INDEX lv_sp INTO ls_a. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - ls_result = eval_bin_op( - iv_op = ls_ins-text is_left = ls_a is_right = ls_b io_env = lo_env ). - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_unary. - READ TABLE lt_stack INDEX lv_sp INTO ls_a. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - CLEAR ls_result. - IF ls_ins-text = `-`. - ls_result-type = zif_mjs=>c_type_number. - ls_result-num = 0 - zcl_mjs_val=>to_number( ls_a ). - ELSE. - ls_result-type = zif_mjs=>c_type_bool. - IF zcl_mjs_val=>is_true( ls_a ) = abap_false. - ls_result-num = 1. - ENDIF. - ENDIF. - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_jump. - lv_ip = ls_ins-target. - - WHEN c_bc_jump_false OR c_bc_jump_true OR c_bc_jump_not_null. - READ TABLE lt_stack INDEX lv_sp INTO ls_a. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - IF ( ls_ins-opcode = c_bc_jump_false AND zcl_mjs_val=>is_true( ls_a ) = abap_false ) - OR ( ls_ins-opcode = c_bc_jump_true AND zcl_mjs_val=>is_true( ls_a ) = abap_true ) - OR ( ls_ins-opcode = c_bc_jump_not_null - AND ls_a-type <> zif_mjs=>c_type_undefined - AND ls_a-type <> zif_mjs=>c_type_null ). - lv_ip = ls_ins-target. - ENDIF. - - WHEN c_bc_eval_node. - ls_result = eval_node( ir_node = ls_ins-node io_env = lo_env ). - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_return. - IF lv_sp > 0. - READ TABLE lt_stack INDEX lv_sp INTO ls_result. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - ELSE. - CLEAR ls_result. - ENDIF. - io_env->returning = abap_true. - io_env->ret_val = ls_result. - rs_val = ls_result. - RETURN. - - WHEN c_bc_get_prop. - READ TABLE lt_stack INDEX lv_sp INTO ls_a. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - ls_result = eval_property_access( - is_obj = ls_a iv_prop = ls_ins-text io_env = lo_env ). - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_get_idx. - READ TABLE lt_stack INDEX lv_sp INTO ls_b. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - READ TABLE lt_stack INDEX lv_sp INTO ls_a. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - lv_prop = zcl_mjs_val=>to_string( ls_b ). - ls_result = eval_property_access( - is_obj = ls_a iv_prop = lv_prop io_env = lo_env ). - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_set_prop. - READ TABLE lt_stack INDEX lv_sp INTO ls_b. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - READ TABLE lt_stack INDEX lv_sp INTO ls_a. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - lr_box = zcl_mjs_val=>box_value( ls_b ). - IF ls_a-type = zif_mjs=>c_type_object AND ls_a-obj IS BOUND. - ls_a-obj->set( iv_key = ls_ins-text ir_val = lr_box ). - ELSEIF ls_a-type = zif_mjs=>c_type_function. - IF ls_a-obj IS NOT BOUND. - CREATE OBJECT ls_a-obj. - ENDIF. - ls_a-obj->set( iv_key = ls_ins-text ir_val = lr_box ). - IF ls_ins-node IS BOUND. - ASSIGN ls_ins-node->* TO . - IF sy-subrc = 0 AND -kind = zif_mjs=>c_node_ident. - lo_env->set( iv_name = -str is_val = ls_a ). - ENDIF. - ENDIF. - ENDIF. - APPEND ls_b TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_call_name. - CLEAR lt_args. - lv_base = lv_sp - ls_ins-arg + 1. - DO ls_ins-arg TIMES. - lv_arg_index = lv_base + sy-index - 1. - READ TABLE lt_stack INDEX lv_arg_index INTO ls_a. - APPEND ls_a TO lt_args. - ENDDO. - lv_delete_count = ls_ins-arg. - DO lv_delete_count TIMES. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - ENDDO. - CLEAR ls_result. - IF ls_ins-text = `Boolean`. - ls_result = zcl_mjs_builtins=>boolean_from_args( lt_args ). - ELSEIF ls_ins-text = `RegExp`. - ls_result = zcl_mjs_builtins=>regexp_from_args( lt_args ). - ELSEIF ls_ins-text = `JSON.stringify`. - ls_result = zcl_mjs_builtins=>json_stringify( lt_args ). - ELSEIF ls_ins-text = `console.log`. - ls_result = zcl_mjs_builtins=>console_log( it_args = lt_args io_env = lo_env ). - ELSE. - lo_env->resolve( - EXPORTING iv_name = ls_ins-text - IMPORTING ev_found = lv_found es_val = ls_fn ). - IF lv_found = abap_true AND ls_fn-type = zif_mjs=>c_type_function AND ls_fn-fn IS BOUND. - ls_result = call_function( ir_fn = ls_fn-fn it_args = lt_args io_env = lo_env ). - ELSE. - ls_result = zcl_mjs_val=>undefined_val( ). - ENDIF. - ENDIF. - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_call_method. - CLEAR lt_args. - lv_base = lv_sp - ls_ins-arg. - READ TABLE lt_stack INDEX lv_base INTO ls_a. - DO ls_ins-arg TIMES. - lv_arg_index = lv_base + sy-index. - READ TABLE lt_stack INDEX lv_arg_index INTO ls_b. - APPEND ls_b TO lt_args. - ENDDO. - lv_delete_count = ls_ins-arg + 1. - DO lv_delete_count TIMES. - DELETE lt_stack INDEX lv_sp. - lv_sp = lv_sp - 1. - ENDDO. - ls_result = eval_method_call( - is_obj = ls_a iv_method = ls_ins-text it_args = lt_args - io_env = lo_env ir_obj_node = ls_ins-node ). - APPEND ls_result TO lt_stack. - lv_sp = lv_sp + 1. - - WHEN c_bc_enter_scope. - APPEND lo_env TO lt_env_stack. - CREATE OBJECT lo_child EXPORTING io_parent = lo_env. - lo_env = lo_child. - - WHEN c_bc_exit_scope. - IF lo_env->parent IS BOUND. - lo_env = lo_env->parent. - ENDIF. - DELETE lt_env_stack INDEX lines( lt_env_stack ). - ENDCASE. - - IF lo_env->returning = abap_true. - io_env->returning = abap_true. - io_env->ret_val = lo_env->ret_val. - rs_val = lo_env->ret_val. - RETURN. - ENDIF. - ENDWHILE. - - IF lv_sp > 0. - READ TABLE lt_stack INDEX lv_sp INTO rs_val. - ELSE. - rs_val = zcl_mjs_val=>undefined_val( ). - ENDIF. - ENDMETHOD. - METHOD eval_node. IF ir_node IS NOT BOUND. CLEAR rs_val. diff --git a/src/zcl_mjs.clas.testclasses.abap b/src/zcl_mjs.clas.testclasses.abap index 0a377fe..f67cfa2 100644 --- a/src/zcl_mjs.clas.testclasses.abap +++ b/src/zcl_mjs.clas.testclasses.abap @@ -108,7 +108,6 @@ 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_bytecode_cache FOR TESTING RAISING zcx_mjs_runtime. METHODS test262 FOR TESTING RAISING zcx_mjs_runtime. @@ -1598,17 +1597,4 @@ CLASS ltcl_test IMPLEMENTATION. exp = |12 undefined| ). ENDMETHOD. - METHOD test_bytecode_cache. - DATA(lv_nl) = cl_abap_char_utilities=>newline. - DATA(lv_js) = - `let sum = 0;` && lv_nl && - `for (let i = 0; i < 10; i++) { sum = sum + i; }` && lv_nl && - `function twice(n) { return n + n; }` && lv_nl && - `console.log(twice(sum));`. - DATA(lv_first) = trim( zcl_mjs=>eval( lv_js ) ). - DATA(lv_cached) = trim( zcl_mjs=>eval( lv_js ) ). - cl_abap_unit_assert=>assert_equals( act = lv_first exp = |90| ). - cl_abap_unit_assert=>assert_equals( act = lv_cached exp = |90| ). - ENDMETHOD. - ENDCLASS. diff --git a/src/zif_mjs.intf.abap b/src/zif_mjs.intf.abap index 9c7f8d2..d3205a2 100644 --- a/src/zif_mjs.intf.abap +++ b/src/zif_mjs.intf.abap @@ -129,19 +129,6 @@ INTERFACE zif_mjs PUBLIC. slot_ok TYPE abap_bool, " X = use slot, ' ' = use name-based lookup END OF ty_node. - " Flat bytecode instruction. text carries a name/operator, node is used only - " by the compatibility fallback for syntax not yet lowered to bytecode. - TYPES: - BEGIN OF ty_bc_instr, - opcode TYPE i, - arg TYPE i, - target TYPE i, - text TYPE string, - num TYPE f, - node TYPE REF TO data, - END OF ty_bc_instr, - tt_bc_code TYPE STANDARD TABLE OF ty_bc_instr WITH EMPTY KEY. - " Function (closure is REF TO object to break circular dep with zcl_mjs_env) TYPES: BEGIN OF ty_function, @@ -155,7 +142,6 @@ INTERFACE zif_mjs PUBLIC. max_slots TYPE i, " number of slot entries needed slot_map TYPE REF TO tt_slot_map, " shared ref — allocated once, reused across calls needs_arguments TYPE abap_bool, " X = function references 'arguments' pseudo-array - bc_code TYPE REF TO tt_bc_code, " lazily compiled flat instruction stream END OF ty_function. ENDINTERFACE. From ed013d557a9c33b7cf49ced59962ddfc9c51dc17 Mon Sep 17 00:00:00 2001 From: Lars HVam Date: Sat, 18 Jul 2026 22:50:40 +0200 Subject: [PATCH 3/5] update --- src/zcl_mjs.clas.abap | 41 +++++++++++++++-- src/zcl_mjs.clas.testclasses.abap | 28 ++++++++++++ src/zcl_mjs_parser.clas.abap | 76 +++++++++++++++++++++++++++---- src/zif_mjs.intf.abap | 19 ++++++-- 4 files changed, 147 insertions(+), 17 deletions(-) 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/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 From 696e50aa04f46abf142cddc10086e27041db909d Mon Sep 17 00:00:00 2001 From: Lars HVam Date: Sun, 19 Jul 2026 13:57:45 +0200 Subject: [PATCH 4/5] cleanup --- src/zcl_mjs_tokenizer.clas.abap | 133 ++++++++++++-------------------- 1 file changed, 48 insertions(+), 85 deletions(-) diff --git a/src/zcl_mjs_tokenizer.clas.abap b/src/zcl_mjs_tokenizer.clas.abap index f7cd382..747d3e2 100644 --- a/src/zcl_mjs_tokenizer.clas.abap +++ b/src/zcl_mjs_tokenizer.clas.abap @@ -1,13 +1,36 @@ 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. @@ -32,41 +55,17 @@ 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. lv_bt = |`|. 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. WHILE lv_i < lv_len. lv_ch = iv_src+lv_i(1). @@ -283,41 +282,16 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. WHEN `t`. lv_sbuf = lv_sbuf && cl_abap_char_utilities=>horizontal_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 && gv_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. @@ -328,19 +302,16 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. 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. @@ -351,23 +322,15 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. 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 `'`. @@ -384,9 +347,9 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. 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. @@ -565,9 +528,9 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. 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 ). + lv_rxpat = lv_rxpat && iv_src+lv_j(1). ELSE. - lv_rxpat = lv_rxpat && substring( val = iv_src off = lv_j len = 1 ). + lv_rxpat = lv_rxpat && iv_src+lv_j(1). ENDIF. lv_j = lv_j + 1. ENDWHILE. From d6b42a3e09ff8aec4f026dbd0b7ec95351509341 Mon Sep 17 00:00:00 2001 From: Lars HVam Date: Sun, 19 Jul 2026 14:25:15 +0200 Subject: [PATCH 5/5] fix --- src/zcl_mjs_tokenizer.clas.abap | 603 ++++++++++++++++++++------------ 1 file changed, 381 insertions(+), 222 deletions(-) diff --git a/src/zcl_mjs_tokenizer.clas.abap b/src/zcl_mjs_tokenizer.clas.abap index 747d3e2..a836e30 100644 --- a/src/zcl_mjs_tokenizer.clas.abap +++ b/src/zcl_mjs_tokenizer.clas.abap @@ -36,9 +36,11 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. 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. @@ -56,63 +58,100 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. DATA lv_esc TYPE c LENGTH 1. DATA lv_esc_cp TYPE i. DATA lv_xh TYPE i. - 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_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. - lv_bt = |`|. + " 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_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`. + " 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. @@ -120,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 . @@ -145,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. @@ -163,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. @@ -182,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. @@ -220,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. @@ -238,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. @@ -255,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. @@ -269,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). @@ -278,11 +316,11 @@ 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_sbuf = lv_sbuf && gv_cr. + lv_sbuf = lv_sbuf && lv_cr. WHEN `b`. " backspace = 0x08 lv_sbuf = lv_sbuf && gv_backspace. WHEN `f`. " form feed = 0x0C @@ -296,8 +334,10 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. 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. @@ -316,8 +356,10 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. 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. @@ -337,13 +379,13 @@ CLASS zcl_mjs_tokenizer IMPLEMENTATION. 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. @@ -372,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 && iv_src+lv_j(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 && iv_src+lv_j(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 .