|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. |
| 4 | + |
| 5 | +## Project Overview |
| 6 | + |
| 7 | +This is `pslua` - a PureScript to Lua compiler backend. It takes PureScript CoreFn (the PureScript compiler's intermediate representation) and compiles it to Lua. The project supports dead code elimination (DCE), code inlining, FFI with Lua, and can emit either Lua modules or standalone applications. |
| 8 | + |
| 9 | +## Build System & Commands |
| 10 | + |
| 11 | +The project uses **Nix with flakes** for reproducible builds and **Cabal** for Haskell development. |
| 12 | + |
| 13 | +### Development Environment |
| 14 | + |
| 15 | +```bash |
| 16 | +# Enter development shell (provides all tools) |
| 17 | +nix develop |
| 18 | + |
| 19 | +# Or use direnv (if configured) |
| 20 | +direnv allow |
| 21 | +``` |
| 22 | + |
| 23 | +### Building |
| 24 | + |
| 25 | +```bash |
| 26 | +# Build the project |
| 27 | +cabal build |
| 28 | + |
| 29 | +# Build specific component |
| 30 | +cabal build exe:pslua |
| 31 | +cabal build lib:pslua |
| 32 | +``` |
| 33 | + |
| 34 | +### Testing |
| 35 | + |
| 36 | +```bash |
| 37 | +# Run all tests with detailed output |
| 38 | +cabal test all --test-show-details=direct |
| 39 | + |
| 40 | +# Run specific test suite |
| 41 | +cabal test spec |
| 42 | + |
| 43 | +# Run tests in watch mode (requires ghcid) |
| 44 | +ghcid --command="cabal repl test:spec" --test=":main" |
| 45 | +``` |
| 46 | + |
| 47 | +The test suite includes: |
| 48 | +- **Unit tests**: Property-based testing with Hedgehog |
| 49 | +- **Golden tests**: Compiles PureScript test modules from `test/ps/golden/Golden/*/Test.purs` to Lua and compares against golden files |
| 50 | +- **Evaluation tests**: Runs generated Lua code and verifies output |
| 51 | +- **Luacheck tests**: Validates generated Lua code syntax |
| 52 | + |
| 53 | +### Testing PureScript Code |
| 54 | + |
| 55 | +Golden tests require compiling PureScript sources first: |
| 56 | + |
| 57 | +```bash |
| 58 | +# Compile PureScript test sources (from test/ps directory) |
| 59 | +cd test/ps |
| 60 | +spago build -u '-g corefn' |
| 61 | +cd ../.. |
| 62 | +``` |
| 63 | + |
| 64 | +### Resetting Golden Files |
| 65 | + |
| 66 | +```bash |
| 67 | +# Remove all golden files and regenerate them |
| 68 | +./scripts/golden_reset |
| 69 | +``` |
| 70 | + |
| 71 | +This finds all files named `golden.*` in `test/ps/output` and deletes them, then runs `cabal test` to regenerate them. |
| 72 | + |
| 73 | +### Code Formatting & Linting |
| 74 | + |
| 75 | +```bash |
| 76 | +# Format Haskell code with Fourmolu |
| 77 | +fourmolu -i lib/ exe/ test/ |
| 78 | + |
| 79 | +# Or use treefmt to format all files |
| 80 | +treefmt |
| 81 | + |
| 82 | +# Run HLint |
| 83 | +hlint lib/ exe/ test/ |
| 84 | + |
| 85 | +# Check Lua files |
| 86 | +luacheck --quiet --std min test/ps/output/ |
| 87 | +``` |
| 88 | + |
| 89 | +### Running the Compiler |
| 90 | + |
| 91 | +```bash |
| 92 | +# Build and run |
| 93 | +cabal run pslua -- --help |
| 94 | + |
| 95 | +# Or after building |
| 96 | +./dist-newstyle/build/x86_64-linux/ghc-9.8.1/pslua-0.1.0.0/x/pslua/build/pslua/pslua --help |
| 97 | + |
| 98 | +# Or via nix |
| 99 | +nix run . -- --help |
| 100 | +``` |
| 101 | + |
| 102 | +Typical usage: |
| 103 | +```bash |
| 104 | +pslua \ |
| 105 | + --foreign-path ./foreign \ |
| 106 | + --ps-output ./output \ |
| 107 | + --lua-output-file ./dist/Main.lua \ |
| 108 | + --entry Main.main |
| 109 | +``` |
| 110 | + |
| 111 | +## Code Architecture |
| 112 | + |
| 113 | +### Compilation Pipeline |
| 114 | + |
| 115 | +The compilation happens in several distinct phases: |
| 116 | + |
| 117 | +``` |
| 118 | +PureScript Source → CoreFn → IR → Lua → Optimized Lua |
| 119 | +``` |
| 120 | + |
| 121 | +1. **CoreFn Reading** (`Language.PureScript.CoreFn.*`) |
| 122 | + - Reads PureScript compiler's CoreFn JSON output |
| 123 | + - Parses module structure, imports, and expressions |
| 124 | + |
| 125 | +2. **IR Translation** (`Language.PureScript.Backend.IR.*`) |
| 126 | + - Converts CoreFn to an intermediate representation (IR) |
| 127 | + - IR is simpler than CoreFn but still high-level |
| 128 | + - Handles data declarations, bindings, and module structure |
| 129 | + |
| 130 | +3. **IR Optimization** (`Language.PureScript.Backend.IR.Optimizer`) |
| 131 | + - Performs optimizations on IR: |
| 132 | + - Eta reduction/expansion |
| 133 | + - Beta reduction |
| 134 | + - Constant folding |
| 135 | + - Case-of-case transformation |
| 136 | + - **Inliner** (`IR.Inliner`): Marks expressions for inlining |
| 137 | + - **Dead Code Elimination** (`IR.DCE`): Removes unused bindings |
| 138 | + |
| 139 | +4. **Linking** (`Language.PureScript.Backend.IR.Linker`) |
| 140 | + - Creates an "UberModule" containing all reachable code |
| 141 | + - Supports two modes: |
| 142 | + - `LinkAsModule`: Creates a Lua module (returns a table) |
| 143 | + - `LinkAsApplication`: Creates a runnable Lua script (calls entry point) |
| 144 | + |
| 145 | +5. **Lua Code Generation** (`Language.PureScript.Backend.Lua`) |
| 146 | + - Converts optimized IR to Lua AST |
| 147 | + - Handles foreign imports via FFI |
| 148 | + - Manages name mangling and scope |
| 149 | + |
| 150 | +6. **Lua Optimization** (`Language.PureScript.Backend.Lua.Optimizer`) |
| 151 | + - Optimizes generated Lua code |
| 152 | + |
| 153 | +7. **Lua Printing** (`Language.PureScript.Backend.Lua.Printer`) |
| 154 | + - Pretty-prints Lua AST to text |
| 155 | + |
| 156 | +### Key Module Structure |
| 157 | + |
| 158 | +**CoreFn Layer** (`Language.PureScript.CoreFn.*`): |
| 159 | +- `CoreFn.Reader`: Reads CoreFn JSON from disk |
| 160 | +- `CoreFn.FromJSON`: JSON deserialization |
| 161 | +- `CoreFn.Expr`: CoreFn expression types |
| 162 | +- `CoreFn.Traversals`: Traversal utilities |
| 163 | + |
| 164 | +**IR Layer** (`Language.PureScript.Backend.IR.*`): |
| 165 | +- `IR.Types`: Core IR data types (`RawExp`, `Module`, `Binding`) |
| 166 | +- `IR.Names`: Name types (`Qualified`, `ModuleName`, etc.) |
| 167 | +- `IR.Linker`: Creates UberModule from multiple modules |
| 168 | +- `IR.Optimizer`: IR-level optimizations |
| 169 | +- `IR.DCE`: Dead code elimination |
| 170 | +- `IR.Inliner`: Inlining annotations and logic |
| 171 | + |
| 172 | +**Lua Backend** (`Language.PureScript.Backend.Lua.*`): |
| 173 | +- `Lua.Types`: Lua AST types (`Chunk`, `Statement`, `Exp`) |
| 174 | +- `Lua.Name`: Safe Lua identifier generation |
| 175 | +- `Lua.Printer`: Pretty-printing Lua code |
| 176 | +- `Lua.Optimizer`: Lua-level optimizations |
| 177 | +- `Lua.DCE`: Lua-specific DCE |
| 178 | +- `Lua.Linker.Foreign`: FFI support for Lua foreign modules |
| 179 | +- `Lua.Fixture`: Runtime support code injected into output |
| 180 | + |
| 181 | +**Main Entry** (`Language.PureScript.Backend`): |
| 182 | +- `compileModules`: Top-level compilation function orchestrating the pipeline |
| 183 | + |
| 184 | +### Important Concepts |
| 185 | + |
| 186 | +**De Bruijn Indices**: The IR uses De Bruijn indices for variable references. A `Ref` contains: |
| 187 | +- A qualified name |
| 188 | +- An index indicating which binding of that name to reference |
| 189 | + |
| 190 | +**Groupings**: Bindings are wrapped in `Grouping`: |
| 191 | +- `Standalone`: Non-recursive binding |
| 192 | +- `RecursiveGroup`: Mutually recursive bindings |
| 193 | + |
| 194 | +**AppOrModule**: Determines compilation mode: |
| 195 | +- `AsModule ModuleName`: Generate a Lua module |
| 196 | +- `AsApplication ModuleName Ident`: Generate an executable that calls the entry point |
| 197 | + |
| 198 | +**UberModule**: A flattened representation of all modules after linking, containing: |
| 199 | +- All reachable bindings |
| 200 | +- Module exports |
| 201 | +- Foreign imports |
| 202 | + |
| 203 | +## Code Style |
| 204 | + |
| 205 | +### Haskell Style |
| 206 | + |
| 207 | +This project follows specific Haskell style conventions: |
| 208 | + |
| 209 | +- **Indentation**: 2 spaces (enforced by Fourmolu) |
| 210 | +- **Line length**: Max 80 characters |
| 211 | +- **Unicode**: Always use unicode syntax (`∷` instead of `::`, `→` instead of `->`) |
| 212 | +- **Prelude**: Uses Relude (not base Prelude) |
| 213 | +- **Imports**: Explicit qualified imports preferred |
| 214 | +- **Extensions**: Many enabled by default (see `pslua.cabal` common stanza) |
| 215 | + |
| 216 | +### Formatting Configuration |
| 217 | + |
| 218 | +- **Fourmolu** (`fourmolu.yaml`): |
| 219 | + - 2-space indentation |
| 220 | + - 80-character column limit |
| 221 | + - Leading commas and function arrows |
| 222 | + - Unicode always |
| 223 | + - Multi-line Haddock style |
| 224 | + |
| 225 | +- **HLint** (`.hlint.yaml`): |
| 226 | + - Configured with project-specific extensions |
| 227 | + - Run with `--color --cpp-simple -XQuasiQuotes -XImportQualifiedPost` |
| 228 | + |
| 229 | +### Section Comments |
| 230 | + |
| 231 | +Use section-style comments to organize code: |
| 232 | + |
| 233 | +```haskell |
| 234 | +-------------------------------------------------------------------------------- |
| 235 | +-- Section Title --------------------------------------------------------------- |
| 236 | + |
| 237 | +code here... |
| 238 | +``` |
| 239 | + |
| 240 | +Both lines should be exactly 80 characters. Helper functions go at the bottom after a "Helper Functions" or "Utility Functions" section. |
| 241 | + |
| 242 | +## Testing Strategy |
| 243 | + |
| 244 | +### Golden Tests |
| 245 | + |
| 246 | +Golden tests are the primary integration testing mechanism: |
| 247 | + |
| 248 | +1. PureScript test files in `test/ps/golden/Golden/*/Test.purs` |
| 249 | +2. Compiled to CoreFn with `spago build -u '-g corefn'` |
| 250 | +3. Test suite reads CoreFn, compiles to IR, generates Lua |
| 251 | +4. Compares against golden files: |
| 252 | + - `golden.ir` - Intermediate representation |
| 253 | + - `golden.lua` - Generated Lua code |
| 254 | + - `eval/golden.txt` - Execution output (if module has a `main` function) |
| 255 | + |
| 256 | +To add a new golden test: |
| 257 | +1. Create `test/ps/golden/Golden/NewTest/Test.purs` |
| 258 | +2. Run `cabal test` - it will fail and create `actual.*` files |
| 259 | +3. Review the actual files |
| 260 | +4. Rename `actual.*` to `golden.*` if correct |
| 261 | +5. Commit the golden files |
| 262 | + |
| 263 | +### Property-Based Tests |
| 264 | + |
| 265 | +The project uses Hedgehog for property-based testing: |
| 266 | +- Generators in `test/Language/PureScript/Backend/IR/Gen.hs` |
| 267 | +- Tests in `test/Language/PureScript/Backend/IR/Spec.hs` and similar |
| 268 | + |
| 269 | +## Development Workflow |
| 270 | + |
| 271 | +1. Make code changes in `lib/` or `exe/` |
| 272 | +2. Format code: `fourmolu -i lib/ exe/ test/` |
| 273 | +3. Run HLint: `hlint lib/ exe/ test/` |
| 274 | +4. Run tests: `cabal test all --test-show-details=direct` |
| 275 | +5. If golden tests fail, inspect `actual.*` files in `test/ps/output/` |
| 276 | +6. Update golden files if changes are correct |
| 277 | + |
| 278 | +## Debugging Tips |
| 279 | + |
| 280 | +- The IR and Lua types have `Show` instances - use `pShowOpt` for pretty debug output |
| 281 | +- Golden test failures show diffs between expected and actual |
| 282 | +- Use `actual.*` files alongside `golden.*` files to debug compilation issues |
| 283 | +- Check `test/ps/output/Golden.*/` directories for generated IR and Lua |
| 284 | +- Lua evaluation errors are captured in the test output |
0 commit comments