mt-lang 0.3.14 → 0.3.17
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 +2 -2
- data/docs/index.html +8 -8
- data/docs/language-design.md +7 -12
- data/docs/language-manual.md +20 -8
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/core/ast.rb +2 -2
- data/lib/milk_tea/core/{binding_types.rb → bindings.rb} +1 -1
- data/lib/milk_tea/core/c_backend.rb +14 -15
- data/lib/milk_tea/core/compile_time.rb +181 -2
- data/lib/milk_tea/core/control_flow/builder.rb +62 -2
- data/lib/milk_tea/core/control_flow/constant_propagation.rb +1 -1
- data/lib/milk_tea/core/{compatibility_helpers.rb → intrinsics.rb} +2 -2
- data/lib/milk_tea/core/lexer.rb +10 -14
- data/lib/milk_tea/core/lowering/async/analysis.rb +3 -9
- data/lib/milk_tea/core/lowering/async/lowering.rb +2 -10
- data/lib/milk_tea/core/lowering/async/normalization.rb +2 -7
- data/lib/milk_tea/core/lowering/block.rb +1 -5
- data/lib/milk_tea/core/lowering/proc.rb +1 -5
- data/lib/milk_tea/core/lowering/scans.rb +3 -1
- data/lib/milk_tea/core/lowering/utils.rb +1 -27
- data/lib/milk_tea/core/lowering.rb +18 -18
- data/lib/milk_tea/core/module_loader.rb +26 -15
- data/lib/milk_tea/core/parser/statements.rb +18 -12
- data/lib/milk_tea/core/parser.rb +17 -17
- data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +3 -11
- data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +2 -3
- data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +1 -1
- data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +1 -2
- data/lib/milk_tea/core/semantic_analyzer/nullability.rb +2 -4
- data/lib/milk_tea/core/semantic_analyzer/statements.rb +2 -8
- data/lib/milk_tea/core/semantic_analyzer.rb +1 -1
- data/lib/milk_tea/core.rb +6 -2
- data/lib/milk_tea/dap/lldb_dap_backend.rb +153 -0
- data/lib/milk_tea/dap/server/lldb_backend.rb +1 -1
- data/lib/milk_tea/dap.rb +1 -1
- data/lib/milk_tea/lsp.rb +1 -7
- data/lib/milk_tea/{bindings/cli.rb → tooling/bindgen_cli.rb} +1 -1
- data/lib/milk_tea/tooling/linter/imports_platform.rb +2 -4
- data/lib/milk_tea/tooling/linter/release_rules.rb +1 -6
- data/lib/milk_tea/tooling/linter/source_helpers.rb +0 -6
- data/lib/milk_tea/tooling/linter/visitors.rb +4 -8
- data/lib/milk_tea/tooling/sexpr_dumper.rb +2 -0
- data/lib/milk_tea/tooling.rb +1 -1
- data/std/asset_pack.mt +1 -1
- data/std/async/libuv_runtime.mt +6 -6
- data/std/async/mailbox.mt +4 -4
- data/std/cookie.mt +2 -2
- data/std/crypto.mt +1 -1
- data/std/curl/runtime.mt +10 -10
- data/std/fs.linux.mt +26 -26
- data/std/fs.windows.mt +22 -22
- data/std/goap.mt +2 -2
- data/std/http/server.mt +21 -21
- data/std/http.mt +14 -14
- data/std/jobs.mt +4 -4
- data/std/json.mt +5 -5
- data/std/log.mt +1 -1
- data/std/mem/tracking.mt +1 -1
- data/std/net/channel.mt +4 -4
- data/std/net/clock.mt +6 -6
- data/std/net/discovery.mt +7 -7
- data/std/net/lobby.mt +10 -10
- data/std/net/manager.mt +1 -1
- data/std/net/mux.mt +4 -4
- data/std/net/nat.mt +4 -4
- data/std/net/packet.mt +3 -3
- data/std/net/punch.mt +3 -3
- data/std/net/rpc.mt +3 -3
- data/std/net/session.mt +15 -15
- data/std/net/stun.mt +4 -4
- data/std/net/turn.mt +4 -4
- data/std/oauth2.mt +12 -12
- data/std/path.mt +8 -8
- data/std/process.mt +4 -4
- data/std/raylib/packed_assets.mt +5 -5
- data/std/serialize.mt +1 -1
- data/std/tar.mt +10 -10
- data/std/terminal.mt +18 -18
- data/std/tls.mt +8 -8
- data/std/uri.mt +1 -1
- data/std/utility.mt +1 -1
- metadata +7 -9
- data/lib/milk_tea/core/compile_time/const_eval.rb +0 -182
- data/lib/milk_tea/core/module_loader/errors.rb +0 -23
- data/lib/milk_tea/dap/backends/lldb_dap.rb +0 -156
- /data/lib/milk_tea/core/lowering/{async.rb → async/frame_builder.rb} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2f84289b1bcb599ca81cbada0b1e1a925d9962ffe186471b84026f372fb358b
|
|
4
|
+
data.tar.gz: 566b98b015518f583e31fa2f0a75753c3b0abfd5a4047210945b3209c3caf1f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee114072cb26ed2527e0b3703cb7f8a913e37234e6937b3a307ce72363e8a6e10a34e0a149d508a8c02e64c1a0a0f5e9b1ae482b76f068bfb6bf0b8784f34a44
|
|
7
|
+
data.tar.gz: 9b29c88530cf6110381123ef8135434caa47aa85abf955a8bd9f25d93fa1b8059691d52804c17e4080a2a3d0ad916aa689470ec8d7719a7186fa62eddb026525
|
data/README.md
CHANGED
|
@@ -477,7 +477,7 @@ Rules:
|
|
|
477
477
|
- Conditions must be `bool`.
|
|
478
478
|
- There is no truthy or falsy coercion from integers or pointers.
|
|
479
479
|
- `pass` is an explicit no-op statement for intentionally empty block bodies.
|
|
480
|
-
-
|
|
480
|
+
- Every block-taking statement accepts a `:`-newline-indented block, or a single statement on the same line after `:`. This applies to `if`/`else` (e.g. `if cond: stmt else: stmt`), `while`, `for`, `defer` (e.g. `defer: cleanup()`), and `unsafe`. `match` arms are the exception: their inline form is a single value expression.
|
|
481
481
|
|
|
482
482
|
`match` supports the following scrutinee types:
|
|
483
483
|
|
|
@@ -615,7 +615,7 @@ Rules:
|
|
|
615
615
|
|
|
616
616
|
`defer`:
|
|
617
617
|
|
|
618
|
-
- `defer expr`
|
|
618
|
+
- `defer: expr` — single-statement inline form
|
|
619
619
|
- `defer:` block form
|
|
620
620
|
- `return` is not allowed inside defer blocks.
|
|
621
621
|
|
data/docs/index.html
CHANGED
|
@@ -658,12 +658,12 @@ function main() -> int:
|
|
|
658
658
|
|
|
659
659
|
## Collections: Vec and Map (import std.hash for primitive keys)
|
|
660
660
|
var entities = vec.Vec[Entity].create()
|
|
661
|
-
defer entities.release()
|
|
661
|
+
defer: entities.release()
|
|
662
662
|
entities.push(Entity.create(1, EntityKind.player, "Hero"))
|
|
663
663
|
entities.push(Entity.create(2, EntityKind.enemy, "Goblin"))
|
|
664
664
|
|
|
665
665
|
var name_counts = map.Map[str, int].create()
|
|
666
|
-
defer name_counts.release()
|
|
666
|
+
defer: name_counts.release()
|
|
667
667
|
name_counts.set("player", 1)
|
|
668
668
|
name_counts.set("enemy", 5)
|
|
669
669
|
|
|
@@ -742,7 +742,7 @@ function main() -> int:
|
|
|
742
742
|
io.print_line(f"Position: #{position.x:.2}, #{position.y:.2}")
|
|
743
743
|
io.print_line(f"ID hex: #{player.entity.id:x}")
|
|
744
744
|
var msg = fmt.format(f"Score: #{score}")
|
|
745
|
-
defer msg.release()
|
|
745
|
+
defer: msg.release()
|
|
746
746
|
|
|
747
747
|
## Heredoc string
|
|
748
748
|
let help = <<-HELP
|
|
@@ -753,7 +753,7 @@ function main() -> int:
|
|
|
753
753
|
|
|
754
754
|
## Defer: cleanup at scope exit (lowers to C cleanup labels)
|
|
755
755
|
let data: own[int] = heap.must_alloc[int](1)
|
|
756
|
-
defer heap.release[int](data)
|
|
756
|
+
defer: heap.release[int](data)
|
|
757
757
|
|
|
758
758
|
## Proc / closure
|
|
759
759
|
let scale = make_scaler(2.5)
|
|
@@ -1598,11 +1598,11 @@ match record:
|
|
|
1598
1598
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
|
1599
1599
|
<pre><code>function main() -> int:
|
|
1600
1600
|
rl.init_window(800, 600, "Game")
|
|
1601
|
-
defer rl.close_window()
|
|
1601
|
+
defer: rl.close_window()
|
|
1602
1602
|
|
|
1603
1603
|
while not rl.window_should_close():
|
|
1604
1604
|
rl.begin_drawing()
|
|
1605
|
-
defer rl.end_drawing()
|
|
1605
|
+
defer: rl.end_drawing()
|
|
1606
1606
|
render_frame()
|
|
1607
1607
|
|
|
1608
1608
|
return 0</code></pre>
|
|
@@ -2149,8 +2149,8 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
|
|
|
2149
2149
|
let closed_sub = window.closed.subscribe(on_close)?
|
|
2150
2150
|
let resized_sub = window.resized.subscribe(on_resize)?
|
|
2151
2151
|
|
|
2152
|
-
defer window.closed.unsubscribe(closed_sub)
|
|
2153
|
-
defer window.resized.unsubscribe(resized_sub)
|
|
2152
|
+
defer: window.closed.unsubscribe(closed_sub)
|
|
2153
|
+
defer: window.resized.unsubscribe(resized_sub)
|
|
2154
2154
|
|
|
2155
2155
|
return Result[void, EventError].success()</code></pre>
|
|
2156
2156
|
</div>
|
data/docs/language-design.md
CHANGED
|
@@ -119,7 +119,7 @@ extending Player:
|
|
|
119
119
|
|
|
120
120
|
function main() -> int:
|
|
121
121
|
rl.init_window(screen_width, screen_height, "Milk Tea")
|
|
122
|
-
defer rl.close_window()
|
|
122
|
+
defer: rl.close_window()
|
|
123
123
|
|
|
124
124
|
var player = Player(
|
|
125
125
|
position = rl.Vector2(x = 400.0, y = 300.0),
|
|
@@ -132,7 +132,7 @@ function main() -> int:
|
|
|
132
132
|
player.update(dt)
|
|
133
133
|
|
|
134
134
|
rl.begin_drawing()
|
|
135
|
-
defer rl.end_drawing()
|
|
135
|
+
defer: rl.end_drawing()
|
|
136
136
|
|
|
137
137
|
rl.clear_background(rl.BLACK)
|
|
138
138
|
rl.draw_circle_v(player.position, player.radius, rl.GOLD)
|
|
@@ -423,16 +423,12 @@ Milk Tea should include a small number of control-flow features that materially
|
|
|
423
423
|
|
|
424
424
|
#### `defer`
|
|
425
425
|
|
|
426
|
-
`defer` registers cleanup code at scope exit and lowers to obvious cleanup labels in C.
|
|
426
|
+
`defer` registers cleanup code at scope exit and lowers to obvious cleanup labels in C. Both forms use the block-header `:`:
|
|
427
427
|
|
|
428
428
|
```mt
|
|
429
|
-
|
|
430
|
-
|
|
431
429
|
function load_texture(path: str) -> Result[Texture, LoadError]:
|
|
432
430
|
let texture = rl.load_texture(path)
|
|
433
|
-
|
|
434
|
-
if texture.id == 0:
|
|
435
|
-
return Result[Texture, LoadError].failure(error= LoadError.file_not_found)
|
|
431
|
+
defer: rl.unload_texture(texture)
|
|
436
432
|
|
|
437
433
|
return Result[Texture, LoadError].success(value= texture)
|
|
438
434
|
```
|
|
@@ -525,7 +521,6 @@ The type system must stay simple, explicit, and close to C.
|
|
|
525
521
|
### Primitive types
|
|
526
522
|
|
|
527
523
|
- `bool`
|
|
528
|
-
- `byte`
|
|
529
524
|
- `char`
|
|
530
525
|
- `byte`, `short`, `int`, `long`
|
|
531
526
|
- `ubyte`, `ushort`, `uint`, `ulong`
|
|
@@ -591,7 +586,7 @@ Notes:
|
|
|
591
586
|
- If low-level code needs to validate raw `array[char, N]` storage as text, that conversion belongs in an explicit helper or imported boundary, not as a built-in method family on raw arrays.
|
|
592
587
|
- Addressable `str_buffer[N]` values also coerce to `span[char]`, so writable foreign text APIs can still accept builders directly when they do not want a second application-facing text abstraction.
|
|
593
588
|
- `str_buffer[N]` is not an ABI type. Raw bindings still spell writable text as `ptr[char]` or `span[char]`; `str_buffer[N]` is the caller-side text object.
|
|
594
|
-
- `str_buffer[N]` has a built-in text surface: `.clear()`, `.assign(str)`, `.append(str)`, `.len()`, `.capacity()`, `.as_str()`, and `.as_cstr()`.
|
|
589
|
+
- `str_buffer[N]` has a built-in text surface: `.clear()`, `.assign(str)`, `.append(str)`, `.assign_format(str)`, `.append_format(str)`, `.len()`, `.capacity()`, `.as_str()`, and `.as_cstr()`.
|
|
595
590
|
- `.assign(...)` replaces the current contents and traps at runtime if the new text exceeds capacity.
|
|
596
591
|
- `.append(...)` extends the current contents and traps at runtime if the appended text would exceed capacity.
|
|
597
592
|
- `.len()` returns the tracked text length, revalidating UTF-8 and rescanning for the trailing NUL if the builder was passed through a writable `span[char]` or `ptr[char]` alias.
|
|
@@ -654,7 +649,7 @@ joins the path with underscores (e.g., `module_Rectangle_Edge`).
|
|
|
654
649
|
|
|
655
650
|
#### Enums
|
|
656
651
|
|
|
657
|
-
Enums
|
|
652
|
+
Enums use an integer backing type, defaulting to `int` when omitted; members auto-increment from 0 or the previous explicit value.
|
|
658
653
|
|
|
659
654
|
```mt
|
|
660
655
|
enum WeaponKind: ubyte
|
|
@@ -665,7 +660,7 @@ enum WeaponKind: ubyte
|
|
|
665
660
|
|
|
666
661
|
#### Flags
|
|
667
662
|
|
|
668
|
-
Flags are named bitmasks with
|
|
663
|
+
Flags are named bitmasks with an integer backing type (defaulting to `int` when omitted).
|
|
669
664
|
|
|
670
665
|
```mt
|
|
671
666
|
flags WindowFlags: uint
|
data/docs/language-manual.md
CHANGED
|
@@ -596,6 +596,8 @@ if x > 10: return "big" else if x > 5: return "med" else: return "small"
|
|
|
596
596
|
|
|
597
597
|
The inline form supports all statement kinds in the body (`return`, `let`, `var`, `defer`, assignment, expression statement, etc.). Blocks and inline form may be mixed: any branch may use either form independently.
|
|
598
598
|
|
|
599
|
+
Every block-taking statement follows the same rule: a `:` followed by a newline starts an indented block, while a `:` followed by anything else on the same line introduces exactly one inline statement. This applies uniformly to `if`/`else`, `while`, `for`, `defer`, and `unsafe` (§4.3, §4.4, §4.5). `match` arms are the exception: their inline form is a single value expression (§4.2).
|
|
600
|
+
|
|
599
601
|
### 4.2 Match
|
|
600
602
|
|
|
601
603
|
Scrutinee types supported:
|
|
@@ -717,6 +719,13 @@ Single-form `for` supports:
|
|
|
717
719
|
- `span[T]`
|
|
718
720
|
- custom structural iterables with a non-editable zero-argument `iter()` method
|
|
719
721
|
|
|
722
|
+
Every loop body accepts either a `:`-newline-indented block or a single statement on the same line as the header:
|
|
723
|
+
|
|
724
|
+
```mt
|
|
725
|
+
while ready: poll()
|
|
726
|
+
for value in values: accumulate(value)
|
|
727
|
+
```
|
|
728
|
+
|
|
720
729
|
Iterator protocol for custom structural iterables:
|
|
721
730
|
|
|
722
731
|
- the iterable value must expose `iter()` with no parameters
|
|
@@ -795,10 +804,10 @@ Rules:
|
|
|
795
804
|
|
|
796
805
|
### 4.4 Defer
|
|
797
806
|
|
|
798
|
-
|
|
807
|
+
Both forms use the block-header `:` introduced in §4:
|
|
799
808
|
|
|
800
809
|
```mt
|
|
801
|
-
defer cleanup()
|
|
810
|
+
defer: cleanup()
|
|
802
811
|
|
|
803
812
|
# or
|
|
804
813
|
|
|
@@ -807,7 +816,10 @@ defer:
|
|
|
807
816
|
release_b()
|
|
808
817
|
```
|
|
809
818
|
|
|
810
|
-
|
|
819
|
+
Rules:
|
|
820
|
+
|
|
821
|
+
- `defer` always requires `:`. A single statement may follow on the same line (`defer: release_a()`), or a `:`-newline-indented block may hold several statements.
|
|
822
|
+
- `return` is not allowed inside defer blocks.
|
|
811
823
|
|
|
812
824
|
### 4.5 Unsafe
|
|
813
825
|
|
|
@@ -995,7 +1007,7 @@ Rules:
|
|
|
995
1007
|
- `ivec2` `ivec3` `ivec4` — integer vectors with `.x` `.y` `.z` `.w` fields
|
|
996
1008
|
- `mat3` `mat4` — column-major matrices; `mat3` has columns `.col0`–`.col2` (each `vec3`), `mat4` has `.col0`–`.col3` (each `vec4`)
|
|
997
1009
|
- `quat` — quaternion with `.x` `.y` `.z` `.w` fields; memory-layout compatible with `vec4`
|
|
998
|
-
- `simd[T, N]` — SIMD vector with `N` lanes of numeric primitive type `T`; total width must be 128 or 256 bits. Supports component-wise arithmetic (`+` `-` `*`
|
|
1010
|
+
- `simd[T, N]` — SIMD vector with `N` lanes of numeric primitive type `T`; total width must be 128 or 256 bits. Supports component-wise arithmetic (`+` `-` `*` `/`, and `%` for integer lanes), bitwise (`&` `|` `^` `~`), shift (`<<` `>>`), and compound assignment. Lane access via compile-time constant index `[i]`. Lowers to GCC/Clang `__attribute__((__vector_size__))`.
|
|
999
1011
|
|
|
1000
1012
|
Primitive type names are reserved. They cannot be reused for value bindings, parameters, locals, import aliases, or type parameters.
|
|
1001
1013
|
|
|
@@ -1031,7 +1043,7 @@ let q = quat(x = 0.0, y = 0.0, z = 0.0, w = 1.0)
|
|
|
1031
1043
|
- `Option[T]`
|
|
1032
1044
|
- `Result[T, E]`
|
|
1033
1045
|
- `SoA[T, N]` — Structure-of-Arrays: transforms `T`'s fields into separate arrays of length `N`; access as `soa[i].field`
|
|
1034
|
-
- `simd[T, N]` — SIMD vector: fixed-width vector of `N` numeric lanes. Constructed with `simd[T, N](lane0, lane1, ...)`. Supports component-wise `+` `-` `*`
|
|
1046
|
+
- `simd[T, N]` — SIMD vector: fixed-width vector of `N` numeric lanes. Constructed with `simd[T, N](lane0, lane1, ...)`. Supports component-wise `+` `-` `*` `/` (and `%` for integer lanes), bitwise `&` `|` `^` `~`, shift `<<` `>>`, unary `-` `~`, and compound assignment. Lane access via compile-time constant `[i]`. Total width must be 128 or 256 bits. Lowers to GCC/Clang vector extensions.
|
|
1035
1047
|
- `dyn[InterfaceName]` — runtime interface value (fat pointer: data + vtable). Constructed via `adapt[Interface](value: ref[T])`. @see §3.5.
|
|
1036
1048
|
- `atomic[T]` — atomic value for lock-free concurrent access. `T` must be a primitive integer or `bool`. Methods: `load() -> T`, `store(value: T)`, `add(value: T) -> T`, `sub(value: T) -> T`, `exchange(value: T) -> T`. All operations use sequential consistency. Lowers to C11 `_Atomic T` with `__atomic_*` builtins. `atomic[T]` is zero-initializable and sendable.
|
|
1037
1049
|
- `(T, U)` — tuple type. Positional fields auto-named `_0`, `_1`. Named fields use `(x = T, y = U)`. Copy by value, returns supported.
|
|
@@ -1042,7 +1054,7 @@ let q = quat(x = 0.0, y = 0.0, z = 0.0, w = 1.0)
|
|
|
1042
1054
|
- for pointer-like bases (`ptr[T]`, `const_ptr[T]`, `own[T]`, `cstr`, `fn(...)`, `proc(...)`, opaque), `T?` is a nullable pointer with `null` as the absent value
|
|
1043
1055
|
- for non-pointer value bases (`int`, `bool`, `float`, structs, ...), `T?` is stored inline by value as a tagged optional (a presence flag plus the value); it copies by value with no hidden heap allocation or pointer aliasing
|
|
1044
1056
|
- `null` expresses absence in any nullable context; the explicit typed `null[...]` form's target must be pointer-like
|
|
1045
|
-
- in nullable pointer-like contexts,
|
|
1057
|
+
- in nullable pointer-like contexts, `zero[ptr[T]]` is rejected; use `null` instead
|
|
1046
1058
|
- at an FFI boundary (`external` / `foreign function` parameters and returns), only pointer-like `T?` is allowed; a non-pointer value nullable such as `int?` is rejected — use `ptr[T]?` or pass an explicit struct
|
|
1047
1059
|
|
|
1048
1060
|
### 6.4 Generics
|
|
@@ -1363,8 +1375,8 @@ function attach(window: ref[Window]) -> Result[void, EventError]:
|
|
|
1363
1375
|
let closed_sub = window.closed.subscribe(on_close)?
|
|
1364
1376
|
let resized_sub = window.resized.subscribe(on_resize)?
|
|
1365
1377
|
|
|
1366
|
-
defer window.closed.unsubscribe(closed_sub)
|
|
1367
|
-
defer window.resized.unsubscribe(resized_sub)
|
|
1378
|
+
defer: window.closed.unsubscribe(closed_sub)
|
|
1379
|
+
defer: window.resized.unsubscribe(resized_sub)
|
|
1368
1380
|
|
|
1369
1381
|
return Result[void, EventError].success()
|
|
1370
1382
|
```
|
data/lib/milk_tea/base.rb
CHANGED
data/lib/milk_tea/core/ast.rb
CHANGED
|
@@ -212,8 +212,8 @@ module MilkTea
|
|
|
212
212
|
ReturnStmt = Data.define(:value, :line, :column, :length) do
|
|
213
213
|
def initialize(value:, line: nil, column: nil, length: nil) = super
|
|
214
214
|
end
|
|
215
|
-
DeferStmt = Data.define(:
|
|
216
|
-
def initialize(
|
|
215
|
+
DeferStmt = Data.define(:body, :line, :column, :length) do
|
|
216
|
+
def initialize(body:, line: nil, column: nil, length: nil) = super
|
|
217
217
|
end
|
|
218
218
|
ErrorBlockStmt = Data.define(:body, :line, :column, :length, :message, :header_type, :header_expression, :header_bindings, :header_iterables) do
|
|
219
219
|
def initialize(body:, line: nil, column: nil, length: nil, message: nil, header_type: nil, header_expression: nil, header_bindings: nil, header_iterables: nil) = super
|
|
@@ -38,6 +38,20 @@ module MilkTea
|
|
|
38
38
|
# scanning a 600 KB source file) never trigger a false positive, yet low
|
|
39
39
|
# enough that a genuinely infinite loop fires a clear fatal within ~1 s.
|
|
40
40
|
LOOP_GUARD_MAX_ITERATIONS = 50_000_000
|
|
41
|
+
EQUALITY_OPERATORS = ["==", "!="].freeze
|
|
42
|
+
|
|
43
|
+
include AggregateUtils
|
|
44
|
+
include TypeSystem
|
|
45
|
+
include Reachability
|
|
46
|
+
include FeatureDetection
|
|
47
|
+
include TypeCollectors
|
|
48
|
+
include Statements
|
|
49
|
+
include ControlFlowEmission
|
|
50
|
+
include RuntimeHelpers
|
|
51
|
+
include FormatHelpers
|
|
52
|
+
include TypeDeclaration
|
|
53
|
+
include Expressions
|
|
54
|
+
include Reinterpret
|
|
41
55
|
|
|
42
56
|
def self.emit(program, emit_line_directives: true, debug_guards: false)
|
|
43
57
|
new(program, emit_line_directives:, debug_guards:).emit
|
|
@@ -294,20 +308,5 @@ module MilkTea
|
|
|
294
308
|
|
|
295
309
|
lines.join("\n").rstrip + "\n"
|
|
296
310
|
end
|
|
297
|
-
|
|
298
|
-
EQUALITY_OPERATORS = ["==", "!="].freeze
|
|
299
|
-
|
|
300
|
-
include AggregateUtils
|
|
301
|
-
include TypeSystem
|
|
302
|
-
include Reachability
|
|
303
|
-
include FeatureDetection
|
|
304
|
-
include TypeCollectors
|
|
305
|
-
include Statements
|
|
306
|
-
include ControlFlowEmission
|
|
307
|
-
include RuntimeHelpers
|
|
308
|
-
include FormatHelpers
|
|
309
|
-
include TypeDeclaration
|
|
310
|
-
include Expressions
|
|
311
|
-
include Reinterpret
|
|
312
311
|
end
|
|
313
312
|
end
|
|
@@ -1,6 +1,185 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "
|
|
3
|
+
require_relative "types/layout"
|
|
4
|
+
|
|
5
|
+
module MilkTea
|
|
6
|
+
module ConstEval
|
|
7
|
+
|
|
8
|
+
def self.evaluate(expression, resolve_identifier:, resolve_member_access:, resolve_type_ref: nil, resolve_call: nil)
|
|
9
|
+
Evaluator.new(
|
|
10
|
+
resolve_identifier:,
|
|
11
|
+
resolve_member_access:,
|
|
12
|
+
resolve_type_ref:,
|
|
13
|
+
resolve_call:,
|
|
14
|
+
).evaluate(expression)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.equality_result(left, right)
|
|
18
|
+
return left == right if left.is_a?(Numeric) && right.is_a?(Numeric)
|
|
19
|
+
return left == right if left.is_a?(String) && right.is_a?(String)
|
|
20
|
+
return left == right if boolean_value?(left) && boolean_value?(right)
|
|
21
|
+
return left == right if left.is_a?(Types::Base) && right.is_a?(Types::Base)
|
|
22
|
+
|
|
23
|
+
nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.boolean_value?(value)
|
|
27
|
+
value == true || value == false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class Evaluator
|
|
31
|
+
def initialize(resolve_identifier:, resolve_member_access:, resolve_type_ref: nil, resolve_call: nil)
|
|
32
|
+
@resolve_identifier = resolve_identifier
|
|
33
|
+
@resolve_member_access = resolve_member_access
|
|
34
|
+
@resolve_type_ref = resolve_type_ref
|
|
35
|
+
@resolve_call = resolve_call
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def evaluate(expression)
|
|
39
|
+
case expression
|
|
40
|
+
when AST::ErrorExpr
|
|
41
|
+
nil
|
|
42
|
+
when AST::ExpressionList
|
|
43
|
+
expression.elements.filter_map { |element| evaluate(element) }
|
|
44
|
+
when AST::IntegerLiteral, AST::FloatLiteral, AST::BooleanLiteral
|
|
45
|
+
expression.value
|
|
46
|
+
when AST::StringLiteral
|
|
47
|
+
expression.value
|
|
48
|
+
when AST::Identifier
|
|
49
|
+
@resolve_identifier&.call(expression)
|
|
50
|
+
when AST::MemberAccess
|
|
51
|
+
@resolve_member_access&.call(expression)
|
|
52
|
+
when AST::Call
|
|
53
|
+
@resolve_call&.call(expression)
|
|
54
|
+
when AST::Specialization
|
|
55
|
+
@resolve_call&.call(expression)
|
|
56
|
+
when AST::SizeofExpr
|
|
57
|
+
type = resolve_layout_type(expression.type)
|
|
58
|
+
type && Layout.size_of(type)
|
|
59
|
+
when AST::AlignofExpr
|
|
60
|
+
type = resolve_layout_type(expression.type)
|
|
61
|
+
type && Layout.alignment_of(type)
|
|
62
|
+
when AST::OffsetofExpr
|
|
63
|
+
type = resolve_layout_type(expression.type)
|
|
64
|
+
if type
|
|
65
|
+
result = Layout.offset_of(type, expression.field)
|
|
66
|
+
return result if result
|
|
67
|
+
|
|
68
|
+
id_expr = AST::Identifier.new(name: expression.field)
|
|
69
|
+
value = @resolve_identifier&.call(id_expr)
|
|
70
|
+
if value.is_a?(Types::FieldHandle)
|
|
71
|
+
return Layout.offset_of(type, value.field_name)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
nil
|
|
75
|
+
when AST::UnaryOp
|
|
76
|
+
evaluate_unary(expression)
|
|
77
|
+
when AST::BinaryOp
|
|
78
|
+
evaluate_binary(expression)
|
|
79
|
+
when AST::IfExpr
|
|
80
|
+
condition = evaluate(expression.condition)
|
|
81
|
+
return unless ConstEval.boolean_value?(condition)
|
|
82
|
+
|
|
83
|
+
evaluate(condition ? expression.then_expression : expression.else_expression)
|
|
84
|
+
else
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def resolve_layout_type(type_ref)
|
|
90
|
+
return unless @resolve_type_ref
|
|
91
|
+
|
|
92
|
+
@resolve_type_ref.call(type_ref)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def evaluate_unary(expression)
|
|
96
|
+
operand = evaluate(expression.operand)
|
|
97
|
+
|
|
98
|
+
case expression.operator
|
|
99
|
+
when "+"
|
|
100
|
+
operand.is_a?(Numeric) ? operand : nil
|
|
101
|
+
when "-"
|
|
102
|
+
operand.is_a?(Numeric) ? -operand : nil
|
|
103
|
+
when "~"
|
|
104
|
+
operand.is_a?(Integer) ? ~operand : nil
|
|
105
|
+
when "not"
|
|
106
|
+
ConstEval.boolean_value?(operand) ? !operand : nil
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def evaluate_binary(expression)
|
|
111
|
+
left = evaluate(expression.left)
|
|
112
|
+
|
|
113
|
+
case expression.operator
|
|
114
|
+
when "and"
|
|
115
|
+
return unless ConstEval.boolean_value?(left)
|
|
116
|
+
return false if left == false
|
|
117
|
+
|
|
118
|
+
right = evaluate(expression.right)
|
|
119
|
+
return right if ConstEval.boolean_value?(right)
|
|
120
|
+
|
|
121
|
+
return nil
|
|
122
|
+
when "or"
|
|
123
|
+
return unless ConstEval.boolean_value?(left)
|
|
124
|
+
return true if left == true
|
|
125
|
+
|
|
126
|
+
right = evaluate(expression.right)
|
|
127
|
+
return right if ConstEval.boolean_value?(right)
|
|
128
|
+
|
|
129
|
+
return nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
right = evaluate(expression.right)
|
|
133
|
+
|
|
134
|
+
case expression.operator
|
|
135
|
+
when "=="
|
|
136
|
+
ConstEval.equality_result(left, right)
|
|
137
|
+
when "!="
|
|
138
|
+
result = ConstEval.equality_result(left, right)
|
|
139
|
+
result.nil? ? nil : !result
|
|
140
|
+
when "+"
|
|
141
|
+
left.is_a?(Numeric) && right.is_a?(Numeric) ? left + right : nil
|
|
142
|
+
when "-"
|
|
143
|
+
left.is_a?(Numeric) && right.is_a?(Numeric) ? left - right : nil
|
|
144
|
+
when "*"
|
|
145
|
+
left.is_a?(Numeric) && right.is_a?(Numeric) ? left * right : nil
|
|
146
|
+
when "/"
|
|
147
|
+
return unless left.is_a?(Numeric) && right.is_a?(Numeric)
|
|
148
|
+
raise CompileTime::Error, "division by zero" if zero_numeric?(right)
|
|
149
|
+
|
|
150
|
+
left / right
|
|
151
|
+
when "%"
|
|
152
|
+
return unless left.is_a?(Integer) && right.is_a?(Integer)
|
|
153
|
+
raise CompileTime::Error, "modulo by zero" if right.zero?
|
|
154
|
+
|
|
155
|
+
left % right
|
|
156
|
+
when "<<"
|
|
157
|
+
left.is_a?(Integer) && right.is_a?(Integer) ? left << right : nil
|
|
158
|
+
when ">>"
|
|
159
|
+
left.is_a?(Integer) && right.is_a?(Integer) ? left >> right : nil
|
|
160
|
+
when "&"
|
|
161
|
+
left.is_a?(Integer) && right.is_a?(Integer) ? left & right : nil
|
|
162
|
+
when "|"
|
|
163
|
+
left.is_a?(Integer) && right.is_a?(Integer) ? left | right : nil
|
|
164
|
+
when "^"
|
|
165
|
+
left.is_a?(Integer) && right.is_a?(Integer) ? left ^ right : nil
|
|
166
|
+
when "<"
|
|
167
|
+
left.is_a?(Numeric) && right.is_a?(Numeric) ? left < right : nil
|
|
168
|
+
when "<="
|
|
169
|
+
left.is_a?(Numeric) && right.is_a?(Numeric) ? left <= right : nil
|
|
170
|
+
when ">"
|
|
171
|
+
left.is_a?(Numeric) && right.is_a?(Numeric) ? left > right : nil
|
|
172
|
+
when ">="
|
|
173
|
+
left.is_a?(Numeric) && right.is_a?(Numeric) ? left >= right : nil
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def zero_numeric?(value)
|
|
178
|
+
(value.is_a?(Integer) && value.zero?) || (value.is_a?(Float) && value.zero?)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
4
183
|
|
|
5
184
|
module MilkTea
|
|
6
185
|
module CompileTime
|
|
@@ -354,4 +533,4 @@ module MilkTea
|
|
|
354
533
|
end
|
|
355
534
|
end
|
|
356
535
|
end
|
|
357
|
-
end
|
|
536
|
+
end
|
|
@@ -212,8 +212,8 @@ module MilkTea
|
|
|
212
212
|
when AST::UnsafeStmt
|
|
213
213
|
build_block(stmt.body, next_id, break_target:, continue_target:)
|
|
214
214
|
when AST::DeferStmt
|
|
215
|
-
body_entry =
|
|
216
|
-
expression_reads, expression_reads_info =
|
|
215
|
+
body_entry = build_block(stmt.body, next_id, break_target:, continue_target:)
|
|
216
|
+
expression_reads, expression_reads_info = statement_list_reads_with_sites(stmt.body)
|
|
217
217
|
defer_id = @graph.add_node(
|
|
218
218
|
kind: :defer,
|
|
219
219
|
statement: stmt,
|
|
@@ -315,6 +315,66 @@ module MilkTea
|
|
|
315
315
|
[reads, reads_info]
|
|
316
316
|
end
|
|
317
317
|
|
|
318
|
+
def statement_list_reads_with_sites(statements)
|
|
319
|
+
reads = Set.new
|
|
320
|
+
reads_info = []
|
|
321
|
+
statement_list_read_identifiers(statements, reads, reads_info)
|
|
322
|
+
[reads, reads_info]
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def statement_list_read_identifiers(statements, reads, reads_info)
|
|
326
|
+
Array(statements).each do |statement|
|
|
327
|
+
statement_read_identifiers(statement, reads, reads_info)
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def statement_read_identifiers(statement, reads, reads_info)
|
|
332
|
+
case statement
|
|
333
|
+
when AST::ExpressionStmt
|
|
334
|
+
read_identifiers(statement.expression, reads, reads_info)
|
|
335
|
+
when AST::DeferStmt
|
|
336
|
+
statement_list_read_identifiers(statement.body, reads, reads_info)
|
|
337
|
+
when AST::LocalDecl
|
|
338
|
+
read_identifiers(statement.value, reads, reads_info)
|
|
339
|
+
statement_list_read_identifiers(statement.else_body, reads, reads_info)
|
|
340
|
+
when AST::Assignment
|
|
341
|
+
read_identifiers(statement.target, reads, reads_info)
|
|
342
|
+
read_identifiers(statement.value, reads, reads_info)
|
|
343
|
+
when AST::ReturnStmt
|
|
344
|
+
read_identifiers(statement.value, reads, reads_info)
|
|
345
|
+
when AST::IfStmt
|
|
346
|
+
statement.branches.each do |branch|
|
|
347
|
+
read_identifiers(branch.condition, reads, reads_info)
|
|
348
|
+
statement_list_read_identifiers(branch.body, reads, reads_info)
|
|
349
|
+
end
|
|
350
|
+
statement_list_read_identifiers(statement.else_body, reads, reads_info)
|
|
351
|
+
when AST::WhileStmt
|
|
352
|
+
read_identifiers(statement.condition, reads, reads_info)
|
|
353
|
+
statement_list_read_identifiers(statement.body, reads, reads_info)
|
|
354
|
+
when AST::ForStmt
|
|
355
|
+
statement.iterables.each { |iterable| read_identifiers(iterable, reads, reads_info) }
|
|
356
|
+
statement_list_read_identifiers(statement.body, reads, reads_info)
|
|
357
|
+
when AST::UnsafeStmt
|
|
358
|
+
statement_list_read_identifiers(statement.body, reads, reads_info)
|
|
359
|
+
when AST::MatchStmt
|
|
360
|
+
read_identifiers(statement.expression, reads, reads_info)
|
|
361
|
+
statement.arms.each do |arm|
|
|
362
|
+
read_identifiers(arm.pattern, reads, reads_info)
|
|
363
|
+
statement_list_read_identifiers(arm.body, reads, reads_info)
|
|
364
|
+
end
|
|
365
|
+
when AST::StaticAssert
|
|
366
|
+
read_identifiers(statement.condition, reads, reads_info)
|
|
367
|
+
read_identifiers(statement.message, reads, reads_info)
|
|
368
|
+
when AST::WhenStmt
|
|
369
|
+
read_identifiers(statement.discriminant, reads, reads_info)
|
|
370
|
+
statement.branches.each do |branch|
|
|
371
|
+
read_identifiers(branch.pattern, reads, reads_info)
|
|
372
|
+
statement_list_read_identifiers(branch.body, reads, reads_info)
|
|
373
|
+
end
|
|
374
|
+
statement_list_read_identifiers(statement.else_body, reads, reads_info)
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
318
378
|
def read_identifiers(expression, names = Set.new, reads_info = [])
|
|
319
379
|
case expression
|
|
320
380
|
when nil
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
require_relative "types/predicates"
|
|
4
4
|
|
|
5
5
|
module MilkTea
|
|
6
|
-
module
|
|
6
|
+
module Intrinsics
|
|
7
7
|
include Types::Predicates
|
|
8
8
|
|
|
9
9
|
BUILTIN_CSTR = Types::Registry.primitive("cstr")
|
|
@@ -78,4 +78,4 @@ module MilkTea
|
|
|
78
78
|
expression.is_a?(AST::StringLiteral) && !expression.cstring && expected_type == BUILTIN_CSTR
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
|
-
end
|
|
81
|
+
end
|
data/lib/milk_tea/core/lexer.rb
CHANGED
|
@@ -106,6 +106,16 @@ module MilkTea
|
|
|
106
106
|
BIN_DIGIT_BYTE = Array.new(256) { |b| b.chr.match?(/[01_]/) }.freeze
|
|
107
107
|
SPACE_BYTE = " ".ord
|
|
108
108
|
|
|
109
|
+
include CharacterClasses
|
|
110
|
+
include Trivia
|
|
111
|
+
include Indentation
|
|
112
|
+
include Numbers
|
|
113
|
+
include Strings
|
|
114
|
+
include Heredocs
|
|
115
|
+
include FormatStrings
|
|
116
|
+
include Symbols
|
|
117
|
+
include Recovery
|
|
118
|
+
|
|
109
119
|
def self.lex(source, path: nil, mode: :syntax_only, recovery_errors: nil)
|
|
110
120
|
result = new(source, path: path, mode:, recovery_errors:).lex
|
|
111
121
|
mode == :with_trivia ? result.tokens : result
|
|
@@ -170,16 +180,6 @@ module MilkTea
|
|
|
170
180
|
@tokens
|
|
171
181
|
end
|
|
172
182
|
|
|
173
|
-
include CharacterClasses
|
|
174
|
-
include Trivia
|
|
175
|
-
include Indentation
|
|
176
|
-
include Numbers
|
|
177
|
-
include Strings
|
|
178
|
-
include Heredocs
|
|
179
|
-
include FormatStrings
|
|
180
|
-
include Symbols
|
|
181
|
-
include Recovery
|
|
182
|
-
|
|
183
183
|
def lex_line(lines, line_index, line, line_number, line_offset, has_newline:)
|
|
184
184
|
tab_index = line.index("\t")
|
|
185
185
|
if tab_index
|
|
@@ -371,8 +371,6 @@ module MilkTea
|
|
|
371
371
|
1
|
|
372
372
|
end
|
|
373
373
|
|
|
374
|
-
# ── newline emission ────────────────────────────────────────────
|
|
375
|
-
|
|
376
374
|
def emit_line_newline(line, line_number, line_offset, has_newline)
|
|
377
375
|
newline_start = line_offset + line.bytesize
|
|
378
376
|
newline_end = has_newline ? (newline_start + 1) : newline_start
|
|
@@ -393,8 +391,6 @@ module MilkTea
|
|
|
393
391
|
end
|
|
394
392
|
end
|
|
395
393
|
|
|
396
|
-
# ── token construction ──────────────────────────────────────────
|
|
397
|
-
|
|
398
394
|
def token(type, lexeme, literal, line, column, start_offset:, end_offset:)
|
|
399
395
|
Token.new(
|
|
400
396
|
type:,
|