Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .cursor/rules/vex-project.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Vex is a statically typed Lisp for building MCP servers. The compiler is written
## Core Philosophy

- **Pure transformations** — each compiler phase is a function: data in, data out, plus diagnostics. No global state, no singletons, no mutable statics.
- **Flat file structure** — one file per concept in `src/`. No subdirectories until a file exceeds ~500 lines.
- **One file, one concept** — each source file in `src/` owns exactly one concept. Split when a file owns two independent concerns, not by line count.
- **Separate AST and HIR** — parser produces untyped `ast::*` types; type checker produces typed `hir::*` types. Never mix them. Codegen only sees HIR.
- **Strict layering** — modules form a DAG. `source.rs` at the bottom, `main.rs` at the top. No dependency cycles.
- **Explicit over clever** — prefer straightforward code over abstractions. No trait-based frameworks, no indirection unless it solves a concrete problem.
Expand Down
2 changes: 1 addition & 1 deletion .cursor/skills/add-compiler-phase/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,4 @@ Unit tests in `#[cfg(test)] mod tests` within the file:
- No mutable statics or global state
- Receive `&mut Vec<Diagnostic>` to push errors, or return `Vec<Diagnostic>` alongside output
- The caller (`lib.rs`) decides whether to continue after errors
- Keep the file under ~500 lines; split only if exceeded
- Each file owns one concept; split when a file owns two independent concerns, not by line count
2 changes: 2 additions & 0 deletions .cursor/skills/pre-commit-checks/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ cargo clippy -- -D warnings
Only create the commit after both commands succeed with no errors.

3. **README trigger check** — before committing, actively verify whether any of the **update-readme** skill triggers have been reached by comparing the staged changes against the trigger conditions in that skill. If any trigger is met, run the **update-readme** skill and stage the updated `README.md` before committing. This check is mandatory on every commit, not optional.

4. **Roadmap review** — before committing, run the **update-roadmap** skill. Compare the staged changes against the roadmap items in `docs/roadmap.md`. If any item's status changed (work started, feature completed, new gap identified), update the roadmap and stage it before committing. This check is mandatory on every commit, not optional.
64 changes: 64 additions & 0 deletions .cursor/skills/update-roadmap/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
name: update-roadmap
description: >-
Reviews and updates the project roadmap with current status of all items.
Use when committing changes, completing a feature, or when the user asks
to update the roadmap.
---

# Update Roadmap

The roadmap lives at `docs/roadmap.md`. It tracks what Vex needs next, the status of each item, and links to `docs/roadmap-rationale.md` for full analysis.

## When to Trigger

- Before every commit (called from the pre-commit-checks skill)
- A feature or milestone is completed
- A new roadmap item is identified or started
- User explicitly asks to update the roadmap

## Gather Current State

Before updating, read these sources to determine what has changed:

1. **`docs/roadmap.md`** — current roadmap with statuses
2. **`docs/roadmap-rationale.md`** — rationale and analysis for each item
3. **`src/lib.rs`** — which modules exist
4. **`src/*.rs`** — list files to see what phases and features are present
5. **Git diff / staged changes** — what changed in the current commit

## Update Process

1. Compare the staged changes against each roadmap item
2. If a roadmap item moved forward (new code, tests, or docs related to it), update its status:
- **Not Started** → **In Progress** when work begins on a branch
- **In Progress** → **Done** when merged to main with tests passing
3. If a completed item is not yet in the "Completed Milestones" section, move it there with the current date
4. If new work reveals a gap not on the roadmap, add it to the appropriate section
5. Cross-check `docs/roadmap-rationale.md` and `docs/language-design.md` for items, features, or gaps not yet tracked in the roadmap — add any missing items to the appropriate section with status **Not Started**
6. Update the **Last reviewed** date at the top of the document to today's date

## What to Check

For each roadmap section, verify:

### Design Constraint Enforcement
- Has the compiler core / binary boundary been enforced? Check if `lib.rs` compile functions do any IO.
- Has resilient parsing been added? Check `parser.rs` for error recovery logic.

### Type System
- Has parametric polymorphism work started? Check `types.rs` for `TypeParam`, check `typechecker.rs` for unification logic.

### Developer Experience
- Has `vex dev`, structured logging, connected REPL, error chain traces, or test framework work started? Check `src/` for new files or CLI commands.

### MCP Framework
- Has any MCP-specific macro or runtime work started? Check for `deftool`, `defresource`, `serve-mcp` in source or examples.

## Rules

- **Only report what is actually implemented.** Read the source — do not guess.
- Keep statuses consistent between the roadmap and the README phase table.
- Do not remove items from the roadmap — mark them Done and move to Completed Milestones.
- Do not add speculative items. Every item must trace back to a concrete gap identified in `docs/roadmap-rationale.md` or `docs/compiler-architecture.md`.
- **Follow `docs/documentation-guidelines.md`** — active voice, bullet points, no filler words.
125 changes: 69 additions & 56 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,11 @@

[![CI](https://github.com/thsfranca/vex/actions/workflows/ci.yml/badge.svg)](https://github.com/thsfranca/vex/actions/workflows/ci.yml)

A statically typed Lisp for building MCP servers. The compiler is written in Rust and transpiles to Go source code.
A statically typed Lisp for building [MCP](https://modelcontextprotocol.io/) servers. The compiler is written in Rust and transpiles to Go source code, which `go build` compiles to a native binary.

**This is a study project.**

## Overview

Vex combines S-expression syntax with compile-time type checking, targeting networked services — especially [MCP](https://modelcontextprotocol.io/) servers. The compiler pipeline produces Go source that `go build` compiles to a native binary. An alternative path runs code directly through a tree-walking interpreter for the REPL.

## Examples
## Quick Look

```lisp
(defn main []
Expand All @@ -28,81 +24,98 @@ Vex combines S-expression syntax with compile-time type checking, targeting netw
```

```lisp
(deftype Point (x Float) (y Float))

(defn distance [p : Point] : String
(str "(" (. p x) ", " (. p y) ")"))
(defmacro unless [test body]
(list (quote if) test (quote nil) body))

(defn main []
(let [origin (Point 0.0 0.0)
target (Point 3.0 4.0)]
(println (str "origin = " (distance origin)))
(println (str "target = " (distance target)))))
(unless (> 1 10)
(println "1 is not greater than 10")))
```

## Usage
More examples in [`examples/`](examples/).

## Features

- **Type system** — `Int`, `Float`, `String`, `Bool`, `Option`, `Result`, records (`deftype`), unions (`defunion`)
- **Functions** — `defn`, `def`, `fn` (lambdas), higher-order functions, closures
- **Control flow** — `if`, `cond`, `match` (pattern matching), `and`, `or`, `let`
- **Macros** — `defmacro` with `quote`/`unquote`/`splice`, automatic hygiene, self-hosted prelude
- **Collections** — `List`, `Map`, `each`, `range`, `map`, `filter`
- **Modules** — `module`, `export`, `import`, Go interop (`import-go`)
- **Concurrency** — `spawn`, `channel`, `send`, `recv`, `select`
- **REPL** — interactive `vex repl` with multi-line input and persistent state

## Getting Started

### Prerequisites

- [Rust](https://www.rust-lang.org/tools/install) (stable)
- [Go](https://go.dev/dl/) (1.21+)

### Build the compiler

```bash
cargo build --release
```

### Compile and run a program

```bash
vex build hello.vx # Compile to ./hello binary
vex build hello.vx -o server # Custom output name
vex build hello.vx --emit-go . # Also write generated Go source
vex run hello.vx # Build and run immediately
vex repl # Interactive REPL
vex build hello.vx --emit-go . # Write generated Go source for inspection
```

## Current Status

All compiler phases are implemented and pass 551 tests. The self-hosted macro system (`defmacro`) supports user-defined compile-time macros with automatic hygiene.

### Compiler Phases

| Phase | Status |
|-------|--------|
| `source.rs` — FileId, Span, SourceMap | Done |
| `diagnostics.rs` — Diagnostic, Severity, formatting | Done |
| `lexer.rs` — Tokenizer | Done |
| `ast.rs` — Untyped AST types | Done |
| `parser.rs` — Recursive descent parser | Done |
| `types.rs` / `hir.rs` / `builtins.rs` — Type system | Done |
| `macro_expand.rs` — Compiler macros + self-hosted `defmacro` with hygiene | Done |
| `typechecker.rs` — AST → HIR | Done |
| `codegen.rs` — HIR → Go source | Done |
| `interpreter.rs` — HIR → Value (tree-walking eval) | Done |
| `lib.rs` / `main.rs` — Full pipeline, CLI | Done |

### Language Features

- **Primitives:** integers, floats, strings, booleans, nil
- **Functions:** `defn`, `def`, `fn` (lambdas), higher-order functions
- **Control flow:** `if`, `cond`, `and`, `or`, `let`, pattern matching (`match`)
- **Macros:** `defmacro`, `quote`, `unquote`, `splice`, macro helpers, automatic hygiene via `gensym`
- **Data types:** records (`deftype`), field access (`.`), unions (`defunion`)
- **Built-in types:** `Option`, `Result`, `List`, `Map`
- **Collections:** `each`, `range`, `map`, `filter`
- **Modules:** `module`, `export`, `import`, Go interop (`import-go`)
- **Concurrency:** `spawn`, `channel`, `send`, `recv`
- **REPL:** interactive `vex repl` with multi-line input and persistent state
- **Built-in functions:** `println`, `str`, `mod`, arithmetic and comparison operators

## Architecture

```
Source → Lexer → Parser → Macro Expand → Type Checker → Codegen → go build → Binary
→ Interpreter (REPL)
```

Each compiler phase is a pure function: data in, data out, plus diagnostics. No global state, no singletons.

| Phase | File | Description |
|-------|------|-------------|
| Source tracking | `source.rs` | `FileId`, `Span`, `SourceMap` |
| Diagnostics | `diagnostics.rs` | Error/warning accumulation and formatting |
| Lexer | `lexer.rs` | Source text → token stream |
| AST | `ast.rs` | Untyped syntax tree types |
| Parser | `parser.rs` | Recursive descent, tokens → AST |
| Macro expansion | `macro_expand.rs` | AST → AST, prelude + `defmacro` with hygiene |
| Type system | `types.rs`, `hir.rs`, `builtins.rs` | Semantic types, typed HIR, built-in registry |
| Type checker | `typechecker.rs` | AST → HIR with type inference |
| Code generation | `codegen.rs` | HIR → Go source |
| Interpreter | `interpreter.rs` | HIR → Value (tree-walking, for REPL) |
| Pipeline | `lib.rs`, `main.rs` | Full compiler pipeline and CLI |

## Current Status

All compiler phases are implemented and pass 551 tests. The pipeline compiles Vex source to working Go binaries end-to-end. The self-hosted macro system (`defmacro`) supports user-defined compile-time macros with automatic hygiene.

See [`docs/roadmap.md`](docs/roadmap.md) for planned features: parametric polymorphism, error propagation macros, exhaustiveness checking, structured concurrency, formatter, LSP, and the MCP framework.

## Documentation

- [`docs/language-design.md`](docs/language-design.md) — syntax, type system, grammar, backend strategy, design decisions
- [`docs/compiler-architecture.md`](docs/compiler-architecture.md) — pipeline, file structure, phase contracts, testing strategy
- [`docs/dependency-management.md`](docs/dependency-management.md) — `vex.mod` manifest, `vex get`, global cache, Go module integration
- [`docs/mvp.md`](docs/mvp.md) — MVP definition and success criteria
| Document | Contents |
|----------|----------|
| [`language-design.md`](docs/language-design.md) | Syntax, type system, grammar, backend strategy, design decisions |
| [`compiler-architecture.md`](docs/compiler-architecture.md) | Pipeline, file structure, phase contracts, testing strategy |
| [`dependency-management.md`](docs/dependency-management.md) | `vex.mod` manifest, `vex get`, global cache, Go module integration |
| [`roadmap.md`](docs/roadmap.md) | Feature roadmap with priorities and status |
| [`roadmap-rationale.md`](docs/roadmap-rationale.md) | Design analysis and trade-offs behind roadmap items |
| [`installation.md`](docs/installation.md) | Installation process, release pipeline, distribution channels |
| [`mvp.md`](docs/mvp.md) | MVP definition and success criteria |

## Development

```bash
cargo build
cargo test
cargo build # Build the compiler
cargo test # Run all 551 tests
cargo clippy # Lint
cargo fmt # Format
```

## License
Expand Down
24 changes: 19 additions & 5 deletions docs/compiler-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ Source (.vx)
└────────┬────────┘
┌───────────────────────┐
│ Summary Extraction │ Vec<ast::TopForm> → ModuleSummary (future, see roadmap §9)
└───────────┬───────────┘
┌──────────────┐
│ Type Checker │ &[ast::TopForm] → hir::Module
└──────┬───────┘
Expand Down Expand Up @@ -86,7 +91,7 @@ Macro expansion sits between Parser and Type Checker:

## 2. File Structure

Flat files. No directories for modules until a file exceeds ~500 lines. Each file owns one concept.
Each file owns one concept. Split when a file owns two independent concerns that don't share state — not by line count. See `docs/roadmap-rationale.md` §0 for the rationale behind this constraint.

```
src/
Expand Down Expand Up @@ -562,7 +567,7 @@ func (McpMessage_Notification) isMcpMessage() {}
```

- The Vex type checker validates all types at the Vex level
- The Go interface is not generic — it uses `any` where Vex type parameters appear — because Go's generics cannot express the full Vex type system
- The Go interface uses `any` where Vex type parameters appear in union variants. Go 1.26 (2026) relaxed recursive type parameter constraints, reducing the impedance mismatch for many patterns — but some Vex type combinations still fall back to `any`
- This is acceptable since the generated code is correct by construction after type checking

### Concurrency Primitives
Expand Down Expand Up @@ -682,12 +687,22 @@ These are tools the Vex compiler and toolchain provide, independent of any frame
| P0 | Error diagnostics | Compiler (`diagnostics.rs`) | Span-based errors with source snippets, line numbers, and underlines. The single most important DX feature for a new language. |
| P0 | REPL | Interpreter (`interpreter.rs`) | Tree-walking evaluation of typed HIR. Instant feedback, no compile cycle. The primary development workflow for a Lisp. |
| P0 | `--emit-go` | CLI (`main.rs`) | Write the generated Go module to a directory instead of deleting it. Essential for understanding and debugging codegen output. |
| P1 | `vex fmt` (formatter) | CLI + `formatter.rs` | Opinionated code formatter for `.vx` files. Table stakes for modern languages. See `roadmap-rationale.md` §6. |
| P1 | `vex dev` (hot reload) | CLI | File watcher that recompiles and restarts on source changes. Sub-second reload is more productive than any debugger for server development. |
| P1 | Structured logging | Stdlib (`vex.log`) | Key-value structured log output. The server developer's primary diagnostic tool in production and development. |
| P1 | Source location mapping | Codegen (`codegen.rs`) | Emit `//line` directives in generated Go so stack traces and profiling tools point to `.vx` source. See `roadmap-rationale.md` §7. |
| P1 | Go toolchain detection | CLI (`main.rs`) | Validate Go installation and version before compilation with clear error messages. See `roadmap-rationale.md` §8. |
| P2 | Connected REPL | Toolchain | REPL that connects to a running `vex dev` process (nREPL model). Evaluate expressions in the server's context, redefine functions without restarting, inspect live state. |
| P2 | Error chain traces | Runtime / stdlib | When a `Result` error propagates through multiple functions, display the full chain with source locations. Makes error flows debuggable without a traditional stack trace debugger. |
| P3 | Test framework | Stdlib (`vex.test`) | Assertions, test discovery, test runner. General-purpose, not framework-specific. |

### Editor tooling

| Priority | Tool | Rationale |
|----------|------|-----------|
| P1 | Tree-sitter grammar | Syntax highlighting across Neovim, Helix, Zed, Emacs, VS Code. Trivial for s-expression syntax. See `roadmap-rationale.md` §11. |
| P2 | `vex lsp` (LSP) | Language server as a CLI subcommand. Map-reduce architecture enabled by per-file independence. See `roadmap-rationale.md` §10. |

### What belongs in the MCP framework, not the language

The following serve MCP server authors specifically and will be part of the future MCP framework (`vex.mcp`), built as a library on top of the language-level tools above:
Expand All @@ -706,7 +721,6 @@ The following serve MCP server authors specifically and will be part of the futu
|---------|-----------|
| **String interning** | Use `String` everywhere. Optimize later if profiling shows it matters. |
| **Arena allocation** | Use `Box` and `Vec`. Swap for arenas later if needed. |
| **User-defined macros (`defmacro`)** | Implemented. All macros — including core control flow (`cond`, `and`, `or`) defined in the self-hosted prelude — execute via a dedicated AST evaluator with automatic hygiene. See `language-design.md` §4.5 and §14.11. |
| **Multi-file compilation** | `SourceMap` supports `FileId` from day one, but the pipeline processes one file at a time. |
| **Error recovery in parser** | Stop at first error initially. Accumulate multiple errors later. |
| **LSP / incremental compilation** | Not a concern at this stage. |
| **Error recovery in parser** | Planned — see `roadmap.md` "Resilient parsing". Stop at first error for now. |
| **LSP / incremental compilation** | Planned — see `roadmap-rationale.md` §9 (summary extraction) and §10 (LSP). Not needed until multi-file support exists. |
Loading
Loading