Skip to content

oisee/zmjs

Repository files navigation

ZMJS

Mini JavaScript Engine for SAP ABAP

A complete JavaScript interpreter in pure ABAP.
No kernel modules. No external dependencies. Just classes.

ABAP 7.58+ 230/266 ABAP test262 242/266 Go test262 21/21 SAP tests 3100 lines MIT


DATA(lv) = zcl_mjs=>eval( |console.log(6 * 7)| ).
" => "42"

What Is This?

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.

Usage

Hello World

DATA(lv) = zcl_mjs=>eval( |console.log("Hello from JS on SAP!")| ).
" lv = "Hello from JS on SAP!\n"

Fibonacci

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"

Classes with Inheritance

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"

Arrow Functions & Modern JS

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"

Load JS from SMW0

" 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");` ).

Feature Demo: ABAP → JS → ABAP (transpiled execution)

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).

Language Support

Data TypesNumbers (float64), strings, booleans, null, undefined, objects, arrays
Variableslet, var, const
Operators+ - * / %   < > <= >= === !== == !=   && || ! ?? ?.   ? :
Strings.length .charAt() .charCodeAt() .indexOf() .substring()
Template Literals`hello ${name}, ${1+2}`
Control Flowif/else   while   for   for...of   for...in   switch/case   break   continue
FunctionsDeclarations, expressions, arrow functions () => {}, closures, recursion
Rest/Spreadfunction f(...args)   [...a, ...b]
Error Handlingthrow   try/catch/finally   new Error(msg)
ObjectsLiterals {x:1}, property access .x / ["x"], assignment
ArraysLiterals [1,2,3], .push(), .length, index access
Classesclass with constructor, methods, static, extends, new
ConstructorsBoolean() Number() String() — type coercion builtins
String Escapes\n \t \r \b \f \v \0 \xNN \uNNNN — full escape sequence support
Numeric LiteralsHex 0xFF, scientific 1e3, dot-prefix .5
Othertypeof   console.log   undefined null Infinity NaN

What's New in v0.3.x

v0.3.3 (2026-04-01): Performance — ABAP interpreter

  • −28% fib(20): 525ms → 378ms
  • −5% loop 10K: 157ms → 149ms
  • Eliminated arguments object allocation for functions that don't use it
  • Args passed as tt_value_slots (direct values, no boxing overhead)
  • Inlined slot access in eval_node — no method dispatch for local variable reads/writes
  • Inlined trivial value constructors (number_val, bool_val, etc.)
  • Try/catch safety net around all cl_abap_codepage=>convert_from calls
  • ABAP test262: 230/266 on SAP

v0.3.2 (2026-04-01): Language + slot optimization — ABAP interpreter

  • arguments object — bound in every function call (unless shadowed by param)
  • Short-circuit && / || / ?? — now returns the deciding operand value
  • Variable slot optimization in ABAP: integer-indexed slots replace per-call hash lookups
  • Go test262: 242/266 (was 233 in v0.3.1)

v0.3.1 (2026-04-01):

  • finally blocks now execute (previously no-op)
  • var declarations inside catch blocks propagate to outer scope
  • String escapes: \r \b \f \v \0 \xNN \uNNNN
  • Boolean() / Number() / String() constructors
  • Go test262: 233/266 (was 178 in v0.2.0)

v0.3.0 (2026-04-01):

  • try/catch/throw — fully working exception handling
  • finally blocks parsed (execution added in v0.3.1)
  • Function expressions in expression position
  • Comma operator in for-loop bodies

Why Run JS on SAP?

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

Proven: abaplint Lexer on SAP

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.

Architecture

                    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 pointeval(), evaluator, built-in methods

Benchmarks

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.

Tests

Go tests (reference implementation)

99 tests covering all language features — run with go test ./pkg/jseval/ in the vibing-steampunk repo.

SAP ABAP unit tests

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)

Test262 Conformance

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
v0.1.0 130 136 266
v0.2.0 178 88 266
v0.3.0 231 35 266 try/catch/throw
v0.3.1 233 33 266 finally, string escapes
v0.3.2 242 24 266 arguments, slot opt
v0.3.3 242 230 36 266 first ABAP run!

Categories covered: numeric literals (hex, scientific, dot-prefix), string escapes (\r\b\f\v\0\xNN\uNNNN), template literals, all operators, control flow, functions, closures, classes, try/catch/finally, throw, Boolean()/Number()/String() constructors, rest/spread, for...of, for...in, switch, arguments object, and more.

Note on failure output: all 36 failures print : undefined because new Test262Error(msg) itself uses plain function construction — the same gap that causes group A to fail. Real error messages are masked. Classification is based on test source, not runner output.

ABAP failure breakdown (36 tests, 9 groups):

Group Root cause Tests
A new PlainFunction() — only class construction handled 11
B with statement not implemented 8
C valueOf/ToPrimitive coercion on objects 8
D Named function expression name leaks to outer scope 3
E Unicode \uNNNN escapes in identifier names 2
F Labeled break from nested loop 1
G delete operator 1
H if (cond) IIFE; nextIIFE; — second statement may attach to if-body 1
I Calling a caught function value as plain e() 1

Group A is the only compatibility-critical gap — new abap.types.Integer({}) and all open-abap-core object construction patterns require plain function constructors. Groups B–I are spec edge cases with low impact on real-world abaplint/open-abap-core usage.

Full inventory: _tmp/codex_report_2026-04-01-test262-conformance-inventory.md

Installation

Via abapGit

  1. Install abapGit on your SAP system
  2. Create package $ZMJS
  3. Clone this repository into the package

Manual Import

Create objects in this order (respects dependencies):

1. ZCL_MJS_OBJ        (no dependencies)
2. ZCL_MJS_ARR        (no dependencies)
3. ZIF_MJS            (references OBJ, ARR)
4. ZCX_MJS_THROW      (references ZIF_MJS)
5. ZCL_MJS_ENV        (references ZIF_MJS)
6. ZCL_MJS_PARSER     (references ZIF_MJS)
7. ZCL_MJS            (references all above)
8. ZMJS_BENCHMARK     (optional — test program)

Limitations

What ZMJS does not support (yet):

  • ++ / -- operators (use i = i + 1)
  • do-while loops
  • Labeled break / continue
  • delete operator
  • arguments.callee (property on the arguments object)
  • Prototype chain (func.prototype — classes use property copy instead)
  • valueOf coercion on objects in arithmetic expressions
  • Regular expressions
  • Destructuring assignments
  • Date, Math, JSON, parseInt, Array.isArray built-ins
  • Module system (import / export)
  • Async / Promises / generators
  • Getters / setters

License

MIT

Credits

Built with Claude Code (Anthropic).

Clean room implementation — the interpreter (tokenizer, parser, evaluator) was written from scratch as a tree-walking interpreter, following the classic approach described in Crafting Interpreters by Robert Nystrom. No code was copied from any existing JS engine.

The abaplint lexer by Lars Hvam (MIT license) is used as a real-world test case — the JS lexer code runs through ZMJS on SAP to tokenize ABAP source.

About

Mini JavaScript Engine for SAP ABAP — tree-walking interpreter, 2100 LOC, Test262 230/266

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors