mt-lang 0.3.28 → 0.3.30

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20058db447f681f83bcc651d9a9772e79b23bd9f1677e5c33d2053bab536a14f
4
- data.tar.gz: 204af7f1018ce7637690eddc2bc9a7f297fc1f10e59c1a573491bd3b7c0b0d1c
3
+ metadata.gz: d6d13cde9fca875b324ff3e0cb3b892c371e2445ac5e061d138bd0dc8b284b88
4
+ data.tar.gz: 2a91eedc02120e0d042bc6bafbfc783a4eefb5bbc44c34940e1ee9be6917405e
5
5
  SHA512:
6
- metadata.gz: ec63256de42954560cedc8e5021370b8f45a64e083ebb839d3057d4ea93816a51d45f950283ece162152ad10be6f1bb6ec4aa54a6ad8ce7b2db107830dd1db6a
7
- data.tar.gz: 616bc238b0ee79df60a7a09fc62b895b1ab7717a1116a6b96c50879549578ba0aab91fbb9a74e40aedfc559973c333a8bc2c2cc40d3d16b6913584a744230819
6
+ metadata.gz: 478dc59b852c703b5cf63ce3cd77a92d6de305424c96cfa7dbeda97ed1882f2d8c52f5770893dc99a3077cb1599cbf0602c6e54b773f219ce859b8b9275c7258
7
+ data.tar.gz: d221d15fe3c3192ee0b10b61101f5234e8b92830f878051c6e2641aca334b7d165c9668bf410a727b4964f02d45805322044470a9e406b971ab7508650d60a00
data/README.md CHANGED
@@ -94,7 +94,7 @@ Supported literals:
94
94
  - floats: `3.14`, `1.2e-3`, `1.0f` (float suffix), `1.0d` (double suffix)
95
95
  - character: `'a'`, `'\n'`, `'\t'`, `'\\'`, `'\''`, `'\0'`, `'\x41'`. Type is `ubyte`. Escape sequences: `\n`, `\r`, `\t`, `\\`, `\'`, `\"`, `\0` (null byte), `\xNN` (hex byte).
96
96
  - booleans: `true`, `false`
97
- - string: `"hello"` -> `str`
97
+ - string: `"hello"` -> `str`. The `+` operator concatenates `str` values: `"hello" + " " + "world"` produces `"hello world"`. For loops or repeated concatenation, prefer `string.String`.
98
98
  - cstring: `c"hello"` -> `cstr`
99
99
  - heredoc string: `<<-TAG ... TAG`
100
100
  - heredoc cstring: `c<<-TAG ... TAG`
@@ -106,7 +106,7 @@ Common punctuation and operators:
106
106
  - delimiters: `(` `)` `[` `]`
107
107
  - access and separators: `:` `,` `.`
108
108
  - type markers: `->` `?`
109
- - arithmetic: `+ - * / %`
109
+ - arithmetic: `+ - * / %` (additionally, `+` on `str` concatenates)
110
110
  - bitwise: `~ & | ^ << >>`
111
111
  - comparison: `== != < <= > >=`
112
112
  - assignment: `= += -= *= /= %= &= |= ^= <<= >>=`
@@ -937,7 +937,7 @@ See module source for full method surface. Iterator forms:
937
937
 
938
938
  Text categories:
939
939
 
940
- - `str` -> string view
940
+ - `str` -> string view. The `+` operator concatenates two `str` values, allocating a new heap-backed string. For loops or repeated concatenation, prefer `string.String` for amortized performance.
941
941
  - `cstr` -> C ABI string
942
942
  - `str_buffer[N]` -> fixed-capacity mutable UTF-8 text buffer
943
943
 
data/docs/index.html CHANGED
@@ -961,7 +961,7 @@ let total = (
961
961
  <tr><td>Delimiters</td><td><code>( ) [ ]</code></td></tr>
962
962
  <tr><td>Separators / Access</td><td><code>: , .</code></td></tr>
963
963
  <tr><td>Type markers</td><td><code>-&gt;</code> <code>?</code></td></tr>
964
- <tr><td>Arithmetic</td><td><code>+ - * / %</code></td></tr>
964
+ <tr><td>Arithmetic</td><td><code>+ - * / %</code> (the <code>+</code> operator on <code>str</code> concatenates, allocating a new heap-backed string)</td></tr>
965
965
  <tr><td>Bitwise</td><td><code>~ &amp; | ^ &lt;&lt; &gt;&gt;</code></td></tr>
966
966
  <tr><td>Comparison</td><td><code>== != &lt; &lt;= &gt; &gt;=</code></td></tr>
967
967
  <tr><td>Assignment</td><td><code>= += -= *= /= %= &amp;= |= ^= &lt;&lt;= &gt;&gt;=</code></td></tr>
@@ -1243,7 +1243,7 @@ let d: dyn[Drawable] = adapt[Drawable](ref_of(entity))</code></pre>
1243
1243
  <tr><td><code>ptr_int</code> <code>ptr_uint</code></td><td>Pointer-sized integers</td></tr>
1244
1244
  <tr><td><code>float</code> <code>double</code></td><td>Floating-point</td></tr>
1245
1245
  <tr><td><code>void</code></td><td>No value</td></tr>
1246
- <tr><td><code>str</code></td><td>UTF-8 string view (borrowed)</td></tr>
1246
+ <tr><td><code>str</code></td><td>UTF-8 string view (borrowed). The <code>+</code> operator concatenates two <code>str</code> values, allocating a new heap-backed string.</td></tr>
1247
1247
  <tr><td><code>cstr</code></td><td>NUL-terminated C string</td></tr>
1248
1248
  <tr><td><code>vec2</code> <code>vec3</code> <code>vec4</code></td><td>Float vectors with <code>.x .y .z .w</code></td></tr>
1249
1249
  <tr><td><code>ivec2</code> <code>ivec3</code> <code>ivec4</code></td><td>Integer vectors</td></tr>
@@ -38,7 +38,7 @@ If code allocates, takes an address, dereferences a raw pointer, performs an FFI
38
38
 
39
39
  FFI visibility belongs at the declaration site. Raw `external` files expose exact ABI types. Imported foreign declarations may project those raw types into ordinary Milk Tea types, but the projection rule, temporary-storage rule, and ownership rule must be declared there instead of repeated at every call site.
40
40
 
41
- The same rule applies to text construction. Plain string literals and format string literals are borrowed `str` values. Any surface that builds owned text must say so explicitly, for example `std.fmt.format(f"...")` when ownership must escape.
41
+ The same rule applies to text construction. Plain string literals and format string literals are borrowed `str` values. The `+` operator on `str` allocates a new heap-backed `str` — the one everyday convenience that does allocate. For loops or amortized building, `string.String` and `str_buffer[N]` remain the explicit surfaces with visible cost. Any other surface that builds owned text must say so explicitly, for example `std.fmt.format(f"...")` when ownership must escape.
42
42
 
43
43
  ### 2. C is the ABI ground truth
44
44
 
@@ -755,7 +755,7 @@ Explicit specialization arguments may be type references like `bytes_for[int](4)
755
755
 
756
756
  Built-in operators should match familiar C behavior where possible:
757
757
 
758
- - arithmetic: `+ - * / %`
758
+ - arithmetic: `+ - * / %` (the `+` operator on `str` concatenates, allocating a new heap-backed string)
759
759
  - comparison: `== != < <= > >=`
760
760
  - boolean: `and or not`
761
761
  - bitwise: `& | ^ ~ << >>`
@@ -124,7 +124,7 @@ Supported literals:
124
124
  - integer: `42`, `0xff`, `0b1010`, with `_` separators. Integer type suffixes: `42u` (`uint`), `0xFFub` (`ubyte`), `100z` (`ptr_uint`), `7i` (`int`), `-1l` (`long`), etc.
125
125
  - float: `3.14`, `1.2e-3`, `1.1920929E-7`, `1.0f` (float suffix), `1.0d` (double suffix)
126
126
  - character: `'a'`, `'\n'`, `'\t'`, `'\\'`, `'\''`, `'\0'`, `'\x41'`. Type is `ubyte`. Escape sequences: `\n`, `\r`, `\t`, `\\`, `\'`, `\"`, `\0` (null), `\xNN` (hex byte).
127
- - string: `"hello"` (`str`). Supported string escapes are `\n`, `\r`, `\t`, `\0` (null), `\"`, `\'`, and `\\`; any other `\x` sequence is taken literally. Hex byte escapes (`\xNN`) are character-literal only, not string-literal.
127
+ - string: `"hello"` (`str`). Supported string escapes are `\n`, `\r`, `\t`, `\0` (null), `\"`, `\'`, and `\\`; any other `\x` sequence is taken literally. Hex byte escapes (`\xNN`) are character-literal only, not string-literal. The `+` operator concatenates `str` values: `"hello" + " " + "world"` produces `"hello world"`. Each `+` allocates a new heap-backed `str`; for loops or repeated concatenation, prefer `string.String` for amortized performance.
128
128
  - cstring: `c"hello"` (`cstr`)
129
129
  - heredoc string: `<<-TAG ... TAG` (`str`)
130
130
  - heredoc cstring: `c<<-TAG ... TAG` (`cstr`)
@@ -1015,7 +1015,7 @@ Rules:
1015
1015
  8. `==`, `!=`
1016
1016
  9. `<`, `<=`, `>`, `>=`
1017
1017
  10. `<<`, `>>`
1018
- 11. `+`, `-`
1018
+ 11. `+`, `-` (additionally, `+` on `str` concatenates; each `+` allocates a new heap-backed `str`)
1019
1019
  12. `*`, `/`, `%`
1020
1020
 
1021
1021
  ### 5.4 Assignment operators
data/lib/milk_tea/base.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "pathname"
4
4
 
5
5
  module MilkTea
6
- VERSION = "0.3.28"
6
+ VERSION = "0.3.30"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -289,6 +289,14 @@ module MilkTea
289
289
  emitted_functions.any? { |function| function_uses_str_equality?(function) }
290
290
  end
291
291
 
292
+ def uses_str_concat_helper?
293
+ return @uses_str_concat if defined?(@uses_str_concat)
294
+
295
+ @uses_str_concat = emitted_functions.any? do |function|
296
+ function_uses_named_call?(function, %w[mt_str_concat])
297
+ end
298
+ end
299
+
292
300
  def uses_variant_equality_helper?
293
301
  emitted_functions.any? { |function| function_uses_variant_equality?(function) }
294
302
  end
@@ -42,6 +42,27 @@ module MilkTea
42
42
  ]
43
43
  end
44
44
 
45
+ def emit_str_concat_helper
46
+ [
47
+ "#define MT_STR_CONCAT_BUF_SIZE 65536",
48
+ "",
49
+ "static _Thread_local char mt_str_concat_buf[MT_STR_CONCAT_BUF_SIZE];",
50
+ "static _Thread_local uintptr_t mt_str_concat_offset = 0;",
51
+ "",
52
+ "static mt_str mt_str_concat(mt_str a, mt_str b) {",
53
+ "#{INDENT}uintptr_t total = a.len + b.len;",
54
+ "#{INDENT}if (mt_str_concat_offset + total > MT_STR_CONCAT_BUF_SIZE) {",
55
+ "#{INDENT * 2}mt_str_concat_offset = 0;",
56
+ "#{INDENT}}",
57
+ "#{INDENT}char* buf = mt_str_concat_buf + mt_str_concat_offset;",
58
+ "#{INDENT}if (a.len > 0) memcpy(buf, a.data, a.len);",
59
+ "#{INDENT}if (b.len > 0) memcpy(buf + a.len, b.data, b.len);",
60
+ "#{INDENT}mt_str_concat_offset += total;",
61
+ "#{INDENT}return (mt_str){ .data = buf, .len = total };",
62
+ "}",
63
+ ]
64
+ end
65
+
45
66
  def emit_variant_equality_helpers
46
67
  emitted_aggregate_variants.flat_map { |variant_decl| emit_variant_equality_helper(variant_decl) }
47
68
  end
@@ -146,6 +146,10 @@ module MilkTea
146
146
  lines.concat(emit_str_equality_helper)
147
147
  lines << ""
148
148
  end
149
+ if uses_str_concat_helper?
150
+ lines.concat(emit_str_concat_helper)
151
+ lines << ""
152
+ end
149
153
  if uses_str_buffer_helpers?
150
154
  lines.concat(emit_utf8_validation_helpers)
151
155
  lines << ""
@@ -1387,6 +1387,10 @@ module MilkTea
1387
1387
  left = cast_expression(left, operand_type) if operand_type
1388
1388
  right = cast_expression(right, operand_type) if operand_type
1389
1389
 
1390
+ if expression.operator == "+" && left_type == @ctx.types.fetch("str") && right_type == @ctx.types.fetch("str")
1391
+ return IR::Call.new(callee: "mt_str_concat", arguments: [left, right], type:)
1392
+ end
1393
+
1390
1394
  expanded = lower_vector_binary_operation(expression.operator, left, left_type, right, right_type, type)
1391
1395
  return expanded if expanded
1392
1396
 
@@ -612,8 +612,8 @@ module MilkTea
612
612
 
613
613
  left_type
614
614
  when "+", "-", "*", "/"
615
- if expression.operator == "+" && (string_like_type?(left_type) || string_like_type?(right_type))
616
- raise_sema_error("operator + does not support str/cstr concatenation; use continued string literals for static text or string.String/str_buffer for dynamic text")
615
+ if expression.operator == "+" && left_type == @ctx.types.fetch("str") && right_type == @ctx.types.fetch("str")
616
+ return left_type
617
617
  end
618
618
 
619
619
  pointer_result = pointer_arithmetic_result(expression.operator, left_type, right_type)
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.28
4
+ version: 0.3.30
5
5
  platform: ruby
6
6
  authors:
7
7
  - Long (Teefan) Tran
@@ -149,7 +149,6 @@ 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
153
152
  - lib/milk_tea.rb
154
153
  - lib/milk_tea/base.rb
155
154
  - lib/milk_tea/bindings.rb
@@ -625,7 +624,7 @@ metadata:
625
624
  homepage_uri: https://teefan.github.io/mt-lang/
626
625
  source_code_uri: https://github.com/teefan/mt-lang
627
626
  post_install_message: |
628
- Milk Tea 0.3.28 installed!
627
+ Milk Tea 0.3.30 installed!
629
628
 
630
629
  System requirements:
631
630
  - A C compiler (gcc or clang) must be available on PATH
@@ -1,737 +0,0 @@
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.