release: 0.0.1

This commit is contained in:
Milovann Yanatchkov 2026-08-31 10:12:17 +02:00
commit 0b9f5898c3
7 changed files with 229 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
/target
/pkg
Cargo.lock
.claude/
.opencode/

31
AGENTS.md Normal file
View file

@ -0,0 +1,31 @@
# AGENTS.md — bimr-wasm
WASM adapter for the [`bimr-engine`](../bimr-engine) eval — the
`compile()` surface consumed by bimr-web. Plan:
[`task/refactor/2026-08-13/white_wasm_surface.md`](../task/refactor/2026-08-13/white_wasm_surface.md).
## Commands
```bash
make test # native cargo test (wasm_bindgen fns are plain Rust)
make pkg # wasm32 build -> wasm-bindgen web pkg -> wasm-opt
make smoke # pkg + Node smoke test (node/smoke.mjs)
```
## Rules
- **Whole-source only** (D8, batch-DSL / Alternative A): the surface is
exactly **one export**, `compile(src)` — no session API, no per-call
construction, no side-query projections. Python emits `.bimr` text and
calls `compile()` once, the same path as the CLI. The incumbent session
bridge was retired with the classic stack (bimr-black).
- The adapter stays **thin**: no engine logic here — no geometry pre-pass
(meshes compute at serialize time), no lazy materialisation, no parameter
parsing. Everything goes through `eval`'s public surface.
- Errors return `{"error": "..."}` JSON strings.
- Dependencies on `eval`/`parser` are git pins on
`bimr-engine` with local `[patch]` overrides — same pattern as the
engine's kernel pin.
- `pkg/` and `target/` are build artifacts — never commit them.
*This document should be updated as the codebase evolves.*

22
Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "bimr-wasm"
version = "0.0.1"
edition = "2024"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
eval = { git = "ssh://git@git.rvba.fr:229/rvba/bimr-engine.git" }
parser = { git = "ssh://git@git.rvba.fr:229/rvba/bimr-engine.git" }
# Pinned to the factory's wasm-bindgen CLI (Makefile WASM_BINDGEN_VERSION).
wasm-bindgen = "=0.2.114"
# Local checkouts override the forge pins (same pattern as bimr-engine's
# kernel patch) so the factory builds against sibling repos.
[patch."ssh://git@git.rvba.fr:229/rvba/bimr-engine.git"]
eval = { path = "../bimr-engine/eval" }
parser = { path = "../bimr-engine/parser" }
[patch."ssh://git@git.rvba.fr:229/rvba/bimr-kernel.git"]
bimr-kernel = { path = "../bimr-kernel" }

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Milovann Yanatchkov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

23
Makefile Normal file
View file

@ -0,0 +1,23 @@
.PHONY: test pkg clean
# Native tests — wasm_bindgen fns are plain Rust; sessions work on the host.
test:
cargo test
# wasm-bindgen web pkg (consumed by the factory `build-wasm-white` and
# node/smoke.mjs). Pins mirror the factory: wasm-bindgen 0.2.114, Binaryen.
pkg:
rustup target add wasm32-unknown-unknown
cargo build --release --target wasm32-unknown-unknown
$(HOME)/.cargo/bin/wasm-bindgen --target web --out-dir pkg \
target/wasm32-unknown-unknown/release/bimr_wasm.wasm
command -v wasm-opt >/dev/null && \
wasm-opt -O pkg/bimr_wasm_bg.wasm -o pkg/bimr_wasm_bg.wasm || true
# Node smoke test over the web pkg.
smoke: pkg
node node/smoke.mjs
clean:
cargo clean
rm -rf pkg

60
node/smoke.mjs Normal file
View file

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: 2026 Milovann Yanatchkov
// SPDX-License-Identifier: MIT
// smoke.mjs — Node test of the wasm-bindgen web pkg (engine.test.mjs pattern
// from bimr-web). Run via `make smoke` after `make pkg`.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
function section(name) {
console.log(`\n# ${name}`);
}
async function loadWasm() {
const jsUrl = new URL("../pkg/bimr_wasm.js", import.meta.url).href;
const bimrWasm = await import(jsUrl);
const wasmBytes = readFileSync(new URL("../pkg/bimr_wasm_bg.wasm", import.meta.url));
await bimrWasm.default({ module_or_path: wasmBytes });
return bimrWasm;
}
const WALL_SRC = `p1 = Point(0,0,0)
p2 = Point(500,0,0)
l1 = Line(p1,p2)
w1 = Wall(l1,20,300)`;
async function main() {
const wasm = await loadWasm();
section("surface — whole-source only (D8)");
{
const exports = Object.keys(wasm).filter((k) => typeof wasm[k] === "function" && !k.startsWith("__"));
assert.deepEqual(
exports.sort(),
["compile", "default", "initSync"],
"no session exports, no side queries",
);
}
section("compile() — wall to IFCX");
{
const out = wasm.compile(WALL_SRC);
assert.match(out, /wall-001/);
assert.match(out, /ifcx_alpha/);
assert.match(out, /faceVertexIndices/);
}
section("compile() — error handling");
{
const out = wasm.compile("w1 = Wall(missing)");
assert.match(out, /error/);
}
console.log("\nAll smoke tests passed.");
}
main().catch((e) => {
console.error(e);
process.exit(1);
});

67
src/lib.rs Normal file
View file

@ -0,0 +1,67 @@
// SPDX-FileCopyrightText: 2026 Milovann Yanatchkov
// SPDX-License-Identifier: MIT
//! bimr-wasm — browser adapter for the [`bimr-engine`](https://git.rvba.fr/rvba/bimr-engine) eval.
//!
//! **Whole-source only**: the adapter exposes exactly one export —
//! `compile(src)`. There is no session layer, no per-call construction, and
//! no side queries: Python emits `.bimr` text and calls `compile()` once,
//! the same path as the CLI. The `.bimr` intermediate is the compatibility
//! layer; whole-source is the single entry point for the CLI, web Python,
//! and LLM paths.
//!
//! Errors return `{"error": "..."}` JSON strings.
use wasm_bindgen::prelude::*;
fn parse_or_error(src: &str) -> Result<parser::ast::Program, String> {
parser::parse(src).map_err(|errors| {
let msg = errors
.iter()
.map(|e| format!("{e:?}"))
.collect::<Vec<_>>()
.join("; ");
format!("{{\"error\": \"{}\"}}", msg.replace('"', "'"))
})
}
/// Compile a `.bimr` source string to IFCX JSON.
///
/// Returns a JSON string — either an IFCX document on success, or
/// `{"error": "..."}` on parse/eval failure.
#[wasm_bindgen]
pub fn compile(src: &str) -> String {
let program = match parse_or_error(src) {
Ok(p) => p,
Err(e) => return e,
};
match eval::eval_program(&program, "model.bimr") {
Ok(store) => eval::to_ifcx(&store),
Err(e) => format!("{{\"error\": \"{e}\"}}"),
}
}
#[cfg(test)]
mod tests {
use super::*;
const WALL_SRC: &str = "\
p1 = Point(0,0,0)
p2 = Point(500,0,0)
l1 = Line(p1,p2)
w1 = Wall(l1,20,300)";
#[test]
fn compile_wall_to_ifcx() {
let out = compile(WALL_SRC);
assert!(out.contains("wall-001"), "missing wall-001: {out}");
assert!(out.contains("ifcx_alpha"));
assert!(out.contains("faceVertexIndices"));
}
#[test]
fn compile_errors_are_error_json() {
let out = compile("w1 = Wall(missing)");
assert!(out.contains("error"), "expected error json: {out}");
}
}