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.
@@ -0,0 +1,1091 @@
1
+ # Self-Host Contract
2
+
3
+ The exact public API surface this document describes is **inferred from the Ruby
4
+ host compiler** (`lib/milk_tea/core/`). A previous working self-host (commit
5
+ `a28545a7`) demonstrated that this contract produces a correct, fixed-point
6
+ compiler. Do not deviate from it without verifying that the Ruby host does the
7
+ same thing.
8
+
9
+ ---
10
+
11
+ ## 0. Import Dependency Graph
12
+
13
+ Modules are listed in **topological dependency order** — a module may only
14
+ import modules that appear earlier in the graph. This guarantees zero circular
15
+ imports within the compiler itself.
16
+
17
+ ```
18
+ ┌─────────────────────┐
19
+ │ token_kinds (enum) │ ← zero imports, always the root
20
+ └────────┬────────────┘
21
+ ┌─────────────────────┼─────────────────────┐
22
+ │ │ │
23
+ ┌────┴────┐ ┌─────┴─────┐ ┌───────┴───────┐
24
+ │ token │ │ keywords │ │ ast (types) │
25
+ │ structs │ │ lookup │ │ SourceFile, │
26
+ └────┬────┘ └───────────┘ │ Decl, Expr, │
27
+ │ │ Stmt, ... │
28
+ │ └───────┬───────┘
29
+ │ │
30
+ ┌────┴──────────────────────────────────────┐ │
31
+ │ lexer │ │
32
+ │ source → tokens, indent/dedent, heredocs │ │
33
+ └───────────────────┬───────────────────────┘ │
34
+ │ │
35
+ ┌───────────────────┼────────────────────────────┘
36
+ │ │
37
+ │ ┌───────┴───────┐
38
+ │ │ parser.state │ (diagnostic types)
39
+ │ └───────┬───────┘
40
+ │ │
41
+ │ ┌───────┴──────────┐
42
+ │ │ parser │
43
+ │ │ tokens → ast │
44
+ │ └───────┬──────────┘
45
+ │ │
46
+ │ ┌──────────────┼─────────────┐
47
+ │ │ │ │
48
+ │ │ ┌─────────┴─────────┐ │
49
+ │ │ │ semantic.types │ │
50
+ │ │ │ Type variant │ │
51
+ │ │ └─────────┬─────────┘ │
52
+ │ │ │ │
53
+ │ │ ┌─────────┴─────────┐ │
54
+ │ │ │ ir (IR types) │ │ ← depends on semantic.types
55
+ │ │ └─────────┬─────────┘ │
56
+ │ │ │ │
57
+ │ │ ┌─────────┴─────────┐ │
58
+ │ │ │ sexpr_dumper │ │ ← depends on ast, token, types, ir
59
+ │ │ └─────────┬─────────┘ │
60
+ │ │ │ │
61
+ │ │ ┌─────────┴─────────────┴┐
62
+ │ │ │ semantic.scope │
63
+ │ │ └─────────┬──────────────┘
64
+ │ │ │
65
+ │ │ ┌─────────┴──────────────┐
66
+ │ │ │ path_resolver │
67
+ │ │ │ platform resolution │
68
+ │ │ └─────────┬──────────────┘
69
+ │ │ │
70
+ │ │ ┌─────────┴───────────────────────────────┐
71
+ │ │ │ semantic.analyzer │
72
+ │ │ │ check_module, Analysis, ModuleBinding │
73
+ │ │ └─────────┬───────────────────────────────┘
74
+ │ │ │
75
+ │ │ ┌─────────┴─────────┐
76
+ │ │ │ control_flow.* │ (depends on analyzer.Analysis)
77
+ │ │ └─────────┬─────────┘
78
+ │ │ │
79
+ │ │ ┌─────────┴─────────┐
80
+ │ │ │ loader.binder │
81
+ │ │ └─────────┬─────────┘
82
+ │ │ │
83
+ │ │ ┌─────────┴─────────┐
84
+ │ │ │ module_loader │
85
+ │ │ │ orchestration │
86
+ │ │ └─────────┬─────────┘
87
+ │ │ │
88
+ │ │ ┌─────────┴─────────┐
89
+ │ │ │ compile_time │ const/const-func eval
90
+ │ │ └─────────┬─────────┘
91
+ │ │ │
92
+ │ │ ┌─────────┴─────────┐
93
+ │ │ │ lowering.utils │ C name mangling, type helpers (needs types, ast, ir)
94
+ │ │ └─────────┬─────────┘
95
+ │ │ │
96
+ │ │ ┌─────────┴─────────┐
97
+ │ │ │ lowering.main │
98
+ │ │ │ AST → IR │
99
+ │ │ └─────────┬─────────┘
100
+ │ │ │
101
+ │ │ ┌─────────┴─────────┐
102
+ │ │ │ lowering.async │
103
+ │ │ └─────────┬─────────┘
104
+ │ │ │
105
+ │ │ ┌─────────┴─────────┐
106
+ │ │ │ c_backend │
107
+ │ │ │ IR → C source │
108
+ │ │ └─────────┬─────────┘
109
+ │ │ │
110
+ │ │ ┌─────────┴─────────┐
111
+ │ │ │ pretty_printer │
112
+ │ │ └───────────────────┘
113
+ │ │
114
+ │ │ ┌───────────────────────┐
115
+ │ └────┤ build / build_cache │
116
+ │ └───────────┬───────────┘
117
+ │ │
118
+ │ ┌─────────────┴─────────────┐
119
+ │ │ main (CLI dispatch) │
120
+ │ │ lex / parse / check / │
121
+ │ │ lower / emit-c / build │
122
+ │ │ run / test / format │
123
+ │ └───────────────────────────┘
124
+ ```
125
+
126
+ ### 0.1 Ownership Cheat Sheet
127
+
128
+ | Data structure | Allocator | Passed as | Freed by |
129
+ |---------------|-----------|-----------|----------|
130
+ | `Token` | arena | value (copy by stack) | arena reset |
131
+ | `TokenKind` | — (enum) | value | — |
132
+ | `Ast.SourceFile` | arena (heap nodes via `ptr[]`) | `ref` or `ptr` | arena reset |
133
+ | `Ast.Expr` / `Ast.Stmt` (recursive) | arena (`ptr[Expr]`) | `ptr` | arena reset |
134
+ | `Types.Type` | arena (`ptr[Type]`) | `ptr` | arena reset |
135
+ | `IR.Program` | arena (heap nodes) | ref or owned | arena reset |
136
+ | `IR.Expr` / `IR.Stmt` (recursive) | arena (`ptr[Expr]`) | `ptr` | arena reset |
137
+ | `Analysis` (per-module sema result) | loader's `Vec` (heap) | `ptr` (borrowed from loader) | loader's `Program.release()` |
138
+ | `ModuleBinding` (per-module export surface) | loader's `Map` (heap) | embedded in Analysis | loader's `Program.release()` |
139
+ | `Program` (loader result) | heap (`Vec` + `Map`) | value/ref | caller's `release()` |
140
+ | `string.String` (diagnostics, C output) | heap | value, `ref` to extend | caller's `release()` |
141
+ | `Vec[T]` (token lists, decl spans) | heap | value, `ref` to push/iter | caller's `release()` |
142
+ | `Map[K,V]` (symbol tables) | heap | value, `ref` to lookup/insert | caller's `release()` |
143
+ | `str` (source slices, keys) | borrows from source buffer | value (copy of fat ptr) | outlives source buffer |
144
+ | `span[T]` (slices of IR / decls) | borrows from owning Vec | value (copy of fat ptr) | outlives owning Vec |
145
+
146
+ ### 0.2 Module Naming Convention
147
+
148
+ | Convention | Meaning |
149
+ |-----------|---------|
150
+ | `mtc.lexer.token` | Module path, same as `import` statement |
151
+ | `token.Token` | Type `Token` from module aliased as `token` |
152
+ | `vec.Vec[T]` | Generic `Vec` from module aliased as `vec` |
153
+
154
+ ---
155
+
156
+ ## 1. Root-Level Data Modules
157
+
158
+ These modules contain **zero logic** — only struct, variant, enum, and flag
159
+ definitions. They can be ported by copying the type definitions exactly.
160
+
161
+ ### 1.1 `mtc.lexer.token_kinds` (TokenKind enum)
162
+
163
+ Imports: **none**.
164
+
165
+ The enum matches the Ruby compiler's token types exactly. **All keyword
166
+ members use the `tk_` prefix** to avoid collisions with Milk Tea's own reserved
167
+ words (e.g., `tk_if` not `if`, `tk_function` not `function`). The parser
168
+ dispatches on `TokenKind` values.
169
+
170
+ See `lib/milk_tea/core/token.rb` `KEYWORDS` and the old self-host's
171
+ `projects/mtc/src/mtc/lexer/token_kinds.mt` at commit `a28545a7` for the full
172
+ member list. The exact numeric values are not reproduced here — they are
173
+ irrelevant to the contract.
174
+
175
+ ### 1.2 `mtc.lexer.token` (Token types)
176
+
177
+ Imports: `mtc.lexer.token_kinds`.
178
+
179
+ ```mt
180
+ public struct TriviaToken:
181
+ kind: token_kinds.TokenKind
182
+ text: str # slice into source
183
+ line: ptr_uint
184
+ column: ptr_uint
185
+ start_offset: ptr_uint
186
+ end_offset: ptr_uint
187
+
188
+ public struct Token:
189
+ kind: token_kinds.TokenKind
190
+ lexeme: str # slice into source
191
+ literal: Option[TokenLiteral]
192
+ line: ptr_uint
193
+ column: ptr_uint
194
+ start_offset: ptr_uint
195
+ end_offset: ptr_uint
196
+ leading_trivia: span[TriviaToken]
197
+ trailing_trivia: span[TriviaToken]
198
+
199
+ public variant TokenLiteral:
200
+ integer_literal(value: long, raw: str)
201
+ float_literal(value: double, raw: str, suffix: Option[str])
202
+ string_literal(value: str)
203
+ char_literal(value: ubyte)
204
+ boolean_literal(value: bool)
205
+
206
+ public struct LexDiagnostic:
207
+ message: cstr
208
+ line: ptr_uint
209
+ column: ptr_uint
210
+ ```
211
+
212
+ ### 1.3 `mtc.lexer.keywords` (Keyword lookup)
213
+
214
+ Imports: `mtc.lexer.token_kinds`.
215
+
216
+ ```mt
217
+ # Returns token_kinds.TokenKind for a keyword string, or Option.none
218
+ public function lookup(word: str) -> Option[token_kinds.TokenKind]:
219
+ ```
220
+
221
+ ### 1.4 `mtc.ast` (AST types)
222
+
223
+ Imports: **none** (no other mtc modules — may use `std.vec`, `std.str`).
224
+
225
+ The AST is a direct 1:1 translation of `lib/milk_tea/core/ast.rb`. Every
226
+ `Data.define` becomes a `struct`; every `case`/`when` discrimination on the
227
+ Ruby class type becomes a `variant` arm. Recursive nodes (`Expr`, `Stmt`,
228
+ `TypeRef`) use `ptr[Expr]` / `ptr[Stmt]` / `ptr[TypeRef]` for heap indirection
229
+ within the arena.
230
+
231
+ Key top-level types:
232
+
233
+ ```mt
234
+ public struct QualifiedName:
235
+ module_name: Option[str]
236
+ name: str
237
+
238
+ public struct SourceFile:
239
+ module_name: str
240
+ module_kind: ModuleKind
241
+ directives: span[Directive]
242
+ declarations: span[Decl]
243
+
244
+ public enum ModuleKind: ubyte
245
+ module_normal = 0
246
+ module_external = 1
247
+
248
+ public variant Directive:
249
+ importing(module_path: QualifiedName, alias: Option[str])
250
+ directive_include(header: str)
251
+ directive_link(library: str)
252
+ directive_compiler_flag(flag: str)
253
+
254
+ # Decl, Expr, Stmt, TypeRef, Param, Field, MethodDef, etc. variants
255
+ # match ast.rb node-for-node — see that file for the complete list.
256
+ ```
257
+
258
+ **Ownership:** Every `ptr[Expr]` / `ptr[Stmt]` / `ptr[TypeRef]` points into the
259
+ arena. The parser allocates them via the arena; the lowering reads them; the
260
+ arena reset frees them. All `span[Decl]`, `span[Field]`, `span[Param]` are
261
+ slices borrowed from arena-allocated Vecs.
262
+
263
+ ### 1.5 `mtc.ir` (IR types)
264
+
265
+ Imports: `mtc.semantic.types` (for `types.Type`), `mtc.ast` (for `ast.ModuleKind`).
266
+
267
+ Pure data — no logic. Mirrors `lib/milk_tea/core/ir.rb` exactly.
268
+
269
+ ```mt
270
+ public struct Include:
271
+ header: str
272
+
273
+ public struct Field:
274
+ name: str
275
+ ty: types.Type
276
+
277
+ public struct Param:
278
+ name: str
279
+ linkage_name: str
280
+ ty: types.Type
281
+ pointer: bool
282
+
283
+ public struct EnumMember:
284
+ name: str
285
+ linkage_name: str
286
+ value: ptr[Expr]
287
+
288
+ public struct AggregateField:
289
+ name: str
290
+ value: ptr[Expr]
291
+
292
+ public struct VariantArm:
293
+ name: str
294
+ linkage_name: str
295
+ fields: span[Field]
296
+
297
+ public struct SwitchCase:
298
+ is_default: bool
299
+ value: ptr[Expr]?
300
+ body: span[Stmt]
301
+
302
+ # Expression variant — ~30 arms, match ir.rb exactly
303
+ public variant Expr:
304
+ expr_name(name: str, ty: types.Type, pointer: bool)
305
+ expr_member(receiver: ptr[Expr], member: str, ty: types.Type)
306
+ expr_call(callee: str, arguments: span[Expr], ty: types.Type)
307
+ expr_binary(operator: str, left: ptr[Expr], right: ptr[Expr], ty: types.Type)
308
+ expr_integer_literal(value: long, ty: types.Type)
309
+ expr_string_literal(value: str, ty: types.Type, cstring: bool)
310
+ # ... (complete list in ir.rb)
311
+
312
+ # Statement variant — ~15 arms, match ir.rb exactly
313
+ public variant Stmt:
314
+ stmt_block(body: span[Stmt])
315
+ stmt_if(condition: ptr[Expr], then_body: span[Stmt], else_body: span[Stmt])
316
+ stmt_return(value: ptr[Expr]?, line: ptr_uint, source_path: str)
317
+ # ... (complete list in ir.rb)
318
+
319
+ public struct Program:
320
+ module_name: str
321
+ module_kind: ast.ModuleKind
322
+ includes: span[Include]
323
+ constants: span[Constant]
324
+ globals: span[Global]
325
+ opaques: span[OpaqueDecl]
326
+ structs: span[StructDecl]
327
+ unions: span[UnionDecl]
328
+ enums: span[EnumDecl]
329
+ variants: span[VariantDecl]
330
+ static_asserts: span[StaticAssert]
331
+ type_aliases: span[TypeAlias]
332
+ functions: span[Function]
333
+ ```
334
+
335
+ ### 1.6 `mtc.sexpr_dumper` (S-expression serializer)
336
+
337
+ Imports: `mtc.lexer.token`, `mtc.ast`, `mtc.semantic.types`, `mtc.ir`.
338
+
339
+ ```mt
340
+ public function dump_tokens(tokens: span[token.Token]) -> str:
341
+ # Returns "(token :type :plus :lexeme "+" ...)" per token
342
+
343
+ public function dump_ast(source_file: ast.SourceFile) -> str:
344
+ # Returns "(SourceFile :module_name ...)" S-expression
345
+
346
+ public function dump_ir(program: ir.Program) -> str:
347
+ # Returns "(Program ...)" S-expression
348
+
349
+ public function dump_type(type: types.Type) -> str:
350
+ # Returns "(Types::Primitive :name "int")" S-expression
351
+
352
+ # Internal helpers (not public, but must exist for the above):
353
+ # emit_token(token, buf)
354
+ # emit_data(node, buf) — dispatches on Data members
355
+ # emit_value(value, buf) — boolean, integer, float, string, symbol, nil
356
+ # emit_string(value, buf) — escapes \ " \n \t \r
357
+ # format_float(value) — sprintf("%g") with ".0" suffix for whole numbers
358
+ # TYPES_SHORT_NAMES — Map[str, str] for type class → short name
359
+ # emit_types_object(type, buf) — dispatches on type variant arms
360
+ # emit_types_fields(type, buf) — per-arm field emission
361
+ ```
362
+
363
+ **Critical invariants** (must match Ruby byte-for-byte):
364
+ - Token kinds use hyphens (`plus-equal` not `plus_equal`)
365
+ - Float literals use raw lexeme (not parsed `double`)
366
+ - String escaping: `\\` `\"` `\n` `\t` `\r`
367
+ - Symbols: `:name` with hyphens
368
+ - Hashes and Sets: silently skipped (not serialized)
369
+ - `nil`, `true`, `false`: bare atoms, no quoting
370
+
371
+ ---
372
+
373
+ ## 2. Lexer (`mtc.lexer.lexer`)
374
+
375
+ Imports: `mtc.lexer.token_kinds`, `mtc.lexer.token`, `mtc.lexer.keywords`, `std.vec`.
376
+
377
+ No other mtc modules. Self-contained: `source → Vec[Token]`.
378
+
379
+ ### 2.1 Public API
380
+
381
+ ```mt
382
+ # Lex source text, calling fatal() on hard errors
383
+ public function lex(source: str, path: str) -> vec.Vec[token.Token]:
384
+
385
+ # Lex source text, collecting recoverable errors in diagnostics
386
+ public function lex_reporting(source: str, diagnostics: ref[vec.Vec[token.LexDiagnostic]], path: str) -> vec.Vec[token.Token]:
387
+ ```
388
+
389
+ ### 2.2 Internal Architecture
390
+
391
+ The old self-host used a `LexSession` struct holding mutable state:
392
+
393
+ ```mt
394
+ struct LexSession:
395
+ tokens: vec.Vec[token.Token]
396
+ indent_stack: vec.Vec[ptr_uint]
397
+ diagnostics: ptr[vec.Vec[token.LexDiagnostic]]?
398
+ continuation_pending: bool
399
+ grouping_depth: int
400
+ source: str
401
+ src_len: ptr_uint
402
+ pos: ptr_uint
403
+ ```
404
+
405
+ Key internal functions:
406
+ - `is_identifier_start_byte(b: ubyte) -> bool`
407
+ - `is_identifier_part_byte(b: ubyte) -> bool`
408
+ - `is_digit_byte(b: ubyte) -> bool`
409
+ - `is_hex_digit_byte(b: ubyte) -> bool`
410
+ - `lex_lines(session: ref[LexSession]) -> void` — main loop
411
+ - `lex_line(session: ref[LexSession]) -> void` — single line
412
+ - `lex_whitespace(session: ref[LexSession]) -> void` — leading whitespace + indent/dedent
413
+ - `lex_identifier_or_keyword(session: ref[LexSession]) -> void`
414
+ - `lex_number(session: ref[LexSession]) -> void`
415
+ - `lex_string(session: ref[LexSession], kind: CStringKind) -> void`
416
+ - `lex_char_literal(session: ref[LexSession]) -> void`
417
+ - `lex_heredoc(session: ref[LexSession], prefix: HeredocPrefix) -> void`
418
+ - `lex_format_interpolation(session: ref[LexSession]) -> void`
419
+ - `lex_operator(session: ref[LexSession]) -> void`
420
+ - `lex_comment(session: ref[LexSession]) -> void`
421
+ - `emit_eof(session: ref[LexSession]) -> vec.Vec[token.Token]`
422
+ - `add_token(session: ref[LexSession], kind: token_kinds.TokenKind, lexeme: str, literal: Option[token.TokenLiteral]) -> void`
423
+
424
+ ### 2.3 Ownership
425
+
426
+ - `source: str` — borrows from caller's arena or `string.String`
427
+ - Returned `vec.Vec[token.Token]` — **owned**. Caller must `release()`.
428
+ - `token.Token.lexeme` — `str` slice into the source buffer (no copy)
429
+ - `token.Token.leading_trivia` / `trailing_trivia` — `span[token.TriviaToken]` slices into the trivia Vec owned by the lexer session
430
+ - `trivia` Vec data — arena-allocated
431
+ - Diagnostic `cstr` messages — static string literals (lifetime = program)
432
+
433
+ ---
434
+
435
+ ## 3. Parser (`mtc.parser.parser`)
436
+
437
+ Imports: `mtc.lexer.token_kinds`, `mtc.lexer.token`, `mtc.ast`, `mtc.parser.state`.
438
+
439
+ ### 3.1 `mtc.parser.state` (Parser state types)
440
+
441
+ Imports: `mtc.lexer.token_kinds`, `mtc.lexer.token`, `std.vec`.
442
+
443
+ ```mt
444
+ public struct ParseDiagnostic:
445
+ line: ptr_uint
446
+ column: ptr_uint
447
+ lexeme: str
448
+ kind: str
449
+ message: str
450
+
451
+ public struct ParserState:
452
+ tokens: span[token.Token]
453
+ pos: ptr_uint
454
+ diagnostics: vec.Vec[ParseDiagnostic]
455
+
456
+ # Token stream helpers
457
+ public function current(state: ref[ParserState]) -> token.Token:
458
+ public function advance(state: ref[ParserState]) -> void:
459
+ public function peek(state: ref[ParserState]) -> token.Token:
460
+ public function check(state: ref[ParserState], kind: token_kinds.TokenKind) -> bool:
461
+ public function expect(state: ref[ParserState], kind: token_kinds.TokenKind) -> Result[token.Token, ParseDiagnostic]:
462
+ public function skip_newlines(state: ref[ParserState]) -> void:
463
+ ```
464
+
465
+ ### 3.2 `mtc.parser.parser` (Parser)
466
+
467
+ ```mt
468
+ # Parse to AST only — no error recovery (for tools that need just the tree)
469
+ public function parse(source: str, path: str) -> ast.SourceFile:
470
+
471
+ # Parse with error collection — continues past errors
472
+ public function parse_collecting_errors(source: str, path: str) -> ParseResult:
473
+
474
+ public struct ParseResult:
475
+ ast: ast.SourceFile
476
+ diagnostics: vec.Vec[state.ParseDiagnostic]
477
+ ```
478
+
479
+ Key internal parsing functions (each returns a struct/ptr for its grammar rule):
480
+ - `parse_source_file(state: ref[ParserState]) -> ast.SourceFile`
481
+ - `parse_declaration(state: ref[ParserState]) -> ast.Decl`
482
+ - `parse_function_def(state, is_public, is_async, is_const, attributes) -> ast.Decl`
483
+ - `parse_struct_def(state, attributes) -> ast.Decl`
484
+ - `parse_enum_def(state, attributes) -> ast.Decl`
485
+ - `parse_type_ref(state) -> ptr[ast.TypeRef]`
486
+ - `parse_expression(state) -> ptr[ast.Expr]`
487
+ - `parse_expression_precedence(state, min_precedence) -> ptr[ast.Expr]`
488
+ - `parse_primary(state) -> ptr[ast.Expr]`
489
+ - `parse_call(state, callee) -> ptr[ast.Expr]`
490
+ - `parse_statement(state) -> ptr[ast.Stmt]`
491
+ - `parse_if_statement(state) -> ptr[ast.Stmt]`
492
+ - `parse_match_statement(state) -> ptr[ast.Stmt]`
493
+ - `parse_while_loop(state) -> ptr[ast.Stmt]`
494
+ - `parse_for_loop(state) -> ptr[ast.Stmt]`
495
+ - `parse_block(state) -> span[ast.Stmt]`
496
+ - `parse_literal(state) -> ptr[ast.Expr]`
497
+ - `parse_string_literal(state) -> ptr[ast.Expr]`
498
+
499
+ ### 3.3 Ownership
500
+
501
+ - `ParserState.tokens` — `span[token.Token]` borrowed from the lexer's Vec (the lexer's Vec must outlive the parser)
502
+ - Returned `ast.SourceFile` — all `ptr[Expr]`/`ptr[Stmt]` point into arena
503
+ - `ParseDiagnostic` strings — arena-allocated (or static for kind names)
504
+ - AST nodes have zero runtime ownership — they are pure data in the arena
505
+
506
+ ---
507
+
508
+ ## 4. Module Loader (`mtc.loader`)
509
+
510
+ ### 4.1 `mtc.loader.path_resolver` (Path resolution)
511
+
512
+ Imports: `std.fs`, `std.string`, `std.path`.
513
+
514
+ ```mt
515
+ public enum Platform: ubyte
516
+ linux = 0
517
+ windows = 1
518
+ wasm = 2
519
+
520
+ # Returns "" for the default platform, ".linux"/".windows"/".wasm" otherwise
521
+ public function platform_suffix(platform: Platform) -> str:
522
+
523
+ # Given a platform-less path, checks if a platform-specific variant exists;
524
+ # returns the detected platform (or Option.none for the generic file)
525
+ public function platform_suffix_for_path(path: str) -> Option[Platform]:
526
+
527
+ # Resolves `a.b.c` → `a/b/c.mt` (preferring `.linux.mt` etc.)
528
+ public function resolve_source_path(path: str, platform: Platform) -> string.String:
529
+
530
+ # Resolves import path `a.b.c` → filesystem path
531
+ public function resolve_module_path(
532
+ module_path: str, roots: span[str], platform: Platform
533
+ ) -> Option[string.String]:
534
+
535
+ # Infers module name from filesystem path relative to roots
536
+ public function infer_module_name(path: str, roots: span[str]) -> string.String:
537
+ ```
538
+
539
+ **Ownership:** All returned `string.String` values are **owned** — caller must `release()`.
540
+
541
+ ### 4.2 `mtc.loader.module_loader` (Loader orchestration)
542
+
543
+ Imports: `mtc.parser.ast`, `mtc.parser.parser`, `mtc.parser.state`,
544
+ `mtc.lexer.lexer` (for C backend — transient, used only in check), `mtc.semantic.analyzer`,
545
+ `mtc.semantic.types`, `mtc.loader.binder`, `mtc.loader.path_resolver`,
546
+ `std.map`, `std.vec`, `std.string`, `std.fs`.
547
+
548
+ ```mt
549
+ public struct LoadedModule:
550
+ module_name: string.String
551
+ path: string.String
552
+ source: string.String
553
+ source_file: ast.SourceFile
554
+ parse_diagnostics: vec.Vec[state.ParseDiagnostic]
555
+
556
+ public struct LoadDiagnostic:
557
+ path: string.String
558
+ line: ptr_uint
559
+ column: ptr_uint
560
+ message: string.String
561
+ severity: str
562
+ code: str
563
+
564
+ public struct Program:
565
+ modules: vec.Vec[LoadedModule]
566
+ order: span[ptr_uint] # dependency-first ordering (indices → modules)
567
+ analyses: vec.Vec[analyzer.Analysis] # one per module, in dependency order
568
+ diagnostics: vec.Vec[LoadDiagnostic]
569
+
570
+ # Full pipeline: parse all imports → topo sort → sema each in order → return
571
+ public function check_program(
572
+ root_path: str, roots: span[str], platform: path_resolver.Platform
573
+ ) -> Program:
574
+
575
+ # Single-file check (no imports)
576
+ public function check_single(
577
+ root_path: str, roots: span[str], platform: path_resolver.Platform
578
+ ) -> Program:
579
+ ```
580
+
581
+ **Ownership:**
582
+ - `Program` is **owned** by the caller — caller must `program.release()`
583
+ - `Program.modules[]` source buffers own the `string.String` — AST slices borrow from these
584
+ - `Program.analyses[]` are arena-allocated (embedded in the Program's arena)
585
+ - `Program.diagnostics[]` strings are heap-allocated — freed by `Program.release()`
586
+ - The `LoadedModule.source_file` AST nodes are arena-allocated
587
+ - All `Vec` and `Map` fields must be `release()`d in `Program.release()`
588
+
589
+ ### 4.3 `mtc.loader.binder` (Module binding)
590
+
591
+ Imports: `mtc.semantic.analyzer`.
592
+
593
+ ```mt
594
+ # Constructs a ModuleBinding from an Analysis, filtering public/private
595
+ public function bind_module(analysis: analyzer.Analysis) -> analyzer.ModuleBinding:
596
+ ```
597
+
598
+ **Ownership:** Returns a `ModuleBinding` with `Map` fields — owned by the caller
599
+ (embedded in the loader's `Program`).
600
+
601
+ ---
602
+
603
+ ## 5. Semantic Analyzer (`mtc.semantic`)
604
+
605
+ ### 5.1 `mtc.semantic.types` (Type model)
606
+
607
+ Imports: `std.string`, `std.str`, `std.fmt`, `std.mem.heap` (for alloc_type).
608
+
609
+ ```mt
610
+ public variant Type:
611
+ ty_primitive(name: str)
612
+ ty_str
613
+ ty_error
614
+ ty_type_meta
615
+ ty_nullable(base: ptr[Type])
616
+ ty_named(name: str, module_name: str)
617
+ ty_imported(module_name: str, name: str, args: span[Type])
618
+ ty_var(name: str)
619
+ ty_dyn(iface: str)
620
+ ty_opaque(module_name: str, name: str)
621
+ ty_generic(name: str, args: span[Type])
622
+ ty_function(params: span[Type], return_type: ptr[Type], variadic: bool, is_proc: bool)
623
+ ty_literal_int(value: long)
624
+ ty_tuple(elements: span[Type], field_names: Option[span[str]])
625
+
626
+ # Allocates a ptr[Type] on the heap for recursive type embedding
627
+ public function alloc_type(value: Type) -> ptr[Type]:
628
+
629
+ # Convenience constructors
630
+ public function primitive(name: str) -> Type:
631
+ public function literal_int(value: long) -> Type:
632
+
633
+ # Predicates (~30 functions — see types.rb for the full list)
634
+ public function is_numeric_name(name: str) -> bool:
635
+ public function is_integer_name(name: str) -> bool:
636
+ public function is_bool(t: Type) -> bool:
637
+ public function is_void(t: Type) -> bool:
638
+ public function is_numeric(t: Type) -> bool:
639
+ public function is_error(t: Type) -> bool:
640
+ public function contains_error(t: Type) -> bool:
641
+ public function is_raw_pointer(t: Type) -> bool:
642
+ public function is_own_type(t: Type) -> bool:
643
+ public function is_ref_type(t: Type) -> bool:
644
+ public function is_nullable_type(t: Type) -> bool:
645
+ public function is_integer_type(t: Type) -> bool:
646
+ public function is_float_type(t: Type) -> bool:
647
+ public function is_char_type(t: Type) -> bool:
648
+ public function is_nominal_type(t: Type) -> bool:
649
+ public function is_generic_type(t: Type) -> bool:
650
+ public function lossless_integer_assignable(target_name: str, source_name: str) -> bool:
651
+ public function definitely_different(a: Type, b: Type) -> bool:
652
+ public function unwrap_nullable(t: Type) -> Type:
653
+ public function nominal_key(t: Type) -> str:
654
+
655
+ # Rendering (for diagnostics)
656
+ public function type_to_string(t: Type) -> str:
657
+
658
+ # Equality (structural, handles recursive types with by-identity caching)
659
+ public function type_equals(a: Type, b: Type) -> bool:
660
+ ```
661
+
662
+ **Ownership:** Types are arena-allocated. `ptr[Type]` for recursive references
663
+ (e.g., nullable base, function params/return) is allocated via `alloc_type` on
664
+ the arena. `span[Type]` for type arguments borrows from a Vec.
665
+
666
+ ### 5.2 `mtc.semantic.scope` (Lexical scope)
667
+
668
+ Imports: `std.map`.
669
+
670
+ ```mt
671
+ # Scope stack — a Map per nesting level, pushed/popped on block entry/exit
672
+ public struct Scope:
673
+ stack: vec.Vec[map_mod.Map[str, bool]]
674
+
675
+ public function push_scope(scope: ref[Scope]) -> void:
676
+ public function pop_scope(scope: ref[Scope]) -> void:
677
+ public function define(scope: ref[Scope], name: str) -> void:
678
+ public function resolve(scope: ref[Scope], name: str) -> bool:
679
+ public function shadow_check(scope: ref[Scope], name: str) -> bool:
680
+ ```
681
+
682
+ ### 5.3 `mtc.semantic.analyzer` (Type checker)
683
+
684
+ Imports: `mtc.parser.ast`, `mtc.semantic.types`, `mtc.semantic.scope`,
685
+ `mtc.semantic.diagnostics`, `mtc.semantic.type_compatibility`,
686
+ `mtc.semantic.expressions`, `mtc.semantic.emit_expansion`,
687
+ `std.map`, `std.vec`, `std.string`, `std.fmt`.
688
+
689
+ ```mt
690
+ public struct SemanticDiagnostic:
691
+ line: ptr_uint
692
+ column: ptr_uint
693
+ message: str
694
+
695
+ public struct ParamEntry:
696
+ name: str
697
+ ty: types.Type
698
+
699
+ public struct FnSig:
700
+ name: str
701
+ params: span[ParamEntry]
702
+ return_type: types.Type
703
+ method_kind: ast.MethodKind
704
+ is_async: bool
705
+ is_variadic: bool
706
+ is_extern: bool
707
+
708
+ public struct FieldEntry:
709
+ name: str
710
+ ty: types.Type
711
+
712
+ public struct ModuleBinding:
713
+ functions: map_mod.Map[str, FnSig]
714
+ structs: map_mod.Map[str, span[FieldEntry]]
715
+ value_types: map_mod.Map[str, types.Type]
716
+ type_aliases: map_mod.Map[str, bool]
717
+ type_alias_types: map_mod.Map[str, types.Type]
718
+ static_member_types: map_mod.Map[str, bool]
719
+ member_keys: map_mod.Map[str, bool]
720
+ method_sigs: map_mod.Map[str, FnSig]
721
+ interfaces: map_mod.Map[str, span[ast.InterfaceMethod]]
722
+ implemented: map_mod.Map[str, span[ast.QualifiedName]]
723
+ match_case_names: map_mod.Map[str, span[str]]
724
+ types: map_mod.Map[str, bool]
725
+ # ... and private_* counterparts for each
726
+
727
+ public struct Analysis:
728
+ module_name: str
729
+ module_kind: ast.ModuleKind
730
+ functions: map_mod.Map[str, FnSig]
731
+ structs: map_mod.Map[str, span[FieldEntry]]
732
+ value_types: map_mod.Map[str, types.Type]
733
+ type_aliases: map_mod.Map[str, types.Type]
734
+ enums: map_mod.Map[str, types.Type]
735
+ variants: map_mod.Map[str, span[str]] # arm names
736
+ resolved_expr_types: map_mod.Map[ptr_uint, types.Type] # expr ptr → type
737
+ resolved_call_kinds: map_mod.Map[ptr_uint, FnSig] # call ptr → resolved function
738
+ diagnostics: vec.Vec[SemanticDiagnostic]
739
+ source_path: str
740
+
741
+ # Single-file analysis (no imports)
742
+ public function check_source_file(file: ast.SourceFile) -> Analysis:
743
+
744
+ # Multi-file analysis (with import bindings)
745
+ public function check_module(
746
+ file: ast.SourceFile,
747
+ imported_modules: ptr[map_mod.Map[str, ModuleBinding]]?,
748
+ module_name: str
749
+ ) -> Analysis:
750
+ ```
751
+
752
+ **Ownership:**
753
+ - `Analysis` is arena-allocated; returned to the loader which stores it in a
754
+ `vec.Vec[Analysis]`
755
+ - All `Map` fields in `Analysis` are heap-allocated — freed when the vec is
756
+ released
757
+ - `FnSig.params` — `span[ParamEntry]` borrows from an arena Vec
758
+ - `resolved_expr_types` keys are `ptr_uint` (pointer addresses) — used as
759
+ identity keys for expression nodes allocated in the arena
760
+
761
+ ### 5.4 `mtc.semantic.compile_time` (Const evaluator)
762
+
763
+ Imports: `mtc.parser.ast`, `mtc.semantic.types`, `std.vec`, `std.map`.
764
+
765
+ ```mt
766
+ # Evaluate a const expression to a compile-time value
767
+ public function eval_const_expression(
768
+ expr: ptr[ast.Expr],
769
+ env: ref[ConstEnv]
770
+ ) -> ConstValue:
771
+
772
+ public variant ConstValue:
773
+ integer(value: long)
774
+ float(value: double)
775
+ string(value: str)
776
+ boolean(value: bool)
777
+ type_value(ty: types.Type)
778
+ void_value
779
+
780
+ public struct ConstEnv:
781
+ # Maps const/let name → ConstValue
782
+ bindings: map_mod.Map[str, ConstValue]
783
+ # Stack of called function names (for recursion detection)
784
+ call_stack: vec.Vec[str]
785
+ ```
786
+
787
+ ### 5.5 `mtc.semantic.intrinsics` (Built-in callable resolver)
788
+
789
+ Imports: `mtc.semantic.types`.
790
+
791
+ ```mt
792
+ # Recognized built-in names (fatal, ref_of, ptr_of, read, size_of, etc.)
793
+ # Returns Option.none if not a builtin; else returns the result type.
794
+ # `env` is the analyzer's internal Context (defined in analyzer.mt).
795
+ public function resolve_intrinsic(
796
+ name: str, arguments: span[ptr[ast.Expr]], env: ref[Context]
797
+ ) -> Option[types.Type]:
798
+ ```
799
+
800
+ ---
801
+
802
+ ## 6. Control Flow Analysis (`mtc.semantic.control_flow`)
803
+
804
+ Imports: `mtc.parser.ast`, `mtc.semantic.types`.
805
+
806
+ ### 6.1 `mtc.semantic.control_flow.builder`
807
+
808
+ ```mt
809
+ public struct CFG:
810
+ blocks: vec.Vec[BasicBlock]
811
+ entry: ptr_uint
812
+ exit: ptr_uint
813
+
814
+ public struct BasicBlock:
815
+ statements: span[ptr[ast.Stmt]]
816
+ successors: span[ptr_uint]
817
+
818
+ public function build_cfg(body: span[ast.Stmt]) -> CFG:
819
+ ```
820
+
821
+ ### 6.2 `mtc.semantic.control_flow.definite_assignment`
822
+
823
+ ```mt
824
+ public struct AssignmentState:
825
+ # Maps local name → assigned?
826
+ locals: map_mod.Map[str, bool]
827
+
828
+ public function check_definite_assignment(body: span[ast.Stmt]) -> vec.Vec[Diagnostic]:
829
+ ```
830
+
831
+ ### 6.3 `mtc.semantic.control_flow.reachability`
832
+
833
+ ```mt
834
+ # Returns diagnostics for: missing return, unreachable code after return/break
835
+ public function check_reachability(body: span[ast.Stmt], expected_return_type: types.Type) -> vec.Vec[Diagnostic]:
836
+ ```
837
+
838
+ ### 6.4 `mtc.semantic.control_flow.nullability`
839
+
840
+ ```mt
841
+ # Tracks nullable → non-null narrowing through if-guards, else-guards, match arms
842
+ public function analyze_nullability(
843
+ body: span[ast.Stmt], initial_state: ref[NullabilityState]
844
+ ) -> NullabilityResult:
845
+ ```
846
+
847
+ ---
848
+
849
+ ## 7. Lowering (`mtc.lowering`)
850
+
851
+ ### 7.1 `mtc.lowering.utils` (C name mangling + helpers)
852
+
853
+ Imports: `mtc.semantic.types`, `mtc.parser.ast`, `mtc.ir`.
854
+
855
+ ```mt
856
+ # C name mangling — produces `linkage_name` values for IR declarations.
857
+ # Module-qualified names become underscore-joined C identifiers.
858
+ public function module_function_c_name(module_name: str, name: str, type_arguments: span[types.Type]) -> str:
859
+ public function function_binding_c_name(binding: analyzer.FnSig, module_name: str) -> str:
860
+ public function external_function_c_name(binding: analyzer.FnSig) -> str:
861
+ public function enum_member_c_name(type: types.Type, member_name: str) -> str:
862
+ public function value_c_name(name: str) -> str:
863
+ public function imported_value_c_name(imported_module: str, name: str) -> str:
864
+ public function module_value_c_name(module_name: str, name: str) -> str:
865
+ public function struct_field_c_name(struct_name: str, field_name: str) -> str:
866
+
867
+ # Type classification helpers (~25 functions — see lowering/utils.rb)
868
+ public function range_iterable?(expression) -> bool:
869
+ public function array_type?(type) -> bool:
870
+ public function array_element_type(type) -> types.Type:
871
+ public function array_length(type) -> ptr_uint:
872
+ public function char_array_text_type?(type) -> bool:
873
+ public function str_buffer_type?(type) -> bool:
874
+ public function str_buffer_capacity(type) -> ptr_uint:
875
+ public function collection_loop_type(type) -> types.Type:
876
+ ```
877
+
878
+ ### 7.2 `mtc.lowering.lowering` (AST → IR)
879
+
880
+ Imports: `mtc.parser.ast`, `mtc.ir`, `mtc.semantic.types`,
881
+ `mtc.semantic.analyzer`, `mtc.loader.module_loader`,
882
+ `mtc.lowering.utils`, `std.vec`, `std.map`, `std.string`, `std.mem.heap`.
883
+
884
+ ```mt
885
+ public struct LoweringError:
886
+ message: str
887
+ line: ptr_uint
888
+ column: ptr_uint
889
+ path: str
890
+
891
+ # The single public entry point: Analysis → IR.Program
892
+ public function lower(program: loader.Program) -> ir.Program:
893
+ ```
894
+
895
+ Key internal functions (these are in the single `lowering.mt` file, not separate modules):
896
+ - `lower_module(loaded: loader.LoadedModule, analysis: analyzer.Analysis) -> ir.Program`
897
+ - `lower_constant_decl(top_level_decl, env) -> void`
898
+ - `lower_function_def(top_level_decl, env) -> void`
899
+ - `lower_struct_def(top_level_decl, env) -> void`
900
+ - `lower_enum_def(top_level_decl, env) -> void`
901
+ - `lower_expression(expr: ptr[ast.Expr], env) -> ptr[ir.Expr]`
902
+ - `lower_statement(stmt: ptr[ast.Stmt], env) -> span[ir.Stmt]`
903
+ - `lower_call(callee_expr, arguments, env) -> ptr[ir.Expr]`
904
+ - `lower_binary_expression(left, op, right, env) -> ptr[ir.Expr]`
905
+ - `lower_member_access(receiver, member, env) -> ptr[ir.Expr]`
906
+ - `lower_match_statement(scrutinee, arms, env) -> span[ir.Stmt]`
907
+ - `lower_if_statement(condition, then_body, else_body, env) -> span[ir.Stmt]`
908
+ - `lower_while_loop(condition, body, env) -> span[ir.Stmt]`
909
+ - `lower_for_loop(iterable, bindings, body, env) -> span[ir.Stmt]`
910
+
911
+ ### 7.3 `mtc.lowering.async` (Async CPS transformation)
912
+
913
+ Imports: `mtc.ir`, `mtc.semantic.types`, `mtc.lowering.utils`.
914
+
915
+ ```mt
916
+ # Transforms an async function's IR body into CPS continuation-passing style
917
+ # with a heap-allocated frame struct
918
+ public function lower_async_function(
919
+ func: ir.Function, module_name: str
920
+ ) -> ir.Function:
921
+
922
+ # Synthesizes a root main() entrypoint for async-main programs
923
+ public function build_root_main_entrypoint(program: ir.Program) -> ir.Program:
924
+ ```
925
+
926
+ **Ownership:**
927
+ - `ir.Program` returned by `lower()` is arena-allocated
928
+ - `ir.Program.functions[]` — `span[ir.Function]` borrowed from arena Vec
929
+ - Each `ir.Function.body` — `span[ir.Stmt]` borrowed from arena
930
+ - `ptr[ir.Expr]` / `ptr[ir.Stmt]` — heap-allocated in the arena
931
+ - `LoweringError` is raised (fatal) — not returned as Result
932
+
933
+ ---
934
+
935
+ ## 8. C Backend (`mtc.c_backend`)
936
+
937
+ Imports: `mtc.ir`, `mtc.semantic.types`, `mtc.lowering.utils`,
938
+ `std.string`, `std.str`, `std.fmt`, `std.map`, `std.vec`, `std.mem.heap`.
939
+
940
+ ```mt
941
+ public struct CBackendError:
942
+ message: str
943
+ line: ptr_uint
944
+ column: ptr_uint
945
+ path: str
946
+
947
+ # The single public entry point: IR → C source text.
948
+ # Returns an owned string.String containing the complete .c file.
949
+ # Merge (`include`) lines, forward declarations, constants, globals,
950
+ # struct/enum/union/variant/opaque type definitions, type aliases,
951
+ # and all function definitions.
952
+ public function generate_c(program: ir.Program) -> string.String:
953
+
954
+ # C keyword table and sanitizer — call from every emission site
955
+ # (18 call sites across type declarations, expressions, runtime helpers)
956
+ # Static string comparison table; no Map allocation needed at runtime.
957
+ static C_KEYWORDS: array[str, 44] = array[str, 44](
958
+ "auto", "break", "case", "char", "const", "continue", "default", "do",
959
+ "double", "else", "enum", "extern", "float", "for", "goto", "if",
960
+ "inline", "int", "long", "register", "return", "short", "signed",
961
+ "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned",
962
+ "void", "volatile", "while", "_Bool", "_Complex", "_Imaginary",
963
+ "alignas", "alignof", "bool", "complex", "imaginary",
964
+ "noreturn", "static_assert", "thread_local",
965
+ )
966
+ public function sanitize_c_identifier(name: str) -> str:
967
+ ```
968
+
969
+ Key internal emission functions:
970
+ - `emit_preamble(program, emitter) -> void`
971
+ - `emit_forward_declarations(program, emitter) -> void`
972
+ - `emit_constant(decl, emitter) -> void`
973
+ - `emit_global(decl, emitter) -> void`
974
+ - `emit_struct_declaration(decl, emitter) -> void`
975
+ - `emit_enum_declaration(decl, emitter) -> void`
976
+ - `emit_variant_declaration(decl, emitter) -> void`
977
+ - `emit_function_definition(func, emitter) -> void`
978
+ - `emit_expression(expr: ptr[ir.Expr]) -> str` # appends to emitter buffer
979
+ - `emit_statement(stmt: ptr[ir.Stmt]) -> void`
980
+ - `emit_switch_statement(expr, cases, emitter) -> void`
981
+ - `c_type(type: types.Type) -> str`
982
+ - `c_declaration(type: types.Type, name: str) -> str`
983
+ - `c_field_declaration(type: types.Type, name: str) -> str`
984
+ - `render_expression(expr) -> str` # returns str slice into emitter buffer
985
+
986
+ ---
987
+
988
+ ## 9. Build System (`mtc.build`, `mtc.build_cache`)
989
+
990
+ ### 9.1 `mtc.build` (Build driver)
991
+
992
+ Imports: `mtc.ir`, `mtc.c_backend`, `mtc.loader.module_loader`,
993
+ `std.fs`, `std.path`, `std.process`, `std.string`, `std.vec`.
994
+
995
+ ```mt
996
+ # Lower + generate C + compile C → binary
997
+ # Returns the output path on success, error message on failure
998
+ public function build(
999
+ program: loader.Program,
1000
+ ir_program: ir.Program,
1001
+ output_path: str,
1002
+ c_compiler: str,
1003
+ roots: span[str],
1004
+ sanitize: bool
1005
+ ) -> Result[string.String, string.String]:
1006
+ ```
1007
+
1008
+ Internal flow:
1009
+ 1. Call `c_backend.generate_c(ir_program)` → C source `string.String`
1010
+ 2. Write C source to temp file via `fs.write_text`
1011
+ 3. Build CC argument list as `Vec[str]`
1012
+ 4. Append vendor lib link flags, binding flags, sanitizer flags
1013
+ 5. `process.capture(command_vec.as_span())` → check exit code
1014
+ 6. Return output path or error
1015
+
1016
+ ### 9.2 `mtc.build_cache` (Incremental cache)
1017
+
1018
+ Imports: `mtc.loader.module_loader`, `std.string`.
1019
+
1020
+ ```mt
1021
+ public function compute_key(
1022
+ program: ref[loader.Program], c_compiler: str
1023
+ ) -> string.String:
1024
+
1025
+ public function lookup(key: str) -> Option[string.String]:
1026
+ public function store(key: str, binary_path: str) -> void:
1027
+ public function materialize(cached_path: str, output_path: str) -> bool:
1028
+ ```
1029
+
1030
+ ---
1031
+
1032
+ ## 10. CLI (`mtc.main`)
1033
+
1034
+ Imports: every module above, plus `std.stdio`, `std.terminal`, `std.process`.
1035
+
1036
+ ```mt
1037
+ # Entry point — the C main() function
1038
+ function main(args: span[str]) -> int:
1039
+
1040
+ # Command dispatch:
1041
+ # if cmd == "lex": lex_command(args)
1042
+ # if cmd == "parse": parse_command(args)
1043
+ # if cmd == "check": check_command(args)
1044
+ # if cmd == "lower": lower_command(args)
1045
+ # if cmd == "emit-c": emit_c_command(args)
1046
+ # if cmd == "build": build_command(args)
1047
+ # if cmd == "run": run_command(args)
1048
+ # if cmd == "test": test_command(args)
1049
+ # if cmd == "format": format_command(args)
1050
+ # if cmd == "debug": debug_command(args)
1051
+
1052
+ # Each command handler parses its own argv portion manually
1053
+ # (while loop with mutable ai: ptr_uint counter)
1054
+ # Every Vec/String allocation uses defer: x.release()
1055
+ ```
1056
+
1057
+ ---
1058
+
1059
+ ## 11. Porting Order & Verification Checkpoints
1060
+
1061
+ | Order | Module | Depends on | Verification |
1062
+ |-------|--------|-----------|-------------|
1063
+ | 1 | `mtc.lexer.token_kinds` | nothing | Compiles |
1064
+ | 2 | `mtc.lexer.token` | token_kinds | Compiles |
1065
+ | 3 | `mtc.lexer.keywords` | token_kinds | Unit test |
1066
+ | 4 | `mtc.ast` | nothing | Compiles |
1067
+ | 5 | `mtc.semantic.types` | nothing (except std) | Unit test for predicates |
1068
+ | 6 | `mtc.ir` | semantic.types, ast | Compiles |
1069
+ | 7 | `mtc.sexpr_dumper` | token, ast, types, ir | Diff vs Ruby S-expr |
1070
+ | 8 | `mtc.lexer.lexer` | token, token_kinds, keywords | `diff <(ruby mtc lex --sexpr f) <(self lex --sexpr f)` |
1071
+ | 9 | `mtc.parser.state` | token, token_kinds | Compiles |
1072
+ | 10 | `mtc.parser.parser` | ast, state, lexer | `diff <(ruby mtc parse --sexpr f) <(self parse --sexpr f)` |
1073
+ | 11 | `mtc.loader.path_resolver` | (stdlib only) | Unit test |
1074
+ | 12 | `mtc.semantic.scope` | (stdlib only) | Unit test |
1075
+ | 13 | `mtc.semantic.intrinsics` | types | Compiles |
1076
+ | 14 | `mtc.semantic.compile_time` | ast, types | Unit test |
1077
+ | 15 | `mtc.semantic.type_compatibility` | types | Unit test |
1078
+ | 16 | `mtc.semantic.expressions` | types | Unit test |
1079
+ | 17 | `mtc.semantic.diagnostics` | (stdlib only) | Compiles |
1080
+ | 18 | `mtc.semantic.analyzer` | all semantic.* | `diff <(ruby mtc check f) <(self check f)` |
1081
+ | 19 | `mtc.semantic.control_flow.*` | analyzer, ast | Unit test |
1082
+ | 20 | `mtc.loader.binder` | analyzer | Unit test |
1083
+ | 21 | `mtc.loader.module_loader` | all above | Multi-file test |
1084
+ | 22 | `mtc.lowering.utils` | types, ast, ir | Unit test |
1085
+ | 23 | `mtc.lowering.lowering` | all above | `diff <(ruby mtc lower f) <(self lower f)` |
1086
+ | 24 | `mtc.lowering.async` | ir, lowering | Async example test |
1087
+ | 25 | `mtc.c_backend` | ir, types, lowering.utils | `diff <(ruby mtc emit-c f) <(self emit-c f)` |
1088
+ | 26 | `mtc.pretty_printer` | ast, ir | AST round-trip test |
1089
+ | 27 | `mtc.build` | c_backend, loader | Self-build |
1090
+ | 28 | `mtc.build_cache` | build | Unit test |
1091
+ | 29 | `mtc.main` | all above | `diff stage1.c stage2.c` = empty |