Add Svelte docs site and WASM playground
This commit is contained in:
parent
58d2c9b423
commit
4020b7c2d5
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -3,3 +3,6 @@
|
|||
/.env
|
||||
/.yoi
|
||||
/result
|
||||
node_modules
|
||||
/dist
|
||||
site/decodal-site/dist
|
||||
|
|
|
|||
112
Cargo.lock
generated
112
Cargo.lock
generated
|
|
@ -11,6 +11,18 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "decodal"
|
||||
version = "0.1.0"
|
||||
|
|
@ -25,12 +37,44 @@ dependencies = [
|
|||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "decodal-wasm"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"decodal-core",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.4"
|
||||
|
|
@ -59,3 +103,71 @@ name = "regex-syntax"
|
|||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.125"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
members = [
|
||||
"crates/decodal-core",
|
||||
"crates/decodal-cli",
|
||||
"crates/decodal-wasm",
|
||||
]
|
||||
resolver = "3"
|
||||
|
||||
|
|
|
|||
14
crates/decodal-wasm/Cargo.toml
Normal file
14
crates/decodal-wasm/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "decodal-wasm"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
decodal-core = { path = "../decodal-core" }
|
||||
wasm-bindgen = "0.2"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
96
crates/decodal-wasm/src/lib.rs
Normal file
96
crates/decodal-wasm/src/lib.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
use decodal_core::{Data, EmptyLoader, Engine};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn evaluate(source: &str) -> String {
|
||||
match evaluate_inner(source) {
|
||||
Ok(output) => format!("{{\"ok\":true,\"output\":{}}}", json_string(&output)),
|
||||
Err(error) => format!("{{\"ok\":false,\"error\":{}}}", json_string(&error)),
|
||||
}
|
||||
}
|
||||
|
||||
fn evaluate_inner(source: &str) -> Result<String, String> {
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
let module = engine
|
||||
.add_root_source("playground", "playground", source)
|
||||
.map_err(format_diagnostic)?;
|
||||
let value = engine.eval_module(module).map_err(format_diagnostic)?;
|
||||
let data = engine.materialize(&value).map_err(format_diagnostic)?;
|
||||
Ok(format_data(&data, 0))
|
||||
}
|
||||
|
||||
fn format_diagnostic(diagnostic: decodal_core::Diagnostic) -> String {
|
||||
format!(
|
||||
"{:?} at {}:{}..{}: {}",
|
||||
diagnostic.kind,
|
||||
diagnostic.span.source.0,
|
||||
diagnostic.span.start,
|
||||
diagnostic.span.end,
|
||||
diagnostic.message,
|
||||
)
|
||||
}
|
||||
|
||||
fn format_data(data: &Data, indent: usize) -> String {
|
||||
match data {
|
||||
Data::String(value) => json_string(value),
|
||||
Data::Int(value) => value.to_string(),
|
||||
Data::Float(value) => value.to_string(),
|
||||
Data::Bool(value) => value.to_string(),
|
||||
Data::Array(items) => {
|
||||
if items.is_empty() {
|
||||
return String::from("[]");
|
||||
}
|
||||
let mut out = String::from("[\n");
|
||||
for (index, item) in items.iter().enumerate() {
|
||||
out.push_str(&" ".repeat(indent + 2));
|
||||
out.push_str(&format_data(item, indent + 2));
|
||||
if index + 1 != items.len() {
|
||||
out.push(',');
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
out.push_str(&" ".repeat(indent));
|
||||
out.push(']');
|
||||
out
|
||||
}
|
||||
Data::Object(fields) => {
|
||||
if fields.is_empty() {
|
||||
return String::from("{}");
|
||||
}
|
||||
let mut out = String::from("{\n");
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
out.push_str(&" ".repeat(indent + 2));
|
||||
out.push_str(&json_string(&field.name));
|
||||
out.push_str(": ");
|
||||
out.push_str(&format_data(&field.value, indent + 2));
|
||||
if index + 1 != fields.len() {
|
||||
out.push(',');
|
||||
}
|
||||
out.push('\n');
|
||||
}
|
||||
out.push_str(&" ".repeat(indent));
|
||||
out.push('}');
|
||||
out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn json_string(value: &str) -> String {
|
||||
let mut out = String::from("\"");
|
||||
for ch in value.chars() {
|
||||
match ch {
|
||||
'"' => out.push_str("\\\""),
|
||||
'\\' => out.push_str("\\\\"),
|
||||
'\n' => out.push_str("\\n"),
|
||||
'\r' => out.push_str("\\r"),
|
||||
'\t' => out.push_str("\\t"),
|
||||
ch if ch.is_control() => {
|
||||
use core::fmt::Write;
|
||||
let _ = write!(out, "\\u{:04x}", ch as u32);
|
||||
}
|
||||
ch => out.push(ch),
|
||||
}
|
||||
}
|
||||
out.push('"');
|
||||
out
|
||||
}
|
||||
|
|
@ -4,11 +4,13 @@ pkgs.mkShell {
|
|||
cargo
|
||||
clippy
|
||||
git
|
||||
lld
|
||||
nodejs
|
||||
nixfmt
|
||||
rustc
|
||||
rustfmt
|
||||
tree-sitter
|
||||
wasm-pack
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
|
|
|
|||
|
|
@ -20,6 +20,50 @@ cargo test -p decodal-core --features regex
|
|||
cargo run -q -p decodal --features regex -- examples/regex/main.dcdl
|
||||
```
|
||||
|
||||
## Web site and playground
|
||||
|
||||
The Svelte documentation site and browser playground are kept in:
|
||||
|
||||
```text
|
||||
site/decodal-site/
|
||||
```
|
||||
|
||||
The site imports Markdown files from `doc/manual/souce/` and renders them as mdBook-style pages.
|
||||
The playground loads `decodal-wasm` and evaluates DCDL entirely in the browser.
|
||||
|
||||
Important files:
|
||||
|
||||
```text
|
||||
site/decodal-site/src/App.svelte
|
||||
site/decodal-site/src/Playground.svelte
|
||||
site/decodal-site/src/lib/docs.js
|
||||
crates/decodal-wasm/src/lib.rs
|
||||
```
|
||||
|
||||
Build the WebAssembly package before building the site:
|
||||
|
||||
```sh
|
||||
cd site/decodal-site
|
||||
npm install
|
||||
npm run build:wasm
|
||||
npm run build
|
||||
```
|
||||
|
||||
`npm run build:wasm` writes generated files into:
|
||||
|
||||
```text
|
||||
site/decodal-site/src/wasm/
|
||||
```
|
||||
|
||||
These generated files are committed so the site can be built without requiring every consumer to regenerate the wasm package first.
|
||||
|
||||
To run the site locally:
|
||||
|
||||
```sh
|
||||
cd site/decodal-site
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Tree-sitter grammar
|
||||
|
||||
The Tree-sitter grammar is kept in:
|
||||
|
|
@ -73,7 +117,7 @@ When the Decodal syntax changes:
|
|||
|
||||
## Development shell
|
||||
|
||||
The Nix development shell includes Rust tooling, Node.js, and Tree-sitter CLI tooling.
|
||||
The Nix development shell includes Rust tooling, Node.js, Tree-sitter CLI tooling, and wasm-pack tooling.
|
||||
|
||||
```sh
|
||||
nix develop
|
||||
|
|
@ -87,5 +131,7 @@ The shell provides:
|
|||
- `clippy`
|
||||
- `node`
|
||||
- `npm`
|
||||
- `wasm-pack`
|
||||
- `lld`
|
||||
- `tree-sitter`
|
||||
- `nixfmt`
|
||||
|
|
|
|||
12
site/decodal-site/index.html
Normal file
12
site/decodal-site/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Decodal</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1270
site/decodal-site/package-lock.json
generated
Normal file
1270
site/decodal-site/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
site/decodal-site/package.json
Normal file
19
site/decodal-site/package.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "decodal-site",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --host 0.0.0.0",
|
||||
"build:wasm": "wasm-pack build ../../crates/decodal-wasm --target web --out-dir ../../site/decodal-site/src/wasm --release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"marked": "^12.0.2",
|
||||
"svelte": "^4.2.18",
|
||||
"vite": "^5.3.5"
|
||||
},
|
||||
"devDependencies": {}
|
||||
}
|
||||
52
site/decodal-site/src/App.svelte
Normal file
52
site/decodal-site/src/App.svelte
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import NavTree from './NavTree.svelte';
|
||||
import Playground from './Playground.svelte';
|
||||
import { nav, renderMarkdown } from './lib/docs.js';
|
||||
|
||||
let route = parseRoute(location.hash);
|
||||
|
||||
onMount(() => {
|
||||
const onHashChange = () => (route = parseRoute(location.hash));
|
||||
addEventListener('hashchange', onHashChange);
|
||||
return () => removeEventListener('hashchange', onHashChange);
|
||||
});
|
||||
|
||||
$: isPlayground = route.kind === 'playground';
|
||||
$: currentSlug = route.slug ?? 'introduction';
|
||||
$: html = isPlayground ? '' : renderMarkdown(currentSlug);
|
||||
|
||||
function parseRoute(hash) {
|
||||
const raw = hash.replace(/^#\/?/, '');
|
||||
if (raw === 'playground') return { kind: 'playground' };
|
||||
if (raw.startsWith('docs/')) {
|
||||
return { kind: 'docs', slug: raw.slice('docs/'.length) || 'introduction' };
|
||||
}
|
||||
return { kind: 'docs', slug: 'introduction' };
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="topbar">
|
||||
<a class="brand" href="#/docs/introduction">Decodal</a>
|
||||
<nav class="topnav">
|
||||
<a href="#/docs/introduction">Docs</a>
|
||||
<a href="#/playground">Playground</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<a class="sidebar-title" href="#/docs/index">Manual</a>
|
||||
<NavTree items={nav} active={currentSlug} />
|
||||
</aside>
|
||||
|
||||
<main class:playground={isPlayground}>
|
||||
{#if isPlayground}
|
||||
<Playground />
|
||||
{:else}
|
||||
<article class="markdown">
|
||||
{@html html}
|
||||
</article>
|
||||
{/if}
|
||||
</main>
|
||||
</div>
|
||||
15
site/decodal-site/src/NavTree.svelte
Normal file
15
site/decodal-site/src/NavTree.svelte
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<script>
|
||||
export let items = [];
|
||||
export let active = '';
|
||||
</script>
|
||||
|
||||
<ul class="nav-tree">
|
||||
{#each items as item}
|
||||
<li>
|
||||
<a class:active={active === item.slug} href={`#/docs/${item.slug}`}>{item.title}</a>
|
||||
{#if item.children}
|
||||
<svelte:self items={item.children} {active} />
|
||||
{/if}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
81
site/decodal-site/src/Playground.svelte
Normal file
81
site/decodal-site/src/Playground.svelte
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const starter = `let
|
||||
Service = {
|
||||
name = String;
|
||||
port = Int & > 443 default 8443;
|
||||
feature.enable = Bool default true;
|
||||
};
|
||||
in
|
||||
Service & {
|
||||
name = "api";
|
||||
port = 9443;
|
||||
}
|
||||
`;
|
||||
|
||||
let source = starter;
|
||||
let output = '';
|
||||
let error = '';
|
||||
let ready = false;
|
||||
let loading = true;
|
||||
let evaluate;
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const wasm = await import('./wasm/decodal_wasm.js');
|
||||
await wasm.default();
|
||||
evaluate = wasm.evaluate;
|
||||
ready = true;
|
||||
run();
|
||||
} catch (err) {
|
||||
error = `Failed to load WASM playground: ${err.message ?? err}`;
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
});
|
||||
|
||||
function run() {
|
||||
if (!evaluate) return;
|
||||
const result = JSON.parse(evaluate(source));
|
||||
if (result.ok) {
|
||||
output = result.output;
|
||||
error = '';
|
||||
} else {
|
||||
output = '';
|
||||
error = result.error;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="playground-page">
|
||||
<div class="playground-header">
|
||||
<div>
|
||||
<h1>Playground</h1>
|
||||
<p>Evaluate Decodal directly in your browser through WebAssembly.</p>
|
||||
</div>
|
||||
<button on:click={run} disabled={!ready}>Run</button>
|
||||
</div>
|
||||
|
||||
{#if loading}
|
||||
<p class="status">Loading WASM...</p>
|
||||
{/if}
|
||||
|
||||
<div class="playground-grid">
|
||||
<label class="pane">
|
||||
<span>Input</span>
|
||||
<textarea bind:value={source} spellcheck="false" on:keydown={(event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') run();
|
||||
}} />
|
||||
</label>
|
||||
|
||||
<section class="pane output-pane">
|
||||
<span>Output</span>
|
||||
{#if error}
|
||||
<pre class="error">{error}</pre>
|
||||
{:else}
|
||||
<pre>{output}</pre>
|
||||
{/if}
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
108
site/decodal-site/src/lib/docs.js
Normal file
108
site/decodal-site/src/lib/docs.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { marked } from 'marked';
|
||||
|
||||
const modules = import.meta.glob('../../../../doc/manual/souce/**/*.md', {
|
||||
query: '?raw',
|
||||
import: 'default',
|
||||
eager: true,
|
||||
});
|
||||
|
||||
export const docs = Object.fromEntries(
|
||||
Object.entries(modules).map(([path, content]) => {
|
||||
const slug = path
|
||||
.replace(/^\.\.\/\.\.\/\.\.\/\.\.\/doc\/manual\/souce\//, '')
|
||||
.replace(/\.md$/, '')
|
||||
.replace(/\/index$/, '');
|
||||
return [slug || 'index', content];
|
||||
}),
|
||||
);
|
||||
|
||||
export const nav = [
|
||||
{ title: 'Introduction', slug: 'introduction' },
|
||||
{
|
||||
title: 'Language Specification',
|
||||
slug: 'language',
|
||||
children: [
|
||||
{ title: 'Syntax', slug: 'language/syntax' },
|
||||
{
|
||||
title: 'Value',
|
||||
slug: 'language/value',
|
||||
children: [
|
||||
{ title: 'String', slug: 'language/value/string' },
|
||||
{ title: 'Int', slug: 'language/value/int' },
|
||||
{ title: 'Float', slug: 'language/value/float' },
|
||||
{ title: 'Bool', slug: 'language/value/bool' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Expression',
|
||||
slug: 'language/expression',
|
||||
children: [
|
||||
{ title: 'Literal', slug: 'language/expression/literal' },
|
||||
{ title: 'Identifier', slug: 'language/expression/identifier' },
|
||||
{ title: 'Path Reference', slug: 'language/expression/path-reference' },
|
||||
{ title: 'Object', slug: 'language/expression/object' },
|
||||
{ title: 'Array', slug: 'language/expression/array' },
|
||||
{ title: 'Function', slug: 'language/expression/function' },
|
||||
{ title: 'Function Call', slug: 'language/expression/function-call' },
|
||||
{ title: 'Let', slug: 'language/expression/let' },
|
||||
{ title: 'Match', slug: 'language/expression/match' },
|
||||
{ title: 'Import', slug: 'language/expression/import' },
|
||||
{ title: 'Composition', slug: 'language/expression/composition' },
|
||||
{ title: 'Default', slug: 'language/expression/default' },
|
||||
{ title: 'String Interpolation', slug: 'language/expression/string-interpolation' },
|
||||
],
|
||||
},
|
||||
{ title: 'Constraints and Defaults', slug: 'language/constraints-and-defaults' },
|
||||
{ title: 'Composition Operators', slug: 'language/operators' },
|
||||
{ title: 'Functions', slug: 'language/functions' },
|
||||
{ title: 'Modules and Imports', slug: 'language/modules-and-imports' },
|
||||
{ title: 'Evaluation Semantics', slug: 'language/evaluation' },
|
||||
{ title: 'Materialization and Errors', slug: 'language/materialization-and-errors' },
|
||||
{ title: 'Naming', slug: 'language/naming' },
|
||||
{ title: 'Examples', slug: 'language/examples' },
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Implementation Design',
|
||||
slug: 'design',
|
||||
children: [
|
||||
{ title: 'Execution Pipeline', slug: 'design/execution-pipeline' },
|
||||
{ title: 'Runtime Model', slug: 'design/runtime-model' },
|
||||
{ title: 'Thunk and Lazy Evaluation', slug: 'design/thunk-and-lazy-evaluation' },
|
||||
{ title: 'Composition and Materialization', slug: 'design/composition-and-materialization' },
|
||||
{ title: 'Diagnostics and Fallback', slug: 'design/diagnostics-and-fallback' },
|
||||
{ title: 'Embedding API', slug: 'design/embedding-api' },
|
||||
{ title: 'Features', slug: 'design/features' },
|
||||
],
|
||||
},
|
||||
{ title: 'Development', slug: 'development' },
|
||||
{ title: 'Open Issues', slug: 'open-issues' },
|
||||
];
|
||||
|
||||
marked.setOptions({
|
||||
gfm: true,
|
||||
mangle: false,
|
||||
headerIds: true,
|
||||
});
|
||||
|
||||
export function renderMarkdown(slug) {
|
||||
const source = docs[slug] ?? docs.index;
|
||||
const html = marked.parse(source ?? '# Not found\n');
|
||||
return html.replace(/href="([^"#][^"]*)\.md(#[^"]*)?"/g, (_all, href, hash = '') => {
|
||||
const target = normalizeDocLink(slug, href);
|
||||
return `href="#/docs/${target}${hash}"`;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeDocLink(currentSlug, href) {
|
||||
const base = currentSlug.includes('/') ? currentSlug.split('/').slice(0, -1) : [];
|
||||
const parts = [...base, ...href.split('/')];
|
||||
const out = [];
|
||||
for (const part of parts) {
|
||||
if (!part || part === '.') continue;
|
||||
if (part === '..') out.pop();
|
||||
else out.push(part);
|
||||
}
|
||||
if (out[out.length - 1] === 'index') out.pop();
|
||||
return out.join('/') || 'index';
|
||||
}
|
||||
8
site/decodal-site/src/main.js
Normal file
8
site/decodal-site/src/main.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import App from './App.svelte';
|
||||
import './style.css';
|
||||
|
||||
const app = new App({
|
||||
target: document.getElementById('app'),
|
||||
});
|
||||
|
||||
export default app;
|
||||
232
site/decodal-site/src/style.css
Normal file
232
site/decodal-site/src/style.css
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
:root {
|
||||
color-scheme: light dark;
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: #f7f7f8;
|
||||
color: #1f2328;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #2563eb;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
align-items: center;
|
||||
background: #111827;
|
||||
color: white;
|
||||
display: flex;
|
||||
height: 56px;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.topnav {
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.topnav a {
|
||||
color: #dbeafe;
|
||||
}
|
||||
|
||||
.layout {
|
||||
display: grid;
|
||||
grid-template-columns: 300px minmax(0, 1fr);
|
||||
min-height: calc(100vh - 56px);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background: #ffffff;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
overflow: auto;
|
||||
padding: 22px 18px;
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
color: #111827;
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.nav-tree {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.nav-tree .nav-tree {
|
||||
margin: 4px 0 6px 12px;
|
||||
}
|
||||
|
||||
.nav-tree a {
|
||||
border-radius: 6px;
|
||||
color: #374151;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
line-height: 1.35;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.nav-tree a.active {
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
main {
|
||||
min-width: 0;
|
||||
padding: 36px;
|
||||
}
|
||||
|
||||
main.playground {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.markdown {
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 24px rgb(15 23 42 / 0.05);
|
||||
margin: 0 auto;
|
||||
max-width: 920px;
|
||||
padding: 24px 36px 42px;
|
||||
}
|
||||
|
||||
.markdown h1,
|
||||
.markdown h2,
|
||||
.markdown h3 {
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.markdown pre,
|
||||
.pane pre {
|
||||
background: #0f172a;
|
||||
border-radius: 10px;
|
||||
color: #e5e7eb;
|
||||
overflow: auto;
|
||||
padding: 14px;
|
||||
}
|
||||
|
||||
.markdown code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
}
|
||||
|
||||
.markdown :not(pre) > code {
|
||||
background: #eef2ff;
|
||||
border-radius: 4px;
|
||||
color: #3730a3;
|
||||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.playground-page {
|
||||
height: calc(100vh - 104px);
|
||||
}
|
||||
|
||||
.playground-header {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.playground-header h1 {
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.playground-header p {
|
||||
color: #4b5563;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
background: #2563eb;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
padding: 10px 18px;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.playground-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
height: calc(100% - 82px);
|
||||
}
|
||||
|
||||
.pane {
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.pane > span {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
color: #374151;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
padding: 10px 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
textarea {
|
||||
border: 0;
|
||||
flex: 1;
|
||||
font: 14px/1.5 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
outline: none;
|
||||
padding: 14px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.output-pane pre {
|
||||
border-radius: 0;
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.output-pane pre.error {
|
||||
color: #fecaca;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.sidebar {
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
border-right: 0;
|
||||
max-height: 260px;
|
||||
}
|
||||
.playground-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
1
site/decodal-site/src/wasm/.gitignore
vendored
Normal file
1
site/decodal-site/src/wasm/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
# wasm-pack output is committed for the playground build.
|
||||
37
site/decodal-site/src/wasm/decodal_wasm.d.ts
vendored
Normal file
37
site/decodal-site/src/wasm/decodal_wasm.d.ts
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
export function evaluate(source: string): string;
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly evaluate: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
readonly __wbindgen_export: (a: number, b: number) => number;
|
||||
readonly __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly __wbindgen_export3: (a: number, b: number, c: number) => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
|
||||
/**
|
||||
* Instantiates the given `module`, which can either be bytes or
|
||||
* a precompiled `WebAssembly.Module`.
|
||||
*
|
||||
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {InitOutput}
|
||||
*/
|
||||
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||
*
|
||||
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||
212
site/decodal-site/src/wasm/decodal_wasm.js
Normal file
212
site/decodal-site/src/wasm/decodal_wasm.js
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/* @ts-self-types="./decodal_wasm.d.ts" */
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @returns {string}
|
||||
*/
|
||||
export function evaluate(source) {
|
||||
let deferred2_0;
|
||||
let deferred2_1;
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
const ptr0 = passStringToWasm0(source, wasm.__wbindgen_export, wasm.__wbindgen_export2);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
wasm.evaluate(retptr, ptr0, len0);
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
deferred2_0 = r0;
|
||||
deferred2_1 = r1;
|
||||
return getStringFromWasm0(r0, r1);
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
wasm.__wbindgen_export3(deferred2_0, deferred2_1, 1);
|
||||
}
|
||||
}
|
||||
function __wbg_get_imports() {
|
||||
const import0 = {
|
||||
__proto__: null,
|
||||
};
|
||||
return {
|
||||
__proto__: null,
|
||||
"./decodal_wasm_bg.js": import0,
|
||||
};
|
||||
}
|
||||
|
||||
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) {
|
||||
return decodeText(ptr >>> 0, len);
|
||||
}
|
||||
|
||||
let cachedUint8ArrayMemory0 = null;
|
||||
function getUint8ArrayMemory0() {
|
||||
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
||||
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8ArrayMemory0;
|
||||
}
|
||||
|
||||
function passStringToWasm0(arg, malloc, realloc) {
|
||||
if (realloc === undefined) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
const ptr = malloc(buf.length, 1) >>> 0;
|
||||
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||
WASM_VECTOR_LEN = buf.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let len = arg.length;
|
||||
let ptr = malloc(len, 1) >>> 0;
|
||||
|
||||
const mem = getUint8ArrayMemory0();
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (; offset < len; offset++) {
|
||||
const code = arg.charCodeAt(offset);
|
||||
if (code > 0x7F) break;
|
||||
mem[ptr + offset] = code;
|
||||
}
|
||||
if (offset !== len) {
|
||||
if (offset !== 0) {
|
||||
arg = arg.slice(offset);
|
||||
}
|
||||
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
||||
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
||||
const ret = cachedTextEncoder.encodeInto(arg, view);
|
||||
|
||||
offset += ret.written;
|
||||
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
||||
}
|
||||
|
||||
WASM_VECTOR_LEN = offset;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
cachedTextDecoder.decode();
|
||||
const MAX_SAFARI_DECODE_BYTES = 2146435072;
|
||||
let numBytesDecoded = 0;
|
||||
function decodeText(ptr, len) {
|
||||
numBytesDecoded += len;
|
||||
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
|
||||
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
cachedTextDecoder.decode();
|
||||
numBytesDecoded = len;
|
||||
}
|
||||
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
const cachedTextEncoder = new TextEncoder();
|
||||
|
||||
if (!('encodeInto' in cachedTextEncoder)) {
|
||||
cachedTextEncoder.encodeInto = function (arg, view) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
view.set(buf);
|
||||
return {
|
||||
read: arg.length,
|
||||
written: buf.length
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
let wasmModule, wasmInstance, wasm;
|
||||
function __wbg_finalize_init(instance, module) {
|
||||
wasmInstance = instance;
|
||||
wasm = instance.exports;
|
||||
wasmModule = module;
|
||||
cachedDataViewMemory0 = null;
|
||||
cachedUint8ArrayMemory0 = null;
|
||||
return wasm;
|
||||
}
|
||||
|
||||
async function __wbg_load(module, imports) {
|
||||
if (typeof Response === 'function' && module instanceof Response) {
|
||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||
try {
|
||||
return await WebAssembly.instantiateStreaming(module, imports);
|
||||
} catch (e) {
|
||||
const validResponse = module.ok && expectedResponseType(module.type);
|
||||
|
||||
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
|
||||
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||
|
||||
} else { throw e; }
|
||||
}
|
||||
}
|
||||
|
||||
const bytes = await module.arrayBuffer();
|
||||
return await WebAssembly.instantiate(bytes, imports);
|
||||
} else {
|
||||
const instance = await WebAssembly.instantiate(module, imports);
|
||||
|
||||
if (instance instanceof WebAssembly.Instance) {
|
||||
return { instance, module };
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
function expectedResponseType(type) {
|
||||
switch (type) {
|
||||
case 'basic': case 'cors': case 'default': return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function initSync(module) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (module !== undefined) {
|
||||
if (Object.getPrototypeOf(module) === Object.prototype) {
|
||||
({module} = module)
|
||||
} else {
|
||||
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
|
||||
}
|
||||
}
|
||||
|
||||
const imports = __wbg_get_imports();
|
||||
if (!(module instanceof WebAssembly.Module)) {
|
||||
module = new WebAssembly.Module(module);
|
||||
}
|
||||
const instance = new WebAssembly.Instance(module, imports);
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
async function __wbg_init(module_or_path) {
|
||||
if (wasm !== undefined) return wasm;
|
||||
|
||||
|
||||
if (module_or_path !== undefined) {
|
||||
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
||||
({module_or_path} = module_or_path)
|
||||
} else {
|
||||
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
|
||||
}
|
||||
}
|
||||
|
||||
if (module_or_path === undefined) {
|
||||
module_or_path = new URL('decodal_wasm_bg.wasm', import.meta.url);
|
||||
}
|
||||
const imports = __wbg_get_imports();
|
||||
|
||||
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
||||
module_or_path = fetch(module_or_path);
|
||||
}
|
||||
|
||||
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
||||
|
||||
return __wbg_finalize_init(instance, module);
|
||||
}
|
||||
|
||||
export { initSync, __wbg_init as default };
|
||||
BIN
site/decodal-site/src/wasm/decodal_wasm_bg.wasm
Normal file
BIN
site/decodal-site/src/wasm/decodal_wasm_bg.wasm
Normal file
Binary file not shown.
8
site/decodal-site/src/wasm/decodal_wasm_bg.wasm.d.ts
vendored
Normal file
8
site/decodal-site/src/wasm/decodal_wasm_bg.wasm.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export const evaluate: (a: number, b: number, c: number) => void;
|
||||
export const __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
export const __wbindgen_export: (a: number, b: number) => number;
|
||||
export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
|
||||
export const __wbindgen_export3: (a: number, b: number, c: number) => void;
|
||||
15
site/decodal-site/src/wasm/package.json
Normal file
15
site/decodal-site/src/wasm/package.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "decodal-wasm",
|
||||
"type": "module",
|
||||
"version": "0.1.0",
|
||||
"files": [
|
||||
"decodal_wasm_bg.wasm",
|
||||
"decodal_wasm.js",
|
||||
"decodal_wasm.d.ts"
|
||||
],
|
||||
"main": "decodal_wasm.js",
|
||||
"types": "decodal_wasm.d.ts",
|
||||
"sideEffects": [
|
||||
"./snippets/*"
|
||||
]
|
||||
}
|
||||
14
site/decodal-site/vite.config.js
Normal file
14
site/decodal-site/vite.config.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
||||
import { defineConfig } from 'vite';
|
||||
import { fileURLToPath, URL } from 'node:url';
|
||||
|
||||
const repoRoot = fileURLToPath(new URL('../..', import.meta.url));
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [svelte()],
|
||||
server: {
|
||||
fs: {
|
||||
allow: [repoRoot],
|
||||
},
|
||||
},
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user