来源资料待手动核验
Zmjs
Mini JavaScript Engine for SAP ABAP — tree-walking interpreter, 2100 LOC, Test262 230/266
项目简介
Mini JavaScript Engine for SAP ABAP — tree-walking interpreter, 2100 LOC, Test262 230/266
Mini JavaScript Engine for SAP ABAP — tree-walking interpreter, 2100 LOC, Test262 230/266
Mini JavaScript Engine for SAP ABAP — tree-walking interpreter, 2100 LOC, Test262 230/266
Mini JavaScript Engine for SAP ABAP
A complete JavaScript interpreter in pure ABAP.
No kernel modules. No external dependencies. Just classes.
DATA(lv) = zcl_mjs=>eval( |console.log(6 * 7)| ).
" => "42"
ZMJS is a tree-walking JavaScript interpreter that runs entirely inside SAP. You pass it a JavaScript source string, it tokenizes, parses into an AST, evaluates — and returns the captured console.log output. All in ~2,100 lines of ABAP.
It supports enough of JavaScript to run real-world JS code on SAP — things like the abaplint lexer, recursive algorithms, classes with inheritance, closures, arrow functions, template literals, and more.
DATA(lv) = zcl_mjs=>eval( |console.log("Hello from JS on SAP!")| ).
" lv = "Hello from JS on SAP!\n"
DATA(lv_js) =
`function fib(n) {` && cl_abap_char_utilities=>newline &&
` if (n <= 1) return n;` && cl_abap_char_utilities=>newline &&
` return fib(n-1) + fib(n-2);` && cl_abap_char_utilities=>newline &&
`}` && cl_abap_char_utilities=>newline &&
`console.log(fib(20));`.
DATA(lv) = zcl_mjs=>eval( lv_js ).
" lv = "6765\n"
DATA(lv_js) =
`class Animal {` && cl_abap_char_utilities=>newline &&
` constructor(name) { this.name = name; }` && cl_abap_char_utilities=>newline &&
` speak() { return this.name + " makes a sound"; }` && cl_abap_char_utilities=>newline &&
`}` && cl_abap_char_utilities=>newline &&
`class Dog extends Animal {` && cl_abap_char_utilities=>newline &&
` speak() { return this.name + " barks"; }` && cl_abap_char_utilities=>newline &&
`}` && cl_abap_char_utilities=>newline &&
`let d = new Dog("Rex");` && cl_abap_char_utilities=>newline &&
`console.log(d.speak());`.
DATA(lv) = zcl_mjs=>eval( lv_js ).
" lv = "Rex barks\n"
DATA(lv_js) =
`const nums = [1, 2, 3, 4, 5];` && cl_abap_char_utilities=>newline &&
`const sum = (...args) => {` && cl_abap_char_utilities=>newline &&
` let s = 0;` && cl_abap_char_utilities=>newline &&
` for (const x of args) s = s + x;` && cl_abap_char_utilities=>newline &&
` return s;` && cl_abap_char_utilities=>newline &&
`};` && cl_abap_char_utilities=>newline &&
`console.log(sum(...nums));`.
DATA(lv) = zcl_mjs=>eval( lv_js ).
" lv = "15\n"
" Upload your .js file to SMW0 as binary, then:
DATA(lv_xstr) = cl_mime_repository=>get( '/SAP/PUBLIC/my_script.js' ).
DATA(lv_js) = cl_abap_conv_codepage=>create_in( codepage = 'UTF-8' )->convert( lv_xstr ).
DATA(lv_result) = zcl_mjs=>eval( lv_js && cl_abap_char_utilities=>newline && `console.log("done");` ).
ZMJS can execute JavaScript produced by the abaplint transpiler. This means you can write ABAP, transpile it to JS offline, and run it through ZMJS on SAP:
Step 1: Write ABAP
REPORT zdemo.
DATA lv_sum TYPE i.
DATA lv_i TYPE i.
lv_sum = 0. lv_i = 1.
WHILE lv_i <= 10.
lv_sum = lv_sum + lv_i.
lv_i = lv_i + 1.
ENDWHILE.
WRITE lv_sum.
Step 2: Transpile (offline, via npx @abaplint/transpiler-cli)
// Generated JS:
let lv_sum = new abap.types.Integer({});
let lv_i = new abap.types.Integer({});
lv_sum.set(abap.IntegerFactory.get(0));
lv_i.set(abap.IntegerFactory.get(1));
while (abap.compare.le(lv_i, abap.IntegerFactory.get(10))) {
lv_sum.set(abap.operators.add(lv_sum, lv_i));
lv_i.set(abap.operators.add(lv_i, abap.IntegerFactory.get(1)));
}
abap.statements.write(lv_sum);
Step 3: Run on SAP (runtime shim + transpiled JS → zcl_mjs=>eval())
" Load runtime shim + transpiled JS from SMW0 or string
DATA(lv_result) = zcl_mjs=>eval( lv_runtime_shim && lv_transpiled_js ).
" lv_result = "55"
The abaplint lexer also runs directly through ZMJS, tokenizing real ABAP source code on SAP (155 tokens in 59ms).
| Data Types | Numbers (float64), strings, booleans, null, undefined, objects, arrays |
| Variables | let, var, const |
| Operators | + - * / % < > <= >= === !== == != && || ! ?? ?. ? : |
| Strings | .length .charAt() .charCodeAt() .indexOf() .substring() |
| Template Literals | `hello ${name}, ${1+2}` |
| Control Flow | if/else while for for...of for...in switch/case break continue |
| Functions | Declarations, expressions, arrow functions () => {}, closures, recursion |
| Rest/Spread | function f(...args) [...a, ...b] |
| Error Handling | throw try/catch/finally new Error(msg) |
| Objects | Literals {x:1}, property access .x / ["x"], assignment |
| Arrays | Literals [1,2,3], .push(), .length, index access |
| Classes | class with constructor, methods, static, extends, new |
| Constructors | Boolean() Number() String() — type coercion builtins |
| String Escapes | \n \t \r \b \f \v \0 \xNN \uNNNN — full escape sequence support |
| Numeric Literals | Hex 0xFF, scientific 1e3, dot-prefix .5 |
| Other |
v0.3.3 (2026-04-01): Performance — ABAP interpreter
arguments object allocation for functions that don't use ittt_value_slots (direct values, no boxing overhead)eval_node — no method dispatch for local variable reads/writesnumber_val, bool_val, etc.)cl_abap_codepage=>convert_from callsv0.3.2 (2026-04-01): Language + slot optimization — ABAP interpreter
arguments object — bound in every function call (unless shadowed by param)&& / || / ?? — now returns the deciding operand valuev0.3.1 (2026-04-01):
finally blocks now execute (previously no-op)var declarations inside catch blocks propagate to outer scope\r \b \f \v \0 \xNN \uNNNNBoolean() / Number() / String() constructorsv0.3.0 (2026-04-01):
try/catch/throw — fully working exception handlingfinally blocks parsed (execution added in v0.3.1)| Use Case | Description |
|---|---|
| Run JS libraries on-stack | Load parsers, validators, or transformers from SMW0 and execute them on the app server — no RFC, no HTTP, no middleware |
| Embeddable scripting | Let users write business rules in JS without custom ABAP development — store scripts in a table, eval at runtime |
| Cross-platform logic | Write validation/transformation logic once in JS, run it in the browser AND on SAP |
| AI agent tooling | Let AI agents generate and execute JS snippets on SAP for data processing and analysis |
| Offline linting | Run abaplint's lexer/parser on SAP to analyze ABAP code without external tools |
The abaplint JavaScript lexer (~130 lines) runs through ZMJS on SAP, correctly tokenizing ABAP source code into ident/number/punct/op/string/comment tokens — 155 tokens in 59ms.
zcl_mjs=>eval( iv_source )
|
+---------+---------+
| |
[Tokenizer] [console.log]
| output capture
[Parser] |
recursive descent |
30 node types |
| |
[Evaluator] |
tree-walking interp. |
scope chain (env) |
| |
+--------+---------+
|
rv_output (string)
| Class | LOC | Role |
|---|---|---|
ZIF_MJS | 106 | Types & constants (28 AST node kinds, value types) |
ZCL_MJS_OBJ | 55 | JS object — hashed property table |
ZCL_MJS_ARR | 27 | JS array — ordered value list |
ZCL_MJS_ENV | 99 | Scope chain — variable lookup with parent chain, output buffer |
ZCX_MJS_THROW | 20 | Exception class for JS throw (cx_no_check, carries JS value) |
ZCL_MJS_PARSER | 888 | Tokenizer + recursive descent parser |
ZCL_MJS | 1082 | Entry point — eval(), evaluator, built-in methods |
Measured on SAP NetWeaver AS ABAP 7.58 (CAL instance, 4 vCPU):
| Benchmark | Result | Time | Notes |
|---|---|---|---|
fib(20) | 6,765 | 378ms | 21,891 recursive calls (v0.3.3, −28% vs v0.3.2) |
fib(25) | 75,025 | ~3,600ms | 242,785 recursive calls |
| Loop sum 0..9999 | 49,995,000 | 149ms | 10K iterations (v0.3.3, −5% vs v0.3.2) |
| String concat 1000x | 1,000 chars | 16ms | |
| abaplint lexer | 155 tokens | 59ms | Real ABAP class source |
| Test262 (266 tests) | 230/266 | 377ms | Full ECMAScript conformance suite |
Go reference implementation (with variable slot optimization):
| Benchmark | Time | Notes |
|---|---|---|
fib(20) | ~107ms | Slot optimization, 1.45x speedup vs baseline |
Tree-walking interpreters trade speed for simplicity. ZMJS is fast enough for scripting, configuration, and running small JS libraries. It's not meant for compute-heavy workloads.
99 tests covering all language features — run with go test ./pkg/jseval/ in the vibing-steampunk repo.
21 test methods across two test classes, all passing on SAP:
ZCL_MJS test include (17 methods):
test_2plus2, test_string, test_if_else, test_function, test_factorial,
test_closure, test_object, test_array, test_class, test_for_loop,
test_while_continue, test_switch, test_typeof, test_string_methods,
test_or_chain, test_space_handling, test262 (44 JS-level assertions)
ZMJS_BENCHMARK program (4 methods):
test262 (45 assertions), test_try_catch (4 try/catch scenarios),
bench_fib (fib(20)=6765 in ~450ms), bench_loop (10K iters in ~150ms)
Two levels of conformance testing:
SAP built-in suite — 45 hand-written tests that ship with ZMJS (all passing):
Numeric literals typeof operator Multiplicative ops
Additive ops String concatenation Relational ops
Strict equality Logical AND/OR/NOT if/else branching
while loops for loops break & continue
Function return switch/case Recursive factorial
Closures (counter) Array ops Object property access
String methods Class + constructor Method calls
Full test262 suite — 266 tests from real ECMAScript262 test cases, run on both the Go reference implementation and the SAP ABAP interpreter:
| Version | Go Pass | ABAP Pass | Fail | Total | Notes |
为保持页面易读性,当前仅展示 README 的前半部分。请打开原始 README 阅读完整内容。
typeof console.log undefined null Infinity NaN |