Skip to content

Commit 3b9fc3b

Browse files
Dale-Blackclaude
andcommitted
JST-DOC-001: Documenter.jl site with embedded playground
Set up Documenter.jl with 5 pages: Home (embedded playground iframe), Getting Started (usage examples), API Reference (@docs blocks), Supported Functions (comprehensive table), Architecture (pipeline docs). Playground is built into docs/src/assets/playground/ during makedocs. 15 new tests verify docs structure. 825 total tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 828a879 commit 3b9fc3b

10 files changed

Lines changed: 591 additions & 0 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
docs/build/
2+
docs/src/assets/playground/

docs/Project.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[deps]
2+
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
3+
JavaScriptTarget = "91c6ed5a-2d04-438e-9585-7332732578c7"
4+
5+
[compat]
6+
Documenter = "1"

docs/make.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using Documenter
2+
using JavaScriptTarget
3+
4+
# Build the playground into docs/src/assets/playground/
5+
playground_dir = joinpath(@__DIR__, "src", "assets", "playground")
6+
mkpath(playground_dir)
7+
build_playground(playground_dir; verbose=false)
8+
9+
makedocs(;
10+
modules=[JavaScriptTarget],
11+
sitename="JavaScriptTarget.jl",
12+
remotes=nothing,
13+
warnonly=[:missing_docs, :docs_block],
14+
pages=[
15+
"Home" => "index.md",
16+
"Getting Started" => "getting_started.md",
17+
"API Reference" => "api.md",
18+
"Supported Functions" => "supported_functions.md",
19+
"Architecture" => "architecture.md",
20+
],
21+
format=Documenter.HTML(;
22+
prettyurls=get(ENV, "CI", "false") == "true",
23+
assets=["assets/playground-embed.css"],
24+
),
25+
)
26+
27+
deploydocs(;
28+
repo="github.com/GroupTherapyOrg/JavaScriptTarget.jl.git",
29+
devbranch="main",
30+
)

docs/src/api.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# API Reference
2+
3+
## Public API
4+
5+
```@docs
6+
compile
7+
compile_module
8+
JSOutput
9+
build_inference_tables
10+
build_playground
11+
```

docs/src/architecture.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Architecture
2+
3+
## Compiler Pipeline
4+
5+
JavaScriptTarget.jl uses Julia's built-in type inference and compilation pipeline to produce JavaScript.
6+
7+
```
8+
Julia source → Julia compiler → Typed IR (CodeInfo) → JavaScriptTarget codegen → JavaScript
9+
```
10+
11+
### Step 1: Type Inference
12+
13+
Julia's compiler infers types for all variables given concrete argument types. `compile(f, (Int32, Int32))` triggers `Base.code_typed(f, (Int32, Int32))` to get fully typed intermediate representation.
14+
15+
### Step 2: IR Walking
16+
17+
The typed IR (`CodeInfo`) contains SSA (Static Single Assignment) statements. Each statement is one of:
18+
19+
| IR Node | JavaScript |
20+
|---|---|
21+
| `ReturnNode(val)` | `return val;` |
22+
| `GotoIfNot(cond, dest)` | `if (!cond) { ... }` |
23+
| `GotoNode(label)` | `while`/`break`/`continue` |
24+
| `PhiNode(edges, values)` | `let var; ... var = val;` |
25+
| `PiNode(val, type)` | No-op (type narrowing) |
26+
| `Expr(:call, f, args...)` | Intrinsic or function call |
27+
| `Expr(:invoke, mi, args...)` | Direct function call |
28+
| `Expr(:new, T, args...)` | `new ClassName(args)` |
29+
30+
### Step 3: Type-Directed Emission
31+
32+
The inferred types control how operations are emitted:
33+
34+
- **Int32**: All arithmetic gets `| 0` coercion. Multiplication uses `Math.imul()`.
35+
- **Float64**: Standard JS number operations, no coercion.
36+
- **String**: Direct JS string. Interpolation becomes template literals.
37+
- **Struct**: ES6 class with `$type` on prototype for dispatch.
38+
- **Nothing**: `null`. **Missing**: `undefined`.
39+
- **Vector**: Compile-time index offset (`a[i]``a[i-1]`).
40+
41+
### Step 4: Output
42+
43+
The codegen produces a `JSOutput` struct containing:
44+
- `.js` — Generated JavaScript
45+
- `.dts` — TypeScript definitions
46+
- `.sourcemap` — V3 source map (if enabled)
47+
- `.exports` — List of exported names
48+
- `.runtime_bytes` — Size of included runtime helpers
49+
50+
## Self-Hosted Browser Playground
51+
52+
The playground runs Julia entirely in the browser using a custom compilation pipeline written in JavaScript:
53+
54+
```
55+
Julia Source
56+
57+
parser.js — Recursive descent parser (tokenizer + Pratt precedence)
58+
59+
lowerer.js — AST → SSA IR with phi nodes
60+
61+
infer.js — Forward SSA type inference (3-tier lookup)
62+
63+
codegen.js — SSA IR + types → JavaScript
64+
65+
eval() — Execute in browser
66+
```
67+
68+
### Pipeline Components
69+
70+
**parser.js** (~1,700 lines) — Hand-written tokenizer and recursive descent parser. Supports all Julia literals, 16 operator precedence levels, function definitions, structs, control flow, lambdas, string interpolation, and type annotations.
71+
72+
**lowerer.js** (~2,075 lines) — Transforms AST into SSA IR. Performs scope analysis, control flow construction with phi nodes, and 15 macro pre-expansions (@show, @assert, etc.).
73+
74+
**infer.js** (~790 lines) — Type inference using pre-computed tables from `types.bin`:
75+
1. FNV-1a hash table lookup for concrete types
76+
2. Parametric rule matching for generic patterns
77+
3. Fallback to `Any`
78+
79+
Forward SSA pass with loop fixed-point iteration (max 4 iterations).
80+
81+
**codegen.js** (~1,340 lines) — Generates JavaScript from SSA IR with type-directed emission. Structure hint analysis identifies if/while/for/try regions. Inline runtime helpers are tree-shakeable.
82+
83+
**runtime.js** (~210 lines) — Output capture, string helpers, struct equality, math helpers, checked arithmetic, and execution sandbox.
84+
85+
**worker.js** (~135 lines) — Web Worker orchestrating the pipeline. Receives Julia source, runs parse→lower→infer→codegen→eval, returns output.
86+
87+
**types.bin** (~82 KB, ~10 KB gzipped) — Pre-computed inference tables generated by `build_inference_tables()`. Contains TypeID registry (55 types), FNV-1a hash table (670 entries), and 123 parametric rules.
88+
89+
### Build Process
90+
91+
```julia
92+
# Generate the complete playground
93+
build_playground("./output"; verbose=true, bundle=false)
94+
```
95+
96+
This copies all JS files, generates `types.bin` from Julia's type system, and includes the CodeMirror-based `index.html`.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
.playground-embed {
2+
border: 1px solid #e0e0e0;
3+
border-radius: 8px;
4+
overflow: hidden;
5+
margin: 1em 0;
6+
}
7+
8+
.playground-embed iframe {
9+
width: 100%;
10+
height: 500px;
11+
border: none;
12+
}
13+
14+
@media (prefers-color-scheme: dark) {
15+
.playground-embed {
16+
border-color: #444;
17+
}
18+
}

docs/src/getting_started.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Getting Started
2+
3+
## Installation
4+
5+
```julia
6+
using Pkg
7+
Pkg.add("JavaScriptTarget")
8+
```
9+
10+
## Basic Usage
11+
12+
### Compiling a Single Function
13+
14+
```julia
15+
import JavaScriptTarget as JST
16+
17+
# Define a Julia function with type annotations
18+
f(x::Int32, y::Int32) = x * y + 1
19+
20+
# Compile to JavaScript
21+
result = JST.compile(f, (Int32, Int32))
22+
println(result.js)
23+
```
24+
25+
Output:
26+
```javascript
27+
function f(x, y) {
28+
return (Math.imul(x, y) + 1) | 0;
29+
}
30+
```
31+
32+
### Compiling Multiple Functions
33+
34+
```julia
35+
helper(x::Float64) = x * x
36+
main(x::Float64) = helper(x) + 1.0
37+
38+
result = JST.compile_module([
39+
(helper, (Float64,)),
40+
(main, (Float64,)),
41+
]; module_format=:esm)
42+
43+
println(result.js)
44+
```
45+
46+
Output:
47+
```javascript
48+
function helper(x) {
49+
return x * x;
50+
}
51+
function main(x) {
52+
return helper(x) + 1.0;
53+
}
54+
export { helper, main };
55+
```
56+
57+
### TypeScript Definitions
58+
59+
Every compilation automatically generates `.d.ts` TypeScript definitions:
60+
61+
```julia
62+
result = JST.compile(f, (Int32, Int32))
63+
println(result.dts)
64+
```
65+
66+
Output:
67+
```typescript
68+
export declare function f(x: number, y: number): number;
69+
```
70+
71+
### Module Formats
72+
73+
The `module_format` option controls the output format:
74+
75+
- `:esm` (default) — ES modules with `export`
76+
- `:cjs` — CommonJS with `module.exports`
77+
- `:iife` — Immediately-invoked function expression
78+
- `:none` — Raw function definitions (useful for testing)
79+
80+
```julia
81+
# CommonJS output
82+
result = JST.compile(f, (Int32, Int32); module_format=:cjs)
83+
84+
# IIFE output
85+
result = JST.compile(f, (Int32, Int32); module_format=:iife)
86+
```
87+
88+
### Source Maps
89+
90+
Enable source maps to map JavaScript output back to Julia source:
91+
92+
```julia
93+
result = JST.compile(f, (Int32, Int32); sourcemap=true)
94+
println(result.sourcemap) # V3 JSON source map
95+
```
96+
97+
## Working with Structs
98+
99+
Julia structs compile to ES6 classes:
100+
101+
```julia
102+
struct Point
103+
x::Float64
104+
y::Float64
105+
end
106+
107+
distance(p::Point) = sqrt(p.x^2 + p.y^2)
108+
109+
result = JST.compile_module([
110+
(distance, (Point,)),
111+
])
112+
```
113+
114+
Output:
115+
```javascript
116+
class Point {
117+
constructor(x, y) { this.x = x; this.y = y; }
118+
}
119+
Point.prototype.$type = 100;
120+
121+
function distance(p) {
122+
return Math.sqrt(p.x * p.x + p.y * p.y);
123+
}
124+
```
125+
126+
## Building the Playground
127+
128+
Generate a self-contained browser playground:
129+
130+
```julia
131+
JST.build_playground("./playground_output"; verbose=true)
132+
```
133+
134+
This creates a directory with all files needed to run Julia in the browser:
135+
- `parser.js` — Julia parser
136+
- `lowerer.js` — AST to SSA IR
137+
- `infer.js` — Type inference engine
138+
- `codegen.js` — IR to JavaScript
139+
- `runtime.js` — Browser runtime helpers
140+
- `worker.js` — Web Worker orchestration
141+
- `types.bin` — Pre-computed type inference tables
142+
- `index.html` — CodeMirror-based playground page

docs/src/index.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# JavaScriptTarget.jl
2+
3+
A standalone Julia-to-JavaScript transpiler. Compile Julia functions to JavaScript that runs in Node.js or the browser.
4+
5+
## Try it
6+
7+
```@raw html
8+
<div class="playground-embed">
9+
<iframe src="../assets/playground/index.html" title="Julia Playground"></iframe>
10+
</div>
11+
```
12+
13+
## Features
14+
15+
- **`compile(f, types)`** — Compile a single Julia function to JavaScript
16+
- **`compile_module(functions)`** — Compile multiple functions into an ES module
17+
- **`.d.ts` generation** — TypeScript definitions with branded types for structs
18+
- **Source maps** — Map JS output back to Julia source lines
19+
- **Self-hosted playground** — Browser-based Julia editor and runner
20+
- **Tree-shakeable runtime** — Only includes helpers your code actually uses
21+
22+
## Quick Start
23+
24+
```julia
25+
import JavaScriptTarget as JST
26+
27+
# Compile a function
28+
f(x::Int32) = x * x + 1
29+
result = JST.compile(f, (Int32,))
30+
println(result.js)
31+
# function f(x) {
32+
# return (Math.imul(x, x) + 1) | 0;
33+
# }
34+
35+
# Compile multiple functions into a module
36+
helper(x::Float64) = sin(x) + cos(x)
37+
main(x::Float64) = helper(x) * 2.0
38+
result = JST.compile_module([
39+
(helper, (Float64,)),
40+
(main, (Float64,)),
41+
]; module_format=:esm)
42+
```
43+
44+
## Installation
45+
46+
```julia
47+
using Pkg
48+
Pkg.add("JavaScriptTarget")
49+
```
50+
51+
## Type Mapping
52+
53+
| Julia Type | JavaScript | Notes |
54+
|---|---|---|
55+
| `Int32` | `number` | All arithmetic gets `\| 0` coercion |
56+
| `Float64` | `number` | No coercion needed |
57+
| `String` | `string` | Direct mapping |
58+
| `Bool` | `boolean` | Direct mapping |
59+
| `Nothing` | `null` | |
60+
| `struct` | ES6 class | `$type` on prototype for dispatch |
61+
| `Vector{T}` | `Array` | 1-indexed access compiled to 0-indexed |
62+
| `Dict{K,V}` | `Map` | |
63+
| `Tuple` | `Array` (readonly) | |

0 commit comments

Comments
 (0)