https://github.com/ydah/zwgsl
Ruby-inspired shading language that compiles to WGSL and GLSL ES 3.0, with HM-style inference, pattern matching, an LSP server, and a WebGPU playground.
https://github.com/ydah/zwgsl
compiler glsl graphics-programming lsp playground programming-language shader shading-language type-inference webgpu wgsl zig
Last synced: 10 days ago
JSON representation
Ruby-inspired shading language that compiles to WGSL and GLSL ES 3.0, with HM-style inference, pattern matching, an LSP server, and a WebGPU playground.
- Host: GitHub
- URL: https://github.com/ydah/zwgsl
- Owner: ydah
- License: mit
- Created: 2026-03-09T15:34:14.000Z (4 months ago)
- Default Branch: main
- Last Pushed: 2026-05-15T13:28:43.000Z (about 1 month ago)
- Last Synced: 2026-05-15T14:32:17.596Z (about 1 month ago)
- Topics: compiler, glsl, graphics-programming, lsp, playground, programming-language, shader, shading-language, type-inference, webgpu, wgsl, zig
- Language: Zig
- Homepage: https://ydah.github.io/zwgsl/
- Size: 476 KB
- Stars: 3
- Watchers: 0
- Forks: 0
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Security: SECURITY.md
- Roadmap: ROADMAP.md
Awesome Lists containing this project
README
# zwgsl
A Ruby-inspired shading language that compiles to WGSL (WebGPU) and GLSL ES 3.0.
Built with Zig for fast compilation, a small runtime surface, and tooling that can
ship as a native library, an LSP server, or a browser wasm module.
Try it in the [Playground](https://ydah.github.io/zwgsl/).
## Why zwgsl?
GPU shading languages are powerful, but the authoring experience is usually rigid:
braces everywhere, repetitive type annotations, and weak structure for reusable
shader logic. `zwgsl` keeps shader code close to Ruby-style flow and expression
syntax while still targeting modern GPU backends.
- `end` blocks instead of braces
- layout-aware indentation and implicit statement separators
- method chains, postfix conditionals, and implicit returns
- `let` bindings, `where` clauses, `type`, `match`, `trait`, and `impl`
- WGSL-first output with a retained GLSL ES 3.0 path
## Features
- Ruby-like syntax with `def`, `do`, `end`, symbols, method calls, and postfix `if` / `unless`
- Indentation-sensitive layout resolver that inserts virtual indent / dedent / statement separators
- `let` bindings and function-local `where` clauses
- HM-style local type inference with let-generalization
- Algebraic data types and constructor registration
- Pattern matching with constructor, wildcard, binding, literal, and guarded arms
- Dependent dimension matching for `Vec(N)`, `Mat(M, N)`, and tensor-style type applications
- Generic structs, constructor inference, and phantom-type-safe annotations
- `trait` / `impl` support with compile-time specialization and inline trait methods in WGSL output
- Multi-stage WGSL pipeline with entry-point-aware HIR and SSA-style CFG MIR: `AST -> HIR -> MIR -> WGSL`
- WGSL sampler lowering for uniforms, function parameters, and immutable local aliases
- Source-aware LSP support: diagnostics, hover with lowering previews, completion, signature help, goto-definition, document symbols, semantic tokens
- Browser playground with Monaco, wasm compilation, and compiler-backed worker tooling
- C API surface for embedding the compiler in other tools
## Documentation
- [Language Reference](docs/language.md)
- [Grammar Summary](docs/spec.md)
- [Feature Matrix](docs/feature-matrix.md)
- [Builtins](docs/builtins.md)
- [C API](docs/c-api.md)
- [Gotchas](docs/gotchas.md)
- [Migration From WGSL And GLSL](docs/migration.md)
- [Design Notes](docs/design-notes.md)
- [Architecture](docs/architecture.md)
- [Roadmap](ROADMAP.md)
- [Security Policy](SECURITY.md)
- [Editor Setup](docs/editor-setup.md)
- [Examples](examples/README.md)
- [Contributing](CONTRIBUTING.md)
## Quick Example
`zwgsl` source:
```ruby
version "300 es"
precision :fragment, :highp
uniform :model_matrix, Mat4
uniform :view_matrix, Mat4
uniform :projection_matrix, Mat4
uniform :light_pos, Vec3
uniform :base_color, Vec4
def phong_strength(normal: Vec3, light_dir: Vec3) -> Float
max(dot(normal.normalize, light_dir.normalize), 0.0)
end
vertex do
input :position, Vec3, location: 0
input :normal, Vec3, location: 1
varying :v_normal, Vec3
varying :v_world_pos, Vec3
def main
world_pos = model_matrix * vec4(position, 1.0)
self.v_normal = mat3(model_matrix) * normal
self.v_world_pos = world_pos.xyz
gl_Position = projection_matrix * view_matrix * world_pos
end
end
fragment do
varying :v_normal, Vec3
varying :v_world_pos, Vec3
output :frag_color, Vec4, location: 0
def main
light_dir = light_pos - v_world_pos
light = phong_strength(v_normal, light_dir)
frag_color = vec4(base_color.rgb * (0.2 + 0.8 * light), base_color.a)
end
end
```
Compiled WGSL vertex output:
```wgsl
struct VertexInput {
@location(0) position: vec3f,
@location(1) normal: vec3f,
}
struct VertexOutput {
@builtin(position) gl_Position: vec4f,
@location(0) v_normal: vec3f,
@location(1) v_world_pos: vec3f,
}
@group(0) @binding(0) var model_matrix: mat4x4f;
@group(0) @binding(1) var view_matrix: mat4x4f;
@group(0) @binding(2) var projection_matrix: mat4x4f;
struct _zwgsl_uniform_light_pos {
@align(16) value: vec3f,
}
@group(0) @binding(3) var light_pos: _zwgsl_uniform_light_pos;
@group(0) @binding(4) var base_color: vec4f;
var gl_Position: vec4f;
var position: vec3f;
var normal: vec3f;
var v_normal: vec3f;
var v_world_pos: vec3f;
fn _zwgsl_vertex_main() {
let world_pos: vec4f = model_matrix * vec4f(position, 1.0);
v_normal = mat3x3f(model_matrix[0].xyz, model_matrix[1].xyz, model_matrix[2].xyz) * normal;
v_world_pos = world_pos.xyz;
gl_Position = projection_matrix * view_matrix * world_pos;
}
@vertex
fn main(input: VertexInput) -> VertexOutput {
position = input.position;
normal = input.normal;
_zwgsl_vertex_main();
var output: VertexOutput;
output.gl_Position = gl_Position;
output.v_normal = v_normal;
output.v_world_pos = v_world_pos;
return output;
}
```
Compiled WGSL fragment output:
```wgsl
struct FragmentInput {
@location(0) v_normal: vec3f,
@location(1) v_world_pos: vec3f,
}
struct FragmentOutput {
@location(0) frag_color: vec4f,
}
@group(0) @binding(0) var model_matrix: mat4x4f;
@group(0) @binding(1) var view_matrix: mat4x4f;
@group(0) @binding(2) var projection_matrix: mat4x4f;
struct _zwgsl_uniform_light_pos {
@align(16) value: vec3f,
}
@group(0) @binding(3) var light_pos: _zwgsl_uniform_light_pos;
@group(0) @binding(4) var base_color: vec4f;
var v_normal: vec3f;
var v_world_pos: vec3f;
var frag_color: vec4f;
fn phong_strength(normal: vec3f, light_dir: vec3f) -> f32 {
return max(dot(normalize(normal), normalize(light_dir)), 0.0);
}
fn _zwgsl_fragment_main() {
let light_dir: vec3f = light_pos.value - v_world_pos;
let light: f32 = phong_strength(v_normal, light_dir);
frag_color = vec4f(base_color.rgb * (0.2 + 0.8 * light), base_color.a);
}
@fragment
fn main(input: FragmentInput) -> FragmentOutput {
v_normal = input.v_normal;
v_world_pos = input.v_world_pos;
_zwgsl_fragment_main();
var output: FragmentOutput;
output.frag_color = frag_color;
return output;
}
```
## Advanced Examples
Pattern matching over ADTs ([Open in Playground](https://ydah.github.io/zwgsl/?sample=adt-match)):
```ruby
type Shape
Circle(radius: Float)
Rect(width: Float, height: Float)
Point
end
def area(shape: Shape) -> Float
match shape
when Circle(radius)
3.14159 * radius * radius
when Rect(width, height)
width * height
when Point
0.0
end
end
compute do
def main
value: Float = area(Circle(2.0))
end
end
```
Dependent dimensions that lower to fixed-size WGSL types ([Open in Playground](https://ydah.github.io/zwgsl/?sample=dependent-dimensions)):
```ruby
compute do
def main
transform: Mat(4, 4) = mat4(1.0)
value: Vec(4) = vec4(1.0)
energy: Float = dot(value, value)
end
end
```
Trait-constrained specialization:
```ruby
trait Numeric
def add(other: Self) -> Self end
def mul(other: Self) -> Self end
end
impl Numeric for Float
def add(other: Self) -> Self
self + other
end
def mul(other: Self) -> Self
self * other
end
end
def lerp(a: T, b: T, t: Float) -> T where T: Numeric
a.mul(1.0 - t).add(b.mul(t))
end
compute do
def main
value: Float = lerp(1.0, 2.0, 0.5)
end
end
```
## Installation
### From Source
Requires Zig 0.15.x.
```sh
git clone https://github.com/ydah/zwgsl
cd zwgsl
zig build -Doptimize=ReleaseFast
```
### Test Suite
```sh
zig build test
bash script/validate_generated_wgsl.sh
zig build benchmark
```
`script/validate_generated_wgsl.sh` validates generated WGSL fixtures when
`naga`, `tint`, or `WGSL_VALIDATOR` is available.
Use `WGSL_VALIDATOR_REQUIRED=1 bash script/validate_generated_wgsl.sh` or
`bash script/validate_generated_wgsl.sh --require-validator` when a missing
external validator should fail the run.
`zig build benchmark` prints CSV compile-time measurements for representative
examples.
### CLI
```sh
zig build
zig-out/bin/zwgsl compile --target wgsl examples/hello_triangle.zw
zig-out/bin/zwgsl check examples/hello_triangle.zw
zig-out/bin/zwgsl fmt --check examples/hello_triangle.zw
zig-out/bin/zwgsl source-map --stage vertex examples/hello_triangle.zw
zig-out/bin/zwgsl lsp
```
Use `--target glsl-es-300` for GLSL ES 3.0 output, `--stage` to select a
single generated stage, and `-o` / `--output` to write compile output to a file.
Use `zwgsl fmt --write ` to rewrite a source file in place.
Use `zwgsl source-map` to emit a JSON mapping from generated WGSL lines back to
source lines when debugging output.
### Browser Wasm Build
```sh
zig build wasm
```
That installs `zig-out/bin/zwgsl.wasm`, which the playground syncs into
`playground/src/generated/zwgsl.wasm` so Vite can fingerprint the asset.
## Artifacts
`zig build` installs:
- `zig-out/lib/libzwgsl.a`
- `zig-out/lib/libzwgsl.dylib` or the platform equivalent shared library
- `zig-out/include/zwgsl.h`
- `zig-out/bin/zwgsl`
- `zig-out/bin/zwgsl-lsp`
The repository also includes a `Dockerfile`, a Homebrew formula template under
`packaging/homebrew/`, and an npm compiler package scaffold under
`packages/compiler/`.
Tagged releases attach a Linux x86_64 CLI/LSP/library archive, a standalone
`zwgsl.wasm` asset, and an `@zwgsl/compiler` npm package tarball with checksum
files for each asset.
## C API
See [C API](docs/c-api.md) for ABI/versioning, options, ownership, and C++
integration notes.
```c
#include "zwgsl.h"
ZwgslOptions options = zwgsl_options_default();
options.target = ZWGSL_TARGET_WGSL;
ZwgslResult result = zwgsl_compile(source, source_len, options);
if (result.error_count == 0) {
if (result.vertex_source != NULL) {
puts(result.vertex_source);
}
if (result.fragment_source != NULL) {
puts(result.fragment_source);
}
if (result.compute_source != NULL) {
puts(result.compute_source);
}
}
zwgsl_free(&result);
```
## LSP
The server entry point is `zwgsl-lsp`, implemented under `src/lsp/`.
See [Editor Setup](docs/editor-setup.md) for Neovim, Helix, VS Code, and Zed
configuration notes, including the local VS Code extension in `editors/vscode`.
Current editor-facing features:
- incremental `didChange` with full-document change compatibility
- diagnostics from compiler errors and warnings
- hover with source-aware type / declaration info and method lowering previews
- completion for locals, declarations, stage builtins, constructors, fields, and methods
- signature help for functions, constructors, and supported builtins
- code actions for common stage declaration, unused uniform, and type/constructor casing fixes
- goto-definition for values, functions, and type declarations
- document symbols for stages, declarations, functions, types, traits, and impls
- document formatting through the same formatter used by `zwgsl fmt`
- rename for resolved document-local symbols
- semantic tokens for keywords, functions, variables, uniforms, varyings, builtins, constructors, traits, types, numbers, strings, comments, operators, and properties
## Playground
The playground lives under `playground/` and uses Monaco plus the real wasm compiler build.
```sh
cd playground
npm install
npm run dev
```
The repository also includes a GitHub Pages workflow that publishes the production
build to `https://ydah.github.io/zwgsl/` when Pages is enabled with `GitHub Actions`
as the publishing source.
Current capabilities:
- Monaco language registration for `zwgsl`
- sample selector backed by repository examples and focused feature fixtures, with `?sample=` direct links and `?source=` share links
- live WGSL compilation through `zwgsl.wasm`
- output tabs for combined WGSL, stage-specific WGSL, diagnostics, and generated resource layout
- compiler-backed diagnostics, hover, completion, signature help, and goto-definition from the wasm build
- WebGPU preview surface with animated `iTime` / `iResolution` uniforms, persisted generated controls with color pickers, sampler placeholders, and 2D texture upload
- `npm run sync-wasm` to refresh the generated wasm asset from `zig-out/bin/zwgsl.wasm`
For a GitHub Pages build, set the base path before running Vite:
```sh
cd playground
PLAYGROUND_BASE_PATH=/zwgsl/ npm run build
```
## Architecture
See [Architecture](docs/architecture.md) for the responsibilities of the
frontend, HIR/MIR WGSL path, retained GLSL path, diagnostics, and tooling entry
points.
```mermaid
flowchart TD
Source["Source (.zw)"] --> Lexer
Lexer --> LayoutResolver["Layout Resolver"]
LayoutResolver --> Parser
Parser --> AST
AST --> HM["HM Inference + Sema"]
HM --> TypedAST["Typed AST"]
TypedAST --> HIR
TypedAST --> IR
HIR --> MIR
MIR --> WGSLEmitter["WGSL Emitter"]
WGSLEmitter --> WGSL["WGSL / WebGPU"]
IR --> GLSLEmitter["GLSL Emitter"]
GLSLEmitter --> GLSL["GLSL ES 3.0"]
```
## Project Status
Implemented behavior is tracked here and in the [Feature Matrix](docs/feature-matrix.md).
Forward-looking work is tracked separately in the [Roadmap](ROADMAP.md).
| Area | Status |
| --- | --- |
| Package version | 0.1.0 |
| Verified Zig | 0.15.2 in CI; requires Zig 0.15.x |
| Verified Node.js | 24 in CI for the playground |
| Lexer + layout resolver | Implemented |
| Parser + source positions | Implemented |
| `let` / `where` | Implemented |
| HM-style local inference | Implemented for local bindings and let-generalization |
| ADTs + pattern matching | Implemented |
| Dependent dimensions | Implemented for `Vec(N)` / `Mat(M, N)` matching and WGSL type lowering |
| Generic structs + phantom tags | Implemented |
| `trait` / `impl` specialization | Implemented as compile-time static dispatch with inline trait methods in WGSL output |
| WGSL pipeline | Implemented as `AST -> HIR -> MIR -> WGSL`, with entry-point HIR and SSA-style CFG-based MIR lowering |
| GLSL ES 3.0 backend | Implemented |
| LSP | Implemented |
| Playground | Implemented |
## Repository Layout
```text
src/
ast.zig
compiler.zig
hir.zig
hir_builder.zig
ir.zig
ir_builder.zig
layout.zig
lsp/
mir.zig
mir_builder.zig
parser.zig
sema.zig
typeclass.zig
wgsl_emitter.zig
tests/
fixtures/
examples/
editors/
vscode/
playground/
include/
```
## License
Licensed under the [MIT License](LICENSE).