mt-lang 0.4.0 → 0.4.2
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 +4 -0
- data/docs/index.html +71 -2
- data/docs/language-design.md +2 -2
- data/docs/language-manual.md +8 -2
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/core/compile_time/method_folding.rb +6 -2
- data/lib/milk_tea/core/compile_time.rb +22 -8
- data/lib/milk_tea/core/lowering/resolve.rb +6 -2
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +2 -1
- data/lib/milk_tea/core/semantic_analyzer/top_level.rb +11 -0
- data/lib/milk_tea/lsp/server/completion.rb +9 -18
- data/lib/milk_tea/lsp/server/utilities.rb +2 -3
- data/lib/milk_tea/lsp/workspace/caches.rb +2 -0
- data/lib/milk_tea/lsp/workspace/definition_index.rb +12 -13
- data/lib/milk_tea/lsp/workspace/store.rb +6 -1
- data/lib/milk_tea/lsp/workspace/utilities.rb +50 -30
- data/lib/milk_tea/lsp/workspace.rb +5 -1
- data/lib/milk_tea/tooling/cli/commands/run.rb +3 -2
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 726a973cae08da6d7927931a6fa51adc8106fd483709dede501b7e551e1d9103
|
|
4
|
+
data.tar.gz: 666501d1a75719851869d1467d7498e84c2b4fd18da6d52a41912343d2f52f43
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 618b5705f9b727ce7d07b6ccf6576aa92d0380d7f4609393790fb8c64df1a3c7bd409151eabc800d95ca01d6636c8e26d43840758db90502ccf2757ad6800190
|
|
7
|
+
data.tar.gz: e9a6840730d61fee0b57c3339779169a31e91d84626230cc353c1e9b31a1e748b58a0b5c205d904bbb32ba0951bfd4874df3374c378a62c8a6e8277bc1fccffe
|
data/README.md
CHANGED
|
@@ -364,6 +364,8 @@ Method kinds:
|
|
|
364
364
|
- `function` -> value receiver
|
|
365
365
|
- `editable function` -> editable receiver
|
|
366
366
|
- `static function` -> no receiver
|
|
367
|
+
- `const function` -> value receiver, compile-time-evaluable
|
|
368
|
+
- `static const function` -> no receiver, compile-time-evaluable
|
|
367
369
|
|
|
368
370
|
Methods may appear inside a struct body (desugared to an `extending` block) or in a separate
|
|
369
371
|
`extending` declaration:
|
|
@@ -424,6 +426,8 @@ const RESULT: int = square(5) # folded to 25 at compile time
|
|
|
424
426
|
|
|
425
427
|
`const function` also generates a normal runtime function, callable from ordinary runtime code.
|
|
426
428
|
|
|
429
|
+
`const` applies to methods as well: `const function` (value receiver) and `static const function` (no receiver) methods fold when called from a compile-time context and also generate normal runtime functions. `editable const function`, `async const function`, and `const` on interface methods are rejected.
|
|
430
|
+
|
|
427
431
|
External functions:
|
|
428
432
|
|
|
429
433
|
```mt
|
data/docs/index.html
CHANGED
|
@@ -556,6 +556,14 @@ function make_buffer[N: int]() -> str_buffer[N]:
|
|
|
556
556
|
var buf: str_buffer[N]
|
|
557
557
|
return buf
|
|
558
558
|
|
|
559
|
+
## ── inline if with a compile-time type comparison ───────────────────
|
|
560
|
+
function size_label[T]() -> str:
|
|
561
|
+
inline if T == int:
|
|
562
|
+
return "32-bit"
|
|
563
|
+
inline if T == float:
|
|
564
|
+
return "float"
|
|
565
|
+
return "other"
|
|
566
|
+
|
|
559
567
|
## ── Error handling with Result[T, E] ────────────────────────────────
|
|
560
568
|
enum LoadError: ubyte
|
|
561
569
|
not_found = 1
|
|
@@ -575,6 +583,12 @@ function load_pair() -> Result[bool, LoadError]:
|
|
|
575
583
|
io.print_line(f"Loaded #{first.name} and #{second.name}")
|
|
576
584
|
return Result[bool, LoadError].success(value = true)
|
|
577
585
|
|
|
586
|
+
## ── Foreign FFI: raw external + foreign projection ──────────────────
|
|
587
|
+
external function atoi(input: cstr) -> int
|
|
588
|
+
|
|
589
|
+
## foreign function projects a raw ABI call into ordinary Milk Tea types
|
|
590
|
+
foreign function parse_int_foreign(input: str as cstr) -> int = atoi
|
|
591
|
+
|
|
578
592
|
## ── Events (fixed-capacity pub/sub, zero heap during dispatch) ──────
|
|
579
593
|
struct DamageEvent:
|
|
580
594
|
target_id: EntityId
|
|
@@ -601,6 +615,19 @@ async function load_scores() -> int:
|
|
|
601
615
|
## ── Compile-time constants for inline if ─────────────────────────────
|
|
602
616
|
const DEBUG: bool = false
|
|
603
617
|
|
|
618
|
+
## ── when: compile-time conditional (only the chosen branch is emitted) ──
|
|
619
|
+
enum Backend: ubyte
|
|
620
|
+
gl = 1
|
|
621
|
+
vulkan = 2
|
|
622
|
+
const TARGET_BACKEND: Backend = Backend.gl
|
|
623
|
+
|
|
624
|
+
function backend_label() -> str:
|
|
625
|
+
when TARGET_BACKEND:
|
|
626
|
+
Backend.gl:
|
|
627
|
+
return "OpenGL"
|
|
628
|
+
Backend.vulkan:
|
|
629
|
+
return "Vulkan"
|
|
630
|
+
|
|
604
631
|
## ── Main entry point ────────────────────────────────────────────────
|
|
605
632
|
function main() -> int:
|
|
606
633
|
## Local declarations: let (immutable), var (mutable)
|
|
@@ -727,12 +754,22 @@ function main() -> int:
|
|
|
727
754
|
Action.idle:
|
|
728
755
|
pass
|
|
729
756
|
|
|
757
|
+
## is: variant arm membership test
|
|
758
|
+
if action is Action.attack:
|
|
759
|
+
io.print_line("action is an attack")
|
|
760
|
+
|
|
730
761
|
## match expression
|
|
731
762
|
let label = match player.level:
|
|
732
763
|
1: "beginner"
|
|
733
764
|
2: "novice"
|
|
734
765
|
_: "advanced"
|
|
735
766
|
|
|
767
|
+
## Struct equality: field-wise ==/!=
|
|
768
|
+
let stats_a = Entity.Stats(attack = 10, defense = 5, speed = 1.0)
|
|
769
|
+
let stats_b = Entity.Stats(attack = 10, defense = 5, speed = 1.0)
|
|
770
|
+
if stats_a == stats_b:
|
|
771
|
+
io.print_line("stats equal")
|
|
772
|
+
|
|
736
773
|
## Explicit cast: T<-value
|
|
737
774
|
let raw_kind = ubyte<-player.entity.kind
|
|
738
775
|
let ratio = float<-score
|
|
@@ -795,6 +832,12 @@ function main() -> int:
|
|
|
795
832
|
inline if DEBUG:
|
|
796
833
|
io.print_line("debug mode")
|
|
797
834
|
|
|
835
|
+
## Foreign FFI, when, and inline-if type dispatch
|
|
836
|
+
let parsed_int = parse_int_foreign("42")
|
|
837
|
+
io.print_line(f"FFI parsed: #{parsed_int}")
|
|
838
|
+
io.print_line(f"Backend: #{backend_label()}")
|
|
839
|
+
io.print_line(f"int label: #{size_label[int]()}")
|
|
840
|
+
|
|
798
841
|
## ── Concurrency ─────────────────────────────────────────────────────
|
|
799
842
|
## parallel for: data-parallel loop dispatched across CPU cores
|
|
800
843
|
var positions = array[float, 4](0.0, 1.0, 2.0, 3.0)
|
|
@@ -829,7 +872,7 @@ function main() -> int:
|
|
|
829
872
|
</div>
|
|
830
873
|
|
|
831
874
|
<div class="callout tip">
|
|
832
|
-
<strong>Tip:</strong> This example covers imports, constants, <code>const function</code>, type aliases, enums, flags, structs (nested, attributed), variants, interfaces, <code>extending</code> methods, generics with constraints, value parameter generics, <code>Result</code> error handling with <code>?</code> and guard binding, events, closures (<code>proc</code>), <code>async</code>/<code>await</code>, concurrency (<code>parallel for</code>, <code>parallel:</code>, <code>detach</code> + <code>gather</code>, <code>atomic[T]</code>), native vector types, arrays, spans, collections, <code>for</code>/<code>while</code>/<code>match</code>/<code>if</code>, variant membership test (<code>is</code>), parallel <code>for</code>, <code>defer</code>, <code>unsafe</code>, <code>ref[T]</code>, <code>own[T]</code>, <code>dyn[Interface]</code>, format strings, heredocs, <code>str_buffer</code>, compile-time reflection, <code>inline for</code>/<code>inline if</code>, <code>static_assert</code>, and explicit casts. See the sections below for full details on each feature.
|
|
875
|
+
<strong>Tip:</strong> This example covers imports, constants, <code>const function</code>, type aliases, enums, flags, structs (nested, attributed), variants, interfaces, foreign functions (<code>external</code> / <code>foreign function</code>), <code>extending</code> methods, generics with constraints, value parameter generics, <code>Result</code> error handling with <code>?</code> and guard binding, events, closures (<code>proc</code>), <code>async</code>/<code>await</code>, concurrency (<code>parallel for</code>, <code>parallel:</code>, <code>detach</code> + <code>gather</code>, <code>atomic[T]</code>), native vector types, arrays, spans, collections, <code>for</code>/<code>while</code>/<code>match</code>/<code>if</code>, variant membership test (<code>is</code>), parallel <code>for</code>, <code>defer</code>, <code>unsafe</code>, <code>ref[T]</code>, <code>own[T]</code>, <code>dyn[Interface]</code>, format strings, heredocs, <code>str_buffer</code>, compile-time reflection, <code>when</code>, <code>inline for</code>/<code>inline if</code> (including type comparison), struct <code>==</code>/<code>!=</code>, <code>static_assert</code>, and explicit casts. See the sections below for full details on each feature.
|
|
833
876
|
</div>
|
|
834
877
|
</section>
|
|
835
878
|
|
|
@@ -1221,7 +1264,7 @@ struct NPC implements Damageable:
|
|
|
1221
1264
|
</div>
|
|
1222
1265
|
|
|
1223
1266
|
<h3>Methods (extending)</h3>
|
|
1224
|
-
<p>
|
|
1267
|
+
<p>Method kinds: <code>function</code> (value receiver), <code>editable function</code> (editable receiver), <code>static function</code> (no receiver), plus compile-time-evaluable <code>const function</code> and <code>static const function</code>. <code>editable const function</code>, <code>async const function</code>, and <code>const</code> on interface methods are rejected.</p>
|
|
1225
1268
|
<div class="code-wrap">
|
|
1226
1269
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1227
1270
|
<pre><code>extending Counter:
|
|
@@ -1394,6 +1437,7 @@ function say_hello(name: str):
|
|
|
1394
1437
|
|
|
1395
1438
|
<h3>const function</h3>
|
|
1396
1439
|
<p>Evaluable at compile time. Generates both a compile-time constant-folding path and a normal runtime function. Recursive calls between <code>const</code> functions are supported. Use <code>const function</code> for reusable compile-time logic; use a block-bodied <code>const X -> T: ...</code> for one-shot computed constants.</p>
|
|
1440
|
+
<p><code>const</code> also applies to methods: <code>const function</code> (value receiver) and <code>static const function</code> (no receiver) fold when called from a compile-time context. <code>editable const function</code>, <code>async const function</code>, and <code>const</code> on interface methods are rejected.</p>
|
|
1397
1441
|
<div class="code-wrap">
|
|
1398
1442
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1399
1443
|
<pre><code>const function square(x: int) -> int:
|
|
@@ -1656,6 +1700,31 @@ unsafe:
|
|
|
1656
1700
|
<section id="compile-time">
|
|
1657
1701
|
<h2>Compile-Time Control Flow</h2>
|
|
1658
1702
|
|
|
1703
|
+
<h3>Block-bodied <code>const</code> and <code>const function</code></h3>
|
|
1704
|
+
<p>Block-bodied <code>const</code> initializers and <code>const function</code> bodies are evaluated at compile time, and the result is folded into the emitted C. Supported surface: literals (character literals fold to their byte value), other <code>const</code> values, arithmetic, <code>str</code> concatenation, <code>if</code>/<code>while</code>/<code>for</code> with <code>break</code> and <code>continue</code>, <code>match</code> expressions, index and range access, struct member access, <code>let</code>/<code>var</code> with destructuring and <code>else:</code> / <code>else as error:</code> guards, assignment (including compound), numeric prefix casts, calls to other const functions and const methods, and the reflection builtins.</p>
|
|
1705
|
+
<div class="code-wrap">
|
|
1706
|
+
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1707
|
+
<pre><code>## str concatenation folds to a literal
|
|
1708
|
+
const GREET: str = "hello" + " " + "world"
|
|
1709
|
+
|
|
1710
|
+
## match expressions fold
|
|
1711
|
+
const LABEL -> str:
|
|
1712
|
+
return match 2:
|
|
1713
|
+
1: "one"
|
|
1714
|
+
2: "two"
|
|
1715
|
+
_: "other"
|
|
1716
|
+
|
|
1717
|
+
## const methods fold too
|
|
1718
|
+
struct Rect:
|
|
1719
|
+
w: int
|
|
1720
|
+
h: int
|
|
1721
|
+
const function area() -> int:
|
|
1722
|
+
return this.w * this.h
|
|
1723
|
+
|
|
1724
|
+
const R: Rect = Rect(w = 10, h = 20)
|
|
1725
|
+
const AREA: int = R.area() ## folded to 200 at compile time</code></pre>
|
|
1726
|
+
</div>
|
|
1727
|
+
|
|
1659
1728
|
<h3>when</h3>
|
|
1660
1729
|
<p>Evaluates discriminant at compile time; only the chosen branch is type-checked and emitted. <code>when</code> may also appear at module level to conditionally include declarations, imports, or type definitions.</p>
|
|
1661
1730
|
<div class="code-wrap">
|
data/docs/language-design.md
CHANGED
|
@@ -79,7 +79,7 @@ The intended reductions are deliberate:
|
|
|
79
79
|
|
|
80
80
|
If a new feature introduces a second ordinary way to express the same concept, the language should delete one of them instead of documenting both.
|
|
81
81
|
|
|
82
|
-
The compile-time evaluation surface is described in [
|
|
82
|
+
The compile-time evaluation surface is described in the [language manual](language-manual.md): block-bodied `const` and `const function` limits in §3.2 and §3.7a, and `when` plus the `inline` statements in §4.7–§4.11.
|
|
83
83
|
|
|
84
84
|
## Overall shape
|
|
85
85
|
|
|
@@ -716,7 +716,7 @@ type FileHandle = ptr[libc.FILE]
|
|
|
716
716
|
|
|
717
717
|
### Generics
|
|
718
718
|
|
|
719
|
-
Generics are useful, but they must stay boring. The compile-time surface is documented in [
|
|
719
|
+
Generics are useful, but they must stay boring. The compile-time surface is documented in the [language manual](language-manual.md); generic bodies participate in that surface by using `when`, `inline for`, `inline while`, `inline match`, `inline if`, `type`-returning functions, `const function`, and block-bodied `const` initializers (`const X -> T: ...`) at the lexical positions where the compile-time rules allow them.
|
|
720
720
|
|
|
721
721
|
Allowed in v1:
|
|
722
722
|
|
data/docs/language-manual.md
CHANGED
|
@@ -208,7 +208,7 @@ const NEXT_POW2 -> int:
|
|
|
208
208
|
return n
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
-
The block body is evaluated at compile time. Allowed inside the block: literals, names of other `const` values, arithmetic, control flow (`if`/`else if`/`else`, `while`, `for`), `let` and `var` declarations, calls to other compile-time functions, and calls to whitelisted builtins (`size_of`, `align_of`, `offset_of`, `fields_of`, `members_of`, `attributes_of`).
|
|
211
|
+
The block body is evaluated at compile time. Allowed inside the block: literals (character literals fold to their byte value), names of other `const` values, arithmetic, `str` concatenation (`+`), control flow (`if`/`else if`/`else`, `while`, `for`, with `break` and `continue`), `match` expressions, index and range access (`arr[i]`, `s[i]`, `arr[start..stop]`, `s[start..stop]`), struct member access, `let` and `var` declarations (including tuple/struct destructuring and `else:` / `else as error:` guard forms), assignment (including compound assignment to block-local struct fields and array elements), numeric prefix casts (`T<-value`), calls to other compile-time functions and const methods, and calls to whitelisted builtins (`size_of`, `align_of`, `offset_of`, `fields_of`, `members_of`, `attributes_of`).
|
|
212
212
|
|
|
213
213
|
Rules:
|
|
214
214
|
|
|
@@ -524,6 +524,8 @@ Kinds:
|
|
|
524
524
|
- `function` (value receiver)
|
|
525
525
|
- `editable function` (editable receiver)
|
|
526
526
|
- `static function` (no receiver)
|
|
527
|
+
- `const function` (value receiver, compile-time-evaluable)
|
|
528
|
+
- `static const function` (no receiver, compile-time-evaluable)
|
|
527
529
|
|
|
528
530
|
Names such as `init` and `default` are ordinary static functions. There is no constructor keyword or hidden initializer syntax.
|
|
529
531
|
|
|
@@ -531,6 +533,9 @@ Method capabilities:
|
|
|
531
533
|
|
|
532
534
|
- async methods are supported
|
|
533
535
|
- generic methods are supported
|
|
536
|
+
- `const function` and `static const function` methods follow the same compile-time body rules as a block-bodied `const` (§3.2). Called from a compile-time context, the call is constant-folded; they also generate normal runtime functions.
|
|
537
|
+
- `editable const function` and `async const function` are rejected.
|
|
538
|
+
- `const` is not allowed on interface methods.
|
|
534
539
|
|
|
535
540
|
### 3.7 Functions
|
|
536
541
|
|
|
@@ -591,10 +596,11 @@ const SQUARE_5: int = square(5) # folded to 25 at compile time
|
|
|
591
596
|
|
|
592
597
|
Rules:
|
|
593
598
|
|
|
594
|
-
- The body
|
|
599
|
+
- The body follows the same compile-time rules as a block-bodied `const` (§3.2).
|
|
595
600
|
- Generates a normal runtime function as well — callable from ordinary runtime code.
|
|
596
601
|
- Called from `const` initializers, `when` discriminants, `inline for` bodies, and other compile-time contexts.
|
|
597
602
|
- Recursive calls between `const` functions are supported.
|
|
603
|
+
- `const` applies to methods as well: `const function` (value receiver) and `static const function` (no receiver) methods fold when called from compile-time contexts (§3.6).
|
|
598
604
|
|
|
599
605
|
### 3.8 External functions
|
|
600
606
|
|
data/lib/milk_tea/base.rb
CHANGED
|
@@ -116,7 +116,7 @@ module MilkTea
|
|
|
116
116
|
func && func.respond_to?(:ast) && func.ast.respond_to?(:const) && func.ast.const
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
def comptime_const_method_body(binding, arguments, scopes:, receiver_value:)
|
|
119
|
+
def comptime_const_method_body(binding, arguments, scopes:, receiver_value:, arg_evaluator: nil)
|
|
120
120
|
method = binding.ast
|
|
121
121
|
return nil unless method.respond_to?(:body) && method.body
|
|
122
122
|
return nil if binding.type_params.any?
|
|
@@ -130,7 +130,11 @@ module MilkTea
|
|
|
130
130
|
end
|
|
131
131
|
|
|
132
132
|
method.params.each_with_index do |param, idx|
|
|
133
|
-
arg_value =
|
|
133
|
+
arg_value = if arg_evaluator
|
|
134
|
+
arg_evaluator.call(arguments[idx].value)
|
|
135
|
+
else
|
|
136
|
+
comptime_eval(arguments[idx].value, scopes)
|
|
137
|
+
end
|
|
134
138
|
return nil unless arg_value
|
|
135
139
|
|
|
136
140
|
initial_vars[param.name] = arg_value
|
|
@@ -713,20 +713,31 @@ module MilkTea
|
|
|
713
713
|
return unless @checker.respond_to?(:comptime_method_binding_for_receiver)
|
|
714
714
|
return unless @checker.respond_to?(:comptime_const_method_body)
|
|
715
715
|
|
|
716
|
+
arg_evaluator = ->(expr) { evaluate_expression(expr, scopes:) }
|
|
717
|
+
|
|
716
718
|
receiver = call_expr.callee.receiver
|
|
717
|
-
|
|
718
|
-
|
|
719
|
+
if receiver.is_a?(AST::Identifier) && @variables.key?(receiver.name)
|
|
720
|
+
receiver_value = @variables[receiver.name]
|
|
721
|
+
return nil if receiver_value.nil? || receiver_value.is_a?(Types::Base)
|
|
722
|
+
|
|
723
|
+
receiver_type = @variable_types[receiver.name]
|
|
724
|
+
return nil unless receiver_type
|
|
725
|
+
|
|
726
|
+
binding = @checker.comptime_method_binding_for_receiver(receiver_type, call_expr.callee.member)
|
|
727
|
+
return nil unless binding
|
|
719
728
|
|
|
720
|
-
|
|
721
|
-
|
|
729
|
+
return @checker.comptime_const_method_body(binding, call_expr.arguments, scopes:, receiver_value:, arg_evaluator:)
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
return nil unless @checker.respond_to?(:resolve_type_expression)
|
|
722
733
|
|
|
723
|
-
receiver_type = @
|
|
734
|
+
receiver_type = @checker.resolve_type_expression(receiver)
|
|
724
735
|
return nil unless receiver_type
|
|
725
736
|
|
|
726
737
|
binding = @checker.comptime_method_binding_for_receiver(receiver_type, call_expr.callee.member)
|
|
727
738
|
return nil unless binding
|
|
728
739
|
|
|
729
|
-
@checker.comptime_const_method_body(binding, call_expr.arguments, scopes:, receiver_value:)
|
|
740
|
+
@checker.comptime_const_method_body(binding, call_expr.arguments, scopes:, receiver_value: nil, arg_evaluator:)
|
|
730
741
|
end
|
|
731
742
|
|
|
732
743
|
def try_const_function_call(call_expr, scopes:)
|
|
@@ -774,9 +785,12 @@ module MilkTea
|
|
|
774
785
|
elsif call_expr.callee.respond_to?(:name)
|
|
775
786
|
call_expr.callee.name
|
|
776
787
|
end
|
|
777
|
-
return unless callee_name
|
|
778
788
|
|
|
779
|
-
type =
|
|
789
|
+
type = if callee_name
|
|
790
|
+
types[callee_name]
|
|
791
|
+
elsif call_expr.callee.is_a?(AST::MemberAccess) && @checker.respond_to?(:resolve_type_expression)
|
|
792
|
+
@checker.resolve_type_expression(call_expr.callee)
|
|
793
|
+
end
|
|
780
794
|
return unless type.is_a?(Types::Struct)
|
|
781
795
|
|
|
782
796
|
fields = {}
|
|
@@ -1875,6 +1875,10 @@ module MilkTea
|
|
|
1875
1875
|
@lowerer.resolve_type_ref(type_ref)
|
|
1876
1876
|
end
|
|
1877
1877
|
|
|
1878
|
+
def resolve_type_expression(expression)
|
|
1879
|
+
@lowerer.resolve_type_expression(expression)
|
|
1880
|
+
end
|
|
1881
|
+
|
|
1878
1882
|
def comptime_struct_field_names(type_name)
|
|
1879
1883
|
@lowerer.comptime_struct_field_names(type_name)
|
|
1880
1884
|
end
|
|
@@ -1883,8 +1887,8 @@ module MilkTea
|
|
|
1883
1887
|
@lowerer.comptime_method_binding_for_receiver(receiver_type, member)
|
|
1884
1888
|
end
|
|
1885
1889
|
|
|
1886
|
-
def comptime_const_method_body(binding, arguments, scopes: nil, receiver_value: nil)
|
|
1887
|
-
@lowerer.comptime_const_method_body(binding, arguments, scopes:, receiver_value:)
|
|
1890
|
+
def comptime_const_method_body(binding, arguments, scopes: nil, receiver_value: nil, arg_evaluator: nil)
|
|
1891
|
+
@lowerer.comptime_const_method_body(binding, arguments, scopes:, receiver_value:, arg_evaluator:)
|
|
1888
1892
|
end
|
|
1889
1893
|
|
|
1890
1894
|
def comptime_expression_type(expression, scopes: nil)
|
|
@@ -190,7 +190,8 @@ module MilkTea
|
|
|
190
190
|
"return type mismatch: expected #{return_type}, got #{value_type}",
|
|
191
191
|
expression: statement.value,
|
|
192
192
|
contextual_int_to_float: contextual_int_to_float_target?(return_type),
|
|
193
|
-
line: statement.line,
|
|
193
|
+
line: (statement.line unless statement.value),
|
|
194
|
+
column: (statement.column unless statement.value),
|
|
194
195
|
)
|
|
195
196
|
when AST::DeferStmt
|
|
196
197
|
with_loop_barrier do
|
|
@@ -424,6 +424,17 @@ module MilkTea
|
|
|
424
424
|
receiver_value = comptime_member_receiver_value(expression.callee.receiver, scopes)
|
|
425
425
|
return comptime_const_method_body(method_binding, expression.arguments, scopes:, receiver_value:)
|
|
426
426
|
end
|
|
427
|
+
|
|
428
|
+
if (struct_type = resolve_type_expression(expression.callee)) && struct_type.is_a?(Types::Struct)
|
|
429
|
+
fields = {}
|
|
430
|
+
expression.arguments.each do |argument|
|
|
431
|
+
val = evaluate_compile_time_const_value(argument.value, scopes:)
|
|
432
|
+
return nil unless val
|
|
433
|
+
|
|
434
|
+
fields[argument.name] = val
|
|
435
|
+
end
|
|
436
|
+
return fields
|
|
437
|
+
end
|
|
427
438
|
when AST::Identifier
|
|
428
439
|
if (struct_type = @ctx.types[expression.callee.name]) && struct_type.is_a?(Types::Struct)
|
|
429
440
|
fields = {}
|
|
@@ -459,8 +459,8 @@ module MilkTea
|
|
|
459
459
|
return nil if prefix.empty?
|
|
460
460
|
return nil unless @current_completion_trigger_kind == TRIGGER_KIND_INCOMPLETE
|
|
461
461
|
|
|
462
|
-
|
|
463
|
-
line_prefix = (
|
|
462
|
+
lines = @workspace.document_lines(uri)
|
|
463
|
+
line_prefix = (lines[lsp_line] || '')[0...lsp_char]
|
|
464
464
|
cached = @completion_session_cache[[uri, lsp_line]]
|
|
465
465
|
return nil unless cached
|
|
466
466
|
return nil unless line_prefix.start_with?(cached[:line_prefix])
|
|
@@ -473,8 +473,8 @@ module MilkTea
|
|
|
473
473
|
def store_completion_session(uri, lsp_line, lsp_char, prefix, response)
|
|
474
474
|
return if prefix.empty?
|
|
475
475
|
|
|
476
|
-
|
|
477
|
-
line_prefix = (
|
|
476
|
+
lines = @workspace.document_lines(uri)
|
|
477
|
+
line_prefix = (lines[lsp_line] || '')[0...lsp_char]
|
|
478
478
|
key = [uri, lsp_line]
|
|
479
479
|
@completion_session_cache[key] = {
|
|
480
480
|
line_prefix: line_prefix,
|
|
@@ -875,8 +875,7 @@ module MilkTea
|
|
|
875
875
|
end
|
|
876
876
|
|
|
877
877
|
def import_completions(uri, lsp_line, lsp_char)
|
|
878
|
-
|
|
879
|
-
lines = content.split("\n", -1)
|
|
878
|
+
lines = @workspace.document_lines(uri)
|
|
880
879
|
line = lines[lsp_line] || ''
|
|
881
880
|
stripped = line.lstrip
|
|
882
881
|
return nil unless stripped.start_with?('import ')
|
|
@@ -936,9 +935,7 @@ module MilkTea
|
|
|
936
935
|
end
|
|
937
936
|
|
|
938
937
|
def attribute_completions(facts, uri, line, char)
|
|
939
|
-
|
|
940
|
-
return nil unless content
|
|
941
|
-
lines = content.split("\n", -1)
|
|
938
|
+
lines = @workspace.document_lines(uri)
|
|
942
939
|
line_text = lines[line] || ''
|
|
943
940
|
return nil if line_text.empty?
|
|
944
941
|
|
|
@@ -980,9 +977,7 @@ module MilkTea
|
|
|
980
977
|
def format_string_completions(facts, uri, line, char)
|
|
981
978
|
return nil unless facts
|
|
982
979
|
|
|
983
|
-
|
|
984
|
-
return nil unless content
|
|
985
|
-
lines = content.split("\n", -1)
|
|
980
|
+
lines = @workspace.document_lines(uri)
|
|
986
981
|
line_text = lines[line] || ''
|
|
987
982
|
return nil if line_text.empty?
|
|
988
983
|
|
|
@@ -1045,9 +1040,7 @@ module MilkTea
|
|
|
1045
1040
|
def named_argument_completions(facts, uri, line, char)
|
|
1046
1041
|
return nil unless facts
|
|
1047
1042
|
|
|
1048
|
-
|
|
1049
|
-
return nil unless content
|
|
1050
|
-
lines = content.split("\n", -1)
|
|
1043
|
+
lines = @workspace.document_lines(uri)
|
|
1051
1044
|
line_text = lines[line] || ''
|
|
1052
1045
|
return nil if line_text.empty?
|
|
1053
1046
|
|
|
@@ -1109,9 +1102,7 @@ module MilkTea
|
|
|
1109
1102
|
def specialization_completions(facts, _uri, line, char)
|
|
1110
1103
|
return nil unless facts
|
|
1111
1104
|
|
|
1112
|
-
|
|
1113
|
-
return nil unless content
|
|
1114
|
-
lines = content.split("\n", -1)
|
|
1105
|
+
lines = @workspace.document_lines(_uri)
|
|
1115
1106
|
line_text = lines[line] || ''
|
|
1116
1107
|
return nil if line_text.empty?
|
|
1117
1108
|
|
|
@@ -295,9 +295,8 @@ module MilkTea
|
|
|
295
295
|
end
|
|
296
296
|
|
|
297
297
|
def current_word_prefix(uri, lsp_line, lsp_char)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
line = lines[lsp_line] || ''
|
|
298
|
+
lines = @workspace.document_lines(uri)
|
|
299
|
+
line = lines[lsp_line] || ''
|
|
301
300
|
# Walk backwards from cursor to find start of current word
|
|
302
301
|
char_idx = [lsp_char - 1, line.length - 1].min
|
|
303
302
|
return '' if char_idx < 0
|
|
@@ -284,6 +284,8 @@ module MilkTea
|
|
|
284
284
|
def invalidate_cache(uri, clear_last_good: false)
|
|
285
285
|
@tokens_cache.delete(uri)
|
|
286
286
|
@ast_cache.delete(uri)
|
|
287
|
+
@definition_token_index.delete(uri)
|
|
288
|
+
@line_cache.delete(uri)
|
|
287
289
|
@symbols_cache.delete(uri)
|
|
288
290
|
@doc_comments_cache.delete(uri)
|
|
289
291
|
@facts_cache_mutex.synchronize do
|
|
@@ -48,7 +48,6 @@ module MilkTea
|
|
|
48
48
|
private
|
|
49
49
|
|
|
50
50
|
def candidate_definition_uris(name, exclude_uri: nil)
|
|
51
|
-
matcher = definition_line_matcher(name)
|
|
52
51
|
open_uris, indexed_uris = @document_state_mutex.synchronize do
|
|
53
52
|
[@open_documents.keys, @indexed_documents.keys]
|
|
54
53
|
end
|
|
@@ -57,33 +56,33 @@ module MilkTea
|
|
|
57
56
|
warmed_candidates = nil
|
|
58
57
|
warmed_uris = nil
|
|
59
58
|
@definition_cache_mutex.synchronize do
|
|
60
|
-
warmed_candidates = @definition_candidate_uris[name].
|
|
59
|
+
warmed_candidates = @definition_candidate_uris[name].to_a
|
|
61
60
|
warmed_uris = @definition_names_by_uri.keys.to_set
|
|
62
61
|
end
|
|
63
62
|
|
|
64
|
-
matches = warmed_candidates.
|
|
65
|
-
next if doc_uri == exclude_uri
|
|
66
|
-
|
|
67
|
-
doc_uri
|
|
68
|
-
end
|
|
63
|
+
matches = warmed_candidates.reject { |doc_uri| doc_uri == exclude_uri }
|
|
69
64
|
|
|
65
|
+
# One-time lazy index: extract each document's definition names and
|
|
66
|
+
# cache them so repeat lookups are Set-membership checks instead of
|
|
67
|
+
# re-scanning every indexed document's full content with a regex on
|
|
68
|
+
# each lookup (which scaled with total workspace bytes).
|
|
70
69
|
ordered_uris.each do |doc_uri|
|
|
71
70
|
next if doc_uri == exclude_uri
|
|
72
71
|
next if warmed_uris.include?(doc_uri)
|
|
73
72
|
|
|
74
73
|
content = get_content(doc_uri)
|
|
75
74
|
next if content.empty?
|
|
76
|
-
next unless content.match?(matcher)
|
|
77
75
|
|
|
78
76
|
warm_definition_candidates_for_uri(doc_uri, content)
|
|
79
|
-
matches << doc_uri
|
|
80
77
|
end
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
@definition_cache_mutex.synchronize do
|
|
80
|
+
@definition_candidate_uris[name].each do |doc_uri|
|
|
81
|
+
matches << doc_uri unless doc_uri == exclude_uri
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
/#{DEFINITION_LINE_PREFIX}#{Regexp.escape(name)}\b/
|
|
85
|
+
matches.uniq
|
|
87
86
|
end
|
|
88
87
|
|
|
89
88
|
def cache_definition_entry(name, entry)
|
|
@@ -126,13 +126,18 @@ module MilkTea
|
|
|
126
126
|
total = paths.length
|
|
127
127
|
paths.each_with_index do |path, idx|
|
|
128
128
|
file_uri = path_to_uri(path)
|
|
129
|
+
content = nil
|
|
129
130
|
@document_state_mutex.synchronize do
|
|
130
|
-
@indexed_documents[file_uri] ||= begin
|
|
131
|
+
content = @indexed_documents[file_uri] ||= begin
|
|
131
132
|
File.read(path)
|
|
132
133
|
rescue StandardError
|
|
133
134
|
nil
|
|
134
135
|
end
|
|
135
136
|
end
|
|
137
|
+
# Warm the definition-name index for this file so later global
|
|
138
|
+
# definition lookups hit Set-membership checks instead of scanning
|
|
139
|
+
# every indexed document's content.
|
|
140
|
+
warm_definition_candidates_for_uri(file_uri, content) if content
|
|
136
141
|
if progress && total > 0
|
|
137
142
|
pct = ((idx + 1) * 100 / total).clamp(0, 100)
|
|
138
143
|
progress.call(pct, "#{idx + 1}/#{total} files")
|
|
@@ -79,10 +79,9 @@ module MilkTea
|
|
|
79
79
|
# Scan text up to the cursor to find the innermost open function call context.
|
|
80
80
|
# Returns { name:, active_parameter: } or nil if not inside a call.
|
|
81
81
|
def find_call_context(uri, lsp_line, lsp_char)
|
|
82
|
-
|
|
83
|
-
return nil if
|
|
82
|
+
lines = document_lines(uri)
|
|
83
|
+
return nil if lines.empty?
|
|
84
84
|
|
|
85
|
-
lines = content.split("\n", -1)
|
|
86
85
|
cursor_line = lines[lsp_line] || ''
|
|
87
86
|
prefix = lsp_line.positive? ? lines[0...lsp_line].join("\n") + "\n" : ''
|
|
88
87
|
text = prefix + cursor_line[0...lsp_char]
|
|
@@ -150,14 +149,19 @@ module MilkTea
|
|
|
150
149
|
end
|
|
151
150
|
|
|
152
151
|
def token_contains_position?(token, target_line, target_char)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
152
|
+
return false if target_line < token.line
|
|
153
|
+
|
|
154
|
+
lexeme = token.lexeme
|
|
155
|
+
unless lexeme.include?("\n")
|
|
156
|
+
return false if target_line > token.line
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
return token.column <= target_char && target_char < (token.column + segments.first.length)
|
|
158
|
+
return token.column <= target_char && target_char < (token.column + lexeme.length)
|
|
159
159
|
end
|
|
160
160
|
|
|
161
|
+
segments = lexeme.split("\n", -1)
|
|
162
|
+
end_line = token.line + segments.length - 1
|
|
163
|
+
return false if target_line > end_line
|
|
164
|
+
|
|
161
165
|
if target_line == token.line
|
|
162
166
|
return token.column <= target_char && target_char <= (token.column + segments.first.length - 1)
|
|
163
167
|
end
|
|
@@ -178,8 +182,7 @@ module MilkTea
|
|
|
178
182
|
end
|
|
179
183
|
|
|
180
184
|
def find_dot_receiver_path(uri, lsp_line, lsp_char)
|
|
181
|
-
|
|
182
|
-
lines = content.split("\n", -1)
|
|
185
|
+
lines = document_lines(uri)
|
|
183
186
|
line_str = lines[lsp_line] || ''
|
|
184
187
|
|
|
185
188
|
idx = [lsp_char - 1, line_str.length - 1].min
|
|
@@ -222,39 +225,56 @@ module MilkTea
|
|
|
222
225
|
# (def, struct, union, enum, flags, variant, type, const, var) for the given name.
|
|
223
226
|
# Returns the identifier Token, or nil if not found.
|
|
224
227
|
def find_definition_token(uri, name, before_line: nil, before_char: nil)
|
|
225
|
-
|
|
226
|
-
return nil if
|
|
227
|
-
|
|
228
|
-
nearest = nil
|
|
229
|
-
tokens.each_cons(2) do |kw_tok, id_tok|
|
|
230
|
-
next unless DEFINITION_KEYWORDS.include?(kw_tok.type)
|
|
231
|
-
next unless id_tok.type == :identifier && id_tok.lexeme == name
|
|
228
|
+
candidates = definition_token_index(uri)[name]
|
|
229
|
+
return nil if candidates.nil? || candidates.empty?
|
|
232
230
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
if nearest.nil? || id_tok.line > nearest.line || (id_tok.line == nearest.line && id_tok.column > nearest.column)
|
|
239
|
-
nearest = id_tok
|
|
231
|
+
if before_line
|
|
232
|
+
matches = candidates.select do |tok|
|
|
233
|
+
tok.line < before_line || (tok.line == before_line && (!before_char || tok.column < before_char))
|
|
240
234
|
end
|
|
235
|
+
return matches.max_by { |tok| [tok.line, tok.column] } unless matches.empty?
|
|
241
236
|
end
|
|
242
237
|
|
|
243
|
-
|
|
238
|
+
candidates.first
|
|
239
|
+
end
|
|
244
240
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
241
|
+
# Return the cached line array for +uri+, so hot request paths (e.g.
|
|
242
|
+
# completion) do not re-split the whole document once per helper.
|
|
243
|
+
def document_lines(uri)
|
|
244
|
+
@document_state_mutex.synchronize do
|
|
245
|
+
return @line_cache[uri] if @line_cache.key?(uri)
|
|
246
|
+
end
|
|
248
247
|
|
|
249
|
-
|
|
248
|
+
lines = get_content(uri).split("\n", -1)
|
|
249
|
+
@document_state_mutex.synchronize do
|
|
250
|
+
@line_cache[uri] = lines
|
|
250
251
|
end
|
|
251
|
-
|
|
252
|
+
lines
|
|
252
253
|
end
|
|
253
254
|
|
|
254
255
|
private
|
|
255
256
|
|
|
256
257
|
# ── Symbol extraction (token-based, no AST position requirement) ────────
|
|
257
258
|
|
|
259
|
+
# Lazily build a per-uri map of definition identifier tokens keyed by
|
|
260
|
+
# name, so repeated definition lookups are O(definitions-with-name)
|
|
261
|
+
# instead of re-scanning the document's full token stream each time.
|
|
262
|
+
def definition_token_index(uri)
|
|
263
|
+
@definition_token_index[uri] ||= begin
|
|
264
|
+
tokens = get_tokens(uri)
|
|
265
|
+
index = Hash.new { |hash, key| hash[key] = [] }
|
|
266
|
+
if tokens
|
|
267
|
+
tokens.each_cons(2) do |kw_tok, id_tok|
|
|
268
|
+
next unless DEFINITION_KEYWORDS.include?(kw_tok.type)
|
|
269
|
+
next unless id_tok.type == :identifier
|
|
270
|
+
|
|
271
|
+
index[id_tok.lexeme] << id_tok
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
index
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
|
|
258
278
|
def extract_symbols_from_tokens(uri)
|
|
259
279
|
tokens = get_tokens(uri)
|
|
260
280
|
return [] if tokens.nil?
|
|
@@ -37,7 +37,7 @@ module MilkTea
|
|
|
37
37
|
# adjacent tokens, and the inner keyword (e.g. :function) is always followed
|
|
38
38
|
# by the identifier. If the lexer ever merges a compound keyword into a
|
|
39
39
|
# single token type (e.g. :const_function), it must be added here.
|
|
40
|
-
DEFINITION_KEYWORDS = %i[function struct union enum flags variant type const var let extending opaque interface event].freeze
|
|
40
|
+
DEFINITION_KEYWORDS = %i[function struct union enum flags variant type const var let extending opaque interface event].to_set.freeze
|
|
41
41
|
DOC_COMMENT_PREFIX = '##'
|
|
42
42
|
DOC_TAG_PATTERN = /\A\s*@([A-Za-z_][A-Za-z0-9_-]*)(?:\s+(.*))?\z/
|
|
43
43
|
DEFINITION_LINE_PREFIX = /^(?:\s)*(?:(?:public|foreign|external)\s+)*(?:function|struct|union|enum|flags|variant|type|const|var|let|extending|opaque|interface|event)\s+/m
|
|
@@ -55,6 +55,8 @@ module MilkTea
|
|
|
55
55
|
@tokens_cache = {} # uri -> [Token]
|
|
56
56
|
@last_good_tokens_cache = {} # uri -> last known-good [Token]
|
|
57
57
|
@ast_cache = {} # uri -> AST::SourceFile (nil on parse failure)
|
|
58
|
+
@definition_token_index = {} # uri -> { name => [Token] } definition tokens by name
|
|
59
|
+
@line_cache = {} # uri -> [String] split lines, refreshed on invalidation
|
|
58
60
|
@facts_cache = {} # uri -> SemanticAnalyzer::Facts (projection of cached tooling snapshot facts)
|
|
59
61
|
@tooling_snapshot_cache = {} # uri -> SemanticAnalyzer::ToolingSnapshot (facts may be nil on structural failure)
|
|
60
62
|
@symbols_cache = {} # uri -> [{name, kind, line, column}]
|
|
@@ -103,6 +105,8 @@ module MilkTea
|
|
|
103
105
|
@tokens_cache.clear
|
|
104
106
|
@last_good_tokens_cache.clear
|
|
105
107
|
@ast_cache.clear
|
|
108
|
+
@definition_token_index.clear
|
|
109
|
+
@line_cache.clear
|
|
106
110
|
@facts_cache.clear
|
|
107
111
|
@tooling_snapshot_cache.clear
|
|
108
112
|
@symbols_cache.clear
|
|
@@ -72,11 +72,12 @@ module MilkTea
|
|
|
72
72
|
argv: @argv.dup,
|
|
73
73
|
**options.except(:timings)
|
|
74
74
|
)
|
|
75
|
-
|
|
75
|
+
live = $stdout.tty?
|
|
76
|
+
unless (@out.equal?($stdout) && live) || preview_notice_emitted
|
|
76
77
|
@out.write(result.stdout)
|
|
77
78
|
end
|
|
78
79
|
@out.flush if @out.respond_to?(:flush)
|
|
79
|
-
@err.write(result.stderr) unless @err.equal?($stderr)
|
|
80
|
+
@err.write(result.stderr) unless @err.equal?($stderr) && live
|
|
80
81
|
info("[cached]") if result.cached
|
|
81
82
|
result.exit_status
|
|
82
83
|
end
|
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.4.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Long (Teefan) Tran
|
|
@@ -629,7 +629,7 @@ metadata:
|
|
|
629
629
|
homepage_uri: https://teefan.github.io/mt-lang/
|
|
630
630
|
source_code_uri: https://github.com/teefan/mt-lang
|
|
631
631
|
post_install_message: |
|
|
632
|
-
Milk Tea 0.4.
|
|
632
|
+
Milk Tea 0.4.2 installed!
|
|
633
633
|
|
|
634
634
|
System requirements:
|
|
635
635
|
- A C compiler (gcc or clang) must be available on PATH
|