diff --git a/.claude/rules/xtask.md b/.claude/rules/xtask.md index 140e32c83..fb0803c3f 100644 --- a/.claude/rules/xtask.md +++ b/.claude/rules/xtask.md @@ -25,9 +25,10 @@ paths: ## Dev tool version pinning -Dev tools whose versions must match Cargo.lock (e.g., `wasm-bindgen-cli`) are installed -via `cargo xtask dev-setup`, which reads the locked version automatically. Never hardcode -these versions in CI workflows or documentation — always use `cargo xtask dev-setup`. +Dev tools whose versions must match Cargo.lock (e.g., `wasm-bindgen-cli`) are pinned in +`crates/xtask/src/dev_setup.rs`. When updating `wasm-bindgen` in Cargo.lock, also update +the pinned version in `dev_setup.rs` to match. CI workflows extract the version directly +from `crates/wasm-quarto-hub-client/Cargo.lock` to avoid duplication. ## Adding a new subcommand diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml deleted file mode 100644 index 95f1af7b7..000000000 --- a/.github/workflows/build-wasm.yml +++ /dev/null @@ -1,58 +0,0 @@ -# Tests a broad set of Quarto functionality that users are likely to encounter. -# A failure indicates some signficant portion of functionality is likely to be broken. -name: Build WASM Artifacts -on: workflow_dispatch -jobs: - build-wasm: - runs-on: ubuntu-latest - - name: Build WASM Artifacts - - steps: - - uses: actions/checkout@v6 - - name: Set up Rust nightly - uses: dtolnay/rust-toolchain@nightly - - - name: Set up build tools - if: runner.os == 'linux' - shell: bash - run: | - sudo apt-get update - sudo apt-get install -y build-essential curl file git - - - name: Set up Clang - uses: egor-tensin/setup-clang@v2 - with: - version: latest - platform: x64 - - - name: Setup wasm-pack - shell: bash - run: | - cargo install wasm-pack - - # tree-sitter setup - - name: Set up tree-sitter CLI (Linux) - if: runner.os == 'Linux' - run: | - sudo apt-get install libc6-dev - sudo apt-get install gcc-multilib - curl -LO https://github.com/tree-sitter/tree-sitter/releases/download/v0.25.8/tree-sitter-linux-x86.gz - gunzip tree-sitter-linux-x86.gz - chmod +x tree-sitter-linux-x86 - sudo mv tree-sitter-linux-x86 /usr/local/bin/tree-sitter - - # build and run tests - - name: Build - run: | - cd crates/wasm-qmd-parser - export CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin -DHAVE_ENDIAN_H" - wasm-pack build --target web --dev - shell: bash - - # upload artifacts - - name: Upload WASM Artifacts - uses: actions/upload-artifact@v7 - with: - name: wasm-qmd-parser - path: crates/wasm-qmd-parser/pkg \ No newline at end of file diff --git a/.github/workflows/hub-client-e2e.yml b/.github/workflows/hub-client-e2e.yml index d8ef6103b..b793c3544 100644 --- a/.github/workflows/hub-client-e2e.yml +++ b/.github/workflows/hub-client-e2e.yml @@ -32,11 +32,9 @@ jobs: [ "$time" != "@0" ] && touch -d "$time" "$file" 2>/dev/null || true done - # Rust toolchain for WASM build - - name: Set up Rust nightly - uses: dtolnay/rust-toolchain@nightly - with: - targets: wasm32-unknown-unknown + # Install toolchain from rust-toolchain.toml (nightly + components + targets) + - name: Set up Rust + run: rustup show active-toolchain - name: Set up Clang uses: egor-tensin/setup-clang@v2 @@ -48,9 +46,6 @@ jobs: with: cache-on-failure: true - - name: Install wasm-pack - run: cargo install wasm-pack - # tree-sitter for grammar builds - name: Set up tree-sitter CLI run: | @@ -72,6 +67,19 @@ jobs: - name: Build TypeScript packages run: npm run build + # wasm-bindgen-cli must match the wasm-bindgen version in Cargo.lock + # Use pre-built binary via taiki-e/install-action (faster, avoids transitive dep issues) + - name: Detect wasm-bindgen version + id: wb-version + run: | + VERSION=$(grep -A1 'name = "wasm-bindgen"' crates/wasm-quarto-hub-client/Cargo.lock | grep version | head -1 | sed 's/.*"\(.*\)"/\1/') + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Install wasm-bindgen-cli + uses: taiki-e/install-action@v2 + with: + tool: wasm-bindgen-cli@${{ steps.wb-version.outputs.version }} + # Build WASM module - name: Build WASM run: | diff --git a/.github/workflows/test-suite.yml b/.github/workflows/test-suite.yml index 4184d6506..5d04f3001 100644 --- a/.github/workflows/test-suite.yml +++ b/.github/workflows/test-suite.yml @@ -46,13 +46,9 @@ jobs: id: set-up-homebrew uses: Homebrew/actions/setup-homebrew@main - # Consistent Rust setup for both platforms - - name: Set up Rust nightly - uses: dtolnay/rust-toolchain@nightly - - - name: Output rust version - shell: bash - run: rustup --version + # Install toolchain from rust-toolchain.toml (nightly + components + targets) + - name: Set up Rust + run: rustup show active-toolchain # Cache Rust AFTER toolchain is set up - name: Cache Rust dependencies diff --git a/.github/workflows/ts-test-suite.yml b/.github/workflows/ts-test-suite.yml index 0acb179ab..2df5437a9 100644 --- a/.github/workflows/ts-test-suite.yml +++ b/.github/workflows/ts-test-suite.yml @@ -46,13 +46,9 @@ jobs: id: set-up-homebrew uses: Homebrew/actions/setup-homebrew@main - # Consistent Rust setup for both platforms - - name: Set up Rust nightly - uses: dtolnay/rust-toolchain@nightly - - - name: Output rust version - shell: bash - run: rustup --version + # Install toolchain from rust-toolchain.toml (nightly + components + targets) + - name: Set up Rust + run: rustup show active-toolchain # # Cache Rust AFTER toolchain is set up # - name: Cache Rust dependencies @@ -123,9 +119,19 @@ jobs: run: brew install llvm shell: bash - - name: Install wasm-bindgen-cli + # wasm-bindgen-cli must match the wasm-bindgen version in Cargo.lock + # Use pre-built binary via taiki-e/install-action (faster, avoids transitive dep issues) + - name: Detect wasm-bindgen version + id: wb-version shell: bash - run: cargo install wasm-bindgen-cli --version 0.2.108 + run: | + VERSION=$(grep -A1 'name = "wasm-bindgen"' crates/wasm-quarto-hub-client/Cargo.lock | grep version | head -1 | sed 's/.*"\(.*\)"/\1/') + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + - name: Install wasm-bindgen-cli + uses: taiki-e/install-action@v2 + with: + tool: wasm-bindgen-cli@${{ steps.wb-version.outputs.version }} - name: Build WASM module shell: bash diff --git a/CLAUDE.md b/CLAUDE.md index 751a2d2ce..39e8571d9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -271,11 +271,12 @@ When fixing ANY bug: - `quarto-treesitter-ast`: generic tree-sitter AST traversal utilities **WASM:** -- `wasm-qmd-parser`: WASM module with entry points from `pampa` (see [crates/wasm-qmd-parser/CLAUDE.md](crates/wasm-qmd-parser/CLAUDE.md) for build instructions) +- `wasm-quarto-hub-client`: WASM client for hub-client (see [crates/wasm-quarto-hub-client/README.md](crates/wasm-quarto-hub-client/README.md) for build instructions) +- `wasm-qmd-parser`: lightweight parsing-only WASM wrapper around `pampa` (dormant — kept for future use) ### `hub-client/` - Quarto Hub web client -A React/TypeScript web application for collaborative editing of Quarto projects. Uses Automerge for real-time sync and the WASM build of `wasm-qmd-parser` for live preview rendering. +A React/TypeScript web application for collaborative editing of Quarto projects. Uses Automerge for real-time sync and the WASM build of `wasm-quarto-hub-client` for live preview rendering. **Key directories:** - `src/components/` - React components (Editor, FileSidebar, tabs, etc.) @@ -309,7 +310,8 @@ All VFS file paths use the `/project/` prefix. When resolving file paths in WASM - `pampa` is the core Quarto engine crate - `quarto-core` handles higher-level orchestration -- `wasm-quarto-hub-client` is the WASM client (NOT wasm-qmd-parser) +- `wasm-quarto-hub-client` is the active WASM client for hub-client +- `wasm-qmd-parser` is dormant — a lightweight parsing-only WASM wrapper kept for future use - Always check `git diff` for uncommitted changes before starting work on a continuation session ## hub-client Commit Instructions diff --git a/Cargo.toml b/Cargo.toml index f7e223eed..7ad82a3bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ ] default-members = ["crates/*", "crates/experiments/reconcile-viewer"] # Excluded crates require special build toolchains or targets: -# - WASM crates: build with wasm-pack or --target wasm32-unknown-unknown +# - WASM crates: require --target wasm32-unknown-unknown and -Zbuild-std (see dev-docs/wasm.md) # - pampa/fuzz: requires nightly Rust + libfuzzer-sys (Linux/macOS only), run via `cargo fuzz` # crates/experiments is a container directory, not a crate, so we exclude it from crates/* matching. exclude = [ @@ -97,9 +97,6 @@ path = "./crates/tree-sitter-qmd" [workspace.dependencies.tree-sitter-sexpr] path = "./crates/tree-sitter-sexpr" -[workspace.dependencies.wasm-qmd-parser] -path = "./crates/wasm-qmd-parser" - [workspace.dependencies.pampa] path = "./crates/pampa" default-features = false @@ -273,7 +270,7 @@ lua-src = { path = "crates/lua-src-wasm" } # Profiles must be set at the workspace level [profile.dev] # Tell `rustc` to optimize for small code size to -# work around "too many locals" error from wasm-pack +# work around "too many locals" error in WASM builds # https://github.com/wasm-bindgen/wasm-bindgen/issues/3451#issuecomment-1562982835 opt-level = "s" diff --git a/crates/wasm-qmd-parser/.appveyor.yml b/crates/wasm-qmd-parser/.appveyor.yml deleted file mode 100644 index 50910bd6f..000000000 --- a/crates/wasm-qmd-parser/.appveyor.yml +++ /dev/null @@ -1,11 +0,0 @@ -install: - - appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - - if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly - - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin - - rustc -V - - cargo -V - -build: false - -test_script: - - cargo test --locked diff --git a/crates/wasm-qmd-parser/.cargo/config.toml b/crates/wasm-qmd-parser/.cargo/config.toml deleted file mode 100644 index 1deefb166..000000000 --- a/crates/wasm-qmd-parser/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.wasm32-unknown-unknown] -rustflags = ["-C", "target-feature=+bulk-memory", "-Zwasm-c-abi=spec"] \ No newline at end of file diff --git a/crates/wasm-qmd-parser/.github/dependabot.yml b/crates/wasm-qmd-parser/.github/dependabot.yml deleted file mode 100644 index 7377d3759..000000000 --- a/crates/wasm-qmd-parser/.github/dependabot.yml +++ /dev/null @@ -1,8 +0,0 @@ -version: 2 -updates: -- package-ecosystem: cargo - directory: "/" - schedule: - interval: daily - time: "08:00" - open-pull-requests-limit: 10 diff --git a/crates/wasm-qmd-parser/.travis.yml b/crates/wasm-qmd-parser/.travis.yml deleted file mode 100644 index 7a913256e..000000000 --- a/crates/wasm-qmd-parser/.travis.yml +++ /dev/null @@ -1,69 +0,0 @@ -language: rust -sudo: false - -cache: cargo - -matrix: - include: - - # Builds with wasm-pack. - - rust: beta - env: RUST_BACKTRACE=1 - addons: - firefox: latest - chrome: stable - before_script: - - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) - - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) - - cargo install-update -a - - curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f - script: - - cargo generate --git . --name testing - # Having a broken Cargo.toml (in that it has curlies in fields) anywhere - # in any of our parent dirs is problematic. - - mv Cargo.toml Cargo.toml.tmpl - - cd testing - - wasm-pack build - - wasm-pack test --chrome --firefox --headless - - # Builds on nightly. - - rust: nightly - env: RUST_BACKTRACE=1 - before_script: - - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) - - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) - - cargo install-update -a - - rustup target add wasm32-unknown-unknown - script: - - cargo generate --git . --name testing - - mv Cargo.toml Cargo.toml.tmpl - - cd testing - - cargo check - - cargo check --target wasm32-unknown-unknown - - cargo check --no-default-features - - cargo check --target wasm32-unknown-unknown --no-default-features - - cargo check --no-default-features --features console_error_panic_hook - - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook - - cargo check --no-default-features --features "console_error_panic_hook wee_alloc" - - cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc" - - # Builds on beta. - - rust: beta - env: RUST_BACKTRACE=1 - before_script: - - (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update) - - (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate) - - cargo install-update -a - - rustup target add wasm32-unknown-unknown - script: - - cargo generate --git . --name testing - - mv Cargo.toml Cargo.toml.tmpl - - cd testing - - cargo check - - cargo check --target wasm32-unknown-unknown - - cargo check --no-default-features - - cargo check --target wasm32-unknown-unknown --no-default-features - - cargo check --no-default-features --features console_error_panic_hook - - cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook - # Note: no enabling the `wee_alloc` feature here because it requires - # nightly for now. diff --git a/crates/wasm-qmd-parser/CLAUDE.md b/crates/wasm-qmd-parser/CLAUDE.md deleted file mode 100644 index 34fbc2211..000000000 --- a/crates/wasm-qmd-parser/CLAUDE.md +++ /dev/null @@ -1,32 +0,0 @@ -# wasm-qmd-parser - -WASM build of the `pampa` qmd parser for use in browser environments. - -## Important: Excluded from Workspace - -This crate is **excluded from the default workspace build** because it cannot be compiled as a native shared library. The dependency chain pulls in V8 (via deno_core) which uses thread-local storage incompatible with native cdylib builds. - -Build this crate explicitly using wasm-pack as described below. - -## Build Instructions - -```bash -cd crates/wasm-qmd-parser - -# macOS only: Use Homebrew LLVM (Apple Clang doesn't support wasm32-unknown-unknown) -# Requires: brew install llvm -export PATH="/opt/homebrew/opt/llvm/bin:$PATH" - -# Set C flags for tree-sitter WASM compilation -# - Include our C shims from wasm-sysroot -# - Define HAVE_ENDIAN_H for tree-sitter's endian detection -export CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin -DHAVE_ENDIAN_H" - -# Build with wasm-pack -# Note: Requires opt-level = "s" in workspace profile.dev to avoid "too many locals" error -wasm-pack build --target web --dev -``` - -## Output - -The built package is output to `pkg/` and can be used directly in web applications or published to npm. diff --git a/crates/wasm-qmd-parser/README.md b/crates/wasm-qmd-parser/README.md index 6b6840850..f6a1f785e 100644 --- a/crates/wasm-qmd-parser/README.md +++ b/crates/wasm-qmd-parser/README.md @@ -1,84 +1,26 @@ -
+# wasm-qmd-parser -

wasm-pack-template

+> **Status: Dormant** — This crate is not actively maintained but kept for +> future use as a lightweight parsing-only WASM module. - A template for kick starting a Rust and WebAssembly project using wasm-pack. +Lightweight WASM wrapper around `pampa` for QMD parsing in browser/extension +contexts. Unlike `wasm-quarto-hub-client` (which bundles the full rendering +stack), this crate exposes only the parser — smaller output, faster load times. -

- Build Status -

+## History -

- Tutorial - | - Chat -

+Originally built with `wasm-pack` and its own C stdlib shim. The C shim now +lives in `wasm-quarto-hub-client/src/c_shim.rs`, and the build toolchain has +shifted to `cargo build` + `wasm-bindgen` CLI (see `dev-docs/wasm.md`). - Built with 🦀🕸 by The Rust and WebAssembly Working Group -
- -## About - -[**📚 Read this template tutorial! 📚**][template-docs] - -This template is designed for compiling Rust libraries into WebAssembly and -publishing the resulting package to NPM. - -Be sure to check out [other `wasm-pack` tutorials online][tutorials] for other -templates and usages of `wasm-pack`. - -[tutorials]: https://rustwasm.github.io/docs/wasm-pack/tutorials/index.html -[template-docs]: https://rustwasm.github.io/docs/wasm-pack/tutorials/npm-browser-packages/index.html - -## 🚴 Usage - -### 🐑 Use `cargo generate` to Clone this Template - -[Learn more about `cargo generate` here.](https://github.com/ashleygwilliams/cargo-generate) - -``` -cargo generate --git https://github.com/rustwasm/wasm-pack-template.git --name my-project -cd my-project -``` - -### 🛠️ Build with `wasm-pack build` - -``` -wasm-pack build -``` - -### 🔬 Test in Headless Browsers with `wasm-pack test` - -``` -wasm-pack test --headless --firefox -``` - -### 🎁 Publish to NPM with `wasm-pack publish` - -``` -wasm-pack publish -``` - -## 🔋 Batteries Included - -* [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) for communicating - between WebAssembly and JavaScript. -* [`console_error_panic_hook`](https://github.com/rustwasm/console_error_panic_hook) - for logging panic messages to the developer console. -* `LICENSE-APACHE` and `LICENSE-MIT`: most Rust projects are licensed this way, so these are included for you +The crate will need updates before it can build again — the `pampa` API has +evolved significantly since this was last maintained. ## License Licensed under either of -* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) -* MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +* Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE_APACHE)) +* MIT license ([LICENSE-MIT](LICENSE_MIT)) at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or -conditions. diff --git a/crates/wasm-qmd-parser/build.md b/crates/wasm-qmd-parser/build.md deleted file mode 100644 index 567e8b28b..000000000 --- a/crates/wasm-qmd-parser/build.md +++ /dev/null @@ -1,24 +0,0 @@ - -```shell -cd crates/wasm-qmd-parser -# To work around this error, because Apple Clang doesn't work with wasm32-unknown-unknown? -# I believe this is not required on a Linux machine. -# Requires `brew install llvm`. -# https://github.com/briansmith/ring/issues/1824 -# error: unable to create target: 'No available targets are compatible with triple "wasm32-unknown-unknown"' -export PATH="/opt/homebrew/opt/llvm/bin:$PATH" -# To tell rustc to include our C shims located in `wasm-sysroot`, which we eventually compile into the project -# with `c_shim.rs`. -# https://github.com/tree-sitter/tree-sitter/discussions/1550#discussioncomment-8445285 -# -# It also seems like we need to define HAVE_ENDIAN_H to tell tree-sitter we have `endian.h` -# as it doesn't seem to pick up on that automatically? -# https://github.com/tree-sitter/tree-sitter/blob/0be215e152d58351d2691484b4398ceff041f2fb/lib/src/portable/endian.h#L18 -export CFLAGS_wasm32_unknown_unknown="-I$(pwd)/wasm-sysroot -Wbad-function-cast -Wcast-function-type -fno-builtin -DHAVE_ENDIAN_H" -# To just build the wasm-qmd-parser crate -# cargo build --target wasm32-unknown-unknown -# To build the wasm-pack bundle -# Note that you'll need `opt-level = "s"` in your `profile.dev` cargo profile -# otherwise you can get a "too many locals" error. -wasm-pack build --target web --dev -``` \ No newline at end of file diff --git a/crates/wasm-qmd-parser/index.html b/crates/wasm-qmd-parser/index.html deleted file mode 100644 index c0cfb8e05..000000000 --- a/crates/wasm-qmd-parser/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - wasm-qmd-parser - - - - - - \ No newline at end of file diff --git a/crates/wasm-qmd-parser/pkg/index.html b/crates/wasm-qmd-parser/pkg/index.html deleted file mode 100644 index 58070d9c9..000000000 --- a/crates/wasm-qmd-parser/pkg/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - wasm-qmd-parser - - - - - - \ No newline at end of file diff --git a/crates/wasm-qmd-parser/src/c_shim.rs b/crates/wasm-qmd-parser/src/c_shim.rs deleted file mode 100644 index b27a56e1c..000000000 --- a/crates/wasm-qmd-parser/src/c_shim.rs +++ /dev/null @@ -1,242 +0,0 @@ -/* - * c_shim.rs - * Copyright (c) 2025 Posit, PBC - */ - -use std::{ - alloc::{self, Layout}, - ffi::{c_char, c_int, c_void}, - mem::align_of, - ptr, -}; - -/* -------------------------------- stdlib.h -------------------------------- */ - -#[no_mangle] -pub unsafe extern "C" fn abort() { - panic!("Aborted from C"); -} - -#[no_mangle] -pub unsafe extern "C" fn malloc(size: usize) -> *mut c_void { - if size == 0 { - return ptr::null_mut(); - } - - let (layout, offset_to_data) = layout_for_size_prepended(size); - let buf = alloc::alloc(layout); - store_layout(buf, layout, offset_to_data) -} - -#[no_mangle] -pub unsafe extern "C" fn calloc(count: usize, size: usize) -> *mut c_void { - if count == 0 || size == 0 { - return ptr::null_mut(); - } - - let (layout, offset_to_data) = layout_for_size_prepended(size * count); - let buf = alloc::alloc_zeroed(layout); - store_layout(buf, layout, offset_to_data) -} - -#[no_mangle] -pub unsafe extern "C" fn realloc(buf: *mut c_void, new_size: usize) -> *mut c_void { - if buf.is_null() { - malloc(new_size) - } else if new_size == 0 { - free(buf); - ptr::null_mut() - } else { - let (old_buf, old_layout) = retrieve_layout(buf); - let (new_layout, offset_to_data) = layout_for_size_prepended(new_size); - let new_buf = alloc::realloc(old_buf, old_layout, new_layout.size()); - store_layout(new_buf, new_layout, offset_to_data) - } -} - -#[no_mangle] -pub unsafe extern "C" fn free(buf: *mut c_void) { - if buf.is_null() { - return; - } - let (buf, layout) = retrieve_layout(buf); - alloc::dealloc(buf, layout); -} - -// In all these allocations, we store the layout before the data for later retrieval. -// This is because we need to know the layout when deallocating the memory. -// Here are some helper methods for that: - -/// Given a pointer to the data, retrieve the layout and the pointer to the layout. -unsafe fn retrieve_layout(buf: *mut c_void) -> (*mut u8, Layout) { - let (_, layout_offset) = Layout::new::() - .extend(Layout::from_size_align(0, align_of::<*const u8>() * 2).unwrap()) - .unwrap(); - - let buf = (buf as *mut u8).offset(-(layout_offset as isize)); - let layout = *(buf as *mut Layout); - - (buf, layout) -} - -/// Calculate a layout for a given size with space for storing a layout at the start. -/// Returns the layout and the offset to the data. -fn layout_for_size_prepended(size: usize) -> (Layout, usize) { - Layout::new::() - .extend(Layout::from_size_align(size, align_of::<*const u8>() * 2).unwrap()) - .unwrap() -} - -/// Store a layout in the pointer, returning a pointer to where the data should be stored. -unsafe fn store_layout(buf: *mut u8, layout: Layout, offset_to_data: usize) -> *mut c_void { - *(buf as *mut Layout) = layout; - (buf as *mut u8).offset(offset_to_data as isize) as *mut c_void -} - -/* -------------------------------- string.h -------------------------------- */ - -#[no_mangle] -pub unsafe extern "C" fn memcpy(dest: *mut c_void, src: *const c_void, size: usize) -> *mut c_void { - std::ptr::copy_nonoverlapping(src, dest, size); - dest -} - -#[no_mangle] -pub unsafe extern "C" fn memmove( - dest: *mut c_void, - src: *const c_void, - size: usize, -) -> *mut c_void { - std::ptr::copy(src, dest, size); - dest -} - -#[no_mangle] -pub unsafe extern "C" fn memset(s: *mut c_void, c: i32, n: usize) -> *mut c_void { - let slice = std::slice::from_raw_parts_mut(s as *mut u8, n); - slice.fill(c as u8); - s -} - -#[no_mangle] -pub unsafe extern "C" fn memcmp(ptr1: *const c_void, ptr2: *const c_void, n: usize) -> c_int { - let s1 = std::slice::from_raw_parts(ptr1 as *const u8, n); - let s2 = std::slice::from_raw_parts(ptr2 as *const u8, n); - - for (a, b) in s1.iter().zip(s2.iter()) { - if *a != *b { - return (*a as i32) - (*b as i32); - } - } - - 0 -} - -#[no_mangle] -pub unsafe extern "C" fn strncmp(ptr1: *const c_void, ptr2: *const c_void, n: usize) -> c_int { - let s1 = std::slice::from_raw_parts(ptr1 as *const u8, n); - let s2 = std::slice::from_raw_parts(ptr2 as *const u8, n); - - for (a, b) in s1.iter().zip(s2.iter()) { - if *a != *b || *a == 0 { - return (*a as i32) - (*b as i32); - } - } - - 0 -} - -/* -------------------------------- wctype.h -------------------------------- */ - -#[no_mangle] -pub unsafe extern "C" fn iswspace(c: c_int) -> bool { - char::from_u32(c as u32).map_or(false, |c| c.is_whitespace()) -} - -#[no_mangle] -pub unsafe extern "C" fn iswalnum(c: c_int) -> bool { - char::from_u32(c as u32).map_or(false, |c| c.is_alphanumeric()) -} - -#[no_mangle] -pub unsafe extern "C" fn iswdigit(c: c_int) -> bool { - char::from_u32(c as u32).map_or(false, |c| c.is_digit(10)) -} - -#[no_mangle] -pub unsafe extern "C" fn iswalpha(c: c_int) -> bool { - char::from_u32(c as u32).map_or(false, |c| c.is_alphabetic()) -} - -// Note: Not provided by https://github.com/cacticouncil/lilypad, but we needed -// this one too. We could contribute this back upstream? Note that -// `towlower()`'s C function docs say it is only guaranteed to work in 1:1 -// mapping cases, so that is what we reimplement here as well. -// https://en.cppreference.com/w/c/string/wide/towlower -#[no_mangle] -pub unsafe extern "C" fn towlower(c: c_int) -> c_int { - char::from_u32(c as u32).map_or(0, |c| { - c.to_lowercase().next().map(|c| c as i32).unwrap_or(0) - }) -} - -/* --------------------------------- time.h --------------------------------- */ - -#[no_mangle] -pub unsafe extern "C" fn clock() -> u64 { - panic!("clock is not supported"); -} - -/* --------------------------------- ctype.h -------------------------------- */ - -#[no_mangle] -pub unsafe extern "C" fn isprint(c: c_int) -> bool { - c >= 32 && c <= 126 -} - -/* --------------------------------- stdio.h -------------------------------- */ - -#[no_mangle] -pub unsafe extern "C" fn fprintf(_file: *mut c_void, _format: *const c_void, _args: ...) -> c_int { - panic!("fprintf is not supported"); -} - -#[no_mangle] -pub unsafe extern "C" fn fputs(_s: *const c_void, _file: *mut c_void) -> c_int { - panic!("fputs is not supported"); -} - -#[no_mangle] -pub unsafe extern "C" fn fputc(_c: c_int, _file: *mut c_void) -> c_int { - panic!("fputc is not supported"); -} - -#[no_mangle] -pub unsafe extern "C" fn fdopen(_fd: c_int, _mode: *const c_void) -> *mut c_void { - panic!("fdopen is not supported"); -} - -#[no_mangle] -pub unsafe extern "C" fn fclose(_file: *mut c_void) -> c_int { - panic!("fclose is not supported"); -} - -#[no_mangle] -pub unsafe extern "C" fn fwrite( - _ptr: *const c_void, - _size: usize, - _nmemb: usize, - _stream: *mut c_void, -) -> usize { - panic!("fwrite is not supported"); -} - -#[no_mangle] -pub unsafe extern "C" fn vsnprintf( - _buf: *mut c_char, - _size: usize, - _format: *const c_char, - _args: ... -) -> c_int { - panic!("vsnprintf is not supported"); -} diff --git a/crates/wasm-qmd-parser/src/lib.rs b/crates/wasm-qmd-parser/src/lib.rs index 26d119d4a..75bee3518 100644 --- a/crates/wasm-qmd-parser/src/lib.rs +++ b/crates/wasm-qmd-parser/src/lib.rs @@ -1,19 +1,14 @@ /* * lib.rs * Copyright (c) 2025 Posit, PBC + * + * DORMANT: This crate does not currently build. The pampa API has evolved + * since it was last maintained. See README.md for context. */ -// For `vsnprintf()` and `fprintf()`, which are variadic. -// Otherwise rustc yells at us that we need to enable this. -#![feature(c_variadic)] - -// Provide rust implementation of blessed stdlib functions to -// tree-sitter itself and any grammars that have `scanner.c`. -// Here is the list blessed for `scanner.c` usage: -// https://github.com/tree-sitter/tree-sitter/blob/master/lib/src/wasm/stdlib-symbols.txt -// But note that we need a few extra for tree-sitter itself. -#[cfg(target_arch = "wasm32")] -pub mod c_shim; +// C stdlib stubs for wasm32 live in wasm-quarto-hub-client/src/c_shim.rs. +// When this crate is revived, extract the c_shim into a shared crate or +// copy it here. mod utils; diff --git a/crates/wasm-qmd-parser/wasm-sysroot/assert.h b/crates/wasm-qmd-parser/wasm-sysroot/assert.h deleted file mode 100644 index 8a17e8dc6..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/assert.h +++ /dev/null @@ -1,4 +0,0 @@ -#pragma once - -#define assert(ignore) ((void)0) -#define static_assert(cnd, msg) assert(cnd && msg) diff --git a/crates/wasm-qmd-parser/wasm-sysroot/ctype.h b/crates/wasm-qmd-parser/wasm-sysroot/ctype.h deleted file mode 100644 index 14175419a..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/ctype.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -int isprint(int c); diff --git a/crates/wasm-qmd-parser/wasm-sysroot/endian.h b/crates/wasm-qmd-parser/wasm-sysroot/endian.h deleted file mode 100644 index 0d6260fcd..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/endian.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include - -#define TS_LITTLE_ENDIAN 1 - -static inline uint16_t le16toh(uint16_t x) { return x; } -static inline uint16_t be16toh(uint16_t x) -{ -#if defined(__GNUC__) || defined(__clang__) - return __builtin_bswap16(x); -#else - return (x << 8) | (x >> 8); -#endif -} \ No newline at end of file diff --git a/crates/wasm-qmd-parser/wasm-sysroot/inttypes.h b/crates/wasm-qmd-parser/wasm-sysroot/inttypes.h deleted file mode 100644 index cfc0873a8..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/inttypes.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -#define PRId32 "d" diff --git a/crates/wasm-qmd-parser/wasm-sysroot/stdbool.h b/crates/wasm-qmd-parser/wasm-sysroot/stdbool.h deleted file mode 100644 index 97287ba6e..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/stdbool.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -#define bool _Bool -#define true 1 -#define false 0 diff --git a/crates/wasm-qmd-parser/wasm-sysroot/stdint.h b/crates/wasm-qmd-parser/wasm-sysroot/stdint.h deleted file mode 100644 index d575a081b..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/stdint.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -typedef signed char int8_t; -typedef short int16_t; -typedef long int32_t; -typedef long long int64_t; - -typedef unsigned char uint8_t; -typedef unsigned short uint16_t; -typedef unsigned long uint32_t; -typedef unsigned long long uint64_t; - -typedef unsigned long size_t; - -typedef unsigned int uintptr_t; - -#define UINT8_MAX 0xff -#define UINT16_MAX 0xffff -#define UINT32_MAX 0xffffffff diff --git a/crates/wasm-qmd-parser/wasm-sysroot/stdio.h b/crates/wasm-qmd-parser/wasm-sysroot/stdio.h deleted file mode 100644 index 6e19a95aa..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/stdio.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -// just some filler type -#define FILE void - -#define stdin NULL -#define stdout NULL -#define stderr NULL - -int fprintf(FILE *__restrict__, const char *__restrict__, ...); -int fputs(const char *__restrict, FILE *__restrict); -int fputc(int, FILE *); -FILE *fdopen(int, const char *); -int fclose(FILE *); - -int vsnprintf(char *s, unsigned long n, const char *format, ...); - -#define sprintf(str, ...) 0 -#define snprintf(str, len, ...) 0 diff --git a/crates/wasm-qmd-parser/wasm-sysroot/stdlib.h b/crates/wasm-qmd-parser/wasm-sysroot/stdlib.h deleted file mode 100644 index 8c8df2a8f..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/stdlib.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once - -#include - -#define NULL ((void*)0) - -void* malloc(size_t size); -void* calloc(size_t nmemb, size_t size); -void free(void* ptr); -void* realloc(void* ptr, size_t size); - -void abort(void); diff --git a/crates/wasm-qmd-parser/wasm-sysroot/string.h b/crates/wasm-qmd-parser/wasm-sysroot/string.h deleted file mode 100644 index c0687e9c8..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/string.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -void *memcpy(void *dest, const void *src, unsigned long n); -void *memmove(void *dest, const void *src, unsigned long n); -void *memset(void *s, int c, unsigned long n); -int memcmp(const void *ptr1, const void *ptr2, unsigned long n); -int strncmp(const char *s1, const char *s2, unsigned long n); diff --git a/crates/wasm-qmd-parser/wasm-sysroot/time.h b/crates/wasm-qmd-parser/wasm-sysroot/time.h deleted file mode 100644 index 3a1583bda..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/time.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -typedef unsigned long clock_t; -#define CLOCKS_PER_SEC ((clock_t)1000000) -clock_t clock(void); diff --git a/crates/wasm-qmd-parser/wasm-sysroot/unistd.h b/crates/wasm-qmd-parser/wasm-sysroot/unistd.h deleted file mode 100644 index b3727dd3e..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/unistd.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -int dup(int); diff --git a/crates/wasm-qmd-parser/wasm-sysroot/wctype.h b/crates/wasm-qmd-parser/wasm-sysroot/wctype.h deleted file mode 100644 index 215910f77..000000000 --- a/crates/wasm-qmd-parser/wasm-sysroot/wctype.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -typedef __WCHAR_TYPE__ wchar_t; -typedef __WINT_TYPE__ wint_t; - -int iswspace(wchar_t ch); -int iswalnum(wint_t _wc); -int iswdigit(wint_t c); -int iswalpha(wint_t c); -wint_t towlower(wint_t wc); diff --git a/crates/wasm-quarto-hub-client/README.md b/crates/wasm-quarto-hub-client/README.md index e43dac81a..552717838 100644 --- a/crates/wasm-quarto-hub-client/README.md +++ b/crates/wasm-quarto-hub-client/README.md @@ -119,4 +119,4 @@ node hub-client/scripts/build-wasm.js ### Build Script -Always use the build script in `hub-client/scripts/build-wasm.js` rather than running `wasm-pack` directly. The script sets the required `CFLAGS_wasm32_unknown_unknown` environment variable to include the wasm-sysroot headers. +Always use the build script in `hub-client/scripts/build-wasm.js` rather than running `cargo build` directly. The script sets the required `CFLAGS_wasm32_unknown_unknown` environment variable to include the wasm-sysroot headers and runs `wasm-bindgen` post-processing. diff --git a/dev-docs/wasm.md b/dev-docs/wasm.md index 929a8c69c..8df55baf7 100644 --- a/dev-docs/wasm.md +++ b/dev-docs/wasm.md @@ -36,11 +36,9 @@ The wasm-sysroot at `crates/wasm-quarto-hub-client/wasm-sysroot/` provides minim headers. The `-fno-builtin` flag is needed because debug-mode builds emit `__builtin_*` intrinsic calls not present in the stub sysroot. -## Native vs WASM Testing +## wasm-qmd-parser (dormant) -Native tests (`cargo nextest run`) use `Lua::new()` with the full C stdlib on all platforms. -WASM-specific code paths use `#[cfg(target_arch = "wasm32")]` guards — never -`#[cfg(any(target_arch = "wasm32", test))]` (see `.claude/rules/wasm.md`). - -Hub-client integration tests (`npm run test:ci`) exercise the compiled WASM module through -the JavaScript API. +A lightweight WASM wrapper around `pampa` only, without the full `quarto-core` rendering +stack. Currently dormant — the crate skeleton is kept for future use as a smaller, faster +WASM module for contexts that only need parsing (not full document rendering). It will need +updates to match the current `pampa` API before it can build again. diff --git a/hub-client/README.md b/hub-client/README.md index 84121bb97..dbec79fc0 100644 --- a/hub-client/README.md +++ b/hub-client/README.md @@ -6,7 +6,7 @@ Web frontend for Quarto Hub - a collaborative document editor using Quarto's WAS - Node.js 18+ - Rust toolchain with `wasm32-unknown-unknown` target (`rustup target add wasm32-unknown-unknown`) -- `wasm-pack` (`cargo install wasm-pack`) +- `wasm-bindgen-cli` (run `cargo dev-setup` to install the correct version) - LLVM (macOS only: `brew install llvm`) ## Development