mt-lang 0.3.26 → 0.3.28
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/README.md +25 -0
- data/docs/language-manual.md +36 -0
- data/docs/self-hosted-compiler-plan.md +737 -0
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/core/parser/declarations.rb +40 -5
- data/lib/milk_tea/core/parser.rb +12 -2
- data/lib/milk_tea/tooling/linter/visitors.rb +156 -3
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 20058db447f681f83bcc651d9a9772e79b23bd9f1677e5c33d2053bab536a14f
|
|
4
|
+
data.tar.gz: 204af7f1018ce7637690eddc2bc9a7f297fc1f10e59c1a573491bd3b7c0b0d1c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ec63256de42954560cedc8e5021370b8f45a64e083ebb839d3057d4ea93816a51d45f950283ece162152ad10be6f1bb6ec4aa54a6ad8ce7b2db107830dd1db6a
|
|
7
|
+
data.tar.gz: 616bc238b0ee79df60a7a09fc62b895b1ab7717a1116a6b96c50879549578ba0aab91fbb9a74e40aedfc559973c333a8bc2c2cc40d3d16b6913584a744230819
|
data/README.md
CHANGED
|
@@ -249,6 +249,10 @@ struct Vec2:
|
|
|
249
249
|
x: float
|
|
250
250
|
y: float
|
|
251
251
|
|
|
252
|
+
# Methods may also be defined directly inside the struct body:
|
|
253
|
+
function length_sq() -> float:
|
|
254
|
+
return this.x * this.x + this.y * this.y
|
|
255
|
+
|
|
252
256
|
@[packed]
|
|
253
257
|
struct Header:
|
|
254
258
|
tag: ubyte
|
|
@@ -310,6 +314,7 @@ variant Token:
|
|
|
310
314
|
Rules:
|
|
311
315
|
|
|
312
316
|
- `struct` and `opaque` may declare nominal interface conformance with `implements`.
|
|
317
|
+
- `struct` bodies may contain `function`, `editable function`, and `static function` declarations directly — they desugar to `extending` blocks targeting the enclosing struct. This is pure syntactic sugar; the compiler emits identical code as a separate `extending` block.
|
|
313
318
|
- `attribute[target, ...]` declares reusable declaration attributes for `struct`, `field`, `callable`, `const`, `event`, `enum`, `flags`, `union`, and `variant` targets.
|
|
314
319
|
- Attributes are applied with one or more leading `@[name(...)]` blocks. Built-in `packed`, `align(bytes)`, and `deprecated(message)` are predefined attributes.
|
|
315
320
|
- `variant` arms may carry named payload fields.
|
|
@@ -358,11 +363,31 @@ Method kinds:
|
|
|
358
363
|
- `editable function` -> editable receiver
|
|
359
364
|
- `static function` -> no receiver
|
|
360
365
|
|
|
366
|
+
Methods may appear inside a struct body (desugared to an `extending` block) or in a separate
|
|
367
|
+
`extending` declaration:
|
|
368
|
+
|
|
369
|
+
```mt
|
|
370
|
+
struct Counter:
|
|
371
|
+
value: int
|
|
372
|
+
|
|
373
|
+
function read() -> int:
|
|
374
|
+
return this.value
|
|
375
|
+
|
|
376
|
+
editable function bump() -> void:
|
|
377
|
+
this.value += 1
|
|
378
|
+
|
|
379
|
+
static function zero() -> Counter:
|
|
380
|
+
return Counter(value = 0)
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Both inline and `extending` forms lower to identical C code.
|
|
384
|
+
|
|
361
385
|
Method notes:
|
|
362
386
|
|
|
363
387
|
- Async methods are supported.
|
|
364
388
|
- Generic methods are supported.
|
|
365
389
|
- There is no constructor keyword. Names like `init` and `default` are ordinary static methods.
|
|
390
|
+
- Methods may be defined inside struct bodies directly (as syntactic sugar for `extending`) or in separate `extending` blocks. Both forms lower to the same C code; the inline form is preferred when the struct and its core methods appear in the same file.
|
|
366
391
|
|
|
367
392
|
## 7. Functions, Externals, And Foreign Functions
|
|
368
393
|
|
data/docs/language-manual.md
CHANGED
|
@@ -300,7 +300,25 @@ Callable and `ref[...]` rules:
|
|
|
300
300
|
struct Vec2:
|
|
301
301
|
x: float
|
|
302
302
|
y: float
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`struct` bodies may also contain `function`, `editable function`, and `static function` declarations directly — they desugar to `extending` blocks targeting the enclosing struct. This is pure syntactic sugar and produces identical C code. See §3.6 for the method kind rules.
|
|
306
|
+
|
|
307
|
+
```mt
|
|
308
|
+
struct Counter:
|
|
309
|
+
value: int
|
|
310
|
+
|
|
311
|
+
function read() -> int:
|
|
312
|
+
return this.value
|
|
313
|
+
|
|
314
|
+
editable function bump() -> void:
|
|
315
|
+
this.value += 1
|
|
316
|
+
|
|
317
|
+
static function zero() -> Counter:
|
|
318
|
+
return Counter(value = 0)
|
|
319
|
+
```
|
|
303
320
|
|
|
321
|
+
```mt
|
|
304
322
|
union Number:
|
|
305
323
|
i: int
|
|
306
324
|
f: float
|
|
@@ -417,7 +435,25 @@ Rules:
|
|
|
417
435
|
|
|
418
436
|
### 3.6 Methods
|
|
419
437
|
|
|
438
|
+
Methods may appear directly inside a struct body (desugared to an `extending` block) or in a separate `extending` declaration. Both forms lower to identical C code.
|
|
439
|
+
|
|
440
|
+
```mt
|
|
441
|
+
# Inline form (sugar)
|
|
442
|
+
struct Counter:
|
|
443
|
+
value: int
|
|
444
|
+
|
|
445
|
+
function read() -> int:
|
|
446
|
+
return this.value
|
|
447
|
+
|
|
448
|
+
editable function bump() -> void:
|
|
449
|
+
this.value += 1
|
|
450
|
+
|
|
451
|
+
static function zero() -> Counter:
|
|
452
|
+
return Counter(value = 0)
|
|
453
|
+
```
|
|
454
|
+
|
|
420
455
|
```mt
|
|
456
|
+
# Equivalent extending form
|
|
421
457
|
extending Counter:
|
|
422
458
|
function read() -> int:
|
|
423
459
|
return this.value
|
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
# Self-Hosted Milk Tea Compiler — Architecture Plan
|
|
2
|
+
|
|
3
|
+
## `projects/mtc`
|
|
4
|
+
|
|
5
|
+
This document describes the architecture and implementation plan for a self-hosted
|
|
6
|
+
Milk Tea compiler, written in Milk Tea, that compiles Milk Tea source to C.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 0. Core Design Principles
|
|
11
|
+
|
|
12
|
+
Three principles separate a modern self-hosted compiler from the Ruby prototype:
|
|
13
|
+
|
|
14
|
+
1. **IR-stage separation**. The Ruby compiler's `Lowering` does too much in one pass — CPS
|
|
15
|
+
transform, monomorphization, desugaring, and C-biased lowering are all intertwined.
|
|
16
|
+
`mtc` uses two distinct IRs between the surface AST and the C backend: **HIR** (typed,
|
|
17
|
+
monomorphized, name-resolved) and **LIR** (flat, C-shaped). Generics are
|
|
18
|
+
monomorphized during the AST → HIR transition rather than deferred to lowering.
|
|
19
|
+
|
|
20
|
+
2. **Modular passes**. Each pass is a standalone module with a single `run(ctx) ->
|
|
21
|
+
Result[ctx, Error]` signature. Passes compose via a pipeline struct. No global
|
|
22
|
+
mutable state.
|
|
23
|
+
|
|
24
|
+
3. **Diagnostics-first**. Every pass accumulates `Diagnostic` values into a shared
|
|
25
|
+
`DiagnosticEngine`. Errors never abort the pipeline; they are collected, sorted by
|
|
26
|
+
location, and emitted as a batch.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 1. Project Layout
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
projects/mtc/
|
|
34
|
+
├── package.toml # kind=application, source_root=src
|
|
35
|
+
├── src/
|
|
36
|
+
│ ├── main.mt # Entry: parse CLI, dispatch compile/run/check/test
|
|
37
|
+
│ ├── mtc/
|
|
38
|
+
│ │ ├── diagnostics.mt # Diagnostic, DiagnosticKind, DiagnosticEngine, SourceSpan
|
|
39
|
+
│ │ ├── token.mt # TokenKind enum, Token struct
|
|
40
|
+
│ │ ├── lexer/
|
|
41
|
+
│ │ │ ├── lexer.mt # Lexer.lex(source, path) -> Result[Vec[Token], Error]
|
|
42
|
+
│ │ │ ├── trivia.mt # Whitespace/comment handling (CST reconstruction)
|
|
43
|
+
│ │ │ └── char_tables.mt # Static lookup tables (is_alpha, is_digit, etc.)
|
|
44
|
+
│ │ ├── parser/
|
|
45
|
+
│ │ │ ├── parser.mt # Recursive-descent parser entry
|
|
46
|
+
│ │ │ ├── expressions.mt # Pratt parser for operator expressions
|
|
47
|
+
│ │ │ ├── declarations.mt # Top-level declarations
|
|
48
|
+
│ │ │ ├── statements.mt # Statement parsing (if, for, match, while, etc.)
|
|
49
|
+
│ │ │ └── recovery.mt # Error recovery synchronizer sets
|
|
50
|
+
│ │ ├── ast.mt # AST node definitions and arena
|
|
51
|
+
│ │ ├── hir.mt # HIR types (typed, monomorphized, name-resolved)
|
|
52
|
+
│ │ ├── lir.mt # LIR types (flat, C-shaped)
|
|
53
|
+
│ │ ├── symbol_table.mt # Scoped symbol table (stacked maps)
|
|
54
|
+
│ │ ├── type_system/
|
|
55
|
+
│ │ │ ├── types.mt # Type variant and registry
|
|
56
|
+
│ │ │ └── layout.mt # size_of / align_of / offset_of (const functions)
|
|
57
|
+
│ │ ├── semantic/
|
|
58
|
+
│ │ │ ├── checker.mt # Phase: AST → HIR (name resolution, type checking)
|
|
59
|
+
│ │ │ ├── infer.mt # Bidirectional type inference
|
|
60
|
+
│ │ │ ├── conform.mt # Interface conformance checking
|
|
61
|
+
│ │ │ ├── monomorphize.mt # Generic instantiation (HIR construction time)
|
|
62
|
+
│ │ │ └── const_eval.mt # Compile-time expression evaluator
|
|
63
|
+
│ │ ├── lowering/
|
|
64
|
+
│ │ │ ├── lower.mt # Phase: HIR → LIR (desugaring, flattening)
|
|
65
|
+
│ │ │ ├── desugar.mt # async→CPS, for→while, match→switch, ?→if-return
|
|
66
|
+
│ │ │ └── cps.mt # Continuation-passing-style transform for async
|
|
67
|
+
│ │ ├── module_loader.mt # Load and resolve transitive imports
|
|
68
|
+
│ │ ├── module_path_resolver.mt # Resolve import paths to filesystem paths
|
|
69
|
+
│ │ ├── module_binder.mt # Public/private visibility split per module
|
|
70
|
+
│ │ ├── backend/
|
|
71
|
+
│ │ │ ├── c_backend.mt # Phase: LIR → C source text
|
|
72
|
+
│ │ │ ├── llvm.mt # Future: LIR → LLVM IR (via external function bindings)
|
|
73
|
+
│ │ │ └── runtime.mt # C runtime helper emission (fatal, format, refcount, etc.)
|
|
74
|
+
│ │ └── pipeline.mt # CompilerPipeline orchestrator struct
|
|
75
|
+
│ └── tests/ # In-language tests using @[test] and std.testing
|
|
76
|
+
│ ├── lexer_test.mt
|
|
77
|
+
│ ├── parser_test.mt
|
|
78
|
+
│ ├── semantic_test.mt
|
|
79
|
+
│ ├── type_test.mt
|
|
80
|
+
│ └── roundtrip_test.mt
|
|
81
|
+
└── tests/ # Canary .mt programs as test corpus (run via mtc test)
|
|
82
|
+
├── lex/
|
|
83
|
+
├── parse/
|
|
84
|
+
├── type/
|
|
85
|
+
├── lower/
|
|
86
|
+
└── roundtrip/ # Self-compilation verification
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Note: LSP support (`lsp/server.mt`, `hover.mt`, `completion.mt`, `goto_def.mt`) is
|
|
90
|
+
deferred to v2. The v1 compiler is CLI-only. The `DiagnosticEngine` is designed to
|
|
91
|
+
accommodate LSP consumers in the future via byte-offset source spans.
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 2. Pipeline Architecture
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
┌─────────┐ ┌─────────┐ ┌───────────┐ ┌──────────┐ ┌─────────┐
|
|
99
|
+
│ Source │───▶│ Lexer │───▶│ Parser │───▶│ Semantic │───▶│ Lowerer │
|
|
100
|
+
│ Text │ │ │ │ │ │ (AST→HIR)│ │(HIR→LIR)│
|
|
101
|
+
└─────────┘ └─────────┘ └───────────┘ └──────────┘ └─────────┘
|
|
102
|
+
│ │ │ │
|
|
103
|
+
▼ ▼ ▼ ▼
|
|
104
|
+
┌─────────────────────────────────────────────────────────┐
|
|
105
|
+
│ DiagnosticEngine │
|
|
106
|
+
└─────────────────────────────────────────────────────────┘
|
|
107
|
+
│
|
|
108
|
+
▼
|
|
109
|
+
┌──────────┐
|
|
110
|
+
│ CBackend │──▶ C source
|
|
111
|
+
└──────────┘
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`CompilerPipeline` is the orchestrator:
|
|
115
|
+
|
|
116
|
+
```mt
|
|
117
|
+
struct CompilerPipeline:
|
|
118
|
+
options: CompileOptions
|
|
119
|
+
diagnostics: DiagnosticEngine
|
|
120
|
+
|
|
121
|
+
function run(source: str, path: str) -> Result[vec.Vec[ubyte], Diagnostic]:
|
|
122
|
+
let tokens = this.lex(source, path)?
|
|
123
|
+
let ast = this.parse(tokens, path)?
|
|
124
|
+
let hir = this.semantic(ast, path)?
|
|
125
|
+
let lir = this.lower(hir)?
|
|
126
|
+
return this.backend(lir)?
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Each pass returns `Result[Output, Diagnostic]`. Critical errors short-circuit via `?`.
|
|
130
|
+
Non-critical diagnostics accumulate in the engine for batch emission.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 3. Memory Model: Arena + Handle Pattern
|
|
135
|
+
|
|
136
|
+
Milk Tea prohibits storing `ref[T]` inside generic containers such as `Vec`.
|
|
137
|
+
Tree-shaped data (AST, HIR, LIR) must therefore avoid `Vec[ref[Expr]]` and similar
|
|
138
|
+
patterns. The standard solution, used by industrial compilers including `rustc`, is
|
|
139
|
+
arena-allocated value storage with opaque index handles.
|
|
140
|
+
|
|
141
|
+
### The pattern
|
|
142
|
+
|
|
143
|
+
Every IR node type has a corresponding `Id` — a plain `ptr_uint` index into a
|
|
144
|
+
`Vec[Node]` owned by an arena struct:
|
|
145
|
+
|
|
146
|
+
```mt
|
|
147
|
+
type ExprId = ptr_uint
|
|
148
|
+
type StmtId = ptr_uint
|
|
149
|
+
type DeclId = ptr_uint
|
|
150
|
+
type TypeId = ptr_uint
|
|
151
|
+
|
|
152
|
+
struct AstArena:
|
|
153
|
+
exprs: Vec[Expr]
|
|
154
|
+
stmts: Vec[Stmt]
|
|
155
|
+
decls: Vec[Decl]
|
|
156
|
+
|
|
157
|
+
editable function alloc_expr(node: Expr) -> ExprId:
|
|
158
|
+
let index: ptr_uint = this.exprs.len()
|
|
159
|
+
this.exprs.push(node)
|
|
160
|
+
return index
|
|
161
|
+
|
|
162
|
+
editable function alloc_stmt(node: Stmt) -> StmtId:
|
|
163
|
+
let index: ptr_uint = this.stmts.len()
|
|
164
|
+
this.stmts.push(node)
|
|
165
|
+
return index
|
|
166
|
+
|
|
167
|
+
editable function alloc_decl(node: Decl) -> DeclId:
|
|
168
|
+
let index: ptr_uint = this.decls.len()
|
|
169
|
+
this.decls.push(node)
|
|
170
|
+
return index
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
All child references in AST/HIR/LIR nodes use `Id` types, never `ref[T]`. Ids are
|
|
174
|
+
plain integers — legal in any container, trivially copyable, and zero-cost to
|
|
175
|
+
resolve through the arena. Bulk deallocation happens when the arena itself is released.
|
|
176
|
+
|
|
177
|
+
The same pattern applies to the type system: types live in a `TypeRegistry` arena
|
|
178
|
+
and are referenced by `TypeId`:
|
|
179
|
+
|
|
180
|
+
```mt
|
|
181
|
+
struct TypeRegistry:
|
|
182
|
+
types: Vec[Type]
|
|
183
|
+
|
|
184
|
+
edible function intern(typ: Type) -> TypeId:
|
|
185
|
+
let index: ptr_uint = this.types.len()
|
|
186
|
+
this.types.push(typ)
|
|
187
|
+
return index
|
|
188
|
+
|
|
189
|
+
function resolve(id: TypeId) -> ptr[Type]?
|
|
190
|
+
```
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Why not `own[T]` or `ref[T]`
|
|
194
|
+
|
|
195
|
+
`ref[T]` is rejected in every relevant tree-storage position:
|
|
196
|
+
|
|
197
|
+
- `Vec[ref[T]]` — rejected at compile time: *"ref types cannot be nested inside Vec"*
|
|
198
|
+
- `ref[T]` as a variant arm payload field — rejected: *"cannot store ref types; declare
|
|
199
|
+
a lifetime on the struct"*
|
|
200
|
+
- `ref[T]` in a struct field auto-generates an implicit lifetime, making the struct
|
|
201
|
+
non-owning and rejected from `Vec`, returns, and module storage
|
|
202
|
+
|
|
203
|
+
`own[T]` (owning heap pointers) is storable in containers and variant fields, but
|
|
204
|
+
using it for every tree child produces millions of separate heap allocations for a
|
|
205
|
+
50K-LOC compiler. The arena pattern stores all nodes contiguously in a single
|
|
206
|
+
allocation per IR stage, yielding far better cache locality and eliminating per-node
|
|
207
|
+
allocation overhead.
|
|
208
|
+
|
|
209
|
+
### Arena implementation
|
|
210
|
+
|
|
211
|
+
The arena pattern above is built on plain `Vec` storage: each arena field (`exprs`,
|
|
212
|
+
`stmts`, `decls`) is a `Vec[Node]`, and `alloc_*` methods simply push the node and
|
|
213
|
+
return `this.vec.len()` as the index. No unsafe code, no pointer arithmetic — just
|
|
214
|
+
contiguous expandable storage with O(1) append and O(1) index resolution via `Vec.get`.
|
|
215
|
+
For release, the arena's `release()` iterates and calls `release()` on each contained
|
|
216
|
+
`Vec` and `Map`, then bulk-deallocates.
|
|
217
|
+
|
|
218
|
+
For lower-level bump-pointer allocation (e.g., in the C backend's output buffer),
|
|
219
|
+
`std.mem.arena` is available but not needed for IR tree storage.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 4. Key Data Structures
|
|
224
|
+
|
|
225
|
+
*Import preamble: `Vec`, `Map`, `Set` are not prelude types (only `Option` and `Result`
|
|
226
|
+
are). Real source files use `import std.vec as vec`, `import std.map as m`, etc.
|
|
227
|
+
and reference them as `vec.Vec[T]`, `m.Map[K,V]`. Code examples below use bare
|
|
228
|
+
`Vec[T]`, `Map[K,V]` for readability.*
|
|
229
|
+
|
|
230
|
+
### AST Nodes (the surface tree)
|
|
231
|
+
|
|
232
|
+
```mt
|
|
233
|
+
variant Expr:
|
|
234
|
+
literal(value: Literal)
|
|
235
|
+
identifier(name: str)
|
|
236
|
+
binary(left: ExprId, op: BinOp, right: ExprId)
|
|
237
|
+
unary(op: UnaryOp, operand: ExprId)
|
|
238
|
+
call(callee: ExprId, args: Vec[ExprId])
|
|
239
|
+
member(obj: ExprId, field: str)
|
|
240
|
+
index(obj: ExprId, idx: ExprId)
|
|
241
|
+
if_expr(cond: ExprId, then_branch: ExprId, else_branch: Option[ExprId])
|
|
242
|
+
match_expr(scrutinee: ExprId, arms: Vec[MatchArm])
|
|
243
|
+
proc_expr(params: Vec[Param], body: StmtId)
|
|
244
|
+
tuple_expr(elements: Vec[ExprId])
|
|
245
|
+
|
|
246
|
+
variant Stmt:
|
|
247
|
+
expr(expr: ExprId)
|
|
248
|
+
let_decl(name: str, typ: Option[TypeId], init: Option[ExprId], guard: Option[Guard])
|
|
249
|
+
assign(target: ExprId, op: AssignOp, value: ExprId)
|
|
250
|
+
if_stmt(cond: ExprId, then_block: Vec[StmtId], else_ifs: Vec[ElseIf],
|
|
251
|
+
else_block: Option[Vec[StmtId]])
|
|
252
|
+
while_stmt(cond: ExprId, body: Vec[StmtId])
|
|
253
|
+
for_stmt(bindings: Vec[ForBinding], iterable: ExprId, body: Vec[StmtId])
|
|
254
|
+
match_stmt(scrutinee: ExprId, arms: Vec[MatchArm])
|
|
255
|
+
return_stmt(value: Option[ExprId])
|
|
256
|
+
defer_stmt(body: Vec[StmtId])
|
|
257
|
+
|
|
258
|
+
variant Decl:
|
|
259
|
+
function_def(name: str, params: Vec[Param], return_type: Option[TypeId],
|
|
260
|
+
body: Option[Vec[StmtId]], visibility: Visibility, is_async: bool)
|
|
261
|
+
struct_def(name: str, fields: Vec[Field], type_params: Vec[TypeParam],
|
|
262
|
+
implements: Vec[QualName], visibility: Visibility)
|
|
263
|
+
variant_def(name: str, arms: Vec[VariantArm], type_params: Vec[TypeParam],
|
|
264
|
+
visibility: Visibility)
|
|
265
|
+
const_decl(name: str, typ: TypeId, init: ExprId, visibility: Visibility)
|
|
266
|
+
import_decl(module_path: Vec[str], alias: Option[str])
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Note: variant arm fields of types like `Vec[ExprId]`, `Option[ExprId]`, and direct
|
|
270
|
+
`ExprId` values are all storable because `ExprId = ptr_uint` is a plain integer.
|
|
271
|
+
|
|
272
|
+
### HIR (typed, monomorphized, name-resolved)
|
|
273
|
+
|
|
274
|
+
HIR is the typed intermediate representation. Generic types are fully monomorphized,
|
|
275
|
+
names are resolved to bindings, and types are attached to every expression node.
|
|
276
|
+
|
|
277
|
+
```mt
|
|
278
|
+
struct HirProgram:
|
|
279
|
+
modules: Vec[HirModule]
|
|
280
|
+
|
|
281
|
+
struct HirModule:
|
|
282
|
+
name: str
|
|
283
|
+
path: str
|
|
284
|
+
declarations: Vec[HirDecl]
|
|
285
|
+
type_registry: TypeRegistry
|
|
286
|
+
symbol_table: SymbolTable
|
|
287
|
+
|
|
288
|
+
variant HirExpr:
|
|
289
|
+
literal(value: Literal, typ: TypeId)
|
|
290
|
+
var_ref(binding: BindingId, typ: TypeId)
|
|
291
|
+
call(callee: HirCallable, args: Vec[HirExprId], typ: TypeId)
|
|
292
|
+
member(obj: HirExprId, field: str, typ: TypeId)
|
|
293
|
+
intrinsic(kind: IntrinsicKind, args: Vec[HirExprId], typ: TypeId)
|
|
294
|
+
|
|
295
|
+
type HirExprId = ptr_uint
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### LIR (flat, C-shaped)
|
|
299
|
+
|
|
300
|
+
LIR matches the Ruby compiler's `IR::Program` in spirit — flat statement lists, no
|
|
301
|
+
type parameters, C-compatible expression forms:
|
|
302
|
+
|
|
303
|
+
```mt
|
|
304
|
+
struct LirProgram:
|
|
305
|
+
modules: Vec[LirModule]
|
|
306
|
+
|
|
307
|
+
struct LirFunction:
|
|
308
|
+
name: str
|
|
309
|
+
linkage_name: str
|
|
310
|
+
params: Vec[LirParam]
|
|
311
|
+
return_type: TypeId
|
|
312
|
+
body: Vec[LirStmt]
|
|
313
|
+
locals: Vec[LirLocal]
|
|
314
|
+
|
|
315
|
+
variant LirExpr:
|
|
316
|
+
name(id: LocalId)
|
|
317
|
+
integer(value: int, width: IntWidth)
|
|
318
|
+
call(target: str, args: Vec[LirExpr])
|
|
319
|
+
member(obj: LirExpr, field: str)
|
|
320
|
+
binop(left: LirExpr, op: BinOp, right: LirExpr)
|
|
321
|
+
address_of(expr: LirExpr)
|
|
322
|
+
load(expr: LirExpr)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## 5. Type System Design
|
|
328
|
+
|
|
329
|
+
The type representation is the compiler's internal model — distinct from the language's
|
|
330
|
+
user-facing types:
|
|
331
|
+
|
|
332
|
+
```mt
|
|
333
|
+
# (All arm names use _type suffix uniformly. Of the 24 reserved primitive type
|
|
334
|
+
# names, only 6 appear here — bool/int/uint/float/char/void — and they require
|
|
335
|
+
# the suffix. Every other name works bare, but we suffix all for consistency.)
|
|
336
|
+
|
|
337
|
+
variant Type:
|
|
338
|
+
# Primitives
|
|
339
|
+
bool_type
|
|
340
|
+
int_type(width: IntWidth)
|
|
341
|
+
uint_type(width: UintWidth)
|
|
342
|
+
float_type(width: FloatWidth)
|
|
343
|
+
char_type
|
|
344
|
+
void_type
|
|
345
|
+
|
|
346
|
+
# Constructors
|
|
347
|
+
ptr_type(inner: TypeId)
|
|
348
|
+
const_ptr_type(inner: TypeId)
|
|
349
|
+
own_type(inner: TypeId)
|
|
350
|
+
ref_type(inner: TypeId)
|
|
351
|
+
span_type(inner: TypeId)
|
|
352
|
+
array_type(inner: TypeId, len: ptr_uint)
|
|
353
|
+
tuple_type(elements: Vec[TypeId])
|
|
354
|
+
func_type(params: Vec[TypeId], ret: TypeId)
|
|
355
|
+
proc_type(params: Vec[TypeId], ret: TypeId)
|
|
356
|
+
|
|
357
|
+
# User-defined
|
|
358
|
+
struct_type(name: QualName, args: Vec[TypeId])
|
|
359
|
+
variant_type(name: QualName, args: Vec[TypeId])
|
|
360
|
+
enum_type(name: QualName)
|
|
361
|
+
dyn_type(interface: QualName, args: Vec[TypeId])
|
|
362
|
+
|
|
363
|
+
# Special
|
|
364
|
+
type_var_type(index: int) # For generic resolution (replaced during monomorphization)
|
|
365
|
+
error_type # Sentinel for recovery
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Types live in the `TypeRegistry` arena and are referenced by `TypeId` everywhere.
|
|
369
|
+
The `type_var` variant represents unresolved generic type parameters during inference;
|
|
370
|
+
it is never present in HIR or LIR — monomorphization replaces every `type_var` with a
|
|
371
|
+
concrete type.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## 6. Symbol Table
|
|
376
|
+
|
|
377
|
+
Stacked scopes with a pre-populated global scope for builtins:
|
|
378
|
+
|
|
379
|
+
```mt
|
|
380
|
+
struct SymbolTable:
|
|
381
|
+
scopes: Vec[Scope]
|
|
382
|
+
|
|
383
|
+
# Each scope is anonymous. Resolution walks the Vec from the innermost scope
|
|
384
|
+
# outward (the index position encodes nesting depth). No parent pointer needed.
|
|
385
|
+
struct Scope:
|
|
386
|
+
types: Map[str, TypeBinding]
|
|
387
|
+
values: Map[str, ValueBinding]
|
|
388
|
+
functions: Map[str, FuncBinding]
|
|
389
|
+
|
|
390
|
+
struct TypeBinding:
|
|
391
|
+
kind: TypeBindingKind
|
|
392
|
+
type_id: TypeId # Index into TypeRegistry
|
|
393
|
+
visibility: Visibility
|
|
394
|
+
definition_span: SourceSpan
|
|
395
|
+
|
|
396
|
+
struct ValueBinding:
|
|
397
|
+
type_id: TypeId # Index into TypeRegistry
|
|
398
|
+
kind: ValueKind
|
|
399
|
+
is_mutable: bool
|
|
400
|
+
definition_span: SourceSpan
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Module-level scope construction follows this order (same as the Ruby checker's phase
|
|
404
|
+
pipeline):
|
|
405
|
+
|
|
406
|
+
1. Install builtin types (bool, int, str, etc.)
|
|
407
|
+
2. Install imports (populate from imported module bindings)
|
|
408
|
+
3. Declare named types (register struct/variant/enum names — enables forward references)
|
|
409
|
+
4. Resolve type aliases and aggregate fields
|
|
410
|
+
5. Declare functions (register signatures — enables forward calls)
|
|
411
|
+
6. Type-check function bodies
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## 7. Module Loading and Visibility
|
|
416
|
+
|
|
417
|
+
The Ruby compiler's module infrastructure (`module_loader.rb`, `module_binder.rb`,
|
|
418
|
+
`module_path_resolver.rb`, `module_roots.rb`) maps to three mtc modules:
|
|
419
|
+
|
|
420
|
+
**`module_path_resolver.mt`** — Given `import std.hash`, resolves to the filesystem
|
|
421
|
+
path `std/hash.mt`. Handles platform-specific variants (`name.linux.mt`),
|
|
422
|
+
`package.source_root` resolution, and in-memory source overrides.
|
|
423
|
+
|
|
424
|
+
**`module_loader.mt`** — Loads and analyzes a module and its transitive imports.
|
|
425
|
+
Returns a `ModuleGraph` containing all parsed and analyzed modules. Handles circular
|
|
426
|
+
imports via forward-declaration bindings, matching the Ruby compiler's two-pass
|
|
427
|
+
strategy.
|
|
428
|
+
|
|
429
|
+
**`module_binder.mt`** — Given a module's `SymbolTable` and `Visibility` annotations,
|
|
430
|
+
produces a public-only view that downstream modules can import. Partitions types,
|
|
431
|
+
values, functions, methods, and interface implementations into public/private sets.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## 8. Milk Tea Feature Mapping
|
|
436
|
+
|
|
437
|
+
| Milk Tea Feature | Compiler Use |
|
|
438
|
+
|---|---|
|
|
439
|
+
| **`variant`** | AST nodes, HIR nodes, LIR expressions, Type representation, DiagnosticKind |
|
|
440
|
+
| **`enum`** | TokenKind, BinOp, UnaryOp, AssignOp, Visibility, PlatformTarget, IntWidth, FloatWidth |
|
|
441
|
+
| **`flags`** | Token modifier flags (is_keyword, is_literal, is_assignment_start) |
|
|
442
|
+
| **`struct`** | Arena, SymbolTable, Scope, Binding, SourceSpan, CompileOptions, Pipeline, Module |
|
|
443
|
+
| **`Result[T, E]` + `?`** | Every pass return type; error propagation through the pipeline |
|
|
444
|
+
| **`Option[T]`** | Optional AST children (else branch, initializer, type annotation, import alias) |
|
|
445
|
+
| **`Vec[T]`** | Token stream, AST child vectors, declaration lists, statement blocks, diagnostic list |
|
|
446
|
+
| **`Map[K,V]`** | Symbol table scopes, type registry index, monomorphization cache, import table |
|
|
447
|
+
| **`Set[T]`** | Used-name tracking, reachability analysis, live-variable sets |
|
|
448
|
+
| **`interface` + `dyn[I]`** | Pass trait (`interface Pass: function run(ctx) -> Result[...]`), AST visitor dispatch |
|
|
449
|
+
| **`const function`** | `size_of(T)`, `align_of(T)`, `offset_of(T, field)` — type layout at compile time |
|
|
450
|
+
| **`when` / `inline if`** | Platform-conditional codegen (linux vs windows vs wasm target dispatch) |
|
|
451
|
+
| **`inline for` + `fields_of`** | Reflective struct field iteration for comparison, hash, format code generation |
|
|
452
|
+
| **`match`** | AST traversal, token dispatch in lexer, operator dispatch, pattern matching on IR nodes |
|
|
453
|
+
| **`array[T,N]`** | Fixed-size lookup tables (operator precedence, keyword trie, char classification) |
|
|
454
|
+
| **`span[T]`** | Zero-copy views over source text, token slices for diagnostics |
|
|
455
|
+
| **`str` / `cstr`** | Source text, identifiers, file paths; C ABI strings for external function calls |
|
|
456
|
+
| **`proc(...)`** | Per-pass callbacks (e.g., `ResolveIdentifier` in the const evaluator) |
|
|
457
|
+
| **`fn(...)`** | Function pointers for static dispatch tables (no-capture callbacks, vtable entries) |
|
|
458
|
+
| **`defer`** | Resource cleanup (file handles, arena deallocation, temp file removal) |
|
|
459
|
+
| **`own[T]`** | Owning heap pointers for module-level data (file contents, cached artifacts) |
|
|
460
|
+
| **`str_buffer[N]`** | C source assembly (building output line by line, no heap allocation per fragment) |
|
|
461
|
+
| **`static_assert`** | Compile-time invariants (type sizes, alignment, field offsets, struct layout) |
|
|
462
|
+
| **`@[test]` + `std.testing`** | Unit tests for lexer, parser, type checker, lowering passes |
|
|
463
|
+
| **`emit`** | Compile-time helpers generated from `const function` (codegen support) |
|
|
464
|
+
| **`SpatialGrid[T]`** | Future: incremental recompilation tracking (dirty region queries) |
|
|
465
|
+
|
|
466
|
+
The following features are valid Milk Tea but are **not used in v1** of mtc — they
|
|
467
|
+
target performance and parallelism, which are v2 concerns:
|
|
468
|
+
|
|
469
|
+
| Deferred Feature | Future Use |
|
|
470
|
+
|---|---|
|
|
471
|
+
| **`parallel for`** | Type-checking independent functions concurrently (no data dependencies) |
|
|
472
|
+
| **`parallel:`** | Concurrent lexing/parsing of independent imported modules |
|
|
473
|
+
| **`async function` + `await`** | LSP request handling (non-blocking I/O), parallel compilation orchestration |
|
|
474
|
+
| **`atomic[T]`** | Shared diagnostic counters across parallel passes |
|
|
475
|
+
| **`SoA[T, N]`** | Hot-path data layouts (cache-friendly token streams in lexer) |
|
|
476
|
+
| **`RingBuffer[T]`** | Work-stealing queues for parallel compilation |
|
|
477
|
+
|
|
478
|
+
---
|
|
479
|
+
|
|
480
|
+
## 9. C Runtime Helpers
|
|
481
|
+
|
|
482
|
+
The C backend must emit runtime support code that the generated C depends on. Rather
|
|
483
|
+
than linking a pre-compiled library, the self-hosted compiler emits helpers inline
|
|
484
|
+
into the generated C file (matching the Ruby compiler's approach):
|
|
485
|
+
|
|
486
|
+
| Helper | Purpose |
|
|
487
|
+
|---|---|
|
|
488
|
+
| `mt_fatal(message, expr, file, line)` | Abort with formatted error (used by bounds checks, `unimplemented`, `fatal()`) |
|
|
489
|
+
| `mt_format_*` (int, float, str, bool, hex, bin, oct) | Format primitives to `string.String` sinks (format string lowering) |
|
|
490
|
+
| `mt_async_frame_*` | Task frame allocation and state management (async CPS lowering) |
|
|
491
|
+
| `mt_proc_retain` / `mt_proc_release` | Ref-counted proc capture lifecycle |
|
|
492
|
+
| `mt_event_*` | Fixed-capacity listener arrays, emit dispatch, subscription management |
|
|
493
|
+
| `mt_loop_N` counters | Per-function loop guard injection (50M iteration cap, catches hangs) |
|
|
494
|
+
|
|
495
|
+
The `backend/runtime.mt` module is a collection of `const` string literals — one
|
|
496
|
+
per helper — selected for emission based on feature detection during the LIR walk.
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## 10. Testing Strategy
|
|
501
|
+
|
|
502
|
+
Milk Tea has a built-in testing framework (`std.testing`, `@[test]` attribute, `mtc
|
|
503
|
+
test` runner). The self-hosted compiler's test suite uses this framework:
|
|
504
|
+
|
|
505
|
+
```mt
|
|
506
|
+
import std.testing as t
|
|
507
|
+
|
|
508
|
+
@[test]
|
|
509
|
+
function test_lex_keywords() -> t.Check:
|
|
510
|
+
let result = Lexer.lex("function if else return", "test.mt")
|
|
511
|
+
let tokens = result?
|
|
512
|
+
t.expect_equal[ptr_uint](tokens.len(), 4)?
|
|
513
|
+
return t.ok()
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Test categories:
|
|
517
|
+
|
|
518
|
+
| Directory | Tests |
|
|
519
|
+
|---|---|
|
|
520
|
+
| `src/tests/lexer_test.mt` | Keyword recognition, char literals, string escapes, number formats, heredocs |
|
|
521
|
+
| `src/tests/parser_test.mt` | AST structure verification for each declaration and statement form |
|
|
522
|
+
| `src/tests/semantic_test.mt` | Type checking, name resolution, interface conformance, generic instantiation |
|
|
523
|
+
| `src/tests/type_test.mt` | Type equality, subtype checks, layout computation, type registiry operations |
|
|
524
|
+
| `src/tests/roundtrip_test.mt` | Compile .mt → C → compile C → run → assert exit code 0 |
|
|
525
|
+
|
|
526
|
+
The `tests/` directory (at the project root, outside `src/`) holds canary `.mt`
|
|
527
|
+
programs that exercise full language features. These are compiled by `mtc test` and
|
|
528
|
+
their exit codes verified against expected values. They also serve as the corpus for
|
|
529
|
+
self-compilation verification (Stage 5 bootstrap).
|
|
530
|
+
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## 11. Bootstrapping Strategy
|
|
534
|
+
|
|
535
|
+
### Stage 1: Lex + Parse Only (Subset)
|
|
536
|
+
|
|
537
|
+
Write a lexer and parser in Milk Tea that can parse Milk Tea source and produce an AST.
|
|
538
|
+
Test by compiling with the Ruby `mtc` and running against the existing test corpus.
|
|
539
|
+
No semantic analysis yet.
|
|
540
|
+
|
|
541
|
+
- **Verification**: AST pretty-print round-trips on `examples/language_baseline.mt`
|
|
542
|
+
- **Files**: `token.mt`, `lexer/*.mt`, `ast.mt`, `parser/*.mt`, `diagnostics.mt`
|
|
543
|
+
|
|
544
|
+
### Stage 2: Semantic Analysis
|
|
545
|
+
|
|
546
|
+
Add name resolution, type checking, and monomorphization. Generics are resolved to
|
|
547
|
+
concrete instantiations during HIR construction. The output HIR need not lower to C
|
|
548
|
+
yet, just pass checking.
|
|
549
|
+
|
|
550
|
+
- **Verification**: All `examples/*.mt` files type-check without errors against `std/`
|
|
551
|
+
- **Files**: `hir.mt`, `symbol_table.mt`, `type_system/*.mt`, `semantic/*.mt`,
|
|
552
|
+
`module_loader.mt`, `module_path_resolver.mt`, `module_binder.mt`
|
|
553
|
+
|
|
554
|
+
### Stage 3: Lowering + C Backend
|
|
555
|
+
|
|
556
|
+
Add desugaring, CPS transform, and C code emission including runtime helpers.
|
|
557
|
+
|
|
558
|
+
- **Verification**: `examples/language_baseline.mt` produces a C file that compiles
|
|
559
|
+
with gcc/clang and produces identical runtime behavior to the Ruby `mtc` compiled
|
|
560
|
+
version
|
|
561
|
+
- **Files**: `lir.mt`, `lowering/*.mt`, `backend/c_backend.mt`, `backend/runtime.mt`
|
|
562
|
+
|
|
563
|
+
### Stage 4: Full Language
|
|
564
|
+
|
|
565
|
+
Implement every remaining feature (async CPS, `parallel for`, events, `emit`, `dyn[T]`,
|
|
566
|
+
struct patterns, format strings, etc.) until `mtc` passes the full test corpus. This is
|
|
567
|
+
the long tail — roughly 70% of the effort targets the last 30% of features.
|
|
568
|
+
|
|
569
|
+
- **Verification**: All `examples/*.mt` and `std/` modules compile and run correctly
|
|
570
|
+
- **Files**: `lowering/cps.mt`, `lowering/desugar.mt` (full)
|
|
571
|
+
|
|
572
|
+
### Stage 5: Self-Compile
|
|
573
|
+
|
|
574
|
+
At this point, `mtc` compiled by the Ruby `mtc` can compile `mtc`'s own source:
|
|
575
|
+
|
|
576
|
+
```sh
|
|
577
|
+
# First bootstrap: Ruby mtc compiles mtc
|
|
578
|
+
ruby-mtc build projects/mtc → ./build/mtc
|
|
579
|
+
|
|
580
|
+
# Second bootstrap: mtc compiles itself
|
|
581
|
+
./build/mtc build projects/mtc → ./build/mtc2
|
|
582
|
+
|
|
583
|
+
# Verify: the two binaries produce identical C for the full compiler
|
|
584
|
+
diff <(./build/mtc compile projects/mtc --no-cache) \
|
|
585
|
+
<(./build/mtc2 compile projects/mtc --no-cache)
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
Once identical output is achieved, the Ruby compiler is retired from the mtc build
|
|
589
|
+
chain. The self-hosted compiler can evolve independently.
|
|
590
|
+
|
|
591
|
+
### Stage 6: Secondary Backend (Future)
|
|
592
|
+
|
|
593
|
+
Once `mtc` is self-hosting, target a secondary backend — LLVM via the C API using
|
|
594
|
+
`external function` bindings, or directly emit machine code. This unlocks native
|
|
595
|
+
compilation speed, debug info (DWARF), and incremental compilation.
|
|
596
|
+
|
|
597
|
+
---
|
|
598
|
+
|
|
599
|
+
## 12. Implementation Risks and Mitigations
|
|
600
|
+
|
|
601
|
+
| Risk | Mitigation |
|
|
602
|
+
|---|---|
|
|
603
|
+
| **Bootstrapping gap**: Milk Tea lacks features that `mtc` needs | Use the Ruby `mtc` as a "supercompiler" — if `mtc` hits a compiler bug in the Ruby compiler during bootstrap, fix the Ruby compiler first. |
|
|
604
|
+
| **Performance of self-compiled code**: The C backend generates naive C; self-compiled `mtc` may be 5-10x slower | Optimize the generated C in the C backend first (fewer temporaries, better loop structure). LLVM backend eliminates this entirely. |
|
|
605
|
+
| **Parser complexity**: The Ruby parser is ~4200 lines of hand-written recursive descent | Start with a clean implementation targeting only the Milk Tea grammar subset needed for `mtc` itself. Avoid parsing external files initially. |
|
|
606
|
+
| **Debugging the self-hosted compiler**: Debugging a compiler compiled by itself is a hall-of-mirrors | Embed verbose tracing behind a `--debug-passes` flag. Use `static_assert` liberally for internal invariants. |
|
|
607
|
+
| **45K LOC of Ruby**: The Ruby compiler is large and deeply integrated | `mtc` is a clean-room rewrite. It does not need to match every feature from day one — only the subset needed to bootstrap itself. |
|
|
608
|
+
| **Standard library dependency**: `mtc` will import `std.*` heavily | The Ruby `mtc` already compiles `std/`. This is the least risky dependency — treat it as a given and focus on the compiler logic. |
|
|
609
|
+
| **Arena exhaustion**: Arena-backed `Vec`s can grow unboundedly during IR construction | Each IR stage gets a fresh arena per compilation unit. Large files may trigger `Vec` resizes but cannot silently corrupt — `Vec` handles its own growable storage. |
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
## 13. First Files to Write (Ordered)
|
|
614
|
+
|
|
615
|
+
1. **`src/mtc/diagnostics.mt`** — `Diagnostic`, `DiagnosticEngine`, `SourceSpan`. Every
|
|
616
|
+
other module depends on these types for error reporting.
|
|
617
|
+
|
|
618
|
+
2. **`src/mtc/token.mt`** — `TokenKind` enum, `Token` struct. No dependencies.
|
|
619
|
+
Verifiable by printing a hardcoded token list.
|
|
620
|
+
|
|
621
|
+
3. **`src/mtc/ast.mt`** — All AST variant and arena types. Depends on `token.mt` and
|
|
622
|
+
`diagnostics.mt`. Defines the data model for the entire compiler.
|
|
623
|
+
|
|
624
|
+
4. **`src/mtc/lexer/lexer.mt`** + **`char_tables.mt`** — Lexer. Outputs `Vec[Token]`.
|
|
625
|
+
Verifiable by dumping tokens for any `.mt` file.
|
|
626
|
+
|
|
627
|
+
5. **`src/mtc/parser/*.mt`** — Parser. Outputs `AstArena` (containing all AST nodes).
|
|
628
|
+
Verifiable by pretty-printing the AST.
|
|
629
|
+
|
|
630
|
+
6. **`src/mtc/type_system/types.mt`** — `Type` variant and `TypeRegistry`.
|
|
631
|
+
|
|
632
|
+
7. **`src/mtc/symbol_table.mt`** — `SymbolTable`, `Scope`, and binding structs.
|
|
633
|
+
|
|
634
|
+
8. **`src/mtc/module_path_resolver.mt`** — Import path resolution.
|
|
635
|
+
|
|
636
|
+
9. **`src/mtc/hir.mt`** — HIR type definitions.
|
|
637
|
+
|
|
638
|
+
10. **`src/mtc/semantic/*.mt`** — Semantic checker (AST → HIR), including
|
|
639
|
+
`monomorphize.mt`.
|
|
640
|
+
|
|
641
|
+
11. **`src/mtc/module_loader.mt`** + **`module_binder.mt`** — Module loading and
|
|
642
|
+
visibility.
|
|
643
|
+
|
|
644
|
+
12. **`src/mtc/lir.mt`** — LIR type definitions.
|
|
645
|
+
|
|
646
|
+
13. **`src/mtc/lowering/*.mt`** — Lowering passes (HIR → LIR).
|
|
647
|
+
|
|
648
|
+
14. **`src/mtc/backend/c_backend.mt`** + **`runtime.mt`** — C code and runtime emission.
|
|
649
|
+
|
|
650
|
+
15. **`src/main.mt`** + **`pipeline.mt`** — CLI entry point and pipeline orchestration.
|
|
651
|
+
|
|
652
|
+
Each file is independently testable as soon as its upstream dependencies are complete.
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## 14. Key Architecture Decisions
|
|
657
|
+
|
|
658
|
+
### Arena + Handle over `ref[T]` in containers
|
|
659
|
+
|
|
660
|
+
Milk Tea's `ref[T]` restriction on containers forces the arena pattern. This is not a
|
|
661
|
+
workaround — it is the correct design. Arena-allocated AST/HIR nodes with index handles
|
|
662
|
+
yield better cache locality than pointer-chasing graphs, enable bulk deallocation, and
|
|
663
|
+
trivially support serialization (copy the `Vec[Node]` — done). The same pattern is used
|
|
664
|
+
by `rustc`, `swiftc`, and `clang`.
|
|
665
|
+
|
|
666
|
+
### Monomorphization during HIR construction, not during lowering
|
|
667
|
+
|
|
668
|
+
Generics are resolved to concrete types during the semantic pass (AST → HIR). This is
|
|
669
|
+
simpler than the Ruby compiler's inline-lowering approach and sufficient for a
|
|
670
|
+
single-compilation-unit compiler. The HIR is fully resolved — every type variable is
|
|
671
|
+
replaced, every generic call has concrete type arguments. This makes the lowering pass a
|
|
672
|
+
pure transformation with no type-level work.
|
|
673
|
+
|
|
674
|
+
If separate compilation of generics becomes necessary later, a dedicated
|
|
675
|
+
monomorphization pass can be inserted between HIR construction and lowering. The IR
|
|
676
|
+
separation already accommodates this.
|
|
677
|
+
|
|
678
|
+
### (Sugar) Inline methods in struct bodies
|
|
679
|
+
|
|
680
|
+
`mtc`'s Ruby compiler supports defining methods directly inside struct bodies —
|
|
681
|
+
no separate `extending` block required. The parser desugars inline `function` /
|
|
682
|
+
`editable function` / `static function` declarations into synthetic `ExtendingBlock`
|
|
683
|
+
nodes targeting the enclosing struct. This is pure syntactic sugar with zero
|
|
684
|
+
semantic or code-generation impact.
|
|
685
|
+
|
|
686
|
+
```mt
|
|
687
|
+
# These are equivalent:
|
|
688
|
+
struct Counter:
|
|
689
|
+
value: int
|
|
690
|
+
|
|
691
|
+
function read() -> int:
|
|
692
|
+
return this.value
|
|
693
|
+
|
|
694
|
+
# same as:
|
|
695
|
+
struct Counter:
|
|
696
|
+
value: int
|
|
697
|
+
|
|
698
|
+
extending Counter:
|
|
699
|
+
function read() -> int:
|
|
700
|
+
return this.value
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
Type parameters declared on the struct are in scope for all inline methods,
|
|
704
|
+
enabling generic methods without re-declaring type params.
|
|
705
|
+
|
|
706
|
+
```mt
|
|
707
|
+
struct Box[T]:
|
|
708
|
+
inner: T
|
|
709
|
+
|
|
710
|
+
function unwrap() -> T:
|
|
711
|
+
return this.inner
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
The self-hosted plan uses this sugar throughout for cleaner code layout — struct
|
|
715
|
+
definitions keep their core methods visually adjacent.
|
|
716
|
+
|
|
717
|
+
### C backend as primary target
|
|
718
|
+
|
|
719
|
+
The C backend is the pragmatic first target: it reuses the Ruby compiler's C runtime
|
|
720
|
+
conventions, is portable to any platform with a C compiler, and lets the self-hosted
|
|
721
|
+
compiler bootstrap on any architecture. LLVM is the strategic second target for native
|
|
722
|
+
compilation speed.
|
|
723
|
+
|
|
724
|
+
### Diagnostics-first design (LSP-ready, not LSP-included)
|
|
725
|
+
|
|
726
|
+
The `DiagnosticEngine` uses byte-offset `SourceSpan` values (not line:column pairs with
|
|
727
|
+
string interpolation), suitable for both CLI output and LSP protocol position encoding.
|
|
728
|
+
Error recovery in the parser produces a best-effort AST even with syntax errors. The
|
|
729
|
+
LSP server itself is deferred to v2, but the data model is designed to accommodate it
|
|
730
|
+
without refactoring.
|
|
731
|
+
|
|
732
|
+
### Symmetric module structure with the Ruby compiler
|
|
733
|
+
|
|
734
|
+
`module_loader.mt`, `module_path_resolver.mt`, and `module_binder.mt` mirror the Ruby
|
|
735
|
+
compiler's module infrastructure. This is intentional — the Ruby compiler's module
|
|
736
|
+
system has been hardened through real-world use and its design is sound. The self-hosted
|
|
737
|
+
compiler reimplements the same semantics in Milk Tea, not a new design.
|
data/lib/milk_tea/base.rb
CHANGED
|
@@ -369,7 +369,7 @@ module MilkTea
|
|
|
369
369
|
AST::AttributeDecl.new(name: name_token.lexeme, targets:, params:, visibility:, line:, column: name_token.column)
|
|
370
370
|
end
|
|
371
371
|
|
|
372
|
-
def parse_struct_decl(packed: false, alignment: nil, visibility: :private, attributes: [])
|
|
372
|
+
def parse_struct_decl(packed: false, alignment: nil, visibility: :private, attributes: [], inline_methods: true)
|
|
373
373
|
line = previous.line
|
|
374
374
|
name_token = consume_name("expected struct name")
|
|
375
375
|
name = name_token.lexeme
|
|
@@ -377,13 +377,32 @@ module MilkTea
|
|
|
377
377
|
implements = parse_implements_clause
|
|
378
378
|
c_name = parse_optional_c_name
|
|
379
379
|
packed, alignment = parse_struct_layout_attributes(attributes) if attributes.any?
|
|
380
|
-
|
|
381
|
-
|
|
380
|
+
receiver_type_param_names = type_params.map(&:name)
|
|
381
|
+
members = with_type_param_names(receiver_type_param_names) do
|
|
382
|
+
parse_recoverable_block do
|
|
383
|
+
parse_struct_member
|
|
384
|
+
end
|
|
382
385
|
end
|
|
383
386
|
fields = members.filter_map { |kind, member| member if kind == :field }
|
|
384
387
|
events = members.filter_map { |kind, member| member if kind == :event }
|
|
385
388
|
nested_types = members.filter_map { |kind, member| member if kind == :nested_type }
|
|
386
|
-
|
|
389
|
+
methods = members.filter_map { |kind, member| member if kind == :method }
|
|
390
|
+
struct_decl = AST::StructDecl.new(name:, type_params:, implements:, c_name:, fields:, events:, nested_types:, attributes:, packed:, alignment:, visibility:, lifetime_params:, line:, column: name_token.column)
|
|
391
|
+
|
|
392
|
+
if inline_methods && methods.any?
|
|
393
|
+
type_ref_args = type_params.map do |tp|
|
|
394
|
+
AST::TypeArgument.new(
|
|
395
|
+
value: AST::TypeRef.new(name: AST::QualifiedName.new(parts: [tp.name]), arguments: [], nullable: false, line: tp.line, column: tp.column),
|
|
396
|
+
line: tp.line,
|
|
397
|
+
column: tp.column,
|
|
398
|
+
)
|
|
399
|
+
end
|
|
400
|
+
type_ref = AST::TypeRef.new(name: AST::QualifiedName.new(parts: [name]), arguments: type_ref_args, nullable: false, line: name_token.line, column: name_token.column)
|
|
401
|
+
extending_block = AST::ExtendingBlock.new(type_name: type_ref, methods:, line: name_token.line, column: name_token.column)
|
|
402
|
+
[struct_decl, extending_block]
|
|
403
|
+
else
|
|
404
|
+
struct_decl
|
|
405
|
+
end
|
|
387
406
|
end
|
|
388
407
|
|
|
389
408
|
def parse_struct_decl_params
|
|
@@ -429,8 +448,24 @@ module MilkTea
|
|
|
429
448
|
[lifetime_params, type_params]
|
|
430
449
|
end
|
|
431
450
|
|
|
451
|
+
def check_method_start?
|
|
452
|
+
saved = @current
|
|
453
|
+
match(:public)
|
|
454
|
+
match(:async)
|
|
455
|
+
match(:editable) if check(:editable)
|
|
456
|
+
match(:static) if check(:static)
|
|
457
|
+
result = check(:function)
|
|
458
|
+
@current = saved
|
|
459
|
+
result
|
|
460
|
+
end
|
|
461
|
+
|
|
432
462
|
def parse_struct_member
|
|
433
463
|
field_attributes = parse_attribute_applications
|
|
464
|
+
|
|
465
|
+
if check_method_start?
|
|
466
|
+
return [:method, parse_method_def(attributes: field_attributes)]
|
|
467
|
+
end
|
|
468
|
+
|
|
434
469
|
visibility, visibility_token = parse_visibility
|
|
435
470
|
|
|
436
471
|
if match(:event)
|
|
@@ -438,7 +473,7 @@ module MilkTea
|
|
|
438
473
|
end
|
|
439
474
|
|
|
440
475
|
if match(:struct)
|
|
441
|
-
return [:nested_type, parse_struct_decl(visibility:, attributes: field_attributes)]
|
|
476
|
+
return [:nested_type, parse_struct_decl(visibility:, attributes: field_attributes, inline_methods: false)]
|
|
442
477
|
end
|
|
443
478
|
|
|
444
479
|
raise error(visibility_token, "public is only allowed on struct events") if visibility == :public
|
data/lib/milk_tea/core/parser.rb
CHANGED
|
@@ -154,13 +154,23 @@ module MilkTea
|
|
|
154
154
|
until eof?
|
|
155
155
|
if errors
|
|
156
156
|
begin
|
|
157
|
-
|
|
157
|
+
result = parse_declaration
|
|
158
|
+
if result.is_a?(Array)
|
|
159
|
+
declarations.concat(result)
|
|
160
|
+
else
|
|
161
|
+
declarations << result
|
|
162
|
+
end
|
|
158
163
|
rescue ParseError => e
|
|
159
164
|
errors << e
|
|
160
165
|
synchronize_to_top_level_boundary
|
|
161
166
|
end
|
|
162
167
|
else
|
|
163
|
-
|
|
168
|
+
result = parse_declaration
|
|
169
|
+
if result.is_a?(Array)
|
|
170
|
+
declarations.concat(result)
|
|
171
|
+
else
|
|
172
|
+
declarations << result
|
|
173
|
+
end
|
|
164
174
|
end
|
|
165
175
|
skip_newlines
|
|
166
176
|
end
|
|
@@ -164,7 +164,10 @@ module MilkTea
|
|
|
164
164
|
end
|
|
165
165
|
end
|
|
166
166
|
check_redundant_type_annotation(statement)
|
|
167
|
-
|
|
167
|
+
if statement.type && statement.value
|
|
168
|
+
flag_redundant_widening_cast(statement.value)
|
|
169
|
+
flag_redundant_literal_cast(statement.value)
|
|
170
|
+
end
|
|
168
171
|
record_ptr_candidate(statement)
|
|
169
172
|
when AST::Assignment
|
|
170
173
|
visit_expression(statement.value) # visit RHS first — reads in RHS count against dead-assignment
|
|
@@ -173,7 +176,10 @@ module MilkTea
|
|
|
173
176
|
mark_mutated(statement.target)
|
|
174
177
|
check_self_assignment(statement)
|
|
175
178
|
check_noop_compound_assignment(statement)
|
|
176
|
-
|
|
179
|
+
if statement.operator == "="
|
|
180
|
+
flag_redundant_widening_cast(statement.value)
|
|
181
|
+
flag_redundant_literal_cast(statement.value)
|
|
182
|
+
end
|
|
177
183
|
when AST::IfStmt
|
|
178
184
|
statement.branches.each do |branch|
|
|
179
185
|
visit_expression(branch.condition)
|
|
@@ -223,7 +229,10 @@ module MilkTea
|
|
|
223
229
|
@loop_depth -= 1
|
|
224
230
|
when AST::ReturnStmt
|
|
225
231
|
visit_expression(statement.value) if statement.value
|
|
226
|
-
|
|
232
|
+
if statement.value
|
|
233
|
+
flag_redundant_widening_cast(statement.value)
|
|
234
|
+
flag_redundant_literal_cast(statement.value)
|
|
235
|
+
end
|
|
227
236
|
when AST::DeferStmt
|
|
228
237
|
with_scope { visit_statement_list(statement.body) }
|
|
229
238
|
when AST::ExpressionStmt
|
|
@@ -278,6 +287,8 @@ module MilkTea
|
|
|
278
287
|
visit_expression(expression.right)
|
|
279
288
|
check_self_comparison(expression)
|
|
280
289
|
check_redundant_bool_compare(expression)
|
|
290
|
+
check_redundant_binary_operand_cast(expression)
|
|
291
|
+
check_redundant_literal_operand_cast(expression)
|
|
281
292
|
when AST::RangeExpr
|
|
282
293
|
visit_expression(expression.start_expr)
|
|
283
294
|
visit_expression(expression.end_expr)
|
|
@@ -1141,6 +1152,140 @@ module MilkTea
|
|
|
1141
1152
|
emit_redundant_cast(value, target_name, "implicit widening makes this cast redundant")
|
|
1142
1153
|
end
|
|
1143
1154
|
|
|
1155
|
+
INTEGER_TYPE_RANGES = {
|
|
1156
|
+
"byte" => (-128..127),
|
|
1157
|
+
"short" => (-32_768..32_767),
|
|
1158
|
+
"int" => (-(2**31)..2**31 - 1),
|
|
1159
|
+
"long" => (-(2**63)..2**63 - 1),
|
|
1160
|
+
"ubyte" => (0..255),
|
|
1161
|
+
"ushort" => (0..65_535),
|
|
1162
|
+
"uint" => (0..2**32 - 1),
|
|
1163
|
+
"ulong" => (0..2**64 - 1),
|
|
1164
|
+
"char" => (0..255),
|
|
1165
|
+
}.freeze
|
|
1166
|
+
|
|
1167
|
+
# Reports an integer-literal cast at a coercion slot as redundant when the
|
|
1168
|
+
# literal would implicitly coerce to the cast target: the compiler accepts
|
|
1169
|
+
# a fitting integer literal for any integer slot (`buf[0] = 1` for a
|
|
1170
|
+
# `ubyte` element), so `ubyte<-1` adds nothing. Out-of-range literals are
|
|
1171
|
+
# left alone because removing the cast would then be a type error.
|
|
1172
|
+
# Widening literal casts are already reported by flag_redundant_widening_cast.
|
|
1173
|
+
def flag_redundant_literal_cast(value)
|
|
1174
|
+
return unless value.is_a?(AST::PrefixCast)
|
|
1175
|
+
return unless @sema_facts
|
|
1176
|
+
|
|
1177
|
+
target_name = type_ref_name(value.target_type)
|
|
1178
|
+
return unless target_name
|
|
1179
|
+
range = INTEGER_TYPE_RANGES[target_name]
|
|
1180
|
+
return unless range
|
|
1181
|
+
|
|
1182
|
+
inner = value.expression
|
|
1183
|
+
return unless inner.is_a?(AST::IntegerLiteral)
|
|
1184
|
+
literal = inner.value
|
|
1185
|
+
return unless literal.is_a?(Integer)
|
|
1186
|
+
return unless range.cover?(literal)
|
|
1187
|
+
|
|
1188
|
+
inner_type = resolve_expr_type(inner)
|
|
1189
|
+
return unless inner_type.is_a?(Types::Primitive)
|
|
1190
|
+
return if inner_type.name == target_name # same-type handled elsewhere
|
|
1191
|
+
return if implicit_cast_allowed?(inner_type, target_name) # widening handled elsewhere
|
|
1192
|
+
|
|
1193
|
+
emit_redundant_cast(value, target_name, "integer literal coercion makes this cast redundant")
|
|
1194
|
+
end
|
|
1195
|
+
|
|
1196
|
+
BINARY_CAST_SAFE_OPS = %w[+ - * / % == != < <= > >=].freeze
|
|
1197
|
+
|
|
1198
|
+
# Reports a widening cast used directly as an operand of an arithmetic or
|
|
1199
|
+
# comparison operator when removing it provably leaves the operation's
|
|
1200
|
+
# result unchanged. An integer->float cast inside a float-family
|
|
1201
|
+
# expression promotes to the sibling's float type either way, so the cast
|
|
1202
|
+
# is redundant. Excluded:
|
|
1203
|
+
# * shifts (`<<`/`>>`) — the shifted operand's width is load-bearing;
|
|
1204
|
+
# * `double` in a `float` context — `float_y + double<-i` is `double`
|
|
1205
|
+
# with the cast but `float` without it;
|
|
1206
|
+
# * cast-chains such as `float<-x + float<-y` — each removal is only
|
|
1207
|
+
# safe if the sibling cast stays, so neither is independently
|
|
1208
|
+
# redundant.
|
|
1209
|
+
def check_redundant_binary_operand_cast(binary)
|
|
1210
|
+
return unless @sema_facts
|
|
1211
|
+
return unless BINARY_CAST_SAFE_OPS.include?(binary.operator)
|
|
1212
|
+
|
|
1213
|
+
check_binary_operand_cast(binary.left, binary.right)
|
|
1214
|
+
check_binary_operand_cast(binary.right, binary.left)
|
|
1215
|
+
end
|
|
1216
|
+
|
|
1217
|
+
def check_binary_operand_cast(operand, sibling)
|
|
1218
|
+
return unless operand.is_a?(AST::PrefixCast)
|
|
1219
|
+
return if sibling.is_a?(AST::PrefixCast)
|
|
1220
|
+
|
|
1221
|
+
target_type = resolve_expr_type(operand)
|
|
1222
|
+
return unless target_type.is_a?(Types::Primitive) && target_type.float?
|
|
1223
|
+
|
|
1224
|
+
inner_type = resolve_expr_type(operand.expression)
|
|
1225
|
+
return unless inner_type.is_a?(Types::Primitive) && inner_type.fixed_width_integer?
|
|
1226
|
+
|
|
1227
|
+
sibling_type = resolve_expr_type(sibling)
|
|
1228
|
+
return unless sibling_type.is_a?(Types::Primitive) && sibling_type.float?
|
|
1229
|
+
return if target_type.name == "double" && sibling_type.name == "float"
|
|
1230
|
+
|
|
1231
|
+
target_name = type_ref_name(operand.target_type)
|
|
1232
|
+
return unless target_name
|
|
1233
|
+
|
|
1234
|
+
emit_redundant_cast(operand, target_name, "implicit float widening makes this cast redundant")
|
|
1235
|
+
end
|
|
1236
|
+
|
|
1237
|
+
COMPARISON_CAST_OPS = %w[== != < <= > >=].freeze
|
|
1238
|
+
|
|
1239
|
+
# Reports an integer-literal cast used directly as a comparison operand
|
|
1240
|
+
# (e.g. `State.idle == ubyte<-0`). The literal coerces to the sibling's
|
|
1241
|
+
# type either way, so `D<-lit` adds nothing when the literal fits `D` and
|
|
1242
|
+
# the sibling is a float-family value, an integer the literal fits in, or
|
|
1243
|
+
# an enum (which compares against any integer literal). Out-of-range
|
|
1244
|
+
# literals are left alone: `ubyte<-300` wraps in `D`, which is not the
|
|
1245
|
+
# same value as the bare literal.
|
|
1246
|
+
def check_redundant_literal_operand_cast(binary)
|
|
1247
|
+
return unless @sema_facts
|
|
1248
|
+
return unless COMPARISON_CAST_OPS.include?(binary.operator)
|
|
1249
|
+
|
|
1250
|
+
check_comparison_literal_operand(binary.left, binary.right)
|
|
1251
|
+
check_comparison_literal_operand(binary.right, binary.left)
|
|
1252
|
+
end
|
|
1253
|
+
|
|
1254
|
+
def check_comparison_literal_operand(operand, sibling)
|
|
1255
|
+
return unless operand.is_a?(AST::PrefixCast)
|
|
1256
|
+
|
|
1257
|
+
target_name = type_ref_name(operand.target_type)
|
|
1258
|
+
return unless target_name
|
|
1259
|
+
target_range = INTEGER_TYPE_RANGES[target_name]
|
|
1260
|
+
return unless target_range
|
|
1261
|
+
|
|
1262
|
+
inner = operand.expression
|
|
1263
|
+
return unless inner.is_a?(AST::IntegerLiteral)
|
|
1264
|
+
literal = inner.value
|
|
1265
|
+
return unless literal.is_a?(Integer)
|
|
1266
|
+
return unless target_range.cover?(literal)
|
|
1267
|
+
|
|
1268
|
+
# Same-type casts are reported by check_redundant_cast.
|
|
1269
|
+
inner_type = resolve_expr_type(inner)
|
|
1270
|
+
return if inner_type.is_a?(Types::Primitive) && inner_type.name == target_name
|
|
1271
|
+
|
|
1272
|
+
sibling_type = resolve_expr_type(sibling)
|
|
1273
|
+
case sibling_type
|
|
1274
|
+
when Types::Primitive
|
|
1275
|
+
return unless sibling_type.float? || sibling_type.integer?
|
|
1276
|
+
if sibling_type.integer?
|
|
1277
|
+
sibling_range = INTEGER_TYPE_RANGES[sibling_type.name]
|
|
1278
|
+
return unless sibling_range && sibling_range.cover?(literal)
|
|
1279
|
+
end
|
|
1280
|
+
when Types::EnumBase
|
|
1281
|
+
nil
|
|
1282
|
+
else
|
|
1283
|
+
return
|
|
1284
|
+
end
|
|
1285
|
+
|
|
1286
|
+
emit_redundant_cast(operand, target_name, "integer literal coercion makes this cast redundant")
|
|
1287
|
+
end
|
|
1288
|
+
|
|
1144
1289
|
def same_resolved_type?(left, right)
|
|
1145
1290
|
return true if left.equal?(right)
|
|
1146
1291
|
|
|
@@ -1177,6 +1322,14 @@ module MilkTea
|
|
|
1177
1322
|
target = find_primitive_type(target_name)
|
|
1178
1323
|
return false unless target
|
|
1179
1324
|
return false unless target.is_a?(Types::Primitive)
|
|
1325
|
+
|
|
1326
|
+
# Fixed-width integers widen implicitly to float/double (matching the
|
|
1327
|
+
# compiler's promotion, e.g. `float + int` or `return int` from a
|
|
1328
|
+
# `-> float` function). `char`/`bool` are not fixed-width integers and
|
|
1329
|
+
# have no implicit conversion to the float family; float->double is not
|
|
1330
|
+
# implicit either.
|
|
1331
|
+
return true if target.float? && inner_type.fixed_width_integer?
|
|
1332
|
+
|
|
1180
1333
|
return false unless inner_type.fixed_width_integer? && target.fixed_width_integer?
|
|
1181
1334
|
|
|
1182
1335
|
if inner_type.signed_integer? == target.signed_integer?
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mt-lang
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.28
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Long (Teefan) Tran
|
|
@@ -149,6 +149,7 @@ files:
|
|
|
149
149
|
- docs/index.html
|
|
150
150
|
- docs/language-design.md
|
|
151
151
|
- docs/language-manual.md
|
|
152
|
+
- docs/self-hosted-compiler-plan.md
|
|
152
153
|
- lib/milk_tea.rb
|
|
153
154
|
- lib/milk_tea/base.rb
|
|
154
155
|
- lib/milk_tea/bindings.rb
|
|
@@ -624,7 +625,7 @@ metadata:
|
|
|
624
625
|
homepage_uri: https://teefan.github.io/mt-lang/
|
|
625
626
|
source_code_uri: https://github.com/teefan/mt-lang
|
|
626
627
|
post_install_message: |
|
|
627
|
-
Milk Tea 0.3.
|
|
628
|
+
Milk Tea 0.3.28 installed!
|
|
628
629
|
|
|
629
630
|
System requirements:
|
|
630
631
|
- A C compiler (gcc or clang) must be available on PATH
|