Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
581b6ab
Ir emitters
wilmveel Mar 3, 2026
2c3d6fe
Add verify:test job to CI pipeline
wilmveel Mar 6, 2026
ee855b9
Revert
wilmveel Mar 12, 2026
55bfd10
Revert
wilmveel Mar 12, 2026
449e257
Remove generated Java 17 conversion test files
wilmveel Mar 12, 2026
2f5f264
Fix ambiguous Client reference in scala-zio example
wilmveel Mar 12, 2026
a3b5d7b
Fix rust-petstore test to await async handler calls
wilmveel Mar 13, 2026
add6d58
Separate Rust client code into its own client/ directory
wilmveel Mar 19, 2026
869d559
Derive verify client test imports from fixture definitions
wilmveel Mar 19, 2026
fe73c4d
Separate client code into its own client/ directory for all languages
wilmveel Mar 23, 2026
515eb68
Add endpoint import to client files for JVM languages
wilmveel Mar 23, 2026
6db5496
Move aggregated Client to the package root for all languages
wilmveel Mar 23, 2026
d724445
Fix TypeScript verify tests by suppressing deprecated moduleResolutio…
wilmveel Mar 24, 2026
4d1b008
Add IR serialization methods to wirespec-serialization npm resource
wilmveel Mar 24, 2026
c15a7c1
Remove unnecessary std::process::exit(0) from Rust verify tests
wilmveel Mar 25, 2026
ddc19db
Add case-insensitive array indexing and static content emission for l…
wilmveel Mar 25, 2026
b1f632d
Make `main` and `functionCall` DSL methods support async execution an…
wilmveel Mar 25, 2026
974f18e
Add `BorrowExpression` support across all language generators and upd…
wilmveel Mar 26, 2026
d385f65
Update `RawRequestIr` query type to align with header structure in wi…
wilmveel Mar 26, 2026
b1593f4
Remove `BorrowExpression` support and associated references from gene…
wilmveel Mar 26, 2026
3ee3a2d
Remove `BorrowExpression` support and associated references from gene…
wilmveel Mar 26, 2026
d6ed761
Refactor Rust IR emitter to replace raw element mappings with DSL-def…
wilmveel Mar 26, 2026
ca79010
Refactor all IR emitters for consistent structure, naming, and idioma…
wilmveel Mar 30, 2026
cd4743b
Add IntegerLiteral and StringLiteral types to IR and group Python imp…
wilmveel Mar 30, 2026
ee3dcc7
Fix TypeScript default switch branch for literal status types
wilmveel Mar 30, 2026
bf3054a
refactor: move validate type-arg suppression from generators to emitt…
wilmveel Apr 2, 2026
4763576
refactor: move Rust Response From impl generation from generator to e…
wilmveel Apr 2, 2026
24080f5
refactor: move Rust serialization borrow-prefix logic from generator …
wilmveel Apr 2, 2026
52ea299
fix: replace silent 'unknown' fallback with explicit error in toRawCo…
wilmveel Apr 2, 2026
e1b5d51
refactor: move Java Wirespec.getType() from generator to emitter tran…
wilmveel Apr 2, 2026
3be9116
refactor: remove isInterface() heuristic by populating struct interfa…
wilmveel Apr 2, 2026
9f8f88a
refactor: move TypeScript pattern switch status extraction from gener…
wilmveel Apr 2, 2026
b1937ee
style: apply spotless formatting fixes
wilmveel Apr 2, 2026
6eefae9
fix: add verify to make all and fix verify test failures
wilmveel Apr 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,21 @@ jobs:
- name: Build IntelliJ plugin
run: ./gradlew :src:ide:intellij-plugin:buildPlugin

verify:

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: gradle
- name: Verify
run: ./gradlew :src:verify:test -Pverify

example-build:

runs-on: ubuntu-latest
Expand Down Expand Up @@ -245,6 +260,10 @@ jobs:
type: gradle
- name: npm-typescript
type: npm
- name: rust-petstore
type: rust
- name: scala-zio
type: scala

steps:
- uses: actions/checkout@v4
Expand All @@ -254,6 +273,7 @@ jobs:
with:
java-version: '21'
distribution: 'temurin'
cache: gradle
- name: Set up GraalVM
if: matrix.type == 'native'
uses: graalvm/setup-graalvm@v1
Expand All @@ -264,8 +284,14 @@ jobs:
if: matrix.type == 'npm'
with:
node-version: 20
- name: Set up Rust
if: matrix.type == 'rust'
uses: dtolnay/rust-toolchain@stable
- name: Set up sbt
if: matrix.type == 'scala'
uses: sbt/setup-sbt@v1
- name: Download Maven Local artifacts
if: matrix.type != 'npm'
if: matrix.type != 'npm' && matrix.type != 'rust' && matrix.type != 'scala'
uses: actions/download-artifact@v4
with:
name: wirespec-m2
Expand All @@ -291,6 +317,14 @@ jobs:
if: matrix.type == 'npm'
working-directory: examples/${{ matrix.name }}
run: npm ci && npm run build
- name: Run ${{ matrix.name }} example
if: matrix.type == 'rust'
working-directory: examples/${{ matrix.name }}
run: bash gen.sh && cargo build && cargo test
- name: Run ${{ matrix.name }} example
if: matrix.type == 'scala'
working-directory: examples/${{ matrix.name }}
run: sbt compile test

success:

Expand All @@ -309,6 +343,7 @@ jobs:
- vscode
- intellij-plugin
- example-test
- verify

steps:
- name: Check CI status
Expand Down
237 changes: 237 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
# Wirespec - Claude Code Instructions

## Generated Code Requirements

All code generated by Wirespec emitters must be **dependency-free**. Generated output must not rely on any external libraries or third-party packages — it must be fully self-contained and runnable with only the target language's standard library.

## IR Emitter Pipeline

The Wirespec compiler uses a four-stage IR pipeline to generate idiomatic code for Java, Kotlin, TypeScript, Python, and Rust:

```
Wirespec Source
Parser ──► AST (Root / Module / Definition)
IrConverter ──► IR File (language-neutral tree)
Language Emitter ──► Transformed IR File (via Transform DSL)
CodeGenerator ──► String (target-language source code)
```

### Stage 1: Parser AST

The parser produces a tree of `Definition` nodes grouped into `Module`s inside a `Root`:

```
Root ─► Module[] ─► Definition[]
```

**Definition types** (sealed hierarchy):
- `Type` — record/struct with a `Shape` (list of `Field`s) and optional `extends`
- `Enum` — set of string entries
- `Union` — set of `Reference` entries
- `Refined` — primitive wrapper with a regex/bound constraint
- `Endpoint` — HTTP endpoint (method, path, queries, headers, requests, responses)
- `Channel` — async messaging channel with a reference type

**Reference type system** (`sealed interface Reference`):
`Any`, `Unit`, `Custom(name)`, `Primitive(type)`, `Iterable(reference)`, `Dict(reference)`. Each carries `isNullable`. `Primitive.Type` variants: `String`, `Integer`, `Number`, `Boolean`, `Bytes` (with optional precision and constraints).

Key files:
- `src/compiler/core/.../parse/ast/Definition.kt`
- `src/compiler/core/.../parse/ast/Reference.kt`
- `src/compiler/core/.../parse/ast/Root.kt`

### Stage 2: Convert (Parser AST → IR)

`IrConverter.kt` maps each parser `Definition` to an IR `File` tree. The entry point dispatches by definition type:

```kotlin
fun DefinitionWirespec.convert(): File = when (this) {
is TypeWirespec -> convert()
is EnumWirespec -> convert()
is UnionWirespec -> convert()
is RefinedWirespec -> convert()
is ChannelWirespec -> convert()
is EndpointWirespec -> convert()
}
```

Each per-definition converter produces a complete IR `File` with the appropriate structs, interfaces, functions, and validation logic. `EndpointWirespec.convert()` is the most complex — it generates Path/Queries/RequestHeaders/Request structs, a Response union hierarchy, serialization functions, and a Handler interface.

Reference conversion maps parser references to IR types: `Custom → Type.Custom`, `Iterable → Type.Array`, `Dict → Type.Dict`, `Primitive → Type.String/Integer/Number/Boolean/Bytes`, wrapping with `Type.Nullable` when `isNullable`.

Key file: `src/compiler/ir/.../converter/IrConverter.kt`

### Stage 3: IR AST

The IR is a language-neutral tree with the following node types:

**Elements** (AST nodes):
- `File(name, elements)` — top-level container
- `Struct(name, fields, constructors, interfaces, elements)` — record/class
- `Interface(name, elements, extends, isSealed, typeParameters, fields)` — interface/protocol
- `Namespace(name, elements, extends)` — grouping container
- `Union(name, members, typeParameters)` — tagged union
- `Enum(name, entries, fields, constructors, elements)` — enumeration (entries have name + values)
- `Function(name, typeParameters, parameters, returnType, body, isAsync, isStatic, isOverride)`
- `Package(path)`, `Import(path, type)`, `RawElement(code)`

**Type system** (`sealed interface Type`):
`Integer(precision)`, `Number(precision)`, `String`, `Boolean`, `Bytes`, `Unit`, `Any`, `Wildcard`, `Reflect`, `Array(elementType)`, `Dict(keyType, valueType)`, `Custom(name, generics)`, `Nullable(type)`

**Statement / Expression hierarchy**: `RawExpression`, `VariableReference`, `FieldCall`, `FunctionCall`, `BinaryOp`, `ConstructorStatement`, `Literal`, `Switch/Case`, `IfExpression`, `StringTemplate`, `MapExpression`, `ReturnStatement`, `Assignment`, constraints (`RegexMatch`, `BoundCheck`), null-handling (`NullCheck`, `NullableMap`, `NullableOf`), and more.

Key file: `src/compiler/ir/.../core/Ast.kt`

### Stage 4: Transform (detailed)

The transform layer is the heart of language-specific adaptation. It lets each emitter reshape the language-neutral IR into a form that generates idiomatic target code.

Key file: `src/compiler/ir/.../core/Transform.kt`

#### Transformer interface

Eight override points, each defaulting to recursive `transformChildren()`:

```kotlin
interface Transformer {
fun transformType(type: Type): Type
fun transformElement(element: Element): Element
fun transformStatement(statement: Statement): Statement
fun transformExpression(expression: Expression): Expression
fun transformField(field: Field): Field
fun transformParameter(parameter: Parameter): Parameter
fun transformConstructor(constructor: Constructor): Constructor
fun transformCase(case: Case): Case
}
```

#### `transformer()` DSL factory

Creates a `Transformer` using a `TransformerBuilder` with a builder-pattern DSL. Only the overrides you specify are applied; all others default to recursive `transformChildren()`:

```kotlin
inline fun transformer(block: TransformerBuilder.() -> Unit): Transformer

// Usage:
transformer {
type { type, transformer -> /* ... */ }
element { element, transformer -> /* ... */ }
statement { statement, transformer -> /* ... */ }
expression { expression, transformer -> /* ... */ }
field { field, transformer -> /* ... */ }
parameter { parameter, transformer -> /* ... */ }
constructor { constructor, transformer -> /* ... */ }
case { case, transformer -> /* ... */ }
}
```

#### `transformChildren()` recursive traversal

Each node type has a `transformChildren(Transformer)` extension that walks into its children. For example, a `Struct` transforms its fields, constructors, and child elements; a `FunctionCall` transforms its receiver, type arguments, and argument expressions. This ensures transforms propagate through the entire tree.

Apply a transformer to any element: `fun <T : Element> T.transform(transformer: Transformer): T`

#### `TransformScope` — block-based transform API

The primary API used by language emitters. Call `element.transform { ... }` to open a scope and chain multiple transforms:

```kotlin
inline fun <E : Element> E.transform(block: TransformScope<E>.() -> Unit): E
```

`TransformScope<E>` methods:

| Method | Purpose |
|---|---|
| `matching<M : Type> { transform }` | Transform all types matching a Kotlin class |
| `matchingElements<M : Element> { transform }` | Transform all elements matching a Kotlin class |
| `fieldsWhere(predicate, transform)` | Transform fields matching a predicate |
| `parametersWhere(predicate, transform)` | Transform parameters matching a predicate |
| `renameType(oldName, newName)` | Rename a `Type.Custom` throughout the tree |
| `renameField(oldName, newName)` | Rename a field throughout the tree |
| `typeByName(name, transform)` | Transform types matching a custom name |
| `injectBefore<T> { produce }` | Insert elements before a matching container |
| `injectAfter<T> { produce }` | Insert elements after a matching container |
| `apply(transformer)` | Apply a pre-built `Transformer` |
| `type { type, transformer -> ... }` | Shorthand: create + apply a type transformer |
| `statement { stmt, transformer -> ... }` | Shorthand: create + apply a statement transformer |
| `expression { expr, transformer -> ... }` | Shorthand: create + apply an expression transformer |
| `field { field, transformer -> ... }` | Shorthand: create + apply a field transformer |
| `parameter { param, transformer -> ... }` | Shorthand: create + apply a parameter transformer |
| `constructor { ctor, transformer -> ... }` | Shorthand: create + apply a constructor transformer |
| `case { case, transformer -> ... }` | Shorthand: create + apply a case transformer |

#### Low-level helper functions

These `internal` extension functions on `Element` power the `TransformScope` methods above:

| Function | Purpose |
|---|---|
| `transformMatching<M : Type>` | Transform all types matching a Kotlin class |
| `transformMatchingElements<M : Element>` | Transform all elements matching a Kotlin class |
| `transformFieldsWhere(predicate, transform)` | Transform fields matching a predicate |
| `transformParametersWhere(predicate, transform)` | Transform parameters matching a predicate |
| `renameType(oldName, newName)` | Rename a `Type.Custom` throughout the tree |
| `renameField(oldName, newName)` | Rename a field throughout the tree |
| `transformTypeByName(name, transform)` | Transform types matching a custom name |
| `injectBefore<T>(produce)` | Insert elements before a matching container |
| `injectAfter<T>(produce)` | Insert elements after a matching container |

#### Read-only traversal utilities

Standalone functions (not an interface) for walking the IR tree without modifying it:

| Function | Purpose |
|---|---|
| `forEachType(action)` | Visit every `Type` node in the tree |
| `forEachElement(action)` | Visit every `Element` node in the tree |
| `forEachField(action)` | Visit every `Field` node in the tree |
| `collectTypes()` | Collect all `Type` nodes into a list |
| `collectCustomTypeNames()` | Collect all `Type.Custom` names into a set |
| `findAll<T>()` | Find all elements of a specific type |
| `findAllTypes<T>()` | Find all types of a specific type |
| `findElement<T>()` | Find the first child element of a specific type |

### Stage 5: Generate

Each language emitter implements `File.generate()` by delegating to a `CodeGenerator` singleton:

```kotlin
interface CodeGenerator {
fun generate(element: Element): String
}
```

Generators: `JavaGenerator`, `KotlinGenerator`, `TypeScriptGenerator`, `PythonGenerator`, `RustGenerator`. Each recursively walks the IR tree and emits the corresponding target-language syntax as a string.

Top-level entry: `fun Element.generateJava()`, `fun Element.generateKotlin()`, etc.

Key files:
- `src/compiler/ir/.../emit/IrEmitter.kt`
- `src/compiler/ir/.../generator/CodeGenerator.kt`
- `src/compiler/ir/.../generator/{Java,Kotlin,TypeScript,Python,Rust}Generator.kt`

### Key File Reference

| File | Purpose |
|---|---|
| `src/compiler/core/.../parse/ast/Definition.kt` | Parser AST definition types |
| `src/compiler/core/.../parse/ast/Reference.kt` | Parser AST reference/type system |
| `src/compiler/ir/.../converter/IrConverter.kt` | Parser AST → IR conversion |
| `src/compiler/ir/.../core/Ast.kt` | IR node types (Element, Type, Statement, Expression) |
| `src/compiler/ir/.../core/Transform.kt` | Transform DSL + TransformScope + traversal utilities |
| `src/compiler/ir/.../emit/IrEmitter.kt` | Emitter interface and orchestration |
| `src/compiler/ir/.../generator/CodeGenerator.kt` | Generator interface + top-level functions |
| `src/compiler/emitters/java/.../JavaIrEmitter.kt` | Java-specific transforms + emit |
| `src/compiler/emitters/kotlin/.../KotlinIrEmitter.kt` | Kotlin-specific transforms + emit |
| `src/compiler/emitters/typescript/.../TypeScriptIrEmitter.kt` | TypeScript-specific transforms + emit |
| `src/compiler/emitters/python/.../PythonIrEmitter.kt` | Python-specific transforms + emit |
| `src/compiler/emitters/rust/.../RustIrEmitter.kt` | Rust-specific transforms + emit |
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.PHONY: *

# The first command will be invoked with `make` only and should be `all`
all: build image test example format
all: build image test example format verify

build: build-wirespec build-site

Expand Down Expand Up @@ -42,7 +42,7 @@ update:
npm install -g @vscode/vsce

verify:
$(shell pwd)/scripts/verify.sh
./gradlew :src:verify:test -Pverify

yolo:
$(shell pwd)/scripts/yolo.sh
4 changes: 3 additions & 1 deletion examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ build:
(cd maven-spring-integration && ./mvnw verify) && \
(cd maven-spring-boot-4-integration && ./mvnw verify) && \
(cd gradle-ktor && ./gradlew check) && \
(cd npm-typescript && npm ci && npm run build)
(cd npm-typescript && npm ci && npm run build) && \
(cd rust-petstore && bash gen.sh && cargo build) && \
(cd scala-zio && sbt compile)

clean:
(cd maven-preprocessor && ./mvnw clean) && \
Expand Down
24 changes: 0 additions & 24 deletions examples/README.md

This file was deleted.

2 changes: 2 additions & 0 deletions examples/rust-petstore/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
/src/gen
Loading
Loading