From 34ca011b9230428df9de628eacc26c186481d138 Mon Sep 17 00:00:00 2001 From: rvba Date: Tue, 1 Sep 2026 10:37:09 +0200 Subject: [PATCH] release: 0.0.1 --- .gitignore | 5 ++++ Cargo.toml | 22 +++++++++++++++++ LICENSE | 21 ++++++++++++++++ Makefile | 23 +++++++++++++++++ node/smoke.mjs | 60 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 198 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 node/smoke.mjs create mode 100644 src/lib.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c2c3de --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +/pkg +Cargo.lock +.claude/ +.opencode/ diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..23e1a28 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "bimr-wasm" +version = "0.0.1" +edition = "2024" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +eval = { git = "https://gitaec.org/rvba/bimr-engine.git" } +parser = { git = "https://gitaec.org/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."https://gitaec.org/rvba/bimr-engine.git"] +eval = { path = "../bimr-engine/eval" } +parser = { path = "../bimr-engine/parser" } + +[patch."https://gitaec.org/rvba/bimr-kernel.git"] +bimr-kernel = { path = "../bimr-kernel" } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d9e1af9 --- /dev/null +++ b/LICENSE @@ -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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bde4be5 --- /dev/null +++ b/Makefile @@ -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 diff --git a/node/smoke.mjs b/node/smoke.mjs new file mode 100644 index 0000000..ff942f8 --- /dev/null +++ b/node/smoke.mjs @@ -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); +}); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..f75f592 --- /dev/null +++ b/src/lib.rs @@ -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::parse(src).map_err(|errors| { + let msg = errors + .iter() + .map(|e| format!("{e:?}")) + .collect::>() + .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}"); + } +}