An online playground for Vector CAPL (the CANoe/CANalyzer node language). Write CAPL in the browser, watch it go through a real compiler pipeline, inspect the AST as JSON, read the generated C, read the generated WebAssembly — and then run that WebAssembly inside a miniature simulated measurement with virtual time, timers, a CAN bus and key events.

Nothing is sent to a server: the compiler runs in the page.

source.can ─▶ lexer ─▶ parser ─▶ AST ─▶ sema ─┬─▶ wasm backend ─▶ .wasm ─▶ simulator

├─▶ C backend ─▶ .c

└─▶ AST backend ─▶ .json

src/ the playground UI (React + Vite + Tailwind v4)

core/ Rust WASM bridge and editor-only syntax lexer

driver.ts frontend contract for capl-wasm

runtime/sim.ts the simulated CANoe measurement (host imports)

components/ editor, panels, architecture tab

rust/ the compiler core as reusable crates

crates/capl-diag spans + diagnostic bag

crates/capl-parse recursive-descent parser and lexer

crates/capl-ast AST, type lattice, visitor, serde JSON export

crates/capl-sema scopes, symbols, types

crates/capl-codegen-wasm wasm module writer (LEB128 + sections, no deps)

crates/capl-codegen-c C99 writer

crates/capl-driver stage orchestration, Backend trait, `capl` CLI

crates/capl-wasm wasm-bindgen facade for this page

The page loads capl-wasm before React starts. It registers the sole compiler

implementation on globalThis.__CAPL_CORE__; TypeScript only owns the editor,

simulation host and presentation layer.

./rust/build-wasm.sh # wasm-pack build → public/capl/capl_wasm.js

cargo run -p capl-driver -- node.can --emit=wat

cargo run -p capl-driver -- node.can --emit=wasm -o node.wasm

cargo test --workspaceThe native driver resolves includes { "file.can" } relative to the source

file. Additional search directories can be supplied with the

CAPL_INCLUDE_PATH environment variable (use the platform path separator).

The browser build has no filesystem access, so includes are currently limited

to native driver use.

The compiler has driver-level integration tests under

rust/crates/capl-driver/tests/. They compile real CAPL programs through the

Rust lexer, parser, semantic pass, C backend, and WebAssembly backend. The

fixtures cover timers and handlers, cyclic CAN messages, arrays and functions,

64-bit and floating-point expressions, plus invalid input diagnostics.

Install cargo-llvm-cov once, then

run:

cd rust

cargo llvm-cov --workspace --all-features --lcov --output-path coverage/lcov.info

cargo llvm-cov --workspace --all-features --html --openOn Windows, ./rust/coverage.ps1 runs the LCOV command and prints the summary.

CI installs the same tool and produces rust/coverage/lcov.info during the

Rust test job, then uploads it as the rust-coverage artifact. The native

coverage command intentionally measures the reusable compiler crates; the

capl-wasm facade is exercised by the browser build and runtime smoke tests.

The wasm backend writes the binary by hand — LEB128, sections, a code builder that emits bytes and WAT listing in lock-step.

0x0000 null guard

0x0040 vararg marshalling area (32 slots × 16 B) used by write()

0x0400 string pool, globals, message objects, function statics

Imports (module "capl", supplied by the host — the browser simulator here, a

CANoe shim or a test bench elsewhere):

write(fmt, argc, args) setTimer(id, ms) setTimerCyclic(id, ms)

cancelTimer(id) output(msgPtr) timeNow() -> i32

random(n) -> i32 strlen(ptr) -> i32 pow/sin/cos

canOnline() canOffline() stopMeasurement()

Exports: memory, the immutable global __msgbuf (address of the event

message object), __init, __set_this, __set_fuel, every user function and

one function per event handler (on_start, on_timer_tick,

on_message_0x100, on_key_103, …).

Every loop back-edge decrements a fuel counter and traps when it hits zero, so

an accidental while (1) {} cannot freeze the tab — the simulator reports it as

a runtime trap instead.

variables{} blocks (which may also define types) · globals · const · named

struct declarations with fields and field arrays · native-driver includes{}

with relative paths and CAPL_INCLUDE_PATH · arrays

and brace initialisers · byte word dword qword int long int64 float double char

· scalar types and arrays, including brace initialisers and multi-dimensional

indexing · message objects with

.id .dlc .can .byte(n) .word(n) .dword(n) and this · timer/msTimer ·

events preStart start stopMeasurement preStop timer message key sysvar envVar errorFrame busOff (with channel-aware matching for on message CAN2.0x100) ·

if else while do for switch (with fall-through) break continue return · the

full C expression grammar including ?:, short-circuit &&/|| and compound

assignment · user functions with recursion, array parameters, and

address-based aggregate parameters/returns · built-ins

write (printf-style), output, elCount, timeNow, random, abs, min,

max, sqrt, pow, sin, cos, floor, ceil, strlen, canOnline,

canOffline, stopMeasurement.

Local aggregates are cleared on declaration by the generated __zero helper,

which keeps examples reproducible. Compile-time constants such as const int N = 3 are folded into immediates and never occupy linear memory.

The current aggregate ABI passes arrays and structures as linear-memory addresses in WASM. The C backend uses array pointers and structure pointers for function boundaries, and structure-returning functions return a pointer to the returned object. This keeps the two backends interoperable with the same source-level model; callers should treat aggregate values as storage-backed objects rather than scalar register values.

The compiler intentionally implements a focused CAPL subset. The following syntax and language features are reserved for future work and should not be treated as supported just because their keywords are recognized by the lexer:

- includesin browser builds, where there is no filesystem include resolver; the native driver supports relative files and- CAPL_INCLUDE_PATH.

- Anonymous structs, recursive layouts, packing/bit-fields, and full CAPL layout compatibility beyond the current named-field subset.

- Database-backed objects and signal access such as $Signal, including message-qualified signals and signal assignments.

- CAPL test modules: testcase,testfunction, test setup/teardown, and test verdict/reporting APIs.

- Aggregate conversions beyond the current address ABI, including exact CAPL copy/alias rules for every array and structure context.

- Additional CAPL event forms and runtime objects beyond the current timer, message, key, system-variable, environment-variable, and bus/error events.

- CAPL-specific attributes, qualifiers, namespaces, and configuration syntax used by CANoe/CANalyzer projects.

- The broader built-in API surface for diagnostics, transport protocols, panels, files, sockets, and measurement configuration.

Each item needs parser, semantic-analysis, backend, and runtime coverage before it should be advertised as supported. New syntax should also receive a driver integration fixture and focused precedence/diagnostic tests where applicable.

- light / dark / system theme, remembered in localStorage

- English / 简体中文, auto-detected from navigator.language

- diagnostics with precise spans, clickable, mirrored in the editor gutter

- per-stage timings, token/node counts and module size in the status bar

- simulation bar: virtual clock, +100 ms/+1 s, frame injection, key events, pending-timer inspector

Keyboard: Ctrl/⌘ + Enter builds and runs.

CAPL Playground is an independent, experimental compatibility and teaching project. It is not sponsored, endorsed, affiliated with, or derived from Vector Informatik GmbH, CANoe, CANalyzer, or any other Vector product. “Vector”, “CAPL”, “CANoe”, and “CANalyzer” are names and trademarks of their respective owners and are used here only to identify the language or behavior this project aims to interoperate with. This project does not include Vector software, proprietary databases, documentation, or test assets, and it does not claim to implement the official CAPL toolchain.

Use of this project is at your own risk. You are responsible for checking the licenses and terms that apply to any source code, specifications, examples, trademarks, reverse-engineering activity, and deployment environment involved in your use of it. This notice is informational and is not legal advice.

Copyright (c) 2026 Wei Li

This project is dual-licensed under either of the following licenses, at your option:

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project shall be dual-licensed as above, without any additional terms or conditions.

The compiler architecture is informed by the design of the DMD front end.