mt-lang 0.3.26 → 0.3.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/docs/self-host-contract.md +1091 -0
- data/docs/self-host-plan.md +768 -0
- data/docs/self-host-progress.md +130 -0
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/tooling/linter/visitors.rb +156 -3
- metadata +5 -2
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
# Self-Host Plan
|
|
2
|
+
|
|
3
|
+
Status: **Planning** — see `docs/self-host-progress.md` for step-by-step tracking.
|
|
4
|
+
|
|
5
|
+
## 0. Goal
|
|
6
|
+
|
|
7
|
+
Port the Milk Tea compiler from Ruby to Milk Tea. The result must achieve a
|
|
8
|
+
**fixed point**: stage2-generated C is byte-identical to stage3-generated C,
|
|
9
|
+
proving the compiler is self-consistent. The working reference commit from the
|
|
10
|
+
previous self-host incarnation that achieved this is `a28545a7`.
|
|
11
|
+
|
|
12
|
+
Ruby host: `v0.3.26`, ~86K lines across 256 files.
|
|
13
|
+
Scope: core compiler pipeline (lex → parse → sema → lower → C backend) plus
|
|
14
|
+
build tooling, bootstrap, and test suite. LSP, DAP, bindgen, and the packages
|
|
15
|
+
subsystem are deferred to post-fixed-point phases.
|
|
16
|
+
|
|
17
|
+
## 1. Architecture
|
|
18
|
+
|
|
19
|
+
### 1.1 Pipeline
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
Source (.mt) → Lexer → Parser → Module Loader → Semantic Analyzer → Control Flow → Lowering → IR → C Backend → C code
|
|
23
|
+
↑ ↑
|
|
24
|
+
Module Binder Compile-Time Eval
|
|
25
|
+
Type System Intrinsics
|
|
26
|
+
AST / Token / IR
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Stage | Ruby source | Self-host module | Lines (Ruby) |
|
|
30
|
+
|-------|-------------|-----------------|-------------|
|
|
31
|
+
| AST / Token / CST / Keywords | `ast.rb` (369), `token.rb` (43), `cst.rb` (64), `keywords.rb` (97) | `mtc/parser/ast.mt`, `mtc/lexer/token.mt`, `mtc/lexer/keywords.mt` | 573 |
|
|
32
|
+
| Lexer | `lexer.rb` (427) + `lexer/` (949) | `mtc/lexer/` | 1,376 |
|
|
33
|
+
| Parser | `parser.rb` (442) + `parser/` (2,788) | `mtc/parser/` | 3,230 |
|
|
34
|
+
| Type System | `types.rb` (1,727) + `types/` (1,448) | `mtc/semantic/types.mt` | 3,175 |
|
|
35
|
+
| Module Loader & Resolver | `module_loader.rb` (717), `module_path_resolver.rb` (152), `module_roots.rb` (86) | `mtc/loader/` | 955 |
|
|
36
|
+
| Module Binder & Bindings | `module_binder.rb` (187), `bindings.rb` (13) + `bindings/` (148) | `mtc/loader/binder.mt` | 348 |
|
|
37
|
+
| Semantic Analyzer | `semantic_analyzer.rb` (355) + `semantic_analyzer/` (10,262) | `mtc/semantic/` | 10,617 |
|
|
38
|
+
| Control Flow | `control_flow.rb` (34) + `control_flow/` (1,148) | `mtc/semantic/control_flow/` | 1,182 |
|
|
39
|
+
| Compile-Time Evaluator | `compile_time.rb` (533) | `mtc/semantic/compile_time.mt` | 533 |
|
|
40
|
+
| Intrinsics | `intrinsics.rb` (87), `flow_scope.rb` (13) | `mtc/semantic/intrinsics.mt` | 100 |
|
|
41
|
+
| Lowering | `lowering.rb` (378) + `lowering/` (15,201) | `mtc/lowering/` | 15,579 |
|
|
42
|
+
| IR | `ir.rb` (88) | `mtc/ir.mt` | 88 |
|
|
43
|
+
| C Backend | `c_backend.rb` (322) + `c_backend/` (5,826) | `mtc/c_backend/` | 6,148 |
|
|
44
|
+
| Pretty Printer | `pretty_printer.rb` (17) + `pretty_printer/` (1,292) | `mtc/pretty_printer/` | 1,309 |
|
|
45
|
+
| S-expression Dumper | `sexpr_dumper.rb` (344) | `mtc/sexpr_dumper.mt` | 344 |
|
|
46
|
+
| Prelude / Async Installers | `prelude_installer.rb` (29), `async_runtime_installer.rb` (26) | merged into module loader / lowering | 55 |
|
|
47
|
+
| Debug Info Formatter | `debug_info_formatter.rb` (454) | `mtc/debug_info.mt` | 454 |
|
|
48
|
+
| CLI Args + Shared Helpers | in `cli.rb` (900+) | folded into `mtc/main.mt` | ~900 |
|
|
49
|
+
| CLI sub-commands | `cli/commands/` (22 files) | folded into `mtc/main.mt` | ~2,500 |
|
|
50
|
+
| Build / Run / Cache | `build.rb`, `build_cache.rb`, `run.rb` | `mtc/build.mt`, `mtc/build_cache.mt` | ~2,500 |
|
|
51
|
+
| **Core total** | **45,268** | | |
|
|
52
|
+
|
|
53
|
+
### 1.2 Module Structure
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
projects/mtc/
|
|
57
|
+
src/mtc/
|
|
58
|
+
main.mt — CLI entrypoint and command dispatch
|
|
59
|
+
build.mt — build orchestration (compile → C → binary)
|
|
60
|
+
build_cache.mt — incremental compilation cache
|
|
61
|
+
version_info.mt — git revision stamp placeholder
|
|
62
|
+
ast.mt — AST node definitions (shared by parser, sema, lowering, C backend)
|
|
63
|
+
ir.mt — IR data types (pure data, no logic)
|
|
64
|
+
sexpr_dumper.mt — S-expression serializer for tokens, AST, types, IR
|
|
65
|
+
lexer/
|
|
66
|
+
token_kinds.mt — TokenKind enum
|
|
67
|
+
token.mt — Token struct, LexDiagnostic
|
|
68
|
+
keywords.mt — keyword lookup table
|
|
69
|
+
lexer.mt — core lexer (lines, tokens, indentation)
|
|
70
|
+
parser/
|
|
71
|
+
token_stream.mt — SyntaxTokenStream wrapper
|
|
72
|
+
state.mt — parser state, diagnostics
|
|
73
|
+
literal_parsing.mt — number/string/char literal parsing
|
|
74
|
+
parser.mt — recursive-descent parser
|
|
75
|
+
loader/
|
|
76
|
+
path_resolver.mt — module path resolution with platform variants
|
|
77
|
+
errors.mt — error types
|
|
78
|
+
binder.mt — ModuleBinding construction from Analysis
|
|
79
|
+
module_loader.mt — transitive import loading, cycle handling, sema orchestration
|
|
80
|
+
semantic/
|
|
81
|
+
types.mt — Type variant, predicates, layout helpers, rendering
|
|
82
|
+
scope.mt — lexical scope tracking
|
|
83
|
+
expressions.mt — expression type inference helpers
|
|
84
|
+
diagnostics.mt — diagnostic struct
|
|
85
|
+
emit_expansion.mt — emit statement expansion
|
|
86
|
+
type_compatibility.mt — assignability, coercion, narrowing rules
|
|
87
|
+
intrinsics.mt — built-in callable handler (fatal, ref_of, read, size_of, etc.)
|
|
88
|
+
compile_time.mt — const/const function evaluator
|
|
89
|
+
analyzer.mt — semantic analysis (declarations + function bodies)
|
|
90
|
+
control_flow/
|
|
91
|
+
builder.mt — CFG construction
|
|
92
|
+
definite_assignment.mt — definite-assignment analysis
|
|
93
|
+
reachability.mt — return-on-all-paths, termination
|
|
94
|
+
nullability.mt — nullable flow narrowing
|
|
95
|
+
constprop.mt — constant propagation
|
|
96
|
+
lowering/
|
|
97
|
+
utils.mt — C name mangling (~20 functions), type classification helpers, range/match utilities
|
|
98
|
+
async.mt — async CPS lowering + root-main synthesis
|
|
99
|
+
lowering.mt — AST → IR transformation (main lowering pass, single module initially)
|
|
100
|
+
c_backend/
|
|
101
|
+
c_backend.mt — C code generation from IR; includes C keyword sanitization (C_KEYWORDS + sanitize_c_identifier)
|
|
102
|
+
pretty_printer/
|
|
103
|
+
ast_formatter.mt — AST → formatted text output
|
|
104
|
+
ir_formatter.mt — IR → text output
|
|
105
|
+
test/
|
|
106
|
+
lexer_test.mt
|
|
107
|
+
parser_test.mt
|
|
108
|
+
semantic_test.mt
|
|
109
|
+
lowering_test.mt
|
|
110
|
+
c_backend_test.mt
|
|
111
|
+
module_loader_test.mt
|
|
112
|
+
path_resolver_test.mt
|
|
113
|
+
ast_formatter_test.mt
|
|
114
|
+
scope_test.mt
|
|
115
|
+
linter_test.mt
|
|
116
|
+
fix_engine_test.mt
|
|
117
|
+
completion_test.mt
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 1.3 Key Data Representations
|
|
121
|
+
|
|
122
|
+
**Type system** — a `variant` (tagged union) mirroring Ruby's `Types::Base` hierarchy:
|
|
123
|
+
|
|
124
|
+
```mt
|
|
125
|
+
public variant Type:
|
|
126
|
+
ty_primitive(name: str)
|
|
127
|
+
ty_str
|
|
128
|
+
ty_error
|
|
129
|
+
ty_nullable(base: ptr[Type])
|
|
130
|
+
ty_named(name: str, module_name: str)
|
|
131
|
+
ty_imported(module_name: str, name: str, args: span[Type])
|
|
132
|
+
ty_generic(name: str, args: span[Type])
|
|
133
|
+
ty_function(params: span[Type], return_type: ptr[Type], variadic: bool, is_proc: bool)
|
|
134
|
+
ty_tuple(elements: span[Type], field_names: Option[span[str]])
|
|
135
|
+
ty_var(name: str) — unresolved type variable
|
|
136
|
+
ty_dyn(iface: str) — dyn[InterfaceName]
|
|
137
|
+
ty_opaque(module_name, name)
|
|
138
|
+
ty_literal_int(value: long) — compile-time integer type arg
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**AST** — structs for non-recursive decls + `ptr` indirection for recursive nodes:
|
|
142
|
+
|
|
143
|
+
- `SourceFile` struct: module name, kind, directives, declarations span
|
|
144
|
+
- `TypeRef` variant: named type, generic type, function type, pointer type, etc.
|
|
145
|
+
- Expression variants: identifier, literal, binary, call, member access, etc.
|
|
146
|
+
- Statement variants: let/var decl, if, while, for, match, return, etc.
|
|
147
|
+
|
|
148
|
+
**IR** — a decoupled variant between Lowering and CBackend (mirrors `ir.rb`):
|
|
149
|
+
|
|
150
|
+
- `Expr` variant: name, member, index, call, unary, binary, conditional, literal, etc.
|
|
151
|
+
- `Stmt` variant: local, assignment, block, if, switch, while, for, return, etc.
|
|
152
|
+
- Program struct: includes, constants, globals, opaques, structs, enums, variants, functions
|
|
153
|
+
|
|
154
|
+
**Symbol tables** — `std.map.Map[str, ...]` for every lookup: functions, struct fields, type aliases, member keys, method signatures, value types, interface bindings. Keys are always `str`.
|
|
155
|
+
|
|
156
|
+
## 2. Memory Management Strategy
|
|
157
|
+
|
|
158
|
+
The Ruby compiler has GC. The self-host must manage memory explicitly. Three
|
|
159
|
+
allocation tiers map to three lifetimes:
|
|
160
|
+
|
|
161
|
+
| Tier | Lifetime | Mechanism | Usage |
|
|
162
|
+
|------|----------|-----------|-------|
|
|
163
|
+
| Per-compilation | Entire `build` invocation | `std.mem.arena` (bump allocator) | Source text, AST nodes, IR nodes, type objects, most temporaries |
|
|
164
|
+
| Per-module | Single module parse/check | Arena mark/reset | Per-file lexer output, parser AST, sema Analysis |
|
|
165
|
+
| Persistent | Cross-module, whole program | `std.map.Map` + `std.vec.Vec` (owning heap) | Loader's `Program`, retained analyses, binding maps |
|
|
166
|
+
|
|
167
|
+
**Arena discipline:** At the start of each module's parse → sema → lower pass,
|
|
168
|
+
mark the arena. Before emitting IR for the next module, reset to that mark.
|
|
169
|
+
The loader retains per-module `Analysis` values in Vec/Map storage (owning
|
|
170
|
+
heap); everything else is arena-allocated and bulk-freed.
|
|
171
|
+
|
|
172
|
+
`std.mem.arena.Arena` provides:
|
|
173
|
+
- `create(capacity_bytes)` / `create_aligned(capacity_bytes, alignment)` — initialize
|
|
174
|
+
- `alloc_bytes(size)` / `alloc_bytes_aligned(size, alignment)` — bump-allocate
|
|
175
|
+
- `mark()` → `ptr_uint` — save position
|
|
176
|
+
- `reset(mark)` — rewind to saved mark
|
|
177
|
+
|
|
178
|
+
**String ownership:** The lexer produces token lexemes as `str` slices into the
|
|
179
|
+
source `string.String`. The source buffer is arena-allocated and outlives all
|
|
180
|
+
slices. No string copies during lexing or parsing. Diagnostic messages and
|
|
181
|
+
cross-module names use `string.String` (owning heap).
|
|
182
|
+
|
|
183
|
+
**Vec and Map lifecycle:** Dynamic collections used as symbol tables and IR
|
|
184
|
+
span sources require explicit `release()`. The convention is: the loader owns
|
|
185
|
+
the top-level `Program` and calls `release()` at teardown; all intermediate
|
|
186
|
+
Vec/Map values are arena-allocated and their release is handled by arena reset.
|
|
187
|
+
|
|
188
|
+
## 3. Bootstrap Architecture
|
|
189
|
+
|
|
190
|
+
### 3.1 Stage Model
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
Stage0 (Ruby host or pre-built snapshot binary)
|
|
194
|
+
↓ compiles
|
|
195
|
+
Stage1 (build/stage1/mtc) — carries stage0 code-gen artifacts, not reproducible
|
|
196
|
+
↓ compiles
|
|
197
|
+
Stage2 (build/stage2/mtc) — clean, distributable artifact
|
|
198
|
+
↓ compiles
|
|
199
|
+
Stage3 (build/stage3/mtc) — verification only
|
|
200
|
+
↓
|
|
201
|
+
diff stage2.c stage3.c — must be empty (fixed point)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
All stages read the same in-tree `std/` sources. There is no stage-specific
|
|
205
|
+
standard library — stdlib is source-only.
|
|
206
|
+
|
|
207
|
+
### 3.2 Stage0 Resolution
|
|
208
|
+
|
|
209
|
+
Resolution order in `tools/bootstrap.sh`:
|
|
210
|
+
|
|
211
|
+
1. `--bootstrap PATH` (explicit CLI argument, highest priority)
|
|
212
|
+
2. `$MTC_BOOTSTRAP` environment variable
|
|
213
|
+
3. `bin/bootstrap-mtc` in the repo root (user-placed binary)
|
|
214
|
+
4. `ruby -Ilib bin/mtc` (Ruby host — the development fallback)
|
|
215
|
+
5. Error: "No bootstrap compiler found"
|
|
216
|
+
|
|
217
|
+
### 3.3 Bootstrap Script
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
tools/bootstrap.sh [OPTIONS]
|
|
221
|
+
|
|
222
|
+
Options:
|
|
223
|
+
--bootstrap PATH Path to stage0 mtc binary (default: auto-detect)
|
|
224
|
+
--stage {1,2,3} Build target stage (default: 3)
|
|
225
|
+
--no-verify Skip stage3 fixed-point check
|
|
226
|
+
--profile {debug,release} Build profile (default: debug)
|
|
227
|
+
--keep-c Save generated C files alongside binaries
|
|
228
|
+
-j N Parallel jobs for C compilation (default: nproc)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Key invariants:
|
|
232
|
+
- `--no-cache` on every stage build — prevents cache poisoning across stages
|
|
233
|
+
- `-I .` to make repo-root `std/` visible as a module root
|
|
234
|
+
- Git revision injected into `version_info.mt` via `sed` before each build
|
|
235
|
+
- Fixed-point verification via `diff stage2.c stage3.c`
|
|
236
|
+
- Stage2 self-test: stage2 builds itself, producing a working binary
|
|
237
|
+
|
|
238
|
+
### 3.4 Development Shortcuts
|
|
239
|
+
|
|
240
|
+
```sh
|
|
241
|
+
# Fast iteration: just stage1, no verification
|
|
242
|
+
tools/bootstrap.sh --stage 1 --no-verify
|
|
243
|
+
|
|
244
|
+
# Verify only: assumes stage2 already built
|
|
245
|
+
tools/bootstrap.sh --verify-only
|
|
246
|
+
|
|
247
|
+
# Self-test: stage2 builds itself
|
|
248
|
+
tools/bootstrap.sh --self-test
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## 4. Implementation Plan
|
|
252
|
+
|
|
253
|
+
### Phase 0: Foundation (types, IR, AST, token definitions)
|
|
254
|
+
|
|
255
|
+
**Goal:** Data structures and utilities that every subsequent phase depends on.
|
|
256
|
+
|
|
257
|
+
| Step | Module | Lines (Ruby) | Verify |
|
|
258
|
+
|------|--------|-------------|--------|
|
|
259
|
+
| 0a | `lexer/token_kinds.mt` — TokenKind enum (all token types) | — | Compiles |
|
|
260
|
+
| 0b | `lexer/token.mt` — Token struct, TriviaToken, LexDiagnostic | 43 | Compiles |
|
|
261
|
+
| 0c | `lexer/keywords.mt` — keyword string → TokenKind lookup table | 97 | Unit tests |
|
|
262
|
+
| 0d | `ast.mt` — full AST: SourceFile, all Decl variants, TypeRef, Expr, Stmt | 369 | Compiles |
|
|
263
|
+
| 0e | `semantic/types.mt` — Type variant, classification predicates, rendering, layout helpers | 3,175 | Unit tests |
|
|
264
|
+
| 0f | `ir.mt` — IR Program, Expr, Stmt, all declaration structs. Imports `mtc.ast` (for `ast.ModuleKind`) and `mtc.semantic.types` (for `types.Type` in field/param definitions). | 88 | Compiles (no logic) |
|
|
265
|
+
| 0g | `sexpr_dumper.mt` — S-expression serialization for tokens, AST, types, and IR; byte-identical output vs Ruby. Used by `mtc lex --sexpr`, `mtc parse --sexpr`, and `mtc lower` for diff-based verification at every phase. | 344 | Diff vs Ruby S-expr output |
|
|
266
|
+
|
|
267
|
+
**Exit criteria:** All types compile under `mtc check`. The types module handles
|
|
268
|
+
the full type model: primitive lookup tables (integer widths, float widths),
|
|
269
|
+
type classification (`is_numeric`, `is_bool`, `is_void`, `contains_error`),
|
|
270
|
+
rendering (`type_to_string`), and layout helpers from `types/layout.rb`.
|
|
271
|
+
|
|
272
|
+
### Phase 1: Lexer
|
|
273
|
+
|
|
274
|
+
**Goal:** Byte-identical token stream vs Ruby lexer.
|
|
275
|
+
|
|
276
|
+
| Step | Module | Verify |
|
|
277
|
+
|------|--------|--------|
|
|
278
|
+
| 1a | `lexer.mt` — character classification, newline/indent handling, line continuation | `lexer_test.mt` |
|
|
279
|
+
| 1b | Identifier and keyword lexing, operator recognition | `lexer_test.mt` |
|
|
280
|
+
| 1c | Number literals (decimal, hex, binary, float, suffixes) | `lexer_test.mt` |
|
|
281
|
+
| 1d | String literals, c-strings, character literals, escapes | `lexer_test.mt` |
|
|
282
|
+
| 1e | Heredocs (plain, c-prefixed, f-prefixed), format strings | `lexer_test.mt` |
|
|
283
|
+
| 1f | Indent/dedent stack, EOF emission, `lex_reporting` with error collection | `lexer_test.mt` |
|
|
284
|
+
|
|
285
|
+
**Exit criteria:** `mtc lex <file>` produces byte-identical token output vs
|
|
286
|
+
Ruby for `examples/language_baseline.mt` and all test fixtures.
|
|
287
|
+
|
|
288
|
+
### Phase 2: Parser
|
|
289
|
+
|
|
290
|
+
**Goal:** Byte-identical AST vs Ruby parser.
|
|
291
|
+
|
|
292
|
+
| Step | Module | Verify |
|
|
293
|
+
|------|--------|--------|
|
|
294
|
+
| 2a | `token_stream.mt` — token stream abstraction | Compiles |
|
|
295
|
+
| 2b | `state.mt` — parser state, diagnostic collection | Compiles |
|
|
296
|
+
| 2c | `literal_parsing.mt` — number/string/char/heredoc literal AST construction | Parser tests |
|
|
297
|
+
| 2d | `ast.mt` — full AST node definitions (SourceFile, Decl, TypeRef, Expr, Stmt) | Compiles |
|
|
298
|
+
| 2e | `parser.mt` — type parsing, attribute parsing | Parser tests |
|
|
299
|
+
| 2f | Expression parsing (precedence climbing), call, member access, specialization | Parser tests |
|
|
300
|
+
| 2g | Statement parsing (let, var, if, while, for, match, return, defer, etc.) | Parser tests |
|
|
301
|
+
| 2h | Declaration parsing (function, struct, enum, variant, interface, etc.) | Parser tests |
|
|
302
|
+
| 2i | Top-level parsing, import resolution, `parse_source_file` | Parser tests |
|
|
303
|
+
| 2j | Error recovery, `parse_collecting_errors` | Parser tests |
|
|
304
|
+
|
|
305
|
+
**Exit criteria:** `mtc parse <file>` produces byte-identical AST vs Ruby for
|
|
306
|
+
all examples and test fixtures. Error recovery matches Ruby for malformed input.
|
|
307
|
+
|
|
308
|
+
### Phase 3: Path Resolver
|
|
309
|
+
|
|
310
|
+
**Goal:** Platform-specific file resolution, import path → filesystem path
|
|
311
|
+
mapping. This module has **no dependency on the semantic analyzer** — it only
|
|
312
|
+
uses `std.fs`, `std.path`, `std.string`. It can be ported immediately after the
|
|
313
|
+
parser, before the full sema stack.
|
|
314
|
+
|
|
315
|
+
| Step | Module | Verify |
|
|
316
|
+
|------|--------|--------|
|
|
317
|
+
| 3a | `path_resolver.mt` — `Platform` enum, `platform_suffix`, `resolve_source_path`, `resolve_module_path`, `infer_module_name` | `path_resolver_test.mt` |
|
|
318
|
+
|
|
319
|
+
**Exit criteria:** Platform-specific file resolution (`.linux.mt`, `.wasm.mt`)
|
|
320
|
+
works; import path `a.b.c` resolves to correct filesystem path.
|
|
321
|
+
|
|
322
|
+
### Phase 4: Semantic Analyzer
|
|
323
|
+
|
|
324
|
+
**Goal:** Type-check all declarations and function bodies. Error diagnostics
|
|
325
|
+
match Ruby for all examples and test fixtures. Ruby reference: `semantic_analyzer.rb` (355) + 16 subfiles (10,262) = 10,617 lines.
|
|
326
|
+
|
|
327
|
+
| Step | Module | Verify |
|
|
328
|
+
|------|--------|--------|
|
|
329
|
+
| 4a | `scope.mt` — lexical scope stack, name resolution helpers | `scope_test.mt` |
|
|
330
|
+
| 4b | `diagnostics.mt` — diagnostic struct, formatting | Compiles |
|
|
331
|
+
| 4c | `type_compatibility.mt` — assignability, coercion, narrowing, widening rules | Semantic tests |
|
|
332
|
+
| 4d | `expressions.mt` — type inference for expressions (read/write target resolution) | Semantic tests |
|
|
333
|
+
| 4e | `intrinsics.mt` — built-in callable handler: classifies calls to `fatal`, `ref_of`, `ptr_of`, `read`, `size_of`, `align_of`, `offset_of`, `reinterpret`, `zero`, `default`, `hash`, `equal`, `order`, `get`, `adapt`, etc. and resolves their types | Semantic tests |
|
|
334
|
+
| 4f | `compile_time.mt` — const/const function evaluator: evaluates integer/float/boolean/string literals, unary/binary ops, if-expressions, const function calls, size_of/align_of/offset_of, handles `ReturnValue` early exit | Semantic tests |
|
|
335
|
+
| 4g | `analyzer.mt` — top-level declaration pass (structs, enums, functions, interfaces, events) | Semantic tests |
|
|
336
|
+
| 4h | Function body checking (statements, return type, `?` propagation, guard narrowing) | Semantic tests |
|
|
337
|
+
| 4i | Interface conformance checking, generic instantiation, constraints | Semantic tests |
|
|
338
|
+
| 4j | `emit_expansion.mt` — emit statement lowering within const functions | Semantic tests |
|
|
339
|
+
|
|
340
|
+
**Exit criteria:** `mtc check <file>` produces byte-identical error diagnostics
|
|
341
|
+
vs Ruby for `examples/language_baseline.mt` and all test fixtures. Clean
|
|
342
|
+
programs produce zero diagnostics.
|
|
343
|
+
|
|
344
|
+
### Phase 4a: Control Flow Analysis
|
|
345
|
+
|
|
346
|
+
**Goal:** Validate control flow correctness. Ruby reference: `control_flow.rb` (34) +
|
|
347
|
+
`control_flow/` (9 subfiles, 1,148 lines) = 1,182 lines. Runs after semantic
|
|
348
|
+
analysis on each function body.
|
|
349
|
+
|
|
350
|
+
| Step | Module | Verify |
|
|
351
|
+
|------|--------|--------|
|
|
352
|
+
| 4a1 | `builder.mt` — CFG construction from AST function bodies, block sequencing | Control flow tests |
|
|
353
|
+
| 4a2 | `termination.mt` / `reachability.mt` — return-on-all-paths verification, unreachable code detection | Control flow tests |
|
|
354
|
+
| 4a3 | `definite_assignment.mt` — mutable/immutable tracking, let vs var, guard narrowing with `else:` | Control flow tests |
|
|
355
|
+
| 4a4 | `nullability_flow.mt` — nullable flow narrowing: `if p != null` narrows to non-null in then-branch, `if p == null` narrows in else-branch | Control flow tests |
|
|
356
|
+
| 4a5 | `constant_propagation.mt` — constant propagation for `when`/`inline if` discriminant evaluation | Control flow tests |
|
|
357
|
+
|
|
358
|
+
**Exit criteria:** Byte-identical control flow diagnostics (missing return,
|
|
359
|
+
unreachable code) vs Ruby for all examples.
|
|
360
|
+
|
|
361
|
+
### Phase 4b: Module Binder
|
|
362
|
+
|
|
363
|
+
### Phase 4b: Module Binder
|
|
364
|
+
|
|
365
|
+
**Goal:** Construct `ModuleBinding` from a completed `Analysis`, filtering
|
|
366
|
+
public/private visibility. Depends on `analyzer.Analysis` being fully built
|
|
367
|
+
(Phase 4) and control flow validated (Phase 4a). Ruby reference:
|
|
368
|
+
`module_binder.rb` (187) + `bindings/` (148) = 335 lines.
|
|
369
|
+
|
|
370
|
+
| Step | Module | Verify |
|
|
371
|
+
|------|--------|--------|
|
|
372
|
+
| 4b1 | `binder.mt` — ModuleBinding construction: copies public FnSig, FieldEntry, type aliases, method sigs, interface info, static members, match case names, and implemented interfaces from Analysis into the binding; constructs private_* counterparts from Analysis fields not marked public | Semantic tests |
|
|
373
|
+
|
|
374
|
+
**Exit criteria:** `ModuleBinding` correctly filters public declarations.
|
|
375
|
+
Cross-module tests produce correct import resolution.
|
|
376
|
+
|
|
377
|
+
### Phase 4c: Module Loader
|
|
378
|
+
|
|
379
|
+
**Goal:** Transitive import resolution, dependency ordering, cycle handling.
|
|
380
|
+
Depends on the parser (Phase 2), semantic analyzer (Phase 4), path resolver
|
|
381
|
+
(Phase 3), and binder (Phase 4b). Ruby reference: `module_loader.rb` (717 lines).
|
|
382
|
+
|
|
383
|
+
| Step | Module | Verify |
|
|
384
|
+
|------|--------|--------|
|
|
385
|
+
| 4c1 | `module_loader.mt` — `LoadedModule` struct, `LoadDiagnostic` struct, `Program` struct, `check_program()` entry point (parse all imports → topo sort → sema each → bind each → return) | `module_loader_test.mt` |
|
|
386
|
+
| 4c2 | Circular import handling (forward bindings → real analysis → re-check) | `module_loader_test.mt` |
|
|
387
|
+
|
|
388
|
+
**Exit criteria:** Multi-file programs with circular imports parse and order
|
|
389
|
+
correctly. `check_program` returns a valid `Program` with all modules analyzed.
|
|
390
|
+
|
|
391
|
+
### Phase 5: Lowering
|
|
392
|
+
|
|
393
|
+
**Goal:** AST → IR transformation. Ruby reference: `lowering.rb` (378) +
|
|
394
|
+
`lowering/` (16 subfiles, 15,201 lines) = 15,579 lines. Keep as a single
|
|
395
|
+
module initially — sub-file splitting is a code-organization concern that can
|
|
396
|
+
be deferred to post-fixed-point. The previous working self-host (commit
|
|
397
|
+
`a28545a7`) proved this approach: a 16,498-line `lowering.mt` was correct and
|
|
398
|
+
maintainable.
|
|
399
|
+
|
|
400
|
+
| Step | Module | Verify |
|
|
401
|
+
|------|--------|--------|
|
|
402
|
+
| 5a | `utils.mt` — C name mangling (~20 functions: module, function, enum member, field, value, imported, external C names), type qualification, struct/enum member C name lookup | Lowering tests |
|
|
403
|
+
| 5b | `lowering.mt` — constant and global lowering | Lowering tests |
|
|
404
|
+
| 5c | Type declaration lowering (struct, enum, union, variant, opaque) | Lowering tests |
|
|
405
|
+
| 5d | Expression lowering (literals, binary, call, member access, index, conditional, reinterpret, specialization) | Lowering tests |
|
|
406
|
+
| 5e | Statement lowering (let, var, if, while, for, match, return, defer, unsafe, `?` propagation) | Lowering tests |
|
|
407
|
+
| 5f | Function definition lowering, entry-point synthesis, foreign function boundary | Lowering tests |
|
|
408
|
+
| 5g | `async.mt` — CPS transformation, frame builder, root-main synthesis for async entrypoints | Lowering tests |
|
|
409
|
+
| 5h | Proc closure lowering, dyn interface dispatch, str_buffer lowering, format string lowering | Lowering tests |
|
|
410
|
+
|
|
411
|
+
**Exit criteria:** `mtc lower <file>` produces byte-identical IR vs Ruby for
|
|
412
|
+
all examples. The lowering handles `resolved_expr_types` and
|
|
413
|
+
`resolved_call_kinds` from the retained Analysis for precise type resolution.
|
|
414
|
+
|
|
415
|
+
### Phase 6: C Backend
|
|
416
|
+
|
|
417
|
+
**Goal:** IR → C source. Ruby reference: `c_backend.rb` (322) + `c_backend/`
|
|
418
|
+
(12 subfiles, 5,826 lines) = 6,148 lines.
|
|
419
|
+
|
|
420
|
+
| Step | Module | Verify |
|
|
421
|
+
|------|--------|--------|
|
|
422
|
+
| 6a | Preamble emission (includes, conditional helpers, runtime helpers), C keyword table (`C_KEYWORDS`) and `sanitize_c_identifier` — every field/member name emitted to C passes through this; must be called at struct/union/variant field declarations, member access, `offsetof`, designated initializers, and equality helpers — all 18 sites (see `type_declaration.rb`, `expressions.rb`, `runtime_helpers.rb`) | C backend tests |
|
|
423
|
+
| 6b | Type declaration emission (enum, flags, struct, union, variant, opaque, type aliases) | C backend tests |
|
|
424
|
+
| 6c | Expression emission (name, literal, binary, call, member, index, reinterpret) | C backend tests |
|
|
425
|
+
| 6d | Statement emission (block, if, switch/match, while, for, return, defer, goto/label) | C backend tests |
|
|
426
|
+
| 6e | Function definition emission, format string lowering, simd lowering | C backend tests |
|
|
427
|
+
| 6f | Prelude type emission (Option/Result structs, nullable opt types), aggregate field construction | C backend tests |
|
|
428
|
+
| 6g | String literal constant section, per-type equality helpers, variant equality helpers | C backend tests |
|
|
429
|
+
|
|
430
|
+
**Exit criteria:** `mtc emit-c <file>` produces byte-identical C vs Ruby for
|
|
431
|
+
`examples/language_baseline.mt` and all examples.
|
|
432
|
+
|
|
433
|
+
### Phase 6a: Pretty Printer
|
|
434
|
+
|
|
435
|
+
**Goal:** AST → formatted text and IR → text for debugging and testing. Ruby
|
|
436
|
+
reference: `pretty_printer.rb` (17) + `pretty_printer/` (3 subfiles, 1,292
|
|
437
|
+
lines) = 1,309 lines.
|
|
438
|
+
|
|
439
|
+
| Step | Module | Verify |
|
|
440
|
+
|------|--------|--------|
|
|
441
|
+
| 6a1 | `ast_formatter.mt` — AST → Milk Tea source text (round-trip formatting) | `ast_formatter_test.mt` |
|
|
442
|
+
| 6a2 | `ir_formatter.mt` — IR → text for `mtc lower` output | Diff vs Ruby `mtc lower` |
|
|
443
|
+
|
|
444
|
+
**Exit criteria:** `mtc parse file.mt | mtc format` round-trips identically.
|
|
445
|
+
`mtc lower` IR text output is byte-identical vs Ruby.
|
|
446
|
+
|
|
447
|
+
### Phase 7: Build System and CLI
|
|
448
|
+
|
|
449
|
+
**Goal:** A working compiler binary that builds itself. Ruby reference:
|
|
450
|
+
`tooling/cli.rb` (~900 lines), `cli/commands/` (22 files), `build.rb`,
|
|
451
|
+
`build_cache.rb`, `run.rb`.
|
|
452
|
+
|
|
453
|
+
**CLI dispatch** — a flat `if cmd == ...` chain in `main.mt` routing to handler
|
|
454
|
+
functions. Manual argv iteration, no option-parsing library. Each sub-command
|
|
455
|
+
(`lex`, `parse`, `check`, `lower`, `emit-c`, `build`, `run`, `test`, `format`,
|
|
456
|
+
`debug`, `lint`) is a function that parses its arguments from a `span[str]`.
|
|
457
|
+
|
|
458
|
+
| Step | Module | Verify |
|
|
459
|
+
|------|--------|--------|
|
|
460
|
+
| 7a | `main.mt` — CLI dispatch for `lex --sexpr`, `parse --sexpr`, `check`, `lower`, `emit-c` (no-build verification commands) | Diff S-expr output vs Ruby |
|
|
461
|
+
| 7b | `build.mt` — process spawning for CC, cache integration, linker flags, platform detection | Self-build |
|
|
462
|
+
| 7c | `build_cache.mt` — incremental compilation cache, content-hash keying, artifact storage | Unit tests |
|
|
463
|
+
| 7d | `main.mt` (continued) — `build`, `run`, `test`, `format`, `debug`, `lint` sub-commands | CLI smoke tests |
|
|
464
|
+
| 7e | `version_info.mt` — git revision stamp placeholder (replaced by `sed` in bootstrap script) | Bootstrap script |
|
|
465
|
+
|
|
466
|
+
**Exit criteria:** Stage1 builds and produces working `build/stage1/mtc`.
|
|
467
|
+
|
|
468
|
+
### Phase 8: Bootstrap and Fixed-Point
|
|
469
|
+
|
|
470
|
+
**Goal:** The compiler is self-consistent.
|
|
471
|
+
|
|
472
|
+
| Step | Verify |
|
|
473
|
+
|------|--------|
|
|
474
|
+
| 8a | `tools/bootstrap.sh` — 3-stage build script | Stage2 builds |
|
|
475
|
+
| 8b | Fixed-point check: `diff stage2.c stage3.c` is empty | Byte-identical |
|
|
476
|
+
| 8c | Self-test: stage2 builds itself to a working binary | Binary runs |
|
|
477
|
+
| 8d | Test suite passes under stage2: `build/stage2/mtc test projects/mtc` | All green |
|
|
478
|
+
|
|
479
|
+
**Exit criteria:** Stage2.c == stage3.c. Stage2 self-builds. Stage2 passes
|
|
480
|
+
all self-host tests.
|
|
481
|
+
|
|
482
|
+
### Phase 9+: LSP, DAP, Bindgen, Linter (Post-Fixed-Point)
|
|
483
|
+
|
|
484
|
+
These subsystems can be added incrementally without breaking the fixed point:
|
|
485
|
+
|
|
486
|
+
| Subsystem | Modules | Lines (est.) |
|
|
487
|
+
|-----------|---------|-------------|
|
|
488
|
+
| LSP server | `mtc/lsp/` (15–20 modules) | ~5,000 |
|
|
489
|
+
| DAP server | `mtc/dap/` (5–7 modules) | ~1,000 |
|
|
490
|
+
| Bindgen | `mtc/bindgen.mt` | ~1,000 |
|
|
491
|
+
| Imported bindings | `mtc/imported_bindings/` (5–7 modules) | ~3,000 |
|
|
492
|
+
| Linter | `mtc/linter/` (6–8 modules) | ~5,000 |
|
|
493
|
+
| Pretty printer (AST/IR) | `mtc/pretty_printer/` | ~2,000 |
|
|
494
|
+
| Packages subsystem | `mtc/packages/` | ~3,000 |
|
|
495
|
+
|
|
496
|
+
## 5. Key Technical Decisions
|
|
497
|
+
|
|
498
|
+
### 5.1 Lowering: Single Module Initially
|
|
499
|
+
|
|
500
|
+
The lowering pass is the largest and most interconnected component (15,579 lines
|
|
501
|
+
in 16 Ruby sub-files). Start as a single `lowering/lowering.mt` module.
|
|
502
|
+
Sub-file splitting is a code-organization concern that can be deferred to
|
|
503
|
+
post-fixed-point. The previous working self-host (commit `a28545a7`) proved
|
|
504
|
+
this approach: a 16,498-line `lowering.mt` was correct and maintainable.
|
|
505
|
+
|
|
506
|
+
### 5.2 Type System: Conservative Analyzer, Structural Lowering
|
|
507
|
+
|
|
508
|
+
The semantic analyzer is intentionally conservative: anything it cannot resolve
|
|
509
|
+
degrades to `ty_error` (compatible with everything). The lowering walks the
|
|
510
|
+
AST directly with access to retained `Analysis` data (function signatures,
|
|
511
|
+
`resolved_expr_types`, `resolved_call_kinds`). When the analyzer's permissive
|
|
512
|
+
types are insufficient, the lowering resolves types structurally.
|
|
513
|
+
|
|
514
|
+
This two-tier approach means the analyzer catches structural errors (duplicate
|
|
515
|
+
declarations, immutable-assignment violations, module inconsistencies) while
|
|
516
|
+
the lowering handles the precise type resolution needed for C code generation.
|
|
517
|
+
|
|
518
|
+
### 5.3 IR as Pure Data
|
|
519
|
+
|
|
520
|
+
The IR (`ir.mt`) contains no lowering or emission logic. It is a frozen shape
|
|
521
|
+
of variant types that both the Lowering and CBackend depend on. This
|
|
522
|
+
decoupling means neither stage depends on the other's internals — they share
|
|
523
|
+
only the IR contract.
|
|
524
|
+
|
|
525
|
+
### 5.4 String Slices, Not Copies
|
|
526
|
+
|
|
527
|
+
The lexer produces token lexemes as `str` slices borrowing from the source
|
|
528
|
+
buffer. The parser constructs AST identifiers and string literals as `str`
|
|
529
|
+
slices into the same buffer. No string heap-allocation during lexing or
|
|
530
|
+
parsing. Only cross-module names (diagnostic messages, binding keys) need
|
|
531
|
+
owning `string.String` values.
|
|
532
|
+
|
|
533
|
+
### 5.5 No Exception Handling — Result Types
|
|
534
|
+
|
|
535
|
+
Milk Tea has no exceptions. Every fallible operation returns `Result[T, E]`
|
|
536
|
+
or `Option[T]`. The `?` operator propagates failures. `let ... else:`
|
|
537
|
+
provides early-exit guard clauses. This is more verbose than Ruby's
|
|
538
|
+
`raise`/`rescue` but structurally identical — every `raise` in Ruby maps to
|
|
539
|
+
`return Result.failure`, and every `rescue` maps to `match` on the result.
|
|
540
|
+
|
|
541
|
+
Error collection (e.g., module_loader's collect-and-continue pattern) uses
|
|
542
|
+
explicit `Vec[Diagnostic]` accumulation rather than rescued exceptions.
|
|
543
|
+
|
|
544
|
+
### 5.6 No Ruby Metaprogramming Equivalents
|
|
545
|
+
|
|
546
|
+
The Ruby compiler uses:
|
|
547
|
+
- `Data.define` → Milk Tea `struct` + `extending` with default parameter values
|
|
548
|
+
- `case`/`when` on AST types → `match` on variant arms
|
|
549
|
+
- `Hash` with symbol keys → `Map[str, V]` with string keys
|
|
550
|
+
- `.each`/`.map`/`.select` → `for` loops or `iter` chains
|
|
551
|
+
- `require`/`require_relative` → `import` statements
|
|
552
|
+
- `gsub`/`split` → `std.str` operations and PCRE2 for regex cases (~59 occurrences)
|
|
553
|
+
- String interpolation `"#{x}"` → `f"#{x}"` format strings
|
|
554
|
+
|
|
555
|
+
### 6.7 Process Spawning
|
|
556
|
+
|
|
557
|
+
`std.process.mt` provides `ChildProcess` for spawning CC and linker
|
|
558
|
+
invocations. `std.stdio.mt` wraps C `FILE*` for reading/writing files.
|
|
559
|
+
Platform-specific file-system operations use `std/fs.linux.mt` /
|
|
560
|
+
`std/fs.windows.mt` (resolved at import time by the platform suffix mechanism).
|
|
561
|
+
|
|
562
|
+
### 6.8 CLI Dispatch
|
|
563
|
+
|
|
564
|
+
The Ruby CLI (`tooling/cli.rb`, ~900 lines) uses a flat `case command` dispatch
|
|
565
|
+
with manual `$stdin`-less argument parsing. Each sub-command module (22 files
|
|
566
|
+
under `cli/commands/`) is included into the CLI class. The self-host flattens
|
|
567
|
+
this into `main.mt`: a single `main(args: span[str])` function with an
|
|
568
|
+
`if cmd == "lex": ... else if cmd == "parse": ...` dispatch chain, each branch
|
|
569
|
+
calling into a dedicated handler function.
|
|
570
|
+
|
|
571
|
+
**Argument parsing** iterates through `args` manually with a mutable counter —
|
|
572
|
+
no option-parsing library:
|
|
573
|
+
|
|
574
|
+
```mt
|
|
575
|
+
function build_command(args: span[str]) -> int:
|
|
576
|
+
var roots = vec.Vec[str].create()
|
|
577
|
+
defer: roots.release()
|
|
578
|
+
var c_compiler = "cc"
|
|
579
|
+
var output_override: Option[str] = Option[str].none
|
|
580
|
+
var use_cache = true
|
|
581
|
+
var ai: ptr_uint = 1
|
|
582
|
+
while ai < args.len:
|
|
583
|
+
let arg = args[ai]
|
|
584
|
+
if arg == "-o":
|
|
585
|
+
output_override = Option[str].some(value = args[ai + 1])
|
|
586
|
+
ai += 2
|
|
587
|
+
continue
|
|
588
|
+
if arg == "--no-cache":
|
|
589
|
+
use_cache = false
|
|
590
|
+
ai += 1
|
|
591
|
+
continue
|
|
592
|
+
# positional operand
|
|
593
|
+
roots.push(arg)
|
|
594
|
+
ai += 1
|
|
595
|
+
# ... use parsed values ...
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
Every `Vec`, `String`, `Map` allocation uses `defer x.release()` for cleanup.
|
|
599
|
+
Error return paths use `let x = expr else: return 1` guards. Format output uses
|
|
600
|
+
`stdio.print_format(c"format %.*s\n", int<-(s.len), s.data)` — the `%.*s`
|
|
601
|
+
C-printf format for non-null-terminated `str` values.
|
|
602
|
+
|
|
603
|
+
The shared CLI conventions are:
|
|
604
|
+
- `-I DIR` / `--root DIR` — module search roots (repeatable)
|
|
605
|
+
- `--platform linux|windows|wasm` — target platform
|
|
606
|
+
- `--sexpr` — S-expression output (lex, parse, lower commands); this replaces
|
|
607
|
+
the `--machine` flag from the previous self-host at commit `a28545a7`
|
|
608
|
+
- `--no-cache` / `--keep-c PATH` / `-o OUTPUT` / `--cc CC` — build flags
|
|
609
|
+
- `--clean` / `--sanitize` / `--profile debug|release` — build options
|
|
610
|
+
- `--timeout SECONDS` / `--mem MB` / `--format human|tap|junit` — test flags
|
|
611
|
+
- Trailing `--` separates compiler flags from program arguments (run command)
|
|
612
|
+
- `-Werror` — treat warnings as errors (check command)
|
|
613
|
+
- `--locked` / `--frozen` — accepted for CLI compatibility but no-ops until
|
|
614
|
+
package-graph resolution is implemented
|
|
615
|
+
|
|
616
|
+
**Build flow** (`build.mt`): `module_loader.check_program()` → `lowering.lower()`
|
|
617
|
+
→ `c_backend.generate_c()` → `fs.write_text()` → build CC argument list as a
|
|
618
|
+
`Vec[str]` → `process.capture(command.as_span())` → check exit code.
|
|
619
|
+
|
|
620
|
+
**Test flow** (`main.mt` test_command): discover `@[test]` functions in
|
|
621
|
+
discovered `.mt` files → build each test file in-process → spawn each test
|
|
622
|
+
binary sandboxed with memory/timeout limits → parse exit code (0 = pass,
|
|
623
|
+
non-zero = fail, 124 = timeout, 137 = OOM).
|
|
624
|
+
|
|
625
|
+
### 6.9 S-expression Verification Format
|
|
626
|
+
|
|
627
|
+
The self-host's primary verification mechanism is byte-identical S-expression
|
|
628
|
+
output against the Ruby compiler. The `mtc lex --sexpr`, `mtc parse --sexpr`,
|
|
629
|
+
and `mtc lower` commands produce S-expression text that must match Ruby's
|
|
630
|
+
`SexprDumper` output character-for-character.
|
|
631
|
+
|
|
632
|
+
Key serialization invariants that must be preserved:
|
|
633
|
+
|
|
634
|
+
```mtc lex --sexpr file.mt
|
|
635
|
+
(token :type :identifier :lexeme "main" :literal nil :line 1 :column 0 ...)
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
```mtc parse --sexpr file.mt
|
|
639
|
+
(SourceFile :module_name "test" :module_kind :module
|
|
640
|
+
:declarations ((FunctionDef :name "main" ...)))
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
```mtc lower file.mt
|
|
644
|
+
(Program :module_name "test"
|
|
645
|
+
:includes ((Include :header "...")))
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
Critical formatting rules:
|
|
649
|
+
- Token types use hyphens (`plus-equal`, `shift-left`) not underscores
|
|
650
|
+
- Float literals use the lexeme string (the raw source text, not the parsed
|
|
651
|
+
`double` value), to preserve precision and avoid platform-specific rounding
|
|
652
|
+
- String escaping: `\\` for backslash, `\"` for quote, `\n` `\t` `\r` for control chars
|
|
653
|
+
- Types objects render as `(Types::ShortName :field value ...)`
|
|
654
|
+
- Symbols render as `:name` with hyphens
|
|
655
|
+
- Hashes and Sets are silently skipped (not serialized)
|
|
656
|
+
- `nil`, `true`, `false` render as bare atoms
|
|
657
|
+
|
|
658
|
+
## 6. Risk Register
|
|
659
|
+
|
|
660
|
+
| Risk | Likelihood | Impact | Mitigation |
|
|
661
|
+
|------|-----------|--------|------------|
|
|
662
|
+
| Arena size underestimation causing OOM | Medium | High | Start with 16 MB arenas; add growth/fallback to heap allocation |
|
|
663
|
+
| Type resolution bugs surfaced only at C emission | High | Medium | `ty_error` is permissive; structural lowering resolves at emit time |
|
|
664
|
+
| Map/Vector iterator invalidation during mutable iteration | Medium | High | Two-pass algorithms: collect keys first, then mutate |
|
|
665
|
+
| Order-dependent lowering (module load order affects type resolution) | Medium | High | Always look up types in the owning module first; fall back to global scan |
|
|
666
|
+
| C keyword sanitization gaps | High | Low | Centralize sanitization in `c_backend.mt` as a single `sanitize_c_identifier` function + `C_KEYWORDS` constant; every emission site (declarations, member access, offsetof, initializers, equality helpers) must call it — the Ruby compiler at commit `8f743bc6` defines the exhaustive call-site list of 18 locations across `type_declaration.rb`, `expressions.rb`, and `runtime_helpers.rb` |
|
|
667
|
+
| Fixed-point breakage from non-deterministic map iteration | Medium | Critical | Use `LinkedMap` where insertion order matters; sort keys otherwise |
|
|
668
|
+
| Compiler performance degradation vs Ruby | High | Medium | Arena allocation is faster than GC; string slices eliminate copies; profile after fixed point |
|
|
669
|
+
| Debugging C output from compiler bugs | High | Medium | `--keep-c` saves C for inspection; `diff` against Ruby C output pinpoints divergence |
|
|
670
|
+
|
|
671
|
+
## 7. Verification Strategy
|
|
672
|
+
|
|
673
|
+
### 7.1 Per-Phase Verification
|
|
674
|
+
|
|
675
|
+
Every phase produces testable output via `--sexpr`. The Ruby compiler serves as
|
|
676
|
+
the oracle. The previous self-host at `a28545a7` used a `--machine` flag for
|
|
677
|
+
human-readable token tables; the new self-host uses Ruby's current `--sexpr`
|
|
678
|
+
S-expression format, which produces deterministic, diffable output:
|
|
679
|
+
|
|
680
|
+
| Phase | Test Command | Oracle |
|
|
681
|
+
|-------|-------------|--------|
|
|
682
|
+
| Lexer | `diff <(ruby mtc lex --sexpr file.mt) <(stage1/mtc lex --sexpr file.mt)` | Ruby SexprDumper |
|
|
683
|
+
| Parser | `diff <(ruby mtc parse --sexpr file.mt) <(stage1/mtc parse --sexpr file.mt)` | Ruby SexprDumper |
|
|
684
|
+
| Semantic | `diff <(ruby mtc check file.mt 2>&1) <(stage1/mtc check file.mt 2>&1)` | Ruby sema diagnostics |
|
|
685
|
+
| Lowering | `diff <(ruby mtc lower file.mt) <(stage1/mtc lower file.mt)` | Ruby SexprDumper IR |
|
|
686
|
+
| C Backend | `diff <(ruby mtc emit-c file.mt) <(stage1/mtc emit-c file.mt)` | Ruby C backend |
|
|
687
|
+
| Build | `diff stage2.c stage3.c` | Stage2 C output |
|
|
688
|
+
| Runtime | Run compiled examples; compare stdout and exit code | Ruby-compiled binary |
|
|
689
|
+
|
|
690
|
+
### 7.2 Language Baseline
|
|
691
|
+
|
|
692
|
+
`examples/language_baseline.mt` (1,935 lines) exercises the complete language
|
|
693
|
+
surface. It is the primary integration test. Every phase must produce
|
|
694
|
+
byte-identical output vs Ruby for this file.
|
|
695
|
+
|
|
696
|
+
### 7.3 Test Suite
|
|
697
|
+
|
|
698
|
+
The self-host test suite mirrors the Ruby test suite's coverage:
|
|
699
|
+
|
|
700
|
+
| Test file | Scope |
|
|
701
|
+
|-----------|-------|
|
|
702
|
+
| `lexer_test.mt` | Token stream correctness, error recovery, edge cases |
|
|
703
|
+
| `parser_test.mt` | AST structure, error recovery, all declaration/expression/statement forms |
|
|
704
|
+
| `semantic_test.mt` | Type checking, interface conformance, generics, control flow |
|
|
705
|
+
| `lowering_test.mt` | IR structure, C name mangling, lowering passes |
|
|
706
|
+
| `c_backend_test.mt` | C output correctness, preamble, type emission |
|
|
707
|
+
| `module_loader_test.mt` | Multi-file programs, circular imports, platform resolution |
|
|
708
|
+
| `path_resolver_test.mt` | Platform-specific file resolution |
|
|
709
|
+
| `ast_formatter_test.mt` | AST → text round-trip |
|
|
710
|
+
| `scope_test.mt` | Lexical scope resolution |
|
|
711
|
+
| `linter_test.mt` | Lint rule detection and auto-fixes |
|
|
712
|
+
| `fix_engine_test.mt` | Text edit engine |
|
|
713
|
+
| `completion_test.mt` | LSP completion |
|
|
714
|
+
|
|
715
|
+
## 8. Standard Library Dependencies
|
|
716
|
+
|
|
717
|
+
The self-host compiler depends on the following `std` modules (all present in
|
|
718
|
+
the current standard library):
|
|
719
|
+
|
|
720
|
+
| Module | Used for |
|
|
721
|
+
|--------|---------|
|
|
722
|
+
| `std.mem.arena` | Per-compilation arena allocation |
|
|
723
|
+
| `std.mem.heap` | Owning heap allocation (Vec backing, Map backing) |
|
|
724
|
+
| `std.vec` | Dynamic arrays (token lists, declaration spans, argument lists) |
|
|
725
|
+
| `std.map` | Symbol tables, type caches, binding maps |
|
|
726
|
+
| `std.string` | Owned string building (diagnostics, C output, cross-module names) |
|
|
727
|
+
| `std.str` | String view operations (equality, starts_with, ends_with, slicing) |
|
|
728
|
+
| `std.fmt` | Format string building, integer/float rendering |
|
|
729
|
+
| `std.fs` | File system (read source, write C output, discover test files) |
|
|
730
|
+
| `std.path` | Path manipulation (join, basename, extension stripping) |
|
|
731
|
+
| `std.process` | Process spawning (CC invocation, linker) |
|
|
732
|
+
| `std.stdio` | C FILE* I/O, printf for debug output |
|
|
733
|
+
| `std.terminal` | Colored terminal output for diagnostics |
|
|
734
|
+
| `std.hash` | `hash[str]` and `equal[str]` for Map keying |
|
|
735
|
+
| `std.iter` | Composable iteration over collections |
|
|
736
|
+
| `std.json` | LSP/DAP protocol serialization (post-fixed-point) |
|
|
737
|
+
| `std.pcre2` | Regular expressions for version parsing (post-fixed-point) |
|
|
738
|
+
| `std.testing` | Test framework (`@[test]`, assertions) |
|
|
739
|
+
|
|
740
|
+
## 9. Configuration
|
|
741
|
+
|
|
742
|
+
### 9.1 Package Manifest
|
|
743
|
+
|
|
744
|
+
```toml
|
|
745
|
+
# projects/mtc/package.toml
|
|
746
|
+
[package]
|
|
747
|
+
name = "mtc"
|
|
748
|
+
version = "0.1.0"
|
|
749
|
+
source_root = "src"
|
|
750
|
+
|
|
751
|
+
[profile]
|
|
752
|
+
default = "debug"
|
|
753
|
+
|
|
754
|
+
[platform]
|
|
755
|
+
default = "linux"
|
|
756
|
+
|
|
757
|
+
[build]
|
|
758
|
+
entry = "src/mtc/main.mt"
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
### 9.2 Bootstrap Environment
|
|
762
|
+
|
|
763
|
+
| Variable | Purpose | Default |
|
|
764
|
+
|----------|---------|---------|
|
|
765
|
+
| `MTC_BOOTSTRAP` | Path to stage0 mtc binary | auto-detect |
|
|
766
|
+
| `CC` | C compiler for native builds | `cc` |
|
|
767
|
+
| `MTC_BUILD_DIR` | Build output directory | `build/` |
|
|
768
|
+
| `XDG_CACHE_HOME` | Cache root for build cache | `~/.cache` |
|