feat(wasm): leo-wasm crate#29453
Draft
mohammadfawaz wants to merge 18 commits into
Draft
Conversation
c17a430 to
aca919d
Compare
e6b7c59 to
e05999b
Compare
Adds a thin `leo-wasm` binding crate that delegates to `leo-compiler` and
`leo-fmt` instead of duplicating the compile pipeline.
# Crate layout
Modeled on the existing `leo-aleo-abi-wasm` crate:
- `crates/leo-wasm/src/lib.rs` exposes the implementations as target-neutral
`*_impl` functions; `#[wasm_bindgen]` shims live in a
`#[cfg(target_arch = "wasm32")] mod wasm_bindings` and forward verbatim.
- `crates/leo-wasm/src/evaluate.rs` (wasm32-only) wraps `Process::evaluate`,
`FinalizeMemory`, and finalize-store readback so the JS side can call
programs without proof generation.
- `crates/leo-wasm/src/project.rs` walks a virtual `{path: contents}` file
map, parses each `program.json`, builds the `import_stubs` map (recursive
for transitive source deps, in-place disassembly for `.aleo` deps), then
delegates to `Compiler::compile_from_directory_with_file_source`.
- Wasm-only deps (`wasm-bindgen`, `console_error_panic_hook`, `getrandom`,
`rand`, `rand_chacha`, `aleo-std`, and the six `snarkvm-*` subset
crates) sit under `[target.'cfg(target_arch = "wasm32")'.dependencies]`
so native builds don't pull them.
# Entry points
- `compile(source, program_json)` → bytecode + ABI JSON
- `format(source)` → formatted Leo source
- `run(source, fn, inputs, program_json)` → execute one fn
- `run_tests(main_source, test_source, program_json)` → run all `@test` fns
- `compile_project(files_json, root)` → compile multi-file/multi-dep project
- `run_project(files_json, root, fn, inputs)` → compile + execute
- `test_project(files_json, root, test_root)` → compile + execute tests
# Compiler changes
- Native-only methods (file IO helpers, `*_from_directory` variants,
`load_import_stubs_for_package*` and the leo-package-driven helpers) are
gated behind `#[cfg(not(target_arch = "wasm32"))]` in
`crates/compiler/src/compiler.rs`. `Compiler::new`, `compile`,
`compile_from_directory_with_file_source`, and `add_import_stubs` are now
available on wasm32.
- `Manifest::read_from_file_source` added in `leo-package` so wasm callers
can resolve manifests against a `FileSource`.
- `FileSource` trait gains default `is_file`/`is_dir`/`exists` methods
with real-path / virtual-FS overrides (`leo-span`).
- `leo-compiler::disassemble_dependency_bytecode` is now `pub` and wasm-safe
(delegates to `leo_disassembler::disassemble_from_str_for_network`).
# Tests
5 native unit tests in `crates/leo-wasm/src/project.rs::tests` cover:
modules-only, modules + sibling `.leo`, single source dep, transitive deps
with parent-dir paths, and `.aleo` bytecode dep.
A separate Playwright suite in the `leo-playground` repo exercises the
in-browser entry points end-to-end against a real `Process::load_web`
execution.
e05999b to
e8f1fe5
Compare
Replace `SvmProgram::<TestnetV0>::parse` in `parse_dependencies_from_aleo` with an inline scanner that walks `import <name>;` lines at the top of the file. Native builds still run the full snarkVM parse first so malformed bytecode surfaces as `snarkvm_parsing_error` — only the import-name extraction is shared with wasm. Why: leo-package's only direct use of `snarkvm::prelude::Program` was for this single step, and the umbrella crate is native-only (it pulls in ledger/proving code). Removing it unblocks wasm32 builds for the rest of the crate. - `Cargo.toml`: `snarkvm` and `ureq` move into `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]`. The wasm-friendly subset crates are *not* added — the inline parser doesn't need them. - `compilation_unit.rs`: `parse_dependencies_from_aleo` splits the validation (native-only) from the import extraction (target-neutral), using a new `extract_aleo_import_names` helper. - 5 unit tests cover basic imports, blank-line / comment tolerance, early-stop at the `program` block, no-imports, and a parity check that compares against snarkVM's own parser on a realistic source.
Extends `FileSource` with default `is_file` / `is_dir` / `exists` methods
(plus native overrides on `DiskFileSource` and virtual-FS overrides on
`InMemoryFileSource`), then threads them through the read-only parts of
`leo-package`:
- `Manifest::read_from_file_source` — read a manifest via any `FileSource`.
Native `read_from_file` becomes a thin `DiskFileSource` wrapper.
- `CompilationUnit::{from_aleo_path,from_package_path,from_test_path}_with_file_source`
variants. Existing `*_path` functions are native-only wrappers.
- `ProgramData` moves from `package.rs` (native-only) into `compilation_unit.rs`
so it's reachable on wasm where the `Package` orchestration layer is gated
out.
- `resolve_dependency_path_relative_to` + `normalize_path_via_file_source`
— handle real-disk canonicalization on native and component normalization
on wasm so virtual-FS paths resolve.
- `resolve_workspace_dependency_with_file_source` — native delegates to the
existing resolver; wasm returns a clear 'use explicit path' error.
Native-only surface gated behind `#[cfg(not(target_arch = "wasm32"))]`:
- `Package` and `Workspace` orchestration (will gain `_with_file_source`
variants in follow-up commits).
- `Manifest::write_to_file`, `CompilationUnit::fetch`, `find_cached_edition`.
- Network helpers: `create_http_agent`, `retry_network_call`,
`fetch_from_network[_plain]`, `fetch_program_from_network`,
`fetch_latest_edition`, `verify_valid_program`.
- Reserved-keyword checks (`reserved_keywords`, `is_valid_program_name`,
`is_valid_library_name`, `is_valid_package_name`) — depend on snarkVM's
keyword tables.
- `MAX_PROGRAM_SIZE` mirrors snarkVM's constant on native and hardcodes the
same value on wasm.
Cargo.toml: `snarkvm` and `ureq` move into
`[target.'cfg(not(target_arch = "wasm32"))'.dependencies]`. A wasm-only
`getrandom = { features = ["wasm_js"] }` dep enables the right backend
for snarkVM's transitive randomness chain.
Verified: `cargo check --workspace` (native) + `cargo check -p leo-package
--target wasm32-unknown-unknown` are both clean; all 43 leo-package tests
pass.
Drop the inline `ManifestLite` / `DependencyLite` shim — now that `leo-package` compiles on wasm32, `Manifest::read_from_file_source` and the canonical `Dependency` struct are reachable from `leo-wasm`. One manifest reader for both CLI and wasm; any future field on `Manifest` flows through automatically.
… CLI
Each `commands::*` module corresponds to one `leo` CLI command:
| `leo` command | wasm module |
|----------------|----------------------|
| `leo build` | `commands::build` |
| `leo run` | `commands::run` |
| `leo test` | `commands::test` |
| `leo fmt` | `commands::format` |
Each module exposes the same JSON-returning `*_impl` entry points the
`#[wasm_bindgen]` shim in `wasm_bindings` wraps verbatim — no JS-facing
API change. Shared plumbing (compile boilerplate, manifest parsing,
JSON shaping) moves into a new `wire` module so each command file
focuses on its own command.
Layout:
- `lib.rs` (was 509 LOC, now 119): crate docs (CLI ↔ wasm lookup table)
+ module decls + `wasm_bindings` shim + re-exports.
- `commands/{build,run,test,format}.rs` (~80-200 LOC each).
- `wire.rs` (~160 LOC): `compile_session`/`compile_with`,
`parse_program_json`/`network_from_*`, `error_json`/`diagnostics_from`/
`import_summaries`/`clone_file_source`.
- `project.rs` and `evaluate.rs` unchanged.
Adding a new wasm command later (deploy, execute, etc.) is now a
single new file in `commands/` plus one wrapper in `wasm_bindings`.
Verified: `cargo check` native + wasm32, 5/5 leo-wasm tests pass, all
23 Playwright tests pass against the rebuilt wasm.
The leo CLI exposes one shape per command (it always reads a project directory); the wasm crate had two parallel impls per command (one single-source for the playground, one project-based). Removed the single-source variants: - `commands::build::compile_impl` (single source) → gone - `commands::build::compile_project_impl` → renamed to `build_impl` - `commands::run::run_impl` (single source) → gone - `commands::run::run_project_impl` → renamed to `run_impl` - `commands::test::run_tests_impl` (main + test src) → gone - `commands::test::test_project_impl` → renamed to `test_impl` - `commands::format::format_impl` stays — matches leo_fmt's per-source CLI primitive (not project-shaped). JS-facing exports follow the new names: `build`, `run`, `test`, `format`, `init`. `compile`, `compile_project`, `run_project`, `run_tests`, `test_project` removed. The playground now constructs a virtual file map at the JS level (helpers `buildMainFiles` / `buildTestFiles` in app.js) — the test package gets its own synthesized `program.json` declaring the main program as a local dep, matching what `leo test` reads off disk. wire.rs sheds the helpers only the deleted impls used (`compile_session`, `compile_with`, `parse_program_json`, `diagnostics_from`). Verified: cargo check native + wasm32, 5/5 leo-wasm project tests pass, all 23 Playwright tests pass against the rebuilt wasm with the new API.
Drop leo-wasm's standalone `evaluate` module and unify execution through `leo_compiler::run::run_without_ledger`, which now compiles on both native and wasm32 (Process loader and finalize-store opener are target-gated). Transitions go through `authorize_unchecked` so no proofs are generated, and finalize logic runs against an in-memory finalize store with the post-finalize mapping state attached to each `EvaluationOutcome`. Review fixes: - `MAX_PROGRAM_SIZE`: single `512_000` literal on both targets; native compile-time assertion against snarkVM's `TestnetV0::MAX_PROGRAM_SIZE`. - `.aleo` import extraction: native uses snarkVM's parsed `imports()` directly; wasm scanner now accepts tab whitespace, strips trailing line comments, and strips block comments. New parity tests vs snarkVM. - `FileSource::canonicalize` hook: `DiskFileSource` keeps real-disk canonicalize; other sources fall back to component normalization, so `normalize_path_via_file_source` no longer leaks host paths. - `walk_deps`: tracks each stub's source; collisions across distinct sources surface as a diagnostic instead of silently winning the first. Threads the parser `BufferEmitter` through so dependency parse errors carry their buffered diagnostics. - Wasm `run`/`test`: read network from the test package's manifest (not the main project's); reject non-testnet manifests up front since `run_without_ledger` is testnet-only; surface `invalid inputs JSON` instead of swallowing parse errors.
The shared `run_without_ledger` glue (program staging, Case construction, single-outcome eval, @test discovery) now lives in `project.rs` next to `compile`/`compile_test`. The command modules just load the project, validate the network, call into the helpers, and shape the playground JSON — same shape as `build.rs`. New `project` API: `TestFn`, `stage_programs`, `run_function`, `find_test_functions`.
Stop re-parsing `program.json` to derive the network. Each wasm entry point now accepts an `env_json` blob — the JSON shape of an `EnvOptions` struct that mirrors the native CLI's `--network` / `--endpoint` / `--private-key` / `--network-retries` flags. Empty defaults to testnet (matches CLI default). `network_from_manifest` is gone. The Manifest struct is unchanged. The `EnvOptions` struct currently lives in `crates/leo-wasm/src/wire.rs` and mirrors the CLI's by hand; sharing the real struct with the CLI is follow-up work (requires either moving it into `leo-package` or making `crates/leo`'s lib wasm-buildable).
…lker Threads `&impl FileSource` through `Package::from_directory_impl` and `graph_build`, splits the network-bound config into a `NetworkConfig` struct, and adds `from_directory_*_with_file_source` entry points that load a `Package` from a virtual file map. Network-dep fetch + workspace resolution stay native-only (the wasm path returns a clear error if either is encountered). leo-wasm's `project.rs` now defers to `Package` for manifest reads, transitive dep resolution, and topological sort. `Project`, `walk_deps`, `collect_import_stubs`, `entry_file_in`, `normalize_path`, `read_manifest`, and `clone_file_source` are gone — replaced by a thin `build_import_stubs` helper that walks `package.compilation_units` and either disassembles bytecode or parses Leo source into a `Stub`. The Compiler runs against the same `FileSource` the walker consulted. Net: ~250-line reduction in leo-wasm; one shared project loader covers both the native CLI and the wasm bindings. Follow-up: lift `handle_build`/`handle_run`/`handle_test` (and `EnvOptions`) into a shared, wasm-buildable surface so leo-wasm becomes thin `wasm-bindgen` shims around the CLI's command impls.
`crates/leo`'s lib is now wasm-buildable. The native CLI surface (cli/, errors/) stays gated behind `#[cfg(not(target_arch = "wasm32"))]` — that tree still pulls in snarkVM's umbrella, tokio, axum, reqwest, terminal UI, and the rest of the native toolbelt. What survives the cfg gate is a new top-level `pub mod options` carrying the wasm-shared option structs. `EnvOptions` and `BuildOptions` move from `cli/commands/common/options.rs` into `crate::options`; all their fields become `pub` (were `pub(crate)`). The CLI module re-exports them via `pub use crate::options::*` so every existing `cli::commands::*` import keeps working unchanged. `leo-wasm` now depends on `leo-lang` and re-exports `EnvOptions` / `BuildOptions` from `leo_lang::options::*` via `crate::wire`. The parallel `EnvOptions` definition that lived in `leo-wasm/src/wire.rs` is gone. JSON `from_json` and `resolved_network()` helpers live on the shared struct so both surfaces consume it the same way. Cargo.toml: native-only deps in `crates/leo` are now under `[target.'cfg(not(target_arch = "wasm32"))'.dependencies]`. `getrandom` gets a `wasm32`-only entry with the `wasm_js` backend (matches the existing pattern in `leo-package`). Follow-up: lift `handle_build`/`handle_run`/`handle_test` into the shared `options` module's neighbour so the leo-wasm command modules become thin wasm-bindgen shims over the CLI's command impls.
The wasm-buildable surface that used to live inside `crates/leo`
(forcing the CLI crate into a dual binary+lib role with a partially
gated dependency tree) and `crates/leo-wasm`'s `project.rs` now live
in a single new library crate: `leo-cli-core`.
Layout:
crates/leo-cli-core/ ← shared, wasm-buildable
src/options.rs EnvOptions, BuildOptions, From<BuildOptions>
for CompilerOptions
src/project.rs Project, compile/compile_test,
stage_programs, run_function,
find_test_functions, TestFn
crates/leo/ ← CLI binary, depends on leo-cli-core
src/cli/commands/common/options.rs
`pub use leo_cli_core::options::{BuildOptions, EnvOptions,
DEFAULT_ENDPOINT};` keeps every existing CLI import working.
crates/leo-wasm/ ← wasm-bindgen layer, depends on leo-cli-core
src/wire.rs `pub use leo_cli_core::options::*;`
src/commands/*.rs call `leo_cli_core::project::*` directly
Net effect: both consumers are now genuine thin wrappers around one
shared core. `crates/leo`'s lib drops the wasm-buildable carve-out
(the cfg-gated `pub mod cli;` and the bespoke top-level `options` /
`project` modules) — the lib is straight-up native again.
`crates/leo-wasm`'s `project.rs` is deleted entirely; the file map
loader, dep-graph walker, and execution helpers live in one place.
Follow-up: lift `handle_build` / `handle_run` / `handle_test` (and
their FileSink abstraction) into `leo-cli-core` so the CLI's
clap-driven structs and `leo-wasm`'s `*_impl` entry points both
become trivial wrappers around `leo_cli_core::handle_*`.
Add `leo_cli_core::network` and `leo_cli_core::validation` modules
that re-export the network-fetch and program-name-validation helpers
from `leo-package`. Every CLI consumer in `crates/leo` is switched to
import from the new path; `crates/leo/src/cli/commands/*.rs` no longer
references `leo_package::fetch_*` / `leo_package::is_valid_*` / etc.
directly.
For now the bodies still live in `crates/leo-package` (behind the
existing `cfg(not(target_arch = "wasm32"))` gates) so this commit is
a pure import-surface move. The follow-up migrates the bodies into
`leo-cli-core` and lets `crates/leo-package` drop its cfg gates.
Affected consumers:
- add.rs, deploy.rs, execute.rs, upgrade.rs, query/{program,transaction}.rs,
common/options.rs, helpers/check_transaction.rs, tests/integration.rs.
…_init `Package::initialize` (and its three Leo source templates) used to live behind `cfg(not(target_arch = "wasm32"))` in `crates/leo-package`, forcing the package crate to carry the on-disk-init logic even though it only runs on the native CLI. Move it out as a free function `leo_cli_core::package_init::initialize_package` and update the single consumer in `crates/leo/src/cli/commands/new.rs` to call it. `crates/leo-package` keeps a small `pub use errors::*;` so the moved function can construct the same `Backtraced` errors. Net: one fewer cfg gate in `crates/package` (and four fewer template functions).
…mpilation_unit
The network-dep fetcher (HTTP + on-disk cache) used to live on
`CompilationUnit::fetch` behind `cfg(not(target_arch = "wasm32"))`.
Move it out as a free function `leo_cli_core::package_fetch::fetch_compilation_unit`.
Both direct callers (`upgrade.rs`, `common/query.rs`) and the
`fetch_network_dependency` path inside `Package::from_directory_impl`
now route through it.
To break the resulting circular-dep concern (crates/package needs to
fetch, but can't depend on leo-cli-core), `NetworkConfig` gains a
`fetcher: CompilationUnitFetcher` fn-pointer field. `Package::from_directory*`
and `from_directory_no_graph` take the fetcher as a new argument. CLI
callers pass `leo_cli_core::package_fetch::fetch_compilation_unit`;
the benchmarks crate passes the new `leo_package::reject_network_fetcher`
helper (local-only fixtures, no network deps expected).
Side effect: `find_cached_edition` and `parse_dependencies_from_aleo`
become `pub` in `leo-package` so the moved fetcher can call them.
Several remaining `cfg(not(target_arch = "wasm32"))` gates in
`crates/package` are dropped (Manifest::{read,write}_to_file,
Package::{from_directory_*}, CompilationUnit::{from_aleo_path,
from_package_path, from_test_path}, `home_path.canonicalize()` in
`from_directory_impl`) — those bodies use only `std::fs` and
`DiskFileSource`, both of which compile on wasm32.
…ackage Move every native-only piece left in `crates/leo-package` over to `leo-cli-core`: - `is_valid_program_name` / `is_valid_library_name` / `reserved_keywords` / `verify_valid_program` → `leo_cli_core::validation::*`. The bodies pull in snarkVM's keyword tables. - `fetch_from_network` / `fetch_from_network_plain` / `fetch_program_from_network` / `fetch_latest_edition` / `create_http_agent` / `retry_network_call` → `leo_cli_core::network::*`. These used `ureq` (HTTP). - `MAX_PROGRAM_SIZE` compile-time assertion against snarkVM's constant → `leo_cli_core::validation` (kept; the literal itself stays in `leo-package` where every dep walker reads it). - Native-only validation branch of `parse_dependencies_from_aleo` is gone — the inline scanner (already wasm-buildable) is now the canonical extractor on every target. Callers that want strict bytecode validation call `verify_valid_program` separately. - Snarkvm parity test for the scanner moved to `leo_cli_core::validation::tests`. `parse_dependencies_from_aleo`'s helper `extract_aleo_import_names` becomes `pub`. - `Workspace` module is no longer cfg-gated. The `is_valid_library_name` call in `Workspace::initialize_skeleton` moves to the CLI's `LeoNew` handler (the only caller). The workspace-dep resolution `resolve_workspace_dependency_with_file_source` drops its native-only branch. - `snarkvm` and `ureq` are no longer in `crates/leo-package`'s native deps (only `leo_cli_core` uses them now). Net: `crates/leo-package` has *zero* `#[cfg(...)]` gates and is purely wasm-buildable. Same for `crates/leo/src/lib.rs` (which was already clean after earlier commits). The CLI keeps depending on `leo-cli-core` for what was native-only, and `leo-wasm` continues to pull only the wasm-buildable surface. `crates/leo`'s CLI commands switch their `leo_package::fetch_*` / `leo_package::is_valid_*` / `leo_package::verify_valid_program` imports to `leo_cli_core::network::*` and `leo_cli_core::validation::*`. The benchmarks crate passes `leo_package::reject_network_fetcher` (a new helper) for its local-only fixture builds since it doesn't have a network fetcher to wire in.
`handle_build` (and its 8 helpers: `compile_leo_source_directory`, `parse_leo_source_directory`, `parse_leo_source_directory_library`, `build_leo_source_directory_library`, `validate_compiled_programs`, `validate_compiled_programs_inner`, `write_interface_abis`, `ensure_parent_dir`, `remove_legacy_build_artifacts`) moves out of `crates/leo/src/cli/commands/build.rs` and into `crates/leo-cli-core/src/commands/build.rs`. The two `DisassembleProcess` / `ProgramForValidation` helper types come with it. Also moves: - `crates/leo/src/errors.rs` → `crates/leo-cli-core/src/errors.rs`, with every `pub(crate)` flipped to `pub`. `crates/leo` re-exports as `pub(crate) use leo_cli_core::errors;` so existing `crate::errors::*` callsites keep working. - `LOCAL_PROGRAM_DEFAULT_EDITION` and `format_program_size` from `crates/leo/src/cli/commands/common/util.rs` into `crates/leo-cli-core::commands` so the shared `handle_build` can reach them without depending on the CLI binary. The `LeoBuild` clap struct in `crates/leo` is now a thin wrapper: it resolves `--network` / `--endpoint` defaults and calls `leo_cli_core::commands::build::handle_build(&self.options, network, &endpoint, ..., &package_path, &home_path)`. `leo-cli-core` Cargo.toml gains `leo-abi` and `itertools` (used by the moved code). Snarkvm/tracing/ureq were already there.
…mmands
`handle_run` and `handle_test` (with `discover_test_functions`) move
from `crates/leo/src/cli/commands/{run,test}.rs` to
`crates/leo-cli-core/src/commands/{run,test}.rs`. The CLI's `LeoRun`
and `LeoTest` clap structs are now thin wrappers — they collect flags,
trigger `LeoBuild` for prelude, then call into core.
Supporting moves (also out of `crates/leo` into `leo-cli-core`):
- `parse_input` + `validate_cli_literal` + helpers (from `commands/mod.rs`).
- `print_program_source`, `check_edition_constructor_requirements`,
`load_extra_programs_into_vm` (from `common/util.rs`).
- `load_latest_programs_from_network` (from `common/query.rs`); the CLI
copy is now a thin wrapper that converts `&Context` → `&Path`.
- `get_endpoint`, `get_network`, `get_private_key`, `get_is_devnet`
(from `common/options.rs`).
- `RunOutput`, `TestOutput`, `TestResult` (from `common/output.rs`) —
`LeoRun::Output = RunOutput` / `LeoTest::Output = TestOutput` paths
keep working via re-export.
`handle_run` takes a `RunArgs<'_>` value (rather than `&LeoRun`) so it
doesn't depend on the clap struct. `handle_test` takes
`(Package, &str, NetworkName, bool)`.
`leo-cli-core` Cargo.toml gains `aleo-std-storage`, `colored`, `rand`
as native-only deps.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on top of #29419.
crates/leo-wasm/:wasm32-unknown-unknownentry-point crate exposingcompile_leo,run_leo_tests, andevaluate_aleoto the browser playground viawasm-bindgen.crates/parser/src/snarkvm_wasm.rs,crates/passes/src/snarkvm_wasm.rs: WASM-friendly shims that stub out native-only snarkVM types soleo-parserandleo-passescompile underwasm32-unknown-unknown.snarkvm-*sub-crates (circuit,ledger-block,ledger-store,synthesizer-process) at the same rev as the mainsnarkvmdep, withwasmfeatures enabled..cargo/config.tomlfor WASM target configuration andtree-sitter/package.jsonfor WASM grammar build.tree-sitter-leo.wasmgrammar used by the playground editor.