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,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 |
|