Skip to content

Commit b8cf907

Browse files
authored
feat: add wasm playground (#8)
* feat: add wasm playground * fix * fix: track wasm artifacts with lfs * fix: include wasm package glue for pages * fix: include wasm package js in tsconfig
1 parent 48392f7 commit b8cf907

36 files changed

Lines changed: 2002 additions & 252 deletions

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.wasm filter=lfs diff=lfs merge=lfs -text

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ vsc-ext/*/node_modules/
1111
vsc-ext/*/*.vsix
1212
website/node_modules/
1313
website/dist/
14+
website/src/wasm/pkg/.gitignore
1415

1516
lk-lsp-debug.log
1617
main

Cargo.lock

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ members = [
55
"completion",
66
"stdlib",
77
"stdlib/common",
8+
"stdlib/web",
89
"stdlib/crates/bytes",
910
"stdlib/crates/chan",
1011
"stdlib/crates/datetime",
@@ -31,6 +32,7 @@ members = [
3132
"lkrt",
3233
"lsp",
3334
"tree-sitter-lk",
35+
"wasm",
3436
]
3537
resolver = "2"
3638

@@ -75,6 +77,9 @@ arcstr = { version = "1", features = ["serde"] }
7577
ureq = "2"
7678
url = "2"
7779
uuid = { version = "1", features = ["v4"] }
80+
wasm-bindgen = "0.2"
81+
serde-wasm-bindgen = "0.6"
82+
js-sys = "0.3"
7883

7984
[profile.release]
8085
panic = "abort"

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Run any example: `lk examples/syntax/closure.lk`
4646

4747
- Rust-inspired syntax with first-class named parameters
4848
- Deterministic bytecode VM with optional concurrency runtime
49+
- Browser-playground wasm facade with a safe stdlib subset
4950
- Batteries-included standard library and LSP-backed tooling
5051
- Project website source lives in `website/` and powers [lang.lollipopkit.com](https://lang.lollipopkit.com).
5152

@@ -87,6 +88,19 @@ assert_eq!(result.display_first_return(), "true");
8788

8889
Note: command-line paths must be relative and sanitized.
8990

91+
#### Website and wasm playground
92+
93+
The website lives in `website/` and includes a `/try` route that lazy-loads the LK wasm runtime.
94+
95+
```bash
96+
cd website
97+
bun run build
98+
```
99+
100+
For local development, `bun run build` runs `website/scripts/build-wasm.mjs`, which invokes `wasm-pack build ../wasm --target web --out-dir ../website/src/wasm/pkg --release`, then regenerates i18n types and builds the Vite app. In that workflow, `website/src/wasm/pkg/` is generated output, is typically ignored, and should not be committed. Cloudflare prebuilt/deployment branches use the checked-in wasm package instead (see `website/scripts/build-wasm.mjs`), so the checked-in `website/src/wasm/pkg/` artifacts must be retained on those branches. Workflow summary: local development regenerates and ignores `pkg/`; deployment/Cloudflare prebuild uses retained or CI-produced wasm artifacts.
101+
102+
The browser playground supports single-file execution with stdout capture, return-value display, parse/runtime diagnostics, and an instruction step limit. It intentionally exposes only the browser-safe stdlib subset; native modules such as `fs`, `io`, `net`, `process`, `env`, `os`, `task`, and `chan` are reported as unavailable in the browser.
103+
90104
#### VS Code
91105

92106
The VS Code support is a single merged extension under `vsc-ext/lsp`. It includes `.lk` language registration, TextMate highlighting, snippets, and the LK LSP client with smart completion for stdlib modules, imported aliases, local symbols, named arguments, and common receiver methods. Use `make debug-lsp-ext` for a local Extension Development Host, or `make vsix` to build the VSIX.

README.zh-CN.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ examples/
3838

3939
## 特性
4040

41+
- 类 Rust 语法,支持一等 named parameters
42+
- 确定性的 bytecode VM,并可选启用并发运行时
43+
- 可编译到浏览器 wasm 的 playground facade,内置安全标准库子集
44+
- 标准库、CLI、LSP 与官网源码都在同一仓库内维护
45+
4146
### 用法
4247

4348
#### 集成(库)
@@ -74,6 +79,19 @@ assert_eq!(result.display_first_return(), "true");
7479

7580
注意:命令行参数路径必须为经净化的相对路径。
7681

82+
#### Website 与 wasm playground
83+
84+
官网位于 `website/`,新增 `/try` 路由会按需加载 LK wasm runtime。
85+
86+
```bash
87+
cd website
88+
bun run build
89+
```
90+
91+
本地开发时,`bun run build` 会执行 `website/scripts/build-wasm.mjs`,由它调用 `wasm-pack build ../wasm --target web --out-dir ../website/src/wasm/pkg --release`,再生成 i18n 类型并构建 Vite 应用;此时 `website/src/wasm/pkg/` 属于生成产物,通常被 git 忽略,开发者不应提交。Cloudflare 预构建/部署分支会从仓库读取已构建 wasm package(见 `website/scripts/build-wasm.mjs`),因此这些分支上的 `website/src/wasm/pkg/` 需要存在并提交,或由 CI/CD 构建流程产出。示例:本地开发:忽略并不提交;部署/Cloudflare 预构建分支:需包含或由构建流程产出。
92+
93+
浏览器 playground 支持单文件运行、stdout 捕获、返回值展示、parse/runtime 诊断和指令步数上限。它只暴露浏览器安全标准库子集;`fs``io``net``process``env``os``task``chan` 等 native 模块会明确报告为浏览器不可用。
94+
7795
#### VS Code
7896

7997
VS Code 支持已合并为 `vsc-ext/lsp` 下的单个扩展,包含 `.lk` 语言注册、TextMate 高亮、代码片段,以及带智能补全的 LK LSP 客户端;补全覆盖 stdlib 模块、导入别名、本地符号、named arguments 和常见 receiver 方法。使用 `make debug-lsp-ext` 启动本地 Extension Development Host,或使用 `make vsix` 构建 VSIX。

core/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ name = "lk_core"
1111
crate-type = ["rlib", "staticlib"]
1212

1313
[features]
14-
default = ["llvm"]
14+
default = ["llvm", "async-runtime"]
15+
async-runtime = ["dep:futures", "dep:tokio"]
1516
llvm = ["dep:llvm-tools"]
1617
vm-profile = []
1718

@@ -22,8 +23,8 @@ serde_json = { workspace = true }
2223
serde_yaml = { workspace = true }
2324
toml = { workspace = true }
2425
once_cell = { workspace = true }
25-
tokio = { workspace = true }
26-
futures = { workspace = true }
26+
tokio = { workspace = true, optional = true }
27+
futures = { workspace = true, optional = true }
2728
dashmap = { workspace = true }
2829
tracing = { workspace = true }
2930
chrono = { workspace = true }

core/src/rt.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
#[cfg(feature = "async-runtime")]
12
mod runtime;
3+
#[cfg(not(feature = "async-runtime"))]
4+
mod unsupported;
25

6+
#[cfg(feature = "async-runtime")]
37
pub use runtime::*;
8+
#[cfg(not(feature = "async-runtime"))]
9+
pub use unsupported::*;

core/src/rt/unsupported.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use anyhow::{Result, anyhow};
2+
3+
use crate::{
4+
val::{HeapStore, RuntimeVal},
5+
vm::copy_runtime_value,
6+
};
7+
8+
#[derive(Clone, Debug)]
9+
pub struct RuntimePayload {
10+
pub value: RuntimeVal,
11+
pub heap: HeapStore,
12+
}
13+
14+
impl RuntimePayload {
15+
pub fn new(value: RuntimeVal, heap: HeapStore) -> Self {
16+
Self { value, heap }
17+
}
18+
19+
pub fn copy_from_value(value: &RuntimeVal, heap: &HeapStore) -> Result<Self> {
20+
let mut payload_heap = HeapStore::new();
21+
let value = copy_runtime_value(value, heap, &mut payload_heap)?;
22+
Ok(Self::new(value, payload_heap))
23+
}
24+
25+
pub fn into_value(self, heap: &mut HeapStore) -> Result<RuntimeVal> {
26+
copy_runtime_value(&self.value, &self.heap, heap)
27+
}
28+
29+
pub fn clone_value_into(&self, heap: &mut HeapStore) -> Result<RuntimeVal> {
30+
copy_runtime_value(&self.value, &self.heap, heap)
31+
}
32+
33+
pub fn nil() -> Self {
34+
Self {
35+
value: RuntimeVal::Nil,
36+
heap: HeapStore::new(),
37+
}
38+
}
39+
}
40+
41+
pub fn init_runtime() -> Result<()> {
42+
Ok(())
43+
}
44+
45+
pub fn shutdown_runtime() {}
46+
47+
pub fn with_runtime<F, R>(_f: F) -> Result<R>
48+
where
49+
F: FnOnce(&Runtime) -> Result<R>,
50+
{
51+
Err(anyhow!("LK async runtime is not available in this build"))
52+
}
53+
54+
#[derive(Debug)]
55+
pub struct Runtime;

core/src/stmt/import.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,9 @@ impl ModuleResolver {
151151
}
152152

153153
pub fn resolve_runtime_module(&self, name: &str) -> Result<RuntimeExport> {
154-
if let Ok(module) = self.stdlib_registry.get_runtime_module(name) {
155-
return Ok(module);
154+
match self.stdlib_registry.get_module(name) {
155+
Ok(module) => return module.runtime_exports(),
156+
Err(_) => {}
156157
}
157158
let Some(root) = self.package_modules.get(name) else {
158159
return Err(anyhow!("Module '{}' not found", name));

0 commit comments

Comments
 (0)