From a2f0175ad47da779b08fbd14a8f769c185a0306a Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Mon, 27 Apr 2026 01:44:02 +0300 Subject: [PATCH 1/5] [zig] Implement compiler --- flake.nix | 2 + mkfile | 16 +++++ packages/zig-runtime/.gitattributes | 2 + packages/zig-runtime/compiler/.gitignore | 3 + packages/zig-runtime/compiler/build.zig | 60 +++++++++++++++++++ packages/zig-runtime/compiler/build.zig.zon | 12 ++++ packages/zig-runtime/package.json | 23 +++++++ packages/zig-runtime/public/zig/bin/zig.wasm | 3 + .../zig-runtime/public/zig/libcompiler_rt.a | 3 + packages/zig-runtime/public/zig/zig.tar.gz | 3 + packages/zig-runtime/src/index.ts | 1 + packages/zig-runtime/src/version.ts | 1 + packages/zig-runtime/tsconfig.json | 16 +++++ packages/zig-runtime/vite.config.js | 32 ++++++++++ pnpm-lock.yaml | 37 ++++++++---- 15 files changed, 202 insertions(+), 12 deletions(-) create mode 100644 packages/zig-runtime/.gitattributes create mode 100644 packages/zig-runtime/compiler/.gitignore create mode 100644 packages/zig-runtime/compiler/build.zig create mode 100644 packages/zig-runtime/compiler/build.zig.zon create mode 100644 packages/zig-runtime/package.json create mode 100644 packages/zig-runtime/public/zig/bin/zig.wasm create mode 100644 packages/zig-runtime/public/zig/libcompiler_rt.a create mode 100644 packages/zig-runtime/public/zig/zig.tar.gz create mode 100644 packages/zig-runtime/src/index.ts create mode 100644 packages/zig-runtime/src/version.ts create mode 100644 packages/zig-runtime/tsconfig.json create mode 100644 packages/zig-runtime/vite.config.js diff --git a/flake.nix b/flake.nix index 38db1c60..d9bf6db0 100644 --- a/flake.nix +++ b/flake.nix @@ -59,6 +59,8 @@ pkgs-unstable.gleam pkgs.python315 pkgs.dotnet-sdk_10 + pkgs-unstable.binaryen + pkgs-unstable.zig ]; shellHook = '' source <(COMPLETE=bash mk) diff --git a/mkfile b/mkfile index c0f695e8..887bbcea 100644 --- a/mkfile +++ b/mkfile @@ -198,6 +198,22 @@ ruby/: bun run index.ts popd +zig/: + pushd packages/zig-runtime + b: + pnpm run build + artifacts: compiler/* + compiler/: + pushd compiler + build: + zig build -Drelease + copy: + rm -rf ../public + mkdir -p ../public/zig + cp -r zig-out/* ../public/zig/ + popd + popd + rust/: pushd packages/rust-runtime p: diff --git a/packages/zig-runtime/.gitattributes b/packages/zig-runtime/.gitattributes new file mode 100644 index 00000000..3c55417e --- /dev/null +++ b/packages/zig-runtime/.gitattributes @@ -0,0 +1,2 @@ +*.a filter=lfs diff=lfs merge=lfs -text +*.tar.gz filter=lfs diff=lfs merge=lfs -text diff --git a/packages/zig-runtime/compiler/.gitignore b/packages/zig-runtime/compiler/.gitignore new file mode 100644 index 00000000..d3dd20ee --- /dev/null +++ b/packages/zig-runtime/compiler/.gitignore @@ -0,0 +1,3 @@ +.zig-cache +zig-pkg +zig-out \ No newline at end of file diff --git a/packages/zig-runtime/compiler/build.zig b/packages/zig-runtime/compiler/build.zig new file mode 100644 index 00000000..65ee58e1 --- /dev/null +++ b/packages/zig-runtime/compiler/build.zig @@ -0,0 +1,60 @@ +// This file is derived from: +// https://github.com/zigtools/playground/blob/9f9403892077b7624b97b8c1cd0ca5504afebfe7/build.zig +// Copyright (c) 2023 zls-in-the-browser contributors +// Licensed under the MIT License +// Modifications made by Roman Krasilnikov. + +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.resolveTargetQuery(.{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + }); + const optimize = b.standardOptimizeOption(.{ + .preferred_optimize_mode = .ReleaseSmall, + }); + + const zig_step = b.step("zig", "compile and install Zig"); + const compiler_rt_step = b.step("zig_compiler_rt", "compile and install compiler_rt"); + const tarball_step = b.step("zig_tarball", "compile and install zig.tar.gz"); + + b.getInstallStep().dependOn(zig_step); + b.getInstallStep().dependOn(compiler_rt_step); + b.getInstallStep().dependOn(tarball_step); + + const zig_dependency = b.dependency("zig", .{ + .target = target, + .optimize = optimize, + .@"version-string" = @as([]const u8, "0.17.0"), + .@"no-lib" = true, + .dev = "wasm", + }); + zig_step.dependOn(installArtifact(b, zig_dependency.artifact("zig"))); + + const lib_compiler_rt = b.addLibrary(.{ .linkage = .static, .name = "compiler_rt", .root_module = b.createModule(.{ .root_source_file = zig_dependency.path("lib/compiler_rt.zig"), .target = target, .optimize = optimize }) }); + compiler_rt_step.dependOn(&b.addInstallArtifact(lib_compiler_rt, .{ .dest_dir = .{ .override = .prefix } }).step); + + const run_tar = b.addSystemCommand(&.{ "tar", "-czf" }); + const zig_tar_gz = run_tar.addOutputFileArg("zig.tar.gz"); + tarball_step.dependOn(&b.addInstallFile(zig_tar_gz, "zig.tar.gz").step); + run_tar.addArg("-C"); + run_tar.addDirectoryArg(zig_dependency.path(".")); + run_tar.addArg("lib/std"); +} + +fn installArtifact(b: *std.Build, artifact: *std.Build.Step.Compile) *std.Build.Step { + const wasm_opt = b.addSystemCommand(&.{ + "wasm-opt", + "-Oz", + "--enable-bulk-memory", + "--enable-mutable-globals", + "--enable-nontrapping-float-to-int", + "--enable-sign-ext", + }); + wasm_opt.addArtifactArg(artifact); + wasm_opt.addArg("-o"); + const file_name = b.fmt("{s}.wasm", .{artifact.name}); + const exe = wasm_opt.addOutputFileArg(file_name); + return &b.addInstallBinFile(exe, file_name).step; +} diff --git a/packages/zig-runtime/compiler/build.zig.zon b/packages/zig-runtime/compiler/build.zig.zon new file mode 100644 index 00000000..d3eec275 --- /dev/null +++ b/packages/zig-runtime/compiler/build.zig.zon @@ -0,0 +1,12 @@ +.{ + .name = .compiler, + .version = "0.16.0", + .dependencies = .{ + .zig = .{ + .url = "git+https://github.com/zigtools/zig?ref=wasm32-wasi#1c430bc130989d285723de8b7aa8e3c77297a266", + .hash = "zig-0.0.0-Fp4XJHzK5Q2xL7r676PlbSvBhz_af_mGXkGPVaXvf_Kx", + }, + }, + .paths = .{""}, + .fingerprint = 0xaa62bd49ef1e104b, +} diff --git a/packages/zig-runtime/package.json b/packages/zig-runtime/package.json new file mode 100644 index 00000000..429ef135 --- /dev/null +++ b/packages/zig-runtime/package.json @@ -0,0 +1,23 @@ +{ + "name": "zig-runtime", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "check": "tsc", + "build": "vite build" + }, + "dependencies": { + "libs": "workspace:*" + }, + "devDependencies": { + "vite": "catalog:", + "vite-plugin-dts": "catalog:" + }, + "exports": { + ".": "./dist/index.js", + "./version": "./dist/version.js", + "./zig.wasm": "./dist/zig/bin/zig.wasm", + "./lib/*": "./dist/zig/*" + } +} \ No newline at end of file diff --git a/packages/zig-runtime/public/zig/bin/zig.wasm b/packages/zig-runtime/public/zig/bin/zig.wasm new file mode 100644 index 00000000..1619a925 --- /dev/null +++ b/packages/zig-runtime/public/zig/bin/zig.wasm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1819113bdbdbc209e7d4ba4b0cb947af4dd40eb63e6b151c3f94a5902686a5b0 +size 3302930 diff --git a/packages/zig-runtime/public/zig/libcompiler_rt.a b/packages/zig-runtime/public/zig/libcompiler_rt.a new file mode 100644 index 00000000..5bb3b614 --- /dev/null +++ b/packages/zig-runtime/public/zig/libcompiler_rt.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5be758b882d6deea103a181b38a4e94048b2bedc336dbaabdfca32aeb4d10b3a +size 168776 diff --git a/packages/zig-runtime/public/zig/zig.tar.gz b/packages/zig-runtime/public/zig/zig.tar.gz new file mode 100644 index 00000000..aae24733 --- /dev/null +++ b/packages/zig-runtime/public/zig/zig.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2930a128e04c5b9d97686b6d856958781cc72970bca9ba40e41501bc094cec4c +size 3418060 diff --git a/packages/zig-runtime/src/index.ts b/packages/zig-runtime/src/index.ts new file mode 100644 index 00000000..61d366eb --- /dev/null +++ b/packages/zig-runtime/src/index.ts @@ -0,0 +1 @@ +export const foo = "foo"; diff --git a/packages/zig-runtime/src/version.ts b/packages/zig-runtime/src/version.ts new file mode 100644 index 00000000..5314a97f --- /dev/null +++ b/packages/zig-runtime/src/version.ts @@ -0,0 +1 @@ +export const version = "0.16.0"; diff --git a/packages/zig-runtime/tsconfig.json b/packages/zig-runtime/tsconfig.json new file mode 100644 index 00000000..5366bc2f --- /dev/null +++ b/packages/zig-runtime/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "@total-typescript/tsconfig/bundler/dom/library-monorepo", + "compilerOptions": { + "lib": [ + "ES2022", + "ESNext.Disposable", + "DOM" + ], + "noUncheckedIndexedAccess": false, + "rootDir": "src", + "types": ["vite/client"] + }, + "include": [ + "src/**/*" + ], +} \ No newline at end of file diff --git a/packages/zig-runtime/vite.config.js b/packages/zig-runtime/vite.config.js new file mode 100644 index 00000000..8ac4ed8e --- /dev/null +++ b/packages/zig-runtime/vite.config.js @@ -0,0 +1,32 @@ +import { resolve } from "path"; +import { defineConfig } from "vite"; +import dts from "vite-plugin-dts"; + +export default defineConfig({ + build: { + lib: { + // Could also be a dictionary or array of multiple entry points + entry: { + index: resolve(__dirname, "src/index.ts"), + version: resolve(__dirname, "src/version.ts"), + }, + formats: ["es"], + // name: "MyLib", + // the proper extensions will be added + // fileName: "index", + }, + rollupOptions: { + // make sure to externalize deps that shouldn't be bundled + // into your library + external: [/^libs\//], + output: { + // Provide global variables to use in the UMD build + // for externalized deps + globals: { + // vue: "Vue", + }, + }, + }, + }, + plugins: [dts()], +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ceb5c6df..a43e8ae3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -218,7 +218,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) vite-plugin-static-copy: specifier: 'catalog:' version: 4.1.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) @@ -234,7 +234,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) vite-plugin-static-copy: specifier: 'catalog:' version: 4.1.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) @@ -253,7 +253,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) packages/java-runtime: dependencies: @@ -306,7 +306,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) vite-plugin-static-copy: specifier: 'catalog:' version: 4.1.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) @@ -325,7 +325,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) vite-plugin-static-copy: specifier: 'catalog:' version: 4.1.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) @@ -350,7 +350,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) vite-plugin-static-copy: specifier: 'catalog:' version: 4.1.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) @@ -369,7 +369,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) packages/typescript-runtime: dependencies: @@ -377,6 +377,19 @@ importers: specifier: 6.0.3 version: 6.0.3 + packages/zig-runtime: + dependencies: + libs: + specifier: workspace:* + version: link:../libs + devDependencies: + vite: + specifier: 'catalog:' + version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) + vite-plugin-dts: + specifier: 'catalog:' + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + packages: '@antfu/install-pkg@1.1.0': @@ -3666,7 +3679,7 @@ snapshots: de-indent: 1.0.2 he: 1.2.0 - '@vue/language-core@2.2.0(typescript@5.9.3)': + '@vue/language-core@2.2.0(typescript@6.0.3)': dependencies: '@volar/language-core': 2.4.11 '@vue/compiler-dom': 3.5.7 @@ -3677,7 +3690,7 @@ snapshots: muggle-string: 0.4.1 path-browserify: 1.0.1 optionalDependencies: - typescript: 5.9.3 + typescript: 6.0.3 '@vue/shared@3.5.7': {} @@ -4858,18 +4871,18 @@ snapshots: uuid: 11.1.0 vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) - vite-plugin-dts@4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)): + vite-plugin-dts@4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)): dependencies: '@microsoft/api-extractor': 7.54.0(@types/node@24.12.2) '@rollup/pluginutils': 5.1.4(rollup@4.53.2) '@volar/typescript': 2.4.11 - '@vue/language-core': 2.2.0(typescript@5.9.3) + '@vue/language-core': 2.2.0(typescript@6.0.3) compare-versions: 6.1.1 debug: 4.4.3 kolorist: 1.8.0 local-pkg: 1.1.2 magic-string: 0.30.21 - typescript: 5.9.3 + typescript: 6.0.3 optionalDependencies: vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) transitivePeerDependencies: From 807cb620c18cfedc31140a01a180828b6468bd87 Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Mon, 27 Apr 2026 11:05:48 +0300 Subject: [PATCH 2/5] [zig] Implement zig program --- packages/libs/package.json | 5 ++ packages/libs/src/actor/model.ts | 21 ++++--- packages/libs/src/context.ts | 2 +- packages/libs/src/type.ts | 6 +- .../src/wasi.ts => libs/src/wasi/fs.ts} | 7 ++- packages/libs/src/wasi/index.ts | 2 + .../src => libs/src/wasi}/stdin.ts | 2 +- packages/ruby-runtime/src/ruby-vm-factory.ts | 48 +++++++++------ packages/ruby-runtime/src/stdin.ts | 61 ------------------- packages/rust-runtime/src/create-wasi.ts | 14 ++--- packages/rust-runtime/src/rust-program.ts | 7 +-- .../rust-runtime/src/rust-test-program.ts | 7 +-- packages/zig-runtime/package.json | 1 + packages/zig-runtime/src/create-wasi.ts | 58 ++++++++++++++++++ packages/zig-runtime/src/index.ts | 3 +- packages/zig-runtime/src/zig-program.ts | 24 ++++++++ pnpm-lock.yaml | 6 ++ 17 files changed, 160 insertions(+), 114 deletions(-) rename packages/{rust-runtime/src/wasi.ts => libs/src/wasi/fs.ts} (91%) create mode 100644 packages/libs/src/wasi/index.ts rename packages/{rust-runtime/src => libs/src/wasi}/stdin.ts (98%) delete mode 100644 packages/ruby-runtime/src/stdin.ts create mode 100644 packages/zig-runtime/src/create-wasi.ts create mode 100644 packages/zig-runtime/src/zig-program.ts diff --git a/packages/libs/package.json b/packages/libs/package.json index f749d552..1f4cd592 100644 --- a/packages/libs/package.json +++ b/packages/libs/package.json @@ -18,12 +18,17 @@ "./compiler/actor": "./dist/compiler/actor.js", "./testing": "./dist/testing/index.js", "./testing/actor": "./dist/testing/actor.js", + "./wasi": "./dist/wasi/index.js", "./*": { "types": "./dist/*.d.ts", "default": "./dist/*.js" } }, + "peerDependencies": { + "@bjorn3/browser_wasi_shim": "^0.4.0" + }, "devDependencies": { + "@bjorn3/browser_wasi_shim": "^0.4.2", "@types/node": "^24.10.1", "vite": "catalog:", "vitest": "catalog:" diff --git a/packages/libs/src/actor/model.ts b/packages/libs/src/actor/model.ts index d43db2be..3a1023ef 100644 --- a/packages/libs/src/actor/model.ts +++ b/packages/libs/src/actor/model.ts @@ -1,6 +1,6 @@ -import type { Brand, AnyKey } from "../type.js"; +import type { Brand } from "../type.js"; import type { Result } from "../result.js"; -import type { Context } from '../context.js'; +import type { Context } from "../context.js"; export enum MessageType { Event = "event", @@ -12,23 +12,28 @@ export interface AbstractMessage { type: T; } -export interface EventMessage - extends AbstractMessage { +export interface EventMessage< + T extends PropertyKey, + P, +> extends AbstractMessage { event: T; payload: P; } export type RequestId = Brand<"RequestId", number>; -export interface RequestMessage - extends AbstractMessage { +export interface RequestMessage< + T extends PropertyKey, + P, +> extends AbstractMessage { id: RequestId; request: T; payload: P; } -export interface ResponseMessage - extends AbstractMessage { +export interface ResponseMessage< + R, +> extends AbstractMessage { id: RequestId; result: R; } diff --git a/packages/libs/src/context.ts b/packages/libs/src/context.ts index b9e7d325..44995391 100644 --- a/packages/libs/src/context.ts +++ b/packages/libs/src/context.ts @@ -71,7 +71,7 @@ export interface RecoverableContext extends Disposable { } export function createRecoverableContext( - contextFactory: () => [Context, () => void] + contextFactory: () => [Context, () => void], ): RecoverableContext { let [ref, cancel] = contextFactory(); const disposable = ref.onCancel(function handleCancel() { diff --git a/packages/libs/src/type.ts b/packages/libs/src/type.ts index d2b797a2..bcc92814 100644 --- a/packages/libs/src/type.ts +++ b/packages/libs/src/type.ts @@ -1,3 +1,3 @@ -export type Brand = Base & { __brand: Name } - -export type AnyKey = keyof any; +export type Brand = Base & { + __brand: Name; +}; diff --git a/packages/rust-runtime/src/wasi.ts b/packages/libs/src/wasi/fs.ts similarity index 91% rename from packages/rust-runtime/src/wasi.ts rename to packages/libs/src/wasi/fs.ts index c5fa0078..b97a5e4d 100644 --- a/packages/rust-runtime/src/wasi.ts +++ b/packages/libs/src/wasi/fs.ts @@ -6,7 +6,8 @@ import { wasi as wasiDef, File, } from "@bjorn3/browser_wasi_shim"; -import { type Result, err, ok, isErr } from "libs/result"; + +import { type Result, err, ok, isErr } from "../result.js"; export function contents(data: Record): Map { return new Map(Object.entries(data)); @@ -24,7 +25,7 @@ export function assertOpenDir(fd: Fd): asserts fd is OpenDirectory { export function lookup( dir: OpenDirectory, - path: string + path: string, ): Result { const { ret, inode_obj } = dir.path_lookup(path, 0); if (ret !== wasiDef.ERRNO_SUCCESS || !inode_obj) { @@ -35,7 +36,7 @@ export function lookup( export function lookupFile( dir: OpenDirectory, - path: string + path: string, ): Result { const r = lookup(dir, path); if (isErr(r)) { diff --git a/packages/libs/src/wasi/index.ts b/packages/libs/src/wasi/index.ts new file mode 100644 index 00000000..51bf7a20 --- /dev/null +++ b/packages/libs/src/wasi/index.ts @@ -0,0 +1,2 @@ +export * from "./fs.js"; +export * from "./stdin.js"; diff --git a/packages/rust-runtime/src/stdin.ts b/packages/libs/src/wasi/stdin.ts similarity index 98% rename from packages/rust-runtime/src/stdin.ts rename to packages/libs/src/wasi/stdin.ts index 7f4e0cfa..59c3a911 100644 --- a/packages/rust-runtime/src/stdin.ts +++ b/packages/libs/src/wasi/stdin.ts @@ -15,7 +15,7 @@ export class Stdin extends Fd { const filestat = new wasi.Filestat( this.ino, wasi.FILETYPE_CHARACTER_DEVICE, - BigInt(0) + BigInt(0), ); return { ret: 0, filestat }; } diff --git a/packages/ruby-runtime/src/ruby-vm-factory.ts b/packages/ruby-runtime/src/ruby-vm-factory.ts index a052a490..3927e62c 100644 --- a/packages/ruby-runtime/src/ruby-vm-factory.ts +++ b/packages/ruby-runtime/src/ruby-vm-factory.ts @@ -1,37 +1,45 @@ -import { RubyVM } from '@ruby/wasm-wasi' -import { Fd, PreopenDirectory, WASI, ConsoleStdout } from "@bjorn3/browser_wasi_shim"; -import { inContext, type Context } from 'libs/context'; -import type { Streams } from 'libs/io'; +import { RubyVM } from "@ruby/wasm-wasi"; +import { + Fd, + PreopenDirectory, + WASI, + ConsoleStdout, +} from "@bjorn3/browser_wasi_shim"; +import { inContext, type Context } from "libs/context"; +import type { Streams } from "libs/io"; +import { Stdin } from "libs/wasi"; -import { Stdin } from './stdin'; - -export async function createRubyVM ( +// https://github.com/ruby/ruby.wasm/blob/ef0300af384779db2e5de1e8c4528597d28eabe2/packages/npm-packages/ruby-wasm-wasi/src/vm.ts#L126 +export async function createRubyVM( ctx: Context, streams: Streams, wasmModule: WebAssembly.Module, ) { - const args: string[] = [] - const env: string[] = [] + const args: string[] = []; + const env: string[] = []; const fds: Fd[] = [ new Stdin(streams.in.read.bind(streams.in)), new ConsoleStdout(streams.out.write.bind(streams.out)), new ConsoleStdout(streams.err.write.bind(streams.err)), new PreopenDirectory("/", new Map()), - ] - const wasi = new WASI(args, env, fds, { debug: false }) - const vm = new RubyVM() + ]; + const wasi = new WASI(args, env, fds, { debug: false }); + const vm = new RubyVM(); const imports = { - wasi_snapshot_preview1: wasi.wasiImport - } - vm.addToImports(imports) + wasi_snapshot_preview1: wasi.wasiImport, + }; + vm.addToImports(imports); - const instance = await inContext(ctx, WebAssembly.instantiate(wasmModule, imports)) - await inContext(ctx, vm.setInstance(instance)) + const instance = await inContext( + ctx, + WebAssembly.instantiate(wasmModule, imports), + ); + await inContext(ctx, vm.setInstance(instance)); //@ts-expect-error lack of type information - wasi.initialize(instance) - vm.initialize() + wasi.initialize(instance); + vm.initialize(); - return vm + return vm; } diff --git a/packages/ruby-runtime/src/stdin.ts b/packages/ruby-runtime/src/stdin.ts deleted file mode 100644 index dbe515a8..00000000 --- a/packages/ruby-runtime/src/stdin.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { Fd, Inode, wasi } from "@bjorn3/browser_wasi_shim"; - -export class Stdin extends Fd { - private ino: bigint; - buffer: Uint8Array = new Uint8Array(); - read: () => Uint8Array; - - constructor(read: () => Uint8Array) { - super(); - this.ino = Inode.issue_ino() - this.read = read; - } - - override fd_filestat_get(): { ret: number; filestat: wasi.Filestat } { - const filestat = new wasi.Filestat( - this.ino, - wasi.FILETYPE_CHARACTER_DEVICE, - BigInt(0) - ); - return { ret: 0, filestat }; - } - - override fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat | null } { - const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0); - fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_READ); - return { ret: 0, fdstat }; - } - - override fd_read(size: number): { ret: number; data: Uint8Array } { - const data = this.read_data(size); - return { ret: 0, data }; - } - - protected read_data(size: number) { - const bl = this.buffer.length; - if (bl >= size) { - const data = this.buffer.subarray(0, size); - this.buffer = this.buffer.subarray(size); - return data; - } - const input = this.read(); - let next: Uint8Array; - if (bl === 0) { - if (input.length <= size) { - return input; - } - next = input; - } else { - next = new Uint8Array(bl + input.length); - next.set(this.buffer); - next.set(input, bl); - if (next.length < size) { - this.buffer = this.buffer.subarray(0, 0); - return next; - } - } - const data = next.subarray(0, size); - this.buffer = next.subarray(size); - return data; - } -} diff --git a/packages/rust-runtime/src/create-wasi.ts b/packages/rust-runtime/src/create-wasi.ts index d6ba021a..ec84a4d2 100644 --- a/packages/rust-runtime/src/create-wasi.ts +++ b/packages/rust-runtime/src/create-wasi.ts @@ -7,13 +7,11 @@ import { Directory, } from "@bjorn3/browser_wasi_shim"; import type { Streams } from "libs/io"; - -import { contents, dir } from "./wasi.js"; -import { Stdin } from "./stdin"; +import { contents, dir, Stdin } from "libs/wasi"; export function createWASI( streams: Streams, - libs: [string, ArrayBuffer][] + libs: [string, ArrayBuffer][], ): WASI { const env: string[] = []; const args = [ @@ -46,19 +44,19 @@ export function createWASI( "x86_64-unknown-linux-gnu": dir({ lib: new Directory( libs.map( - ([lib, buffer]) => [lib, new File(buffer)] as [string, File] - ) + ([lib, buffer]) => [lib, new File(buffer)] as [string, File], + ), ), }), }), }), - }) + }), ); const rootDir = new PreopenDirectory( "/", contents({ "main.rs": new File([]), - }) + }), ); const descriptors: Array = [ new Stdin(streams.in.read.bind(streams.in)), diff --git a/packages/rust-runtime/src/rust-program.ts b/packages/rust-runtime/src/rust-program.ts index 0353eade..cdca1a9f 100644 --- a/packages/rust-runtime/src/rust-program.ts +++ b/packages/rust-runtime/src/rust-program.ts @@ -2,8 +2,7 @@ import type { OpenDirectory, WASI } from "@bjorn3/browser_wasi_shim"; import type { Program } from "libs/compiler"; import { inContext, type Context } from "libs/context"; import { isErr } from "libs/result"; - -import { assertOpenDir, lookupFile } from "./wasi"; +import { assertOpenDir, lookupFile } from "libs/wasi"; // TODO: extract common code with RustTestProgram export class RustProgram implements Program { @@ -12,7 +11,7 @@ export class RustProgram implements Program { constructor( protected readonly code: string, protected readonly wasi: WASI, - protected readonly miriModule: WebAssembly.Module + protected readonly miriModule: WebAssembly.Module, ) {} async run(ctx: Context): Promise { @@ -33,7 +32,7 @@ export class RustProgram implements Program { }, }, wasi_snapshot_preview1: this.wasi.wasiImport, - }) + }), ); // @ts-expect-error lack of type information const exitCode = this.wasi.start(instance); diff --git a/packages/rust-runtime/src/rust-test-program.ts b/packages/rust-runtime/src/rust-test-program.ts index b2153e22..8101417b 100644 --- a/packages/rust-runtime/src/rust-test-program.ts +++ b/packages/rust-runtime/src/rust-test-program.ts @@ -2,8 +2,7 @@ import { Fd, WASI, OpenDirectory, File } from "@bjorn3/browser_wasi_shim"; import type { TestProgram } from "libs/testing"; import { inContext, type Context } from "libs/context"; import { isErr } from "libs/result"; - -import { assertOpenDir, lookupFile } from "./wasi"; +import { assertOpenDir, lookupFile } from "libs/wasi"; export abstract class RustTestProgram implements TestProgram { protected threads_count = 1; @@ -12,7 +11,7 @@ export abstract class RustTestProgram implements TestProgram { protected readonly code: string, protected readonly wasi: WASI, protected readonly miriModule: WebAssembly.Module, - protected readonly outputPath: string + protected readonly outputPath: string, ) {} async run(ctx: Context, input: I): Promise { @@ -33,7 +32,7 @@ export abstract class RustTestProgram implements TestProgram { }, }, wasi_snapshot_preview1: this.wasi.wasiImport, - }) + }), ); // @ts-expect-error lack of type information const exitCode = this.wasi.start(instance); diff --git a/packages/zig-runtime/package.json b/packages/zig-runtime/package.json index 429ef135..753298ab 100644 --- a/packages/zig-runtime/package.json +++ b/packages/zig-runtime/package.json @@ -8,6 +8,7 @@ "build": "vite build" }, "dependencies": { + "@bjorn3/browser_wasi_shim": "^0.4.2", "libs": "workspace:*" }, "devDependencies": { diff --git a/packages/zig-runtime/src/create-wasi.ts b/packages/zig-runtime/src/create-wasi.ts new file mode 100644 index 00000000..12f323d4 --- /dev/null +++ b/packages/zig-runtime/src/create-wasi.ts @@ -0,0 +1,58 @@ +import { + ConsoleStdout, + Inode, + PreopenDirectory, + WASI, + File, +} from "@bjorn3/browser_wasi_shim"; +import type { Streams } from "libs/io"; +import { Stdin } from "libs/wasi"; + +export interface CompilerWASIOptions { + streams: Streams; + source: string; + libCompilerRt: ArrayBuffer; +} +const compilerArgs = [ + "zig.wasm", + "build-exe", + "main.zig", + "libcompiler_rt.a", + "-fno-compiler-rt", // manually linked because the self hosted webassembly backend cannot compile it by itself + "-fno-entry", // prevent the native webassembly backend from adding a start function to the module +]; +const compilerEnv: string[] = []; +const textEncoder = new TextEncoder(); +export function createCompilerWASI({ + streams, + source, + libCompilerRt, +}: CompilerWASIOptions) { + const descriptors = [ + new Stdin(streams.in.read.bind(streams.in)), + new ConsoleStdout(streams.out.write.bind(streams.out)), + new ConsoleStdout(streams.err.write.bind(streams.err)), + new PreopenDirectory( + ".", + new Map([ + ["main.zig", new File(textEncoder.encode(source))], + ["libcompiler_rt.a", new File(libCompilerRt)], + ]), + ), + new PreopenDirectory("/lib", new Map()), + new PreopenDirectory("/cache", new Map()), + ]; + return new WASI(compilerArgs, compilerEnv, descriptors, { debug: false }); +} + +const programArgs = ["main.wasm"]; +const programEnv: string[] = []; +export function createProgramWASI(streams: Streams) { + const descriptors = [ + new Stdin(streams.in.read.bind(streams.in)), + new ConsoleStdout(streams.out.write.bind(streams.out)), + new ConsoleStdout(streams.err.write.bind(streams.err)), + new PreopenDirectory(".", new Map()), + ]; + return new WASI(programArgs, programEnv, descriptors, { debug: false }); +} diff --git a/packages/zig-runtime/src/index.ts b/packages/zig-runtime/src/index.ts index 61d366eb..8db89bcf 100644 --- a/packages/zig-runtime/src/index.ts +++ b/packages/zig-runtime/src/index.ts @@ -1 +1,2 @@ -export const foo = "foo"; +export * from "./zig-program.js"; +export * from "./create-wasi.js"; diff --git a/packages/zig-runtime/src/zig-program.ts b/packages/zig-runtime/src/zig-program.ts new file mode 100644 index 00000000..4da5d8b2 --- /dev/null +++ b/packages/zig-runtime/src/zig-program.ts @@ -0,0 +1,24 @@ +import type { WASI } from "@bjorn3/browser_wasi_shim"; +import type { Program } from "libs/compiler"; +import { inContext, type Context } from "libs/context"; + +export class ZigProgram implements Program { + constructor( + protected readonly wasi: WASI, + protected readonly programModule: BufferSource, + ) {} + + async run(ctx: Context): Promise { + const { instance } = await inContext( + ctx, + WebAssembly.instantiate(this.programModule, { + wasi_snapshot_preview1: this.wasi.wasiImport, + }), + ); + // @ts-expect-error lack of type information + const exitCode = this.wasi.start(instance); + if (exitCode !== 0) { + throw new Error(`Code execution failed with exit code ${exitCode}`); + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a43e8ae3..f2e0d197 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -279,6 +279,9 @@ importers: packages/libs: devDependencies: + '@bjorn3/browser_wasi_shim': + specifier: ^0.4.2 + version: 0.4.2 '@types/node': specifier: ^24.10.1 version: 24.12.2 @@ -379,6 +382,9 @@ importers: packages/zig-runtime: dependencies: + '@bjorn3/browser_wasi_shim': + specifier: ^0.4.2 + version: 0.4.2 libs: specifier: workspace:* version: link:../libs From 365e5a3e1f8f3eefc2e5e618bec75fb0e8050f2c Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Mon, 27 Apr 2026 18:23:45 +0300 Subject: [PATCH 3/5] [zig] Introduce zig compiler --- packages/zig-runtime/package.json | 5 +- packages/zig-runtime/src/create-wasi.ts | 65 +++++++++++++++++++----- packages/zig-runtime/src/index.ts | 3 +- packages/zig-runtime/src/zig-compiler.ts | 52 +++++++++++++++++++ packages/zig-runtime/vite.config.js | 2 +- 5 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 packages/zig-runtime/src/zig-compiler.ts diff --git a/packages/zig-runtime/package.json b/packages/zig-runtime/package.json index 753298ab..97999c7c 100644 --- a/packages/zig-runtime/package.json +++ b/packages/zig-runtime/package.json @@ -7,11 +7,14 @@ "check": "tsc", "build": "vite build" }, + "peerDependencies": { + "@bjorn3/browser_wasi_shim": "^0.4.0" + }, "dependencies": { - "@bjorn3/browser_wasi_shim": "^0.4.2", "libs": "workspace:*" }, "devDependencies": { + "@bjorn3/browser_wasi_shim": "catalog:", "vite": "catalog:", "vite-plugin-dts": "catalog:" }, diff --git a/packages/zig-runtime/src/create-wasi.ts b/packages/zig-runtime/src/create-wasi.ts index 12f323d4..40b6641c 100644 --- a/packages/zig-runtime/src/create-wasi.ts +++ b/packages/zig-runtime/src/create-wasi.ts @@ -4,15 +4,45 @@ import { PreopenDirectory, WASI, File, + Directory, } from "@bjorn3/browser_wasi_shim"; import type { Streams } from "libs/io"; import { Stdin } from "libs/wasi"; -export interface CompilerWASIOptions { - streams: Streams; - source: string; - libCompilerRt: ArrayBuffer; +interface FileData { + path: string[]; + data: Uint8Array; } + +function convert(files: FileData[], i = 0): Directory { + const data = new Map(); + while (files.length > 0) { + const toConvert: FileData[] = []; + let wIndex = 0; + let key: string | undefined = undefined; + for (let rIndex = 0; rIndex < files.length; rIndex++) { + const f = files[rIndex]; + if (f.path.length <= i) { + throw new Error(`Path too short at index ${i}: ${f.path}`); + } else if (f.path.length - 1 === i) { + data.set(f.path[i], new File(f.data)); + } else if (key === undefined) { + key = f.path[i]; + toConvert.push(f); + } else if (f.path[i] === key) { + toConvert.push(f); + } else { + files[wIndex++] = files[rIndex]; + } + } + files.length = wIndex; + if (toConvert.length > 0) { + data.set(key!, convert(toConvert, i + 1)); + } + } + return new Directory(data); +} + const compilerArgs = [ "zig.wasm", "build-exe", @@ -22,12 +52,23 @@ const compilerArgs = [ "-fno-entry", // prevent the native webassembly backend from adding a start function to the module ]; const compilerEnv: string[] = []; -const textEncoder = new TextEncoder(); -export function createCompilerWASI({ - streams, - source, - libCompilerRt, -}: CompilerWASIOptions) { +const LIB_PREFIX = "lib/"; +export function createCompilerWASI( + streams: Streams, + libCompilerRt: ArrayBuffer, + stdLibFiles: { + filename: string; + fileData: Uint8Array; + }[], +) { + const files: FileData[] = []; + for (const f of stdLibFiles) { + if (!f.filename.startsWith(LIB_PREFIX)) { + continue; + } + const path = f.filename.slice(LIB_PREFIX.length).split("/"); + files.push({ path, data: f.fileData }); + } const descriptors = [ new Stdin(streams.in.read.bind(streams.in)), new ConsoleStdout(streams.out.write.bind(streams.out)), @@ -35,11 +76,11 @@ export function createCompilerWASI({ new PreopenDirectory( ".", new Map([ - ["main.zig", new File(textEncoder.encode(source))], + ["main.zig", new File([])], ["libcompiler_rt.a", new File(libCompilerRt)], ]), ), - new PreopenDirectory("/lib", new Map()), + new PreopenDirectory("/lib", convert(files).contents), new PreopenDirectory("/cache", new Map()), ]; return new WASI(compilerArgs, compilerEnv, descriptors, { debug: false }); diff --git a/packages/zig-runtime/src/index.ts b/packages/zig-runtime/src/index.ts index 8db89bcf..26a727d4 100644 --- a/packages/zig-runtime/src/index.ts +++ b/packages/zig-runtime/src/index.ts @@ -1,2 +1,3 @@ -export * from "./zig-program.js"; export * from "./create-wasi.js"; +export * from "./zig-compiler.js"; +export * from "./zig-program.js"; diff --git a/packages/zig-runtime/src/zig-compiler.ts b/packages/zig-runtime/src/zig-compiler.ts new file mode 100644 index 00000000..56c42f96 --- /dev/null +++ b/packages/zig-runtime/src/zig-compiler.ts @@ -0,0 +1,52 @@ +import type { OpenDirectory, WASI } from "@bjorn3/browser_wasi_shim"; +import { inContext, type Context } from "libs/context"; +import { isErr } from "libs/result"; +import { assertOpenDir, lookupFile } from "libs/wasi"; + +export class ZigCompiler { + protected textEncoder = new TextEncoder(); + + constructor( + protected readonly wasi: WASI, + protected readonly zigModule: WebAssembly.Module, + ) {} + + async compile(ctx: Context, source: string) { + this.writeSourceCode(source); + const instance = await inContext( + ctx, + WebAssembly.instantiate(this.zigModule, { + wasi_snapshot_preview1: this.wasi.wasiImport, + }), + ); + // @ts-expect-error lack of type information + const exitCode = this.wasi.start(instance); + if (exitCode !== 0) { + throw new Error(`Code execution failed with exit code ${exitCode}`); + } + return this.getWasmFile().data; + } + + protected get rootDir(): OpenDirectory { + const dir = this.wasi.fds[3]; + console.log(dir); + assertOpenDir(dir); + return dir; + } + + protected writeSourceCode(code: string) { + const file = lookupFile(this.rootDir, "main.zig"); + if (isErr(file)) { + throw new Error(`Failed to read main file: ${file.error}`); + } + file.value.data = this.textEncoder.encode(code); + } + + protected getWasmFile() { + const file = lookupFile(this.rootDir, "main.wasm"); + if (isErr(file)) { + throw new Error(`Failed to read compiled file: ${file.error}`); + } + return file.value; + } +} diff --git a/packages/zig-runtime/vite.config.js b/packages/zig-runtime/vite.config.js index 8ac4ed8e..5b18c7a6 100644 --- a/packages/zig-runtime/vite.config.js +++ b/packages/zig-runtime/vite.config.js @@ -18,7 +18,7 @@ export default defineConfig({ rollupOptions: { // make sure to externalize deps that shouldn't be bundled // into your library - external: [/^libs\//], + external: [/^libs\//, "@bjorn3/browser_wasi_shim"], output: { // Provide global variables to use in the UMD build // for externalized deps From 11eca3ac40030a589a49786e9b4a221910ec3390 Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Mon, 27 Apr 2026 18:24:58 +0300 Subject: [PATCH 4/5] [libs] Mark `@bjorn3/browser_wasi_shim` as peer dependency --- packages/ruby-runtime/package.json | 5 ++++- packages/ruby-runtime/vite.config.js | 2 +- packages/rust-runtime/package.json | 5 ++++- packages/rust-runtime/vite.config.js | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/ruby-runtime/package.json b/packages/ruby-runtime/package.json index 26459b19..18883173 100644 --- a/packages/ruby-runtime/package.json +++ b/packages/ruby-runtime/package.json @@ -7,12 +7,15 @@ "check": "tsc", "build": "vite build" }, + "peerDependencies": { + "@bjorn3/browser_wasi_shim": "^0.4.0" + }, "dependencies": { - "@bjorn3/browser_wasi_shim": "^0.4.2", "@ruby/wasm-wasi": "2.9.3-2.9.4", "libs": "workspace:*" }, "devDependencies": { + "@bjorn3/browser_wasi_shim": "catalog:", "@ruby/4.0-wasm-wasi": "2.9.3-2.9.4", "vite": "catalog:", "vite-plugin-dts": "catalog:", diff --git a/packages/ruby-runtime/vite.config.js b/packages/ruby-runtime/vite.config.js index 10e189e2..59361a13 100644 --- a/packages/ruby-runtime/vite.config.js +++ b/packages/ruby-runtime/vite.config.js @@ -19,7 +19,7 @@ export default defineConfig({ rollupOptions: { // make sure to externalize deps that shouldn't be bundled // into your library - external: [/^libs\//], + external: [/^libs\//, "@bjorn3/browser_wasi_shim"], output: { // Provide global variables to use in the UMD build // for externalized deps diff --git a/packages/rust-runtime/package.json b/packages/rust-runtime/package.json index 88a760b4..56edc246 100644 --- a/packages/rust-runtime/package.json +++ b/packages/rust-runtime/package.json @@ -7,11 +7,14 @@ "check": "tsc", "build": "vite build" }, + "peerDependencies": { + "@bjorn3/browser_wasi_shim": "^0.4.0" + }, "dependencies": { - "@bjorn3/browser_wasi_shim": "^0.4.2", "libs": "workspace:*" }, "devDependencies": { + "@bjorn3/browser_wasi_shim": "catalog:", "vite": "catalog:", "vite-plugin-dts": "catalog:" }, diff --git a/packages/rust-runtime/vite.config.js b/packages/rust-runtime/vite.config.js index 8ac4ed8e..5b18c7a6 100644 --- a/packages/rust-runtime/vite.config.js +++ b/packages/rust-runtime/vite.config.js @@ -18,7 +18,7 @@ export default defineConfig({ rollupOptions: { // make sure to externalize deps that shouldn't be bundled // into your library - external: [/^libs\//], + external: [/^libs\//, "@bjorn3/browser_wasi_shim"], output: { // Provide global variables to use in the UMD build // for externalized deps From cfd63c5bfb4fad7a1f4b861efb448299473df8a1 Mon Sep 17 00:00:00 2001 From: Roman Krasilnikov Date: Mon, 27 Apr 2026 18:26:02 +0300 Subject: [PATCH 5/5] [app] Add support for `Zig` language --- apps/ppp/package.json | 4 +- apps/ppp/src/lib/fetch.ts | 3 +- apps/ppp/src/lib/language.ts | 10 +- apps/ppp/src/lib/monaco.ts | 96 ++--- apps/ppp/src/lib/runtime/descriptions.ts | 46 +-- apps/ppp/src/lib/runtime/test-descriptions.ts | 46 +-- .../src/lib/runtime/zig/compiler-factory.ts | 58 +++ .../src/lib/runtime/zig/description.svelte | 9 + apps/ppp/src/lib/runtime/zig/worker.ts | 6 + apps/ppp/src/lib/zig/LICENSE | 21 ++ apps/ppp/src/lib/zig/README.md | 3 + .../ppp/src/lib/zig/language-configuration.ts | 55 +++ apps/ppp/src/lib/zig/zig.tmLanguage.json | 340 ++++++++++++++++++ apps/ppp/src/routes/(app)/editor/_program.zig | 5 + apps/ppp/src/routes/(app)/editor/runtimes.ts | 6 + mkfile | 26 +- pnpm-lock.yaml | 58 +-- pnpm-workspace.yaml | 1 + 18 files changed, 666 insertions(+), 127 deletions(-) create mode 100644 apps/ppp/src/lib/runtime/zig/compiler-factory.ts create mode 100644 apps/ppp/src/lib/runtime/zig/description.svelte create mode 100644 apps/ppp/src/lib/runtime/zig/worker.ts create mode 100644 apps/ppp/src/lib/zig/LICENSE create mode 100644 apps/ppp/src/lib/zig/README.md create mode 100644 apps/ppp/src/lib/zig/language-configuration.ts create mode 100644 apps/ppp/src/lib/zig/zig.tmLanguage.json create mode 100644 apps/ppp/src/routes/(app)/editor/_program.zig diff --git a/apps/ppp/package.json b/apps/ppp/package.json index 7b3e92c1..024277f9 100644 --- a/apps/ppp/package.json +++ b/apps/ppp/package.json @@ -18,6 +18,7 @@ "test": "npm run test:unit -- --run" }, "dependencies": { + "@andrewbranch/untar.js": "^1.0.3", "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", "dotnet-runtime": "workspace:*", @@ -37,7 +38,8 @@ "python-runtime": "workspace:*", "ruby-runtime": "workspace:*", "rust-runtime": "workspace:*", - "typescript-runtime": "workspace:*" + "typescript-runtime": "workspace:*", + "zig-runtime": "workspace:*" }, "devDependencies": { "@eslint/compat": "^2.0.5", diff --git a/apps/ppp/src/lib/fetch.ts b/apps/ppp/src/lib/fetch.ts index 645ec54e..ddf128b7 100644 --- a/apps/ppp/src/lib/fetch.ts +++ b/apps/ppp/src/lib/fetch.ts @@ -9,7 +9,8 @@ type CachePrefix = | 'php-cache@' | 'python-cache@' | 'ruby-cache@' - | 'rust-cache@'; + | 'rust-cache@' + | 'zig-cache@'; export async function createCachedFetch(prefix: CachePrefix, key: string) { return createFetch(await cache(prefix, await hashString(key))); diff --git a/apps/ppp/src/lib/language.ts b/apps/ppp/src/lib/language.ts index e836e4db..c8a88769 100644 --- a/apps/ppp/src/lib/language.ts +++ b/apps/ppp/src/lib/language.ts @@ -11,6 +11,7 @@ import VscodeIconsFileTypeGleam from '~icons/vscode-icons/file-type-gleam'; import VscodeIconsFileTypeCsharp from '~icons/vscode-icons/file-type-csharp'; import VscodeIconsFileTypeJava from '~icons/vscode-icons/file-type-java'; import VscodeIconsFileTypeRuby from '~icons/vscode-icons/file-type-ruby'; +import VscodeIconsFileTypeZig from '~icons/vscode-icons/file-type-zig'; export enum Language { JavaScript = 'javascript', @@ -22,7 +23,8 @@ export enum Language { Gleam = 'gleam', CSharp = 'csharp', Java = 'java', - Ruby = 'ruby' + Ruby = 'ruby', + Zig = 'zig' } export const LANGUAGES = Object.values(Language).sort(); @@ -37,7 +39,8 @@ export const LANGUAGE_TITLE: Record = { [Language.Gleam]: 'Gleam', [Language.CSharp]: 'CSharp', [Language.Java]: 'Java', - [Language.Ruby]: 'Ruby' + [Language.Ruby]: 'Ruby', + [Language.Zig]: 'Zig' }; export const LANGUAGE_ICONS: Record>> = { @@ -50,7 +53,8 @@ export const LANGUAGE_ICONS: Record = { - [Language.PHP]: "php", - [Language.TypeScript]: "typescript", - [Language.JavaScript]: "javascript", - [Language.Python]: "python", - [Language.Go]: "go", - [Language.Rust]: "rust", - [Language.Gleam]: Language.Gleam, - [Language.CSharp]: "csharp", - [Language.Java]: "java", - [Language.Ruby]: "ruby", + [Language.PHP]: 'php', + [Language.TypeScript]: 'typescript', + [Language.JavaScript]: 'javascript', + [Language.Python]: 'python', + [Language.Go]: 'go', + [Language.Rust]: 'rust', + [Language.Gleam]: Language.Gleam, + [Language.CSharp]: 'csharp', + [Language.Java]: 'java', + [Language.Ruby]: 'ruby', + [Language.Zig]: Language.Zig }; -const LANGUAGE_ID_SCOPE_NAME = { - [Language.Gleam]: "source.gleam", -}; - -monaco.languages.register({ id: Language.Gleam }); -monaco.languages.setLanguageConfiguration(Language.Gleam, gleamConfiguration); +const SCOPE_NAME_META = new Map< + string, + { lang: Language; grammarUrl: string; config: monaco.languages.LanguageConfiguration } +>([ + [ + 'source.gleam', + { lang: Language.Gleam, grammarUrl: gleamGrammarUrl, config: gleamConfiguration } + ], + ['source.zig', { lang: Language.Zig, grammarUrl: zigGrammarUrl, config: zigConfiguration }] +]); + +for (const [, meta] of SCOPE_NAME_META) { + monaco.languages.register({ id: meta.lang }); + monaco.languages.setLanguageConfiguration(meta.lang, meta.config); +} export async function loadTmGrammars() { - await loadWASM(onigasmWasmUrl); - - const registry = new Registry({ - getGrammarDefinition: async (scopeName) => { - switch (scopeName) { - case LANGUAGE_ID_SCOPE_NAME[Language.Gleam]: - return { - format: "json", - content: await (await fetch(gleamGrammarUrl)).json(), - }; - default: - throw new Error(`Unknown scope name: ${scopeName}`); - } - }, - }); - - const grammars = new Map(Object.entries(LANGUAGE_ID_SCOPE_NAME)); - - return wireTmGrammars(monaco, registry, grammars); + await loadWASM(onigasmWasmUrl); + + const registry = new Registry({ + getGrammarDefinition: async (scopeName) => { + const config = SCOPE_NAME_META.get(scopeName); + if (!config) { + throw new Error(`Unknown scope name: ${scopeName}`); + } + return { + format: 'json', + content: await fetch(config.grammarUrl).then((r) => r.json()) + }; + } + }); + + const grammars = new Map(SCOPE_NAME_META.entries().map(([scope, meta]) => [meta.lang, scope])); + return wireTmGrammars(monaco, registry, grammars); } diff --git a/apps/ppp/src/lib/runtime/descriptions.ts b/apps/ppp/src/lib/runtime/descriptions.ts index 3536c2b1..2ae2ea89 100644 --- a/apps/ppp/src/lib/runtime/descriptions.ts +++ b/apps/ppp/src/lib/runtime/descriptions.ts @@ -1,27 +1,29 @@ -import type { Component } from "svelte"; +import type { Component } from 'svelte'; -import { Language } from "$lib/language"; +import { Language } from '$lib/language'; -import JsDescription from "./js/description.svelte"; -import TsDescription from "./ts/description.svelte"; -import PhpDescription from "./php/description.svelte"; -import PyDescription from "./python/description.svelte"; -import GoDescription from "./go/description.svelte"; -import RustDescription from "./rust/description.svelte"; -import GleamDescription from "./gleam/description.svelte"; -import DotnetDescription from "./dotnet/description.svelte"; -import JavaDescription from "./java/description.svelte"; -import RubyDescription from "./ruby/description.svelte"; +import JsDescription from './js/description.svelte'; +import TsDescription from './ts/description.svelte'; +import PhpDescription from './php/description.svelte'; +import PyDescription from './python/description.svelte'; +import GoDescription from './go/description.svelte'; +import RustDescription from './rust/description.svelte'; +import GleamDescription from './gleam/description.svelte'; +import DotnetDescription from './dotnet/description.svelte'; +import JavaDescription from './java/description.svelte'; +import RubyDescription from './ruby/description.svelte'; +import ZigDescription from './zig/description.svelte'; export const DESCRIPTIONS: Record = { - [Language.JavaScript]: JsDescription, - [Language.TypeScript]: TsDescription, - [Language.PHP]: PhpDescription, - [Language.Python]: PyDescription, - [Language.Go]: GoDescription, - [Language.Rust]: RustDescription, - [Language.Gleam]: GleamDescription, - [Language.CSharp]: DotnetDescription, - [Language.Java]: JavaDescription, - [Language.Ruby]: RubyDescription, + [Language.JavaScript]: JsDescription, + [Language.TypeScript]: TsDescription, + [Language.PHP]: PhpDescription, + [Language.Python]: PyDescription, + [Language.Go]: GoDescription, + [Language.Rust]: RustDescription, + [Language.Gleam]: GleamDescription, + [Language.CSharp]: DotnetDescription, + [Language.Java]: JavaDescription, + [Language.Ruby]: RubyDescription, + [Language.Zig]: ZigDescription }; diff --git a/apps/ppp/src/lib/runtime/test-descriptions.ts b/apps/ppp/src/lib/runtime/test-descriptions.ts index d64c57d6..c9e6bdc0 100644 --- a/apps/ppp/src/lib/runtime/test-descriptions.ts +++ b/apps/ppp/src/lib/runtime/test-descriptions.ts @@ -1,27 +1,29 @@ -import type { Component } from "svelte"; +import type { Component } from 'svelte'; -import { Language } from "$lib/language"; +import { Language } from '$lib/language'; -import JsDescription from "./js/description.svelte"; -import TsDescription from "./ts/description.svelte"; -import PhpDescription from "./php/description.svelte"; -import PyDescription from "./python/description.svelte"; -import GoDescription from "./go/description.svelte"; -import RustDescription from "./rust/description.svelte"; -import GleamDescription from "./gleam/description.svelte"; -import DotnetDescription from "./dotnet/test-description.svelte"; -import JavaDescription from "./java/test-description.svelte"; -import RubyDescription from "./ruby/description.svelte"; +import JsDescription from './js/description.svelte'; +import TsDescription from './ts/description.svelte'; +import PhpDescription from './php/description.svelte'; +import PyDescription from './python/description.svelte'; +import GoDescription from './go/description.svelte'; +import RustDescription from './rust/description.svelte'; +import GleamDescription from './gleam/description.svelte'; +import DotnetDescription from './dotnet/test-description.svelte'; +import JavaDescription from './java/test-description.svelte'; +import RubyDescription from './ruby/description.svelte'; +import ZigDescription from './zig/description.svelte'; export const DESCRIPTIONS: Record = { - [Language.JavaScript]: JsDescription, - [Language.TypeScript]: TsDescription, - [Language.PHP]: PhpDescription, - [Language.Python]: PyDescription, - [Language.Go]: GoDescription, - [Language.Rust]: RustDescription, - [Language.Gleam]: GleamDescription, - [Language.CSharp]: DotnetDescription, - [Language.Java]: JavaDescription, - [Language.Ruby]: RubyDescription, + [Language.JavaScript]: JsDescription, + [Language.TypeScript]: TsDescription, + [Language.PHP]: PhpDescription, + [Language.Python]: PyDescription, + [Language.Go]: GoDescription, + [Language.Rust]: RustDescription, + [Language.Gleam]: GleamDescription, + [Language.CSharp]: DotnetDescription, + [Language.Java]: JavaDescription, + [Language.Ruby]: RubyDescription, + [Language.Zig]: ZigDescription }; diff --git a/apps/ppp/src/lib/runtime/zig/compiler-factory.ts b/apps/ppp/src/lib/runtime/zig/compiler-factory.ts new file mode 100644 index 00000000..d0b2caab --- /dev/null +++ b/apps/ppp/src/lib/runtime/zig/compiler-factory.ts @@ -0,0 +1,58 @@ +import { untar } from '@andrewbranch/untar.js'; +import type { CompilerFactory, Program } from 'libs/compiler'; +import type { Streams } from 'libs/io'; +import { createLogger } from 'libs/logger'; +import { createCompilerWASI, createProgramWASI, ZigCompiler, ZigProgram } from 'zig-runtime'; + +import zigWasmUrl from 'zig-runtime/zig.wasm?url'; +import compilerRtUrl from 'zig-runtime/lib/libcompiler_rt.a?url'; +import stdLibUrl from 'zig-runtime/lib/zig.tar.gz?url'; + +import { createCachedFetch } from '$lib/fetch'; + +export const makeZigCompiler: CompilerFactory = async (ctx, streams) => { + const logger = createLogger(streams.out); + const fetcher = await createCachedFetch( + 'zig-cache@', + `${zigWasmUrl}|${compilerRtUrl}|${stdLibUrl}` + ); + async function fetch(url: string, action: (r: Response) => R): Promise { + const response = await fetcher(url, { signal: ctx.signal }); + const result = await action(response); + logger.info(`Loaded ${url}`); + return result; + } + const [zigWasmModule, compilerRtArrayBuffer, stdLibFiles] = await Promise.all([ + fetch(zigWasmUrl, (r) => WebAssembly.compileStreaming(r)), + fetch(compilerRtUrl, (r) => r.arrayBuffer()), + fetch(stdLibUrl, async (r) => { + let arrayBuffer = await r.arrayBuffer(); + const magicNumber = new Uint8Array(arrayBuffer).slice(0, 2); + if (magicNumber[0] == 0x1f && magicNumber[1] == 0x8b) { + const ds = new DecompressionStream('gzip'); + const response = new Response(new Response(arrayBuffer).body!.pipeThrough(ds)); + arrayBuffer = await response.arrayBuffer(); + } else { + // already decompressed + } + return untar(arrayBuffer); + }) + ]); + const compilerWasi = createCompilerWASI(streams, compilerRtArrayBuffer, stdLibFiles); + const programWasi = createProgramWASI(streams); + const compiler = new ZigCompiler(compilerWasi, zigWasmModule); + return { + async compile(ctx, files) { + if (files.length !== 1) { + throw new Error('Compilation of multiple files is not implemented'); + } + const program = await compiler.compile(ctx, files[0].content); + return new ZigProgram( + programWasi, + program.buffer instanceof ArrayBuffer + ? (program as Uint8Array) + : new Uint8Array(program) + ); + } + }; +}; diff --git a/apps/ppp/src/lib/runtime/zig/description.svelte b/apps/ppp/src/lib/runtime/zig/description.svelte new file mode 100644 index 00000000..b4a1df4a --- /dev/null +++ b/apps/ppp/src/lib/runtime/zig/description.svelte @@ -0,0 +1,9 @@ +

Your code is compiled to wasm and executed in a web worker environment.

+ +

+ The solution is based on an approach from Zigtools Playground +

diff --git a/apps/ppp/src/lib/runtime/zig/worker.ts b/apps/ppp/src/lib/runtime/zig/worker.ts new file mode 100644 index 00000000..b3a457d0 --- /dev/null +++ b/apps/ppp/src/lib/runtime/zig/worker.ts @@ -0,0 +1,6 @@ +import { startCompilerActor } from 'libs/compiler/actor'; +import { createContext } from 'libs/context'; + +import { makeZigCompiler } from './compiler-factory'; + +startCompilerActor(createContext(), makeZigCompiler); diff --git a/apps/ppp/src/lib/zig/LICENSE b/apps/ppp/src/lib/zig/LICENSE new file mode 100644 index 00000000..571e549d --- /dev/null +++ b/apps/ppp/src/lib/zig/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Marc Tiehuis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/apps/ppp/src/lib/zig/README.md b/apps/ppp/src/lib/zig/README.md new file mode 100644 index 00000000..780ddcec --- /dev/null +++ b/apps/ppp/src/lib/zig/README.md @@ -0,0 +1,3 @@ +# Credits + +This files are extracted from the [vscode-zig](https://codeberg.org/ziglang/vscode-zig) with MIT license. diff --git a/apps/ppp/src/lib/zig/language-configuration.ts b/apps/ppp/src/lib/zig/language-configuration.ts new file mode 100644 index 00000000..23cd3fd5 --- /dev/null +++ b/apps/ppp/src/lib/zig/language-configuration.ts @@ -0,0 +1,55 @@ +import type { languages } from 'monaco-editor'; + +export default { + comments: { + lineComment: '//' + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" } + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" } + ] + // folding: { + // markers: { + // start: '// zig fmt: off\\b', + // end: '// zig fmt: on\\b' + // } + // }, + // onEnterRules: [ + // { + // beforeText: '^\\s*//!.*$', + // action: { + // indent: 'none', + // appendText: '//! ' + // } + // }, + // { + // beforeText: '^\\s*///.*$', + // action: { + // indent: 'none', + // appendText: '/// ' + // } + // }, + // { + // beforeText: '^\\s*\\\\\\\\.*$', + // action: { + // indent: 'none', + // appendText: '\\\\' + // } + // } + // ] +} satisfies languages.LanguageConfiguration; diff --git a/apps/ppp/src/lib/zig/zig.tmLanguage.json b/apps/ppp/src/lib/zig/zig.tmLanguage.json new file mode 100644 index 00000000..78ae72e4 --- /dev/null +++ b/apps/ppp/src/lib/zig/zig.tmLanguage.json @@ -0,0 +1,340 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "zig", + "scopeName": "source.zig", + "fileTypes": ["zig", "zon"], + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#strings" + }, + { + "include": "#keywords" + }, + { + "include": "#operators" + }, + { + "include": "#punctuation" + }, + { + "include": "#numbers" + }, + { + "include": "#support" + }, + { + "include": "#variables" + } + ], + "repository": { + "variables": { + "patterns": [ + { + "name": "meta.function.declaration.zig", + "patterns": [ + { + "match": "\\b(fn)\\s+([A-Z][a-zA-Z0-9]*)\\b", + "captures": { + "1": { + "name": "storage.type.function.zig" + }, + "2": { + "name": "entity.name.type.zig" + } + } + }, + { + "match": "\\b(fn)\\s+([_a-zA-Z][_a-zA-Z0-9]*)\\b", + "captures": { + "1": { + "name": "storage.type.function.zig" + }, + "2": { + "name": "entity.name.function.zig" + } + } + }, + { + "begin": "\\b(fn)\\s+@\"", + "end": "\"", + "name": "entity.name.function.string.zig", + "beginCaptures": { + "1": { + "name": "storage.type.function.zig" + } + }, + "patterns": [ + { + "include": "#stringcontent" + } + ] + }, + { + "name": "keyword.default.zig", + "match": "\\b(const|var|fn)\\b" + } + ] + }, + { + "name": "meta.function.call.zig", + "patterns": [ + { + "match": "([A-Z][a-zA-Z0-9]*)(?=\\s*\\()", + "name": "entity.name.type.zig" + }, + { + "match": "([_a-zA-Z][_a-zA-Z0-9]*)(?=\\s*\\()", + "name": "entity.name.function.zig" + } + ] + }, + { + "name": "meta.variable.zig", + "patterns": [ + { + "match": "\\b[_a-zA-Z][_a-zA-Z0-9]*\\b", + "name": "variable.zig" + }, + { + "begin": "@\"", + "end": "\"", + "name": "variable.string.zig", + "patterns": [ + { + "include": "#stringcontent" + } + ] + } + ] + } + ] + }, + "keywords": { + "patterns": [ + { + "match": "\\binline\\b(?!\\s*\\bfn\\b)", + "name": "keyword.control.repeat.zig" + }, + { + "match": "\\b(while|for)\\b", + "name": "keyword.control.repeat.zig" + }, + { + "name": "keyword.storage.zig", + "match": "\\b(extern|packed|export|pub|noalias|inline|comptime|volatile|align|linksection|threadlocal|allowzero|noinline|callconv|addrspace)\\b" + }, + { + "name": "keyword.structure.zig", + "match": "\\b(struct|enum|union|opaque)\\b" + }, + { + "name": "keyword.statement.zig", + "match": "\\b(asm|unreachable)\\b" + }, + { + "name": "keyword.control.flow.zig", + "match": "\\b(break|return|continue|defer|errdefer)\\b" + }, + { + "name": "keyword.control.async.zig", + "match": "\\b(resume|suspend|nosuspend)\\b" + }, + { + "name": "keyword.control.trycatch.zig", + "match": "\\b(try|catch)\\b" + }, + { + "name": "keyword.control.conditional.zig", + "match": "\\b(if|else|switch|orelse)\\b" + }, + { + "name": "keyword.constant.default.zig", + "match": "\\b(null|undefined)\\b" + }, + { + "name": "keyword.constant.bool.zig", + "match": "\\b(true|false)\\b" + }, + { + "name": "keyword.default.zig", + "match": "\\b(test|and|or)\\b" + }, + { + "name": "keyword.type.zig", + "match": "\\b(bool|void|noreturn|type|error|anyerror|anyframe|anytype|anyopaque)\\b" + }, + { + "name": "keyword.type.integer.zig", + "match": "\\b(f16|f32|f64|f80|f128|u\\d+|i\\d+|isize|usize|comptime_int|comptime_float)\\b" + }, + { + "name": "keyword.type.c.zig", + "match": "\\b(c_char|c_short|c_ushort|c_int|c_uint|c_long|c_ulong|c_longlong|c_ulonglong|c_longdouble)\\b" + } + ] + }, + "operators": { + "patterns": [ + { + "name": "keyword.operator.c-pointer.zig", + "match": "(?<=\\[)\\*c(?=\\])" + }, + { + "name": "keyword.operator.comparison.zig", + "match": "(\\b(and|or)\\b)|(==|!=|<=|>=|<|>)" + }, + { + "name": "keyword.operator.arithmetic.zig", + "match": "(-%?|\\+%?|\\*%?|/|%)=?" + }, + { + "name": "keyword.operator.bitwise.zig", + "match": "(<<%?|>>|!|~|&|\\^|\\|)=?" + }, + { + "name": "keyword.operator.special.zig", + "match": "(==|\\+\\+|\\*\\*|->)" + }, + { + "name": "keyword.operator.assignment.zig", + "match": "=" + }, + { + "name": "keyword.operator.question.zig", + "match": "\\?" + } + ] + }, + "comments": { + "patterns": [ + { + "name": "comment.line.documentation.zig", + "begin": "//[!/](?=[^/])", + "end": "$", + "patterns": [ + { + "include": "#commentContents" + } + ] + }, + { + "name": "comment.line.double-slash.zig", + "begin": "//", + "end": "$", + "patterns": [ + { + "include": "#commentContents" + } + ] + } + ] + }, + "commentContents": { + "patterns": [ + { + "match": "\\b(TODO|FIXME|XXX|NOTE)\\b:?", + "name": "keyword.todo.zig" + } + ] + }, + "punctuation": { + "patterns": [ + { + "name": "punctuation.accessor.zig", + "match": "\\." + }, + { + "name": "punctuation.comma.zig", + "match": "," + }, + { + "name": "punctuation.separator.key-value.zig", + "match": ":" + }, + { + "name": "punctuation.terminator.statement.zig", + "match": ";" + } + ] + }, + "strings": { + "patterns": [ + { + "name": "string.quoted.double.zig", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "include": "#stringcontent" + } + ] + }, + { + "name": "string.multiline.zig", + "begin": "\\\\\\\\", + "end": "$" + }, + { + "name": "string.quoted.single.zig", + "match": "'([^'\\\\]|\\\\(x\\h{2}|[0-2][0-7]{,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.))'" + } + ] + }, + "stringcontent": { + "patterns": [ + { + "name": "constant.character.escape.zig", + "match": "\\\\([nrt'\"\\\\]|(x[0-9a-fA-F]{2})|(u\\{[0-9a-fA-F]+\\}))" + }, + { + "name": "invalid.illegal.unrecognized-string-escape.zig", + "match": "\\\\." + } + ] + }, + "numbers": { + "patterns": [ + { + "name": "constant.numeric.hexfloat.zig", + "match": "\\b0x[0-9a-fA-F][0-9a-fA-F_]*(\\.[0-9a-fA-F][0-9a-fA-F_]*)?([pP][+-]?[0-9a-fA-F_]+)?\\b" + }, + { + "name": "constant.numeric.float.zig", + "match": "\\b[0-9][0-9_]*(\\.[0-9][0-9_]*)?([eE][+-]?[0-9_]+)?\\b" + }, + { + "name": "constant.numeric.decimal.zig", + "match": "\\b[0-9][0-9_]*\\b" + }, + { + "name": "constant.numeric.hexadecimal.zig", + "match": "\\b0x[a-fA-F0-9_]+\\b" + }, + { + "name": "constant.numeric.octal.zig", + "match": "\\b0o[0-7_]+\\b" + }, + { + "name": "constant.numeric.binary.zig", + "match": "\\b0b[01_]+\\b" + }, + { + "name": "constant.numeric.invalid.zig", + "match": "\\b[0-9](([eEpP][+-])|[0-9a-zA-Z_])*(\\.(([eEpP][+-])|[0-9a-zA-Z_])*)?([eEpP][+-])?[0-9a-zA-Z_]*\\b" + } + ] + }, + "support": { + "patterns": [ + { + "comment": "Built-in functions", + "name": "support.function.builtin.zig", + "match": "@[_a-zA-Z][_a-zA-Z0-9]*" + } + ] + } + } +} diff --git a/apps/ppp/src/routes/(app)/editor/_program.zig b/apps/ppp/src/routes/(app)/editor/_program.zig new file mode 100644 index 00000000..27cedd42 --- /dev/null +++ b/apps/ppp/src/routes/(app)/editor/_program.zig @@ -0,0 +1,5 @@ +const std = @import("std"); + +pub fn main(init: std.process.Init) !void { + try std.Io.File.stdout().writeStreamingAll(init.io, "Hello, World!\n"); +} diff --git a/apps/ppp/src/routes/(app)/editor/runtimes.ts b/apps/ppp/src/routes/(app)/editor/runtimes.ts index 50b6ff75..f6d368b6 100644 --- a/apps/ppp/src/routes/(app)/editor/runtimes.ts +++ b/apps/ppp/src/routes/(app)/editor/runtimes.ts @@ -12,6 +12,7 @@ import GleamWorker from '$lib/runtime/gleam/worker?worker'; import JavaWorker from '$lib/runtime/java/worker?worker'; import RubyWorker from '$lib/runtime/ruby/worker?worker'; import DotnetWorker from '$lib/runtime/dotnet/worker?worker'; +import ZigWorker from '$lib/runtime/zig/worker?worker'; import phpProgram from './_program.php?raw'; import tsProgram from './_program.ts?raw'; @@ -23,6 +24,7 @@ import gleamProgram from './_program.gleam?raw'; import javaProgram from './_program.java?raw'; import csProgram from './_program.cs?raw'; import rubyProgram from './_program.rb?raw'; +import zigProgram from './_program.zig?raw'; interface Runtime { initialValue: string; @@ -69,5 +71,9 @@ export const RUNTIMES: Record = { [Language.Ruby]: { initialValue: rubyProgram, compilerFactory: makeRemoteCompilerFactory(RubyWorker) + }, + [Language.Zig]: { + initialValue: zigProgram, + compilerFactory: makeRemoteCompilerFactory(ZigWorker) } }; diff --git a/mkfile b/mkfile index 887bbcea..b60ff856 100644 --- a/mkfile +++ b/mkfile @@ -14,18 +14,6 @@ b: p: pnpm run preview -ppp/: - pushd apps/ppp - c: - pnpm run check - d: - pnpm run dev - b: - pnpm run build - p: - pnpm run preview - popd - artifacts: */artifacts libs/: @@ -281,4 +269,16 @@ java/: bun run preview bun run dev popd - popd \ No newline at end of file + popd + +ppp/: + pushd apps/ppp + c: + pnpm run check + d: + pnpm run dev + b: + pnpm run build + p: + pnpm run preview + popd diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2e0d197..b0f29a0e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,9 @@ settings: catalogs: default: + '@bjorn3/browser_wasi_shim': + specifier: ^0.4.2 + version: 0.4.2 vite: specifier: ^8.0.10 version: 8.0.10 @@ -46,6 +49,9 @@ importers: apps/ppp: dependencies: + '@andrewbranch/untar.js': + specifier: ^1.0.3 + version: 1.0.3 '@xterm/addon-fit': specifier: ^0.11.0 version: 0.11.0 @@ -106,6 +112,9 @@ importers: typescript-runtime: specifier: workspace:* version: link:../../packages/typescript-runtime + zig-runtime: + specifier: workspace:* + version: link:../../packages/zig-runtime devDependencies: '@eslint/compat': specifier: ^2.0.5 @@ -218,7 +227,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) vite-plugin-static-copy: specifier: 'catalog:' version: 4.1.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) @@ -234,7 +243,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) vite-plugin-static-copy: specifier: 'catalog:' version: 4.1.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) @@ -253,7 +262,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) packages/java-runtime: dependencies: @@ -309,7 +318,7 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) vite-plugin-static-copy: specifier: 'catalog:' version: 4.1.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) @@ -328,16 +337,13 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) vite-plugin-static-copy: specifier: 'catalog:' version: 4.1.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) packages/ruby-runtime: dependencies: - '@bjorn3/browser_wasi_shim': - specifier: ^0.4.2 - version: 0.4.2 '@ruby/wasm-wasi': specifier: 2.9.3-2.9.4 version: 2.9.3-2.9.4 @@ -345,6 +351,9 @@ importers: specifier: workspace:* version: link:../libs devDependencies: + '@bjorn3/browser_wasi_shim': + specifier: 'catalog:' + version: 0.4.2 '@ruby/4.0-wasm-wasi': specifier: 2.9.3-2.9.4 version: 2.9.3-2.9.4 @@ -353,26 +362,26 @@ importers: version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) vite-plugin-static-copy: specifier: 'catalog:' version: 4.1.0(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) packages/rust-runtime: dependencies: - '@bjorn3/browser_wasi_shim': - specifier: ^0.4.2 - version: 0.4.2 libs: specifier: workspace:* version: link:../libs devDependencies: + '@bjorn3/browser_wasi_shim': + specifier: 'catalog:' + version: 0.4.2 vite: specifier: 'catalog:' version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) packages/typescript-runtime: dependencies: @@ -382,22 +391,25 @@ importers: packages/zig-runtime: dependencies: - '@bjorn3/browser_wasi_shim': - specifier: ^0.4.2 - version: 0.4.2 libs: specifier: workspace:* version: link:../libs devDependencies: + '@bjorn3/browser_wasi_shim': + specifier: 'catalog:' + version: 0.4.2 vite: specifier: 'catalog:' version: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) vite-plugin-dts: specifier: 'catalog:' - version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) + version: 4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)) packages: + '@andrewbranch/untar.js@1.0.3': + resolution: {integrity: sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==} + '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} @@ -2813,6 +2825,8 @@ packages: snapshots: + '@andrewbranch/untar.js@1.0.3': {} + '@antfu/install-pkg@1.1.0': dependencies: package-manager-detector: 1.5.0 @@ -3685,7 +3699,7 @@ snapshots: de-indent: 1.0.2 he: 1.2.0 - '@vue/language-core@2.2.0(typescript@6.0.3)': + '@vue/language-core@2.2.0(typescript@5.9.3)': dependencies: '@volar/language-core': 2.4.11 '@vue/compiler-dom': 3.5.7 @@ -3696,7 +3710,7 @@ snapshots: muggle-string: 0.4.1 path-browserify: 1.0.1 optionalDependencies: - typescript: 6.0.3 + typescript: 5.9.3 '@vue/shared@3.5.7': {} @@ -4877,18 +4891,18 @@ snapshots: uuid: 11.1.0 vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) - vite-plugin-dts@4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@6.0.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)): + vite-plugin-dts@4.5.4(@types/node@24.12.2)(rollup@4.53.2)(typescript@5.9.3)(vite@8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1)): dependencies: '@microsoft/api-extractor': 7.54.0(@types/node@24.12.2) '@rollup/pluginutils': 5.1.4(rollup@4.53.2) '@volar/typescript': 2.4.11 - '@vue/language-core': 2.2.0(typescript@6.0.3) + '@vue/language-core': 2.2.0(typescript@5.9.3) compare-versions: 6.1.1 debug: 4.4.3 kolorist: 1.8.0 local-pkg: 1.1.2 magic-string: 0.30.21 - typescript: 6.0.3 + typescript: 5.9.3 optionalDependencies: vite: 8.0.10(@types/node@24.12.2)(esbuild@0.28.0)(jiti@2.6.1)(yaml@2.5.1) transitivePeerDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index f10b2022..75ad27f6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,3 +7,4 @@ catalog: vitest: ^4.1.5 vite-plugin-dts: ^4.5.4 vite-plugin-static-copy: ^4.1.0 + "@bjorn3/browser_wasi_shim": "^0.4.2"