Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
56be7c7
Add WASM compilation and web TUI REPL for slox interpreter
claude Jan 5, 2026
a715244
Fix GitHub Pages deployment
claude Jan 5, 2026
ed5465f
Fix SwiftWasm toolchain URLs
claude Jan 5, 2026
8152724
Fix SwiftWasm tar extraction (strip-components=1)
claude Jan 5, 2026
1add918
Fix carton download - use direct binary instead of tarball
claude Jan 5, 2026
7531eaf
Simplify workflow: use swift build directly instead of carton
claude Jan 5, 2026
be9e56b
Fix JavaScriptKit API compatibility issues in WASM entry point
claude Jan 5, 2026
c90408b
Fix browser-wasi-shim and JavaScriptKit runtime URLs
claude Jan 5, 2026
525c5ce
Add local WASI and JavaScriptKit runtime modules
claude Jan 5, 2026
487cc2e
Fix syntax error in wasi-loader.js (escaped newline)
claude Jan 5, 2026
17dd765
Add missing JavaScriptKit _no_catch function variants
claude Jan 6, 2026
48cf6c1
Add swjs_load_typed_array to JavaScriptKit runtime
claude Jan 6, 2026
0bcd9e5
Disable Jekyll processing in GitHub Pages deployment
claude Jan 6, 2026
2e2ae9c
Remove configure-pages step to avoid Jekyll trigger
claude Jan 6, 2026
f3cb023
Fix swjs_get_prop/set_prop: name is object ref, not memory pointer
claude Jan 6, 2026
5254b2e
Use official JavaScriptKit runtime from npm package
claude Jan 6, 2026
eea409d
Remove JavaScriptEventLoop dependency (not needed for sync REPL)
claude Jan 6, 2026
d55efee
Build WASM in reactor mode for JavaScriptKit compatibility
claude Jan 6, 2026
6693817
Use @main entry point for reactor mode initialization
claude Jan 6, 2026
859e0da
Use @_cdecl to export slox_init function for JS to call
claude Jan 6, 2026
511dbb3
Modern minimalistic terminal UI with full keyboard support
claude Jan 6, 2026
34e056b
Add WASM init feedback, man-page help, fixed 80-col terminal
claude Jan 6, 2026
1f91d43
Fix man page formatting to fit 80 columns
claude Jan 6, 2026
e42fc9a
Fix output callback - pass JSValue.string directly to JS callback
claude Jan 6, 2026
cfa2073
Fix output callback, add build timestamp, fix help formatting
claude Jan 6, 2026
f7d344e
Add Swift output tests, fix terminal help rendering
claude Jan 6, 2026
997c1eb
Fix WASM unreachable error: use JSFunction instead of JSObject for ca…
claude Jan 6, 2026
47d535a
Fix ambiguous JSValue.function by using .object for JSClosure
claude Jan 6, 2026
9783ef4
Add debug logging to trace unreachable error
claude Jan 6, 2026
bef6ce1
Fix optional chaining error in log function
claude Jan 6, 2026
e2e5061
Fix console.log call - extract JSObject first
claude Jan 6, 2026
645c31c
Force unwrap console.log call result
claude Jan 6, 2026
0e7c026
Skip buildTime setting to isolate crash
claude Jan 6, 2026
3be2fee
Log build time to console instead of setting as property
claude Jan 6, 2026
96e43a3
Add REPL-style eval that always outputs result
claude Jan 6, 2026
782df0a
Add magic commands (%env, %globals, %reset) for interpreter inspection
claude Jan 6, 2026
392cc9b
Remove GitHub Pages deployment, keep build-only workflow
claude Jan 6, 2026
964258c
Revert "Remove GitHub Pages deployment, keep build-only workflow"
claude Jan 6, 2026
40469d0
Improve REPL: build time display, multiline input, cursor navigation
claude Jan 6, 2026
9076606
Fix build time: pass via sloxReady callback instead of separate function
claude Jan 6, 2026
e97d60a
Remove build time display to fix unreachable error
claude Jan 6, 2026
36a8f74
Inject build time directly into app.js during CI build
claude Jan 6, 2026
e6531d0
Add documentation and inline comments
claude Jan 6, 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
60 changes: 60 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Build and Deploy to GitHub Pages

on:
push:
branches: [ main, master, claude/* ]
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: "pages"
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-22.04
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install SwiftWasm
run: |
set -ex
curl -L -o swiftwasm.tar.gz "https://github.com/swiftwasm/swift/releases/download/swift-wasm-5.9.2-RELEASE/swift-wasm-5.9.2-RELEASE-ubuntu22.04_x86_64.tar.gz"
mkdir -p $HOME/swiftwasm
tar -xzf swiftwasm.tar.gz -C $HOME/swiftwasm --strip-components=1
ls -la $HOME/swiftwasm/
echo "$HOME/swiftwasm/usr/bin" >> $GITHUB_PATH
$HOME/swiftwasm/usr/bin/swift --version

- name: Build WASM
run: |
set -ex
swift build --triple wasm32-unknown-wasi -c release --product slox-wasm \
-Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor \
-Xlinker --export=slox_init
ls -la .build/release/
cp .build/release/slox-wasm.wasm web/
# Inject build timestamp into JS
BUILD_TIME=$(date -u +"%Y-%m-%d %H:%M UTC")
sed -i "s/__BUILD_TIME__/$BUILD_TIME/" web/app.js

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './web'

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
178 changes: 178 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# CLAUDE.md - Project Guide for AI Assistants

This document provides context for AI assistants working on the slox project.

## Project Overview

**slox** is a Swift implementation of the Lox programming language from "Crafting Interpreters" by Robert Nystrom. It compiles to WebAssembly for browser-based execution via a terminal-style REPL.

## Architecture

```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ web/app.js │────▶│ slox-wasm.wasm │────▶│ SloxCore │
│ (xterm.js) │◀────│ (JavaScriptKit) │◀────│ (Swift Lox) │
└─────────────────┘ └──────────────────┘ └─────────────────┘
```

### Key Components

| Component | Location | Purpose |
|-----------|----------|---------|
| SloxCore | `Sources/SloxCore/` | Core interpreter library |
| slox-wasm | `Sources/slox-wasm/main.swift` | WASM entry point & JS API |
| Web REPL | `web/app.js` | Terminal UI (xterm.js) |
| CLI | `Sources/slox/` | Native command-line tool |

## Build Commands

```bash
# Native build (for local testing)
swift build

# Run tests
swift test

# WASM build (requires SwiftWasm toolchain)
swift build --triple wasm32-unknown-wasi -c release --product slox-wasm \
-Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor \
-Xlinker --export=slox_init

# Copy WASM to web folder
cp .build/release/slox-wasm.wasm web/
```

## WASM/JavaScript Integration

### Exported API (`window.slox`)

| Function | Signature | Description |
|----------|-----------|-------------|
| `initInterpreter` | `(callback: Function) -> bool` | Initialize with output callback |
| `execute` | `(source: string) -> void` | Execute Lox code (REPL mode) |
| `getEnvironment` | `() -> string` | Get local scope as string |
| `getGlobals` | `() -> string` | Get global definitions |
| `reset` | `() -> void` | Reset interpreter state |

### JavaScriptKit Notes

- `JSClosure` instances must be stored globally to prevent GC
- Use `JSFunction` (not `JSObject`) for JavaScript function callbacks
- Use `.object()` (not `.function()`) when assigning closures to properties
- `@_cdecl("name")` exports functions with predictable C names

## Interpreter Pipeline

```
Source → Scanner → Tokens → Parser → AST → Resolver → Interpreter → Result
```

### Key Classes

| Class | File | Responsibility |
|-------|------|----------------|
| `Driver` | `Driver.swift` | Orchestrates the pipeline |
| `Scanner` | `Scanner.swift` | Lexical analysis |
| `Parser` | `Parser.swift` | Syntax analysis (recursive descent) |
| `Resolver` | `Resolver.swift` | Variable binding resolution |
| `Interpreter` | `Interpreter.swift` | AST evaluation |

### Driver Methods

- `run(source:)` - Batch execution (print statements only)
- `runRepl(source:)` - REPL mode (returns evaluation result)
- `getGlobals()` - Introspection for `%globals` command
- `getEnvironment()` - Introspection for `%env` command
- `reset()` - Clear user-defined state

## Web REPL Features

### Terminal Commands
- `help` - Display language reference
- `clear` - Clear screen

### Magic Commands
- `%env` - Show current local scope
- `%globals` - Show global definitions
- `%reset` - Reset interpreter state

### Input Features
- Multiline input (unclosed braces/parens/strings continue)
- Command history (Up/Down arrows)
- Cursor navigation (Left/Right, Ctrl+Left/Right, Home/End)
- Ctrl+C to cancel, Ctrl+L to clear screen

## Testing

```bash
# Run all tests
swift test

# Run specific test file
swift test --filter OutputTests
```

### Test Categories (OutputTests.swift)
- Print output tests
- REPL evaluation tests
- Magic command support tests

## CI/CD

GitHub Actions workflow (`.github/workflows/deploy.yml`):
1. Install SwiftWasm toolchain
2. Build WASM binary
3. Inject build timestamp into `web/app.js`
4. Deploy to GitHub Pages

### Build Time Injection
The placeholder `__BUILD_TIME__` in `web/app.js` is replaced with the actual build timestamp during CI via `sed`.

## File Structure

```
slox/
├── Sources/
│ ├── SloxCore/ # Core interpreter library
│ │ ├── Driver.swift # Main entry point
│ │ ├── Scanner.swift # Lexer
│ │ ├── Parser.swift # Parser
│ │ ├── Resolver.swift # Variable resolution
│ │ ├── Interpreter.swift # Execution
│ │ └── ... # AST, Environment, etc.
│ ├── slox/ # Native CLI
│ │ └── main.swift
│ └── slox-wasm/ # WASM target
│ └── main.swift # JS API exports
├── Tests/
│ └── SloxCoreTests/ # Unit tests
├── web/ # Static web files
│ ├── index.html
│ ├── app.js # REPL implementation
│ └── *.js/*.mjs # Runtime dependencies
├── Package.swift # Swift package manifest
└── .github/workflows/ # CI configuration
```

## Common Tasks

### Adding a New Built-in Function
1. Add to `Interpreter.defineGlobals()` in `Interpreter.swift`
2. Add tests in `OutputTests.swift`

### Modifying WASM API
1. Update `Sources/slox-wasm/main.swift`
2. Add corresponding handler in `web/app.js`
3. Store any new `JSClosure` in a global variable

### Adding a New Magic Command
1. Add handler in `SloxRepl.handleMagicCommand()` in `app.js`
2. Add Swift support in `Driver.swift` if needed
3. Update help text in `MANPAGE` constant
4. Add tests in `OutputTests.swift`

## Known Limitations

- WASM binary is ~1.5MB (JavaScriptKit overhead)
- No file I/O in browser environment
- `clock()` uses JavaScript `Date.now()` via configured provider
34 changes: 29 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
// swift-tools-version:5.3
// swift-tools-version:5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "slox",
products: [
.executable(name: "slox", targets: ["slox"]),
.executable(name: "slox-wasm", targets: ["slox-wasm"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.3.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.2.0"),
.package(url: "https://github.com/swiftwasm/JavaScriptKit", from: "0.19.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "SloxCore",
dependencies: [],
path: "Sources/SloxCore"
),
.executableTarget(
name: "slox",
dependencies: [
"SloxCore",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]),
],
path: "Sources/slox"
),
.executableTarget(
name: "slox-wasm",
dependencies: [
"SloxCore",
.product(name: "JavaScriptKit", package: "JavaScriptKit"),
],
path: "Sources/slox-wasm"
),
.testTarget(
name: "SloxCoreTests",
dependencies: ["SloxCore"],
path: "Tests/SloxCoreTests"
),
]
)
File renamed without changes.
File renamed without changes.
Loading
Loading