Make decodal the published library crate

This commit is contained in:
Keisuke Hirata 2026-06-25 22:51:16 +09:00
parent 6e2363632b
commit 87fef44c68
No known key found for this signature in database
20 changed files with 159 additions and 96 deletions

8
Cargo.lock generated
View File

@ -27,21 +27,21 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
name = "decodal" name = "decodal"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"decodal-core", "regex",
] ]
[[package]] [[package]]
name = "decodal-core" name = "decodal-cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"regex", "decodal",
] ]
[[package]] [[package]]
name = "decodal-wasm" name = "decodal-wasm"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"decodal-core", "decodal",
"serde_json", "serde_json",
"wasm-bindgen", "wasm-bindgen",
] ]

View File

@ -2,40 +2,39 @@
Decodal is a small deterministic DSL for describing, composing, validating, and materializing structured data. Decodal is a small deterministic DSL for describing, composing, validating, and materializing structured data.
It is designed around a lightweight Rust core: It is designed around a lightweight Rust library:
- host-supplied imports through `SourceLoader` - host-supplied imports through `SourceLoader`
- no filesystem access in `decodal-core` - no filesystem access in the library core
- concrete and abstract values with constraints and defaults - concrete and abstract values with constraints and defaults
- deterministic expression evaluation - deterministic expression evaluation
- optional regex support behind a Cargo feature - optional regex support behind a Cargo feature
- browser playground support through WebAssembly - browser playground support through WebAssembly
## Install the CLI ## Library crate
```sh Embedded hosts should depend on `decodal` and provide imports with a `SourceLoader`.
cargo install decodal
```toml
[dependencies]
decodal = "0.1"
``` ```
Run a Decodal file: ## CLI
A standalone CLI is kept in this repository as the `decodal-cli` workspace package.
It builds a `decodal` binary, but it is not the primary crates.io package.
Run a Decodal file from the repository:
```sh ```sh
decodal examples/advanced/main.dcdl cargo run -q -p decodal-cli -- examples/advanced/main.dcdl
``` ```
Enable optional regex support when needed: Enable optional regex support when needed:
```sh ```sh
cargo install decodal --features regex cargo run -q -p decodal-cli --features regex -- examples/regex/main.dcdl
```
## Library crate
Embedded hosts should depend on `decodal-core` and provide imports with a `SourceLoader`.
```toml
[dependencies]
decodal-core = "0.1"
``` ```
## Web playground ## Web playground

View File

@ -1,5 +1,5 @@
[package] [package]
name = "decodal" name = "decodal-cli"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
@ -9,10 +9,15 @@ readme.workspace = true
description = "Command-line interface for the Decodal data description language." description = "Command-line interface for the Decodal data description language."
keywords = ["decodal", "dsl", "config", "cli"] keywords = ["decodal", "dsl", "config", "cli"]
categories = ["command-line-utilities", "config"] categories = ["command-line-utilities", "config"]
publish = false
[[bin]]
name = "decodal"
path = "src/main.rs"
[features] [features]
default = [] default = []
regex = ["decodal-core/regex"] regex = ["decodal/regex"]
[dependencies] [dependencies]
decodal-core = { version = "0.1.0", path = "../decodal-core" } decodal = { version = "0.1.0", path = "../decodal-core" }

View File

@ -4,7 +4,7 @@ use std::{
process::ExitCode, process::ExitCode,
}; };
use decodal_core::{ use decodal::{
Data, Diagnostic, DiagnosticKind, Engine, LoadedSource, SourceId, SourceLoader, Span, Data, Diagnostic, DiagnosticKind, Engine, LoadedSource, SourceId, SourceLoader, Span,
format_diagnostic_with, format_diagnostic_with,
}; };
@ -28,7 +28,7 @@ fn run() -> Result<(), String> {
Ok(()) Ok(())
} }
"--version" | "-V" => { "--version" | "-V" => {
println!("Decodal {}", decodal_core::version()); println!("Decodal {}", decodal::version());
Ok(()) Ok(())
} }
"check" => { "check" => {

View File

@ -1,12 +1,12 @@
[package] [package]
name = "decodal-core" name = "decodal"
version.workspace = true version.workspace = true
edition.workspace = true edition.workspace = true
rust-version.workspace = true rust-version.workspace = true
license.workspace = true license.workspace = true
repository.workspace = true repository.workspace = true
readme.workspace = true readme.workspace = true
description = "Core parser, evaluator, and embedding API for the Decodal data description language." description = "Parser, evaluator, and embedding API for the Decodal data description language."
keywords = ["decodal", "dsl", "config", "schema"] keywords = ["decodal", "dsl", "config", "schema"]
categories = ["config", "parser-implementations"] categories = ["config", "parser-implementations"]

View File

@ -1,6 +1,6 @@
use decodal_core::{Data, EmptyLoader, Engine, HostValue}; use decodal::{Data, EmptyLoader, Engine, HostValue};
fn main() -> decodal_core::Result<()> { fn main() -> decodal::Result<()> {
let mut engine = Engine::new(EmptyLoader); let mut engine = Engine::new(EmptyLoader);
engine.bind_global( engine.bind_global(

View File

@ -9,12 +9,13 @@ readme.workspace = true
description = "WebAssembly wrapper for evaluating Decodal in browser playgrounds." description = "WebAssembly wrapper for evaluating Decodal in browser playgrounds."
keywords = ["decodal", "wasm", "dsl", "config"] keywords = ["decodal", "wasm", "dsl", "config"]
categories = ["wasm", "config"] categories = ["wasm", "config"]
publish = false
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib"]
[dependencies] [dependencies]
decodal-core = { version = "0.1.0", path = "../decodal-core" } decodal = { version = "0.1.0", path = "../decodal-core" }
serde_json.workspace = true serde_json.workspace = true
wasm-bindgen.workspace = true wasm-bindgen.workspace = true

View File

@ -1,6 +1,6 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use decodal_core::{ use decodal::{
Data, Diagnostic, DiagnosticKind, EmptyLoader, Engine, LoadedSource, SourceId, SourceLoader, Data, Diagnostic, DiagnosticKind, EmptyLoader, Engine, LoadedSource, SourceId, SourceLoader,
Span, format_diagnostic_with, Span, format_diagnostic_with,
}; };
@ -81,7 +81,7 @@ impl SourceLoader for VirtualLoader {
&mut self, &mut self,
current_key: Option<&str>, current_key: Option<&str>,
specifier: &str, specifier: &str,
) -> decodal_core::Result<LoadedSource> { ) -> decodal::Result<LoadedSource> {
let key = resolve_import(current_key, specifier).ok_or_else(|| { let key = resolve_import(current_key, specifier).ok_or_else(|| {
Diagnostic::new( Diagnostic::new(
DiagnosticKind::Import, DiagnosticKind::Import,
@ -139,7 +139,7 @@ fn normalize_path(path: &str) -> Option<String> {
} }
} }
fn format_diagnostic_with_root(diagnostic: &decodal_core::Diagnostic, root_name: &str) -> String { fn format_diagnostic_with_root(diagnostic: &decodal::Diagnostic, root_name: &str) -> String {
format_diagnostic_with(diagnostic, |source| { format_diagnostic_with(diagnostic, |source| {
(source == SourceId(0)).then_some(root_name) (source == SourceId(0)).then_some(root_name)
}) })

View File

@ -24,7 +24,7 @@ Primitive type names such as `String`, `Int`, `Float`, and `Bool` are handled be
The host can bind values before adding or evaluating user sources. The host can bind values before adding or evaluating user sources.
```rust ```rust
use decodal_core::{EmptyLoader, Engine, HostValue}; use decodal::{EmptyLoader, Engine, HostValue};
let mut engine = Engine::new(EmptyLoader); let mut engine = Engine::new(EmptyLoader);

View File

@ -37,7 +37,7 @@ primitive type conflict や明らかな numeric bound conflict は合成時に
## Core defaults ## Core defaults
`decodal-core` defaults to `std` only. `decodal` defaults to `std` only.
```toml ```toml
[features] [features]
@ -46,7 +46,7 @@ std = []
regex = ["std", "dep:regex"] regex = ["std", "dep:regex"]
``` ```
Building `decodal-core` with `--no-default-features` keeps the core in `no_std + alloc` mode and avoids optional dependencies. Building `decodal` with `--no-default-features` keeps the core in `no_std + alloc` mode and avoids optional dependencies.
## Regex ## Regex
@ -54,7 +54,7 @@ Regex constraints are implemented behind the `regex` feature.
When the feature is disabled, regex constraints parse and compose, but validating a concrete value against them returns an unsupported feature diagnostic. When the feature is disabled, regex constraints parse and compose, but validating a concrete value against them returns an unsupported feature diagnostic.
```sh ```sh
cargo run -q -p decodal --features regex -- examples/regex/main.dcdl cargo run -q -p decodal-cli --features regex -- examples/regex/main.dcdl
``` ```
Regex constraints are accumulated during `&` composition. Regex constraints are accumulated during `&` composition.
@ -63,5 +63,5 @@ Concrete strings must match every regex constraint attached to the abstract valu
## CLI features ## CLI features
`decodal-cli` exposes a matching `regex` feature that enables `decodal-core/regex`. `decodal-cli` exposes a matching `regex` feature that enables `decodal/regex`.
The feature is not enabled by default so the default CLI binary remains small. The feature is not enabled by default so the default CLI binary remains small.

View File

@ -9,27 +9,25 @@ Run the normal Rust checks from the repository root.
```sh ```sh
cargo fmt --check cargo fmt --check
cargo test cargo test
cargo check -p decodal-core --no-default-features cargo check -p decodal --no-default-features
nix flake check nix flake check
``` ```
Regex support is optional and should be tested explicitly when touched. Regex support is optional and should be tested explicitly when touched.
```sh ```sh
cargo test -p decodal-core --features regex cargo test -p decodal --features regex
cargo run -q -p decodal --features regex -- examples/regex/main.dcdl cargo run -q -p decodal-cli --features regex -- examples/regex/main.dcdl
``` ```
## crates.io release ## crates.io release
Crates are published as versioned workspace packages under the dual license `MIT OR Apache-2.0`. The primary crates.io package is `decodal`, which contains the embeddable library.
The dependency order matters because `decodal` and `decodal-wasm` depend on `decodal-core` from crates.io. Workspace support crates such as `decodal-cli` and `decodal-wasm` are not published for 0.1.0.
The project is dual licensed as `MIT OR Apache-2.0`.
```sh ```sh
cargo publish -p decodal-core
# wait until crates.io index has decodal-core 0.1.0
cargo publish -p decodal cargo publish -p decodal
cargo publish -p decodal-wasm
``` ```
Before publishing, run: Before publishing, run:
@ -37,12 +35,10 @@ Before publishing, run:
```sh ```sh
cargo fmt --check cargo fmt --check
cargo test cargo test
cargo check -p decodal-core --no-default-features cargo check -p decodal --no-default-features
cargo publish -p decodal-core --dry-run cargo publish -p decodal --dry-run
``` ```
`cargo publish --dry-run` for `decodal` and `decodal-wasm` will fail until the matching `decodal-core` version is already visible in the crates.io index.
## Web site and playground ## Web site and playground
The Astro documentation site and browser playground are kept in: The Astro documentation site and browser playground are kept in:

View File

@ -16,7 +16,7 @@ This example exercises multiple current Decodal features together:
Run it with: Run it with:
```sh ```sh
cargo run -q -p decodal -- examples/advanced/main.dcdl cargo run -q -p decodal-cli -- examples/advanced/main.dcdl
``` ```
The entrypoint is `main.dcdl`. The entrypoint is `main.dcdl`.

View File

@ -3,7 +3,7 @@
Regex constraints are optional. Run this example with the `regex` feature enabled: Regex constraints are optional. Run this example with the `regex` feature enabled:
```sh ```sh
cargo run -q -p decodal --features regex -- examples/regex/main.dcdl cargo run -q -p decodal-cli --features regex -- examples/regex/main.dcdl
``` ```
Without the feature, regex validation returns an unsupported feature diagnostic. Without the feature, regex validation returns an unsupported feature diagnostic.

View File

@ -37,7 +37,7 @@ rustPlatform.buildRustPackage {
cargoBuildFlags = [ cargoBuildFlags = [
"-p" "-p"
"decodal" "decodal-cli"
]; ];
doInstallCheck = true; doInstallCheck = true;

View File

@ -0,0 +1,55 @@
# Decodal
Decodal is a small deterministic DSL for describing, composing, validating, and materializing structured data.
It is designed around a lightweight Rust library:
- host-supplied imports through `SourceLoader`
- no filesystem access in the library core
- concrete and abstract values with constraints and defaults
- deterministic expression evaluation
- optional regex support behind a Cargo feature
- browser playground support through WebAssembly
## Library crate
Embedded hosts should depend on `decodal` and provide imports with a `SourceLoader`.
```toml
[dependencies]
decodal = "0.1"
```
## CLI
A standalone CLI is kept in this repository as the `decodal-cli` workspace package.
It builds a `decodal` binary, but it is not the primary crates.io package.
Run a Decodal file from the repository:
```sh
cargo run -q -p decodal-cli -- examples/advanced/main.dcdl
```
Enable optional regex support when needed:
```sh
cargo run -q -p decodal-cli --features regex -- examples/regex/main.dcdl
```
## Web playground
The static documentation site and browser playground live under:
```text
site/decodal-site/
```
## License
Licensed under either of:
- Apache License, Version 2.0
- MIT license
at your option.

View File

@ -9,12 +9,13 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
export interface InitOutput { export interface InitOutput {
readonly memory: WebAssembly.Memory; readonly memory: WebAssembly.Memory;
readonly evaluate: (a: number, b: number, c: number) => void; readonly evaluate: (a: number, b: number) => [number, number];
readonly evaluateProject: (a: number, b: number, c: number, d: number, e: number) => void; readonly evaluateProject: (a: number, b: number, c: number, d: number) => [number, number];
readonly __wbindgen_add_to_stack_pointer: (a: number) => number; readonly __wbindgen_externrefs: WebAssembly.Table;
readonly __wbindgen_export: (a: number, b: number) => number; readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
readonly __wbindgen_export3: (a: number, b: number, c: number) => void; readonly __wbindgen_free: (a: number, b: number, c: number) => void;
readonly __wbindgen_start: () => void;
} }
export type SyncInitInput = BufferSource | WebAssembly.Module; export type SyncInitInput = BufferSource | WebAssembly.Module;

View File

@ -8,18 +8,14 @@ export function evaluate(source) {
let deferred2_0; let deferred2_0;
let deferred2_1; let deferred2_1;
try { try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const ptr0 = passStringToWasm0(source, wasm.__wbindgen_export, wasm.__wbindgen_export2);
const len0 = WASM_VECTOR_LEN; const len0 = WASM_VECTOR_LEN;
wasm.evaluate(retptr, ptr0, len0); const ret = wasm.evaluate(ptr0, len0);
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); deferred2_0 = ret[0];
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); deferred2_1 = ret[1];
deferred2_0 = r0; return getStringFromWasm0(ret[0], ret[1]);
deferred2_1 = r1;
return getStringFromWasm0(r0, r1);
} finally { } finally {
wasm.__wbindgen_add_to_stack_pointer(16); wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
} }
} }
@ -32,25 +28,30 @@ export function evaluateProject(entry, files_json) {
let deferred3_0; let deferred3_0;
let deferred3_1; let deferred3_1;
try { try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); const ptr0 = passStringToWasm0(entry, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const ptr0 = passStringToWasm0(entry, wasm.__wbindgen_export, wasm.__wbindgen_export2);
const len0 = WASM_VECTOR_LEN; const len0 = WASM_VECTOR_LEN;
const ptr1 = passStringToWasm0(files_json, wasm.__wbindgen_export, wasm.__wbindgen_export2); const ptr1 = passStringToWasm0(files_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len1 = WASM_VECTOR_LEN; const len1 = WASM_VECTOR_LEN;
wasm.evaluateProject(retptr, ptr0, len0, ptr1, len1); const ret = wasm.evaluateProject(ptr0, len0, ptr1, len1);
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); deferred3_0 = ret[0];
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); deferred3_1 = ret[1];
deferred3_0 = r0; return getStringFromWasm0(ret[0], ret[1]);
deferred3_1 = r1;
return getStringFromWasm0(r0, r1);
} finally { } finally {
wasm.__wbindgen_add_to_stack_pointer(16); wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
wasm.__wbindgen_export3(deferred3_0, deferred3_1, 1);
} }
} }
function __wbg_get_imports() { function __wbg_get_imports() {
const import0 = { const import0 = {
__proto__: null, __proto__: null,
__wbindgen_init_externref_table: function() {
const table = wasm.__wbindgen_externrefs;
const offset = table.grow(4);
table.set(0, undefined);
table.set(offset + 0, undefined);
table.set(offset + 1, null);
table.set(offset + 2, true);
table.set(offset + 3, false);
},
}; };
return { return {
__proto__: null, __proto__: null,
@ -58,14 +59,6 @@ function __wbg_get_imports() {
}; };
} }
let cachedDataViewMemory0 = null;
function getDataViewMemory0() {
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
}
return cachedDataViewMemory0;
}
function getStringFromWasm0(ptr, len) { function getStringFromWasm0(ptr, len) {
return decodeText(ptr >>> 0, len); return decodeText(ptr >>> 0, len);
} }
@ -149,8 +142,8 @@ function __wbg_finalize_init(instance, module) {
wasmInstance = instance; wasmInstance = instance;
wasm = instance.exports; wasm = instance.exports;
wasmModule = module; wasmModule = module;
cachedDataViewMemory0 = null;
cachedUint8ArrayMemory0 = null; cachedUint8ArrayMemory0 = null;
wasm.__wbindgen_start();
return wasm; return wasm;
} }

View File

@ -1,9 +1,10 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export const memory: WebAssembly.Memory; export const memory: WebAssembly.Memory;
export const evaluate: (a: number, b: number, c: number) => void; export const evaluate: (a: number, b: number) => [number, number];
export const evaluateProject: (a: number, b: number, c: number, d: number, e: number) => void; export const evaluateProject: (a: number, b: number, c: number, d: number) => [number, number];
export const __wbindgen_add_to_stack_pointer: (a: number) => number; export const __wbindgen_externrefs: WebAssembly.Table;
export const __wbindgen_export: (a: number, b: number) => number; export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number; export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
export const __wbindgen_export3: (a: number, b: number, c: number) => void; export const __wbindgen_free: (a: number, b: number, c: number) => void;
export const __wbindgen_start: () => void;

View File

@ -1,7 +1,13 @@
{ {
"name": "decodal-wasm", "name": "decodal-wasm",
"type": "module", "type": "module",
"description": "WebAssembly wrapper for evaluating Decodal in browser playgrounds.",
"version": "0.1.0", "version": "0.1.0",
"license": "MIT OR Apache-2.0",
"repository": {
"type": "git",
"url": "https://gitea.hareworks.net/Hare/Decodal"
},
"files": [ "files": [
"decodal_wasm_bg.wasm", "decodal_wasm_bg.wasm",
"decodal_wasm.js", "decodal_wasm.js",
@ -11,5 +17,11 @@
"types": "decodal_wasm.d.ts", "types": "decodal_wasm.d.ts",
"sideEffects": [ "sideEffects": [
"./snippets/*" "./snippets/*"
],
"keywords": [
"decodal",
"wasm",
"dsl",
"config"
] ]
} }