Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,27 @@ jobs:
STACK_SPECIFICATION_DIR: ${{ github.workspace }}/.stack-specification
run: cargo +stable test -p stack-formatter --features conformance --test conformance --locked

- name: Run canonical layout snapshot
env:
STACK_SPECIFICATION_DIR: ${{ github.workspace }}/.stack-specification
run: cargo +stable test -p stack-engine --features conformance canonical_complete_semantics_matches_snapshot --locked

- name: Build pure engine for WebAssembly
run: |
rustup target add wasm32-unknown-unknown wasm32-wasip1 --toolchain stable
cargo +stable build -p stack-engine --target wasm32-unknown-unknown --locked

- name: Install WebAssembly test runtime
uses: taiki-e/install-action@e67fa11c4b9316fa714ddf0abed07a0c3143b95b # v2.87.4
with:
tool: wasmtime@48.0.1
fallback: none

- name: Verify native and WebAssembly geometry parity
env:
CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime
run: cargo +stable test -p stack-engine --lib --target wasm32-wasip1 geometry_matches_cross_target_numeric_fixture --locked

- name: Run Clippy
run: cargo +stable clippy --workspace --all-targets --all-features --locked -- -D warnings

Expand All @@ -60,6 +81,8 @@ jobs:
fallback: none

- name: Enforce unit-test coverage
env:
STACK_SPECIFICATION_DIR: ${{ github.workspace }}/.stack-specification
run: cargo +stable llvm-cov --lib --workspace --all-features --locked --fail-under-lines 95 --fail-under-functions 95 --fail-under-regions 95

msrv:
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

`stack-sh/engine` is the pure Rust execution engine for Stack architecture diagrams.

The workspace now provides canonical Stack source formatting and the pure `stack-engine` operation facade. Layout, theme resolution, SVG rendering, and WebAssembly adapters remain planned work.
The workspace now provides canonical Stack source formatting, the pure `stack-engine` operation facade, and deterministic theme-aware scene layout. SVG rendering and WebAssembly adapters remain planned work.

## Planned workspace

- `stack-engine`: implemented operation/output boundary plus planned theme resolution, deterministic layout, validation beyond the compiler stage, and standalone SVG rendering;
- `stack-engine`: implemented operation/output boundary, theme resolution, deterministic scene layout, and validation beyond the compiler stage, plus planned standalone SVG rendering;
- `stack-formatter`: comment-preserving canonical formatting for Stack source files (implemented);
- a WebAssembly adapter exposing the same pure operations to browser consumers.

Expand All @@ -25,19 +25,23 @@ The workspace uses Rust 2024 with Rust 1.85 as its minimum supported version. Ru
```sh
cargo test --workspace
STACK_SPECIFICATION_DIR=../specification cargo test -p stack-formatter --features conformance --test conformance
STACK_SPECIFICATION_DIR=../specification cargo test -p stack-engine --features conformance canonical_complete_semantics_matches_snapshot
cargo build -p stack-engine --target wasm32-unknown-unknown
CARGO_TARGET_WASM32_WASIP1_RUNNER=wasmtime cargo test -p stack-engine --lib --target wasm32-wasip1 geometry_matches_cross_target_numeric_fixture
cargo fmt --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo doc --workspace --no-deps
```

`stack-formatter` is pure and accepts source bytes or UTF-8 text. Lexical and syntax errors return diagnostics without formatted output. Syntactically valid source remains formattable when semantic diagnostics exist.

`stack-engine` exposes byte-oriented `format`, `check`, and reserved `render` methods through an engine bound to the embedded or a caller-provided validated catalog. Every normal output carries engine, authored language, theme catalog version, and theme catalog revision metadata. User-source failures stay in ordered portable diagnostics. Invalid provided catalogs and unavailable pipeline stages use a separate operational-error channel. Compiler-valid rendering remains unavailable until layout and SVG integration land.
`stack-engine` exposes byte-oriented `format`, `check`, and reserved `render` methods through an engine bound to the embedded or a caller-provided validated catalog. Every normal output carries engine, authored language, theme catalog version, and theme catalog revision metadata. User-source failures stay in ordered portable diagnostics. Invalid provided catalogs, invalid normalized containment, and unavailable pipeline stages use a separate operational-error channel. Checks and compiler-valid render attempts now resolve the requested theme and validate a deterministic integer scene; rendering remains unavailable until SVG integration lands. CI executes one exact numeric geometry fixture in both the native suite and a WASI build.

## Architecture

- [`docs/decisions/0001-build-the-formatter-from-compiler-models.md`](./docs/decisions/0001-build-the-formatter-from-compiler-models.md)
- [`docs/decisions/0002-use-a-pure-versioned-engine-facade.md`](./docs/decisions/0002-use-a-pure-versioned-engine-facade.md)
- [`docs/decisions/0003-use-integer-ranked-scene-layout.md`](./docs/decisions/0003-use-integer-ranked-scene-layout.md)
- [`docs/dependency-audit.md`](./docs/dependency-audit.md)

## Licensing
Expand Down
3 changes: 3 additions & 0 deletions crates/stack-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ license.workspace = true
repository.workspace = true
description = "Pure execution facade for Stack diagram operations"

[features]
conformance = []

[dependencies]
stack-compiler.workspace = true
stack-formatter.workspace = true
Expand Down
35 changes: 35 additions & 0 deletions crates/stack-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ use std::fmt;

use stack_compiler::diagnostic as compiler_diagnostic;

mod scene;

/// Version of the Rust engine facade.
pub const ENGINE_VERSION: &str = env!("CARGO_PKG_VERSION");

Expand Down Expand Up @@ -109,6 +111,9 @@ impl<'catalog> Engine<'catalog> {
/// Runs the currently available compiler stages without producing SVG.
pub fn check(&self, source: &[u8]) -> OperationResult<CheckOutput> {
let compiled = stack_compiler::compile_bytes(source);
if let Some(diagram) = &compiled.diagram {
self.validate_scene(diagram)?;
}
Ok(CheckOutput {
diagnostics: portable_diagnostics(compiled.diagnostics),
metadata: self.metadata(declared_language_version(source)),
Expand All @@ -133,6 +138,10 @@ impl<'catalog> Engine<'catalog> {
});
}

if let Some(diagram) = &compiled.diagram {
self.validate_scene(diagram)?;
}

Err(OperationalError::PipelineUnavailable {
operation: Operation::Render,
})
Expand All @@ -146,6 +155,20 @@ impl<'catalog> Engine<'catalog> {
theme_catalog_revision: self.catalog_revision.to_owned(),
}
}

fn validate_scene(&self, diagram: &stack_compiler::ir::Diagram) -> OperationResult<()> {
let scene = scene::layout(diagram, self.catalog).map_err(|error| {
OperationalError::InvalidIntermediateRepresentation {
reason: error.reason(),
}
})?;
if !scene.geometry_is_valid() {
return Err(OperationalError::InvalidIntermediateRepresentation {
reason: "layout produced invalid containment or overlap geometry",
});
}
Ok(())
}
}

/// Operation whose execution may produce an operational error.
Expand Down Expand Up @@ -178,6 +201,11 @@ pub enum OperationalError {
/// Stable explanation of the violated catalog invariant.
reason: &'static str,
},
/// Compiler or layout data violates an invariant required by pure execution.
InvalidIntermediateRepresentation {
/// Stable explanation of the violated invariant.
reason: &'static str,
},
/// The requested pure stage has not landed in this engine revision.
PipelineUnavailable {
/// Operation whose downstream stages are unavailable.
Expand All @@ -189,6 +217,9 @@ impl fmt::Display for OperationalError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidCatalog { reason } => write!(formatter, "invalid theme catalog: {reason}"),
Self::InvalidIntermediateRepresentation { reason } => {
write!(formatter, "invalid intermediate representation: {reason}")
}
Self::PipelineUnavailable { operation } => {
write!(formatter, "{operation} pipeline is unavailable")
}
Expand Down Expand Up @@ -548,5 +579,9 @@ mod tests {
.to_string(),
"render pipeline is unavailable"
);
assert_eq!(
OperationalError::InvalidIntermediateRepresentation { reason: "reason" }.to_string(),
"invalid intermediate representation: reason"
);
}
}
Loading