๐Ÿ“ฆ ubugeeei / fwgsl

[WIP] A pure functional language that compiles to WGSL (WebGPU Shading Language).

โ˜… 3 stars โ‘‚ 0 forks ๐Ÿ‘ 3 watching
๐Ÿ“ฅ Clone https://github.com/ubugeeei/fwgsl.git
HTTPS git clone https://github.com/ubugeeei/fwgsl.git
SSH git clone git@github.com:ubugeeei/fwgsl.git
CLI gh repo clone ubugeeei/fwgsl
Loading files...
๐Ÿ“„ README.md

fwgsl

A pure functional language that compiles to WGSL (WebGPU Shading Language).

fwgsl brings ML/Haskell-style programming to the GPU โ€” static typing, Hindley-Milner inference, algebraic data types, pattern matching, and function composition โ€” all compiled down to valid WGSL that runs on WebGPU.

Examples

Hello World

double : I32 -> I32
double x = x + x

main : I32 -> I32
main x =
  let y = double x
  in y + 1

Algebraic Data Types and Pattern Matching

data Color = Red | Green | Blue

show : Color -> I32
show c = match c
  | Red   -> 0
  | Green -> 1
  | Blue  -> 2

Lambda Expressions and Function Composition

apply : (I32 -> I32) -> I32 -> I32
apply f x = f x

inc : I32 -> I32
inc = \x -> x + 1

doubleInc : I32 -> I32
doubleInc = double . inc

Shader Entry Points

@vertex
vs_main : VertexInput -> Vec4F
vs_main input = input.position

@fragment
fs_main : FragmentInput -> Vec4F
fs_main input = vec4 1.0 0.0 0.0 1.0

@compute @workgroup_size(64, 1, 1)
cs_main : ComputeInput -> ()
cs_main input =
  let idx = input.global_invocation_id.x
  in store output idx (load input_buf idx)

GPU Particle Simulation

A more complete example showing ADTs, pattern matching, and compute shaders working together:

data ParticleState
  = Active { position : Vec3F, velocity : Vec3F, life : F32 }
  | Dead

stepParticle : F32 -> ParticleState -> ParticleState
stepParticle dt particle =
  match particle
    | Active { position, velocity, life } ->
      if life - dt <= 0.0
        then Dead
        else Active
          { position = position + velocity * dt
          , velocity = velocity + gravity * dt
          , life     = life - dt
          }
    | Dead -> Dead

@compute @workgroup_size(64, 1, 1)
main : ComputeInput -> ()
main input =
  let idx    = input.global_invocation_id.x
      state  = load particles idx
      state' = stepParticle deltaTime state
  in  store particles idx state'

Compiles to:

struct ParticleState {
  tag: u32,
  position: vec3<f32>,
  velocity: vec3<f32>,
  life: f32,
}

fn stepParticle(dt: f32, particle: ParticleState) -> ParticleState {
  if (particle.tag == 0u) {
    let position = particle.position;
    let velocity = particle.velocity;
    let life = particle.life;
    if (life - dt <= 0.0) {
      return ParticleState(1u, vec3<f32>(0.0), vec3<f32>(0.0), 0.0);
    } else {
      return ParticleState(0u, position + velocity * dt,
        velocity + vec3<f32>(0.0, -9.8, 0.0) * dt, life - dt);
    }
  } else {
    return particle;
  }
}

@compute @workgroup_size(64, 1, 1)
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
  let idx = gid.x;
  let state = particles[idx];
  let state_prime = stepParticle(deltaTime, state);
  particles[idx] = state_prime;
}

Language Features

Type System

  • Hindley-Milner type inference โ€” full type inference with let-generalization; type annotations optional but supported
  • Static typing โ€” all types resolved at compile time, no runtime type checks
  • Dependent-type dimensions โ€” Vec 3 F32 compiles to vec3<f32>, Mat 4 4 F32 to mat4x4<f32>, with compile-time dimension validation
  • Type classes โ€” Functor, Applicative, Monad with instance resolution (no special symbols like <%%INLINECODE4%%gt;)
  • Type signatures โ€” add : I32 -> I32 -> I32 using : (not ::)

Functional Programming

  • Algebraic data types โ€” data Color = Red | Green | Blue compiled to tagged WGSL structs
  • Pattern matching โ€” match with pipe-delimited arms, compiled to decision trees via Maranget's algorithm
  • Lambda expressions โ€” \x y -> x + y
  • Let bindings โ€” let x = 1 in x + 2 with block-style let ... in ...
  • Function composition โ€” normalize . getVelocity with the dot operator
  • Currying and partial application โ€” all functions are curried
  • Operators as functions โ€” (+), (*) can be passed as first-class values
  • Backtick infix โ€” ` x clamp lo turns any function into an infix operator - **If-then-else** โ€” if x == 0 then 1 else 2 as expressions ### Syntax - **Indentation-sensitive layout** โ€” Haskell 2010-style layout rules with virtual braces and semicolons - **Entry point attributes** โ€” @vertex, @fragment, @compute @workgroupsize(64, 1, 1) map directly to WGSL shader stages - **Expression-oriented** โ€” everything is an expression, including if, let, and match - **Pratt parsing** โ€” operator precedence: . > application > *// > +/- > comparison > && > || > backtick > $ ### Compiler Transforms - **Monomorphization** โ€” polymorphic functions specialized at each call site for WGSL compatibility - **Defunctionalization** โ€” higher-order functions lowered to tagged structs with switch dispatch - **Tail-call elimination** โ€” tail recursion converted to loops (WGSL forbids recursion) - **ADT lowering** โ€” algebraic data types compiled to flat structs with tag fields ## Architecture Written in Rust, inspired by [Oxc](https://github.com/oxc-project/oxc)'s arena-allocated design for fast compilation. %%CODEBLOCK6%% ### Crates | Crate | Purpose | |-------|---------| | fwgslallocator | Arena allocation (bumpalo wrapper) | | fwgslspan | Source spans, atoms, source types | | fwgsldiagnostics | Error/warning reporting with labels (miette-based) | | fwgslsyntax | SyntaxKind enum for all tokens and node kinds | | fwgslparser | Hand-written lexer, Haskell-style layout resolver, recursive descent + Pratt parser | | fwgsltypechecker | Type representation, HM inference engine, union-find unification | | fwgslsemantic | Name resolution, scope analysis, type checking with constructor registration | | fwgslhir | High-level IR โ€” desugared, type-annotated, still functional | | fwgslmir | Mid-level IR โ€” monomorphized, first-order, imperative, WGSL-shaped | | fwgslwgslcodegen | MIR to WGSL text emission with struct ordering and name mangling | | fwgsllanguageserver | LSP server (tower-lsp) โ€” diagnostics, hover, completion, goto-definition, semantic tokens | | fwgslintegrationtests | End-to-end pipeline tests (256+ tests) | | fwgslcli | Command-line interface | | fwgslwasm | WASM target for web playground | ## Getting Started Requires [Rust](https://rustup.rs/) and [mise](https://mise.jdx.dev/). %%CODEBLOCK7%% ## Tooling ### Language Server (LSP) The fwgsl-lsp binary provides IDE support via the Language Server Protocol: - **Diagnostics** โ€” parse and type errors reported on open/change - **Hover** โ€” shows inferred types for identifiers and keyword descriptions - **Completion** โ€” keywords, built-in types (I32, F32, Vec3F`, ...), WGSL builtins, and document identifiers
  • Go to Definition โ€” jump to function and type definitions
  • Semantic Tokens โ€” syntax-aware highlighting for keywords, types, operators, strings, numbers, and comments

Web Playground

A browser-based playground with Monaco editor, WGSL output pane, and WebGPU live preview.

mise run wasm       # build WASM module
mise run playground # serve playground
mise run dev        # quick dev server (no WASM rebuild)

Linter and Formatter

Planned: CST-based formatter (preserves comments, canonical indentation) and modular lint rules (unused variables, incomplete patterns, WGSL-incompatible recursion).

WGSL Constraints

WGSL imposes severe restrictions that the compiler must bridge:

ConstraintHow fwgsl handles it
No recursionTail-call elimination to loops; general recursion detected and rejected
No dynamic allocationAll data is fixed-size; arena allocation is compile-time only
No first-class functionsDefunctionalization: closures become tagged structs + dispatch
No genericsMonomorphization: polymorphic code specialized per call site
No ADTsTagged structs with flat field layout

License

MIT