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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/docs/index.html +8 -8
  4. data/docs/language-design.md +7 -12
  5. data/docs/language-manual.md +20 -8
  6. data/lib/milk_tea/base.rb +1 -1
  7. data/lib/milk_tea/core/ast.rb +2 -2
  8. data/lib/milk_tea/core/{binding_types.rb → bindings.rb} +1 -1
  9. data/lib/milk_tea/core/c_backend.rb +14 -15
  10. data/lib/milk_tea/core/compile_time.rb +181 -2
  11. data/lib/milk_tea/core/control_flow/builder.rb +62 -2
  12. data/lib/milk_tea/core/control_flow/constant_propagation.rb +1 -1
  13. data/lib/milk_tea/core/{compatibility_helpers.rb → intrinsics.rb} +2 -2
  14. data/lib/milk_tea/core/lexer.rb +10 -14
  15. data/lib/milk_tea/core/lowering/async/analysis.rb +3 -9
  16. data/lib/milk_tea/core/lowering/async/lowering.rb +2 -10
  17. data/lib/milk_tea/core/lowering/async/normalization.rb +2 -7
  18. data/lib/milk_tea/core/lowering/block.rb +1 -5
  19. data/lib/milk_tea/core/lowering/proc.rb +1 -5
  20. data/lib/milk_tea/core/lowering/scans.rb +3 -1
  21. data/lib/milk_tea/core/lowering/utils.rb +1 -27
  22. data/lib/milk_tea/core/lowering.rb +18 -18
  23. data/lib/milk_tea/core/module_loader.rb +26 -15
  24. data/lib/milk_tea/core/parser/statements.rb +18 -12
  25. data/lib/milk_tea/core/parser.rb +17 -17
  26. data/lib/milk_tea/core/pretty_printer/ast_formatter.rb +3 -11
  27. data/lib/milk_tea/core/semantic_analyzer/analysis_context.rb +2 -3
  28. data/lib/milk_tea/core/semantic_analyzer/flow_refinement.rb +1 -1
  29. data/lib/milk_tea/core/semantic_analyzer/function_binding.rb +1 -2
  30. data/lib/milk_tea/core/semantic_analyzer/nullability.rb +2 -4
  31. data/lib/milk_tea/core/semantic_analyzer/statements.rb +2 -8
  32. data/lib/milk_tea/core/semantic_analyzer.rb +1 -1
  33. data/lib/milk_tea/core.rb +6 -2
  34. data/lib/milk_tea/dap/lldb_dap_backend.rb +153 -0
  35. data/lib/milk_tea/dap/server/lldb_backend.rb +1 -1
  36. data/lib/milk_tea/dap.rb +1 -1
  37. data/lib/milk_tea/lsp.rb +1 -7
  38. data/lib/milk_tea/{bindings/cli.rb → tooling/bindgen_cli.rb} +1 -1
  39. data/lib/milk_tea/tooling/linter/imports_platform.rb +2 -4
  40. data/lib/milk_tea/tooling/linter/release_rules.rb +1 -6
  41. data/lib/milk_tea/tooling/linter/source_helpers.rb +0 -6
  42. data/lib/milk_tea/tooling/linter/visitors.rb +4 -8
  43. data/lib/milk_tea/tooling/sexpr_dumper.rb +2 -0
  44. data/lib/milk_tea/tooling.rb +1 -1
  45. data/std/asset_pack.mt +1 -1
  46. data/std/async/libuv_runtime.mt +6 -6
  47. data/std/async/mailbox.mt +4 -4
  48. data/std/cookie.mt +2 -2
  49. data/std/crypto.mt +1 -1
  50. data/std/curl/runtime.mt +10 -10
  51. data/std/fs.linux.mt +26 -26
  52. data/std/fs.windows.mt +22 -22
  53. data/std/goap.mt +2 -2
  54. data/std/http/server.mt +21 -21
  55. data/std/http.mt +14 -14
  56. data/std/jobs.mt +4 -4
  57. data/std/json.mt +5 -5
  58. data/std/log.mt +1 -1
  59. data/std/mem/tracking.mt +1 -1
  60. data/std/net/channel.mt +4 -4
  61. data/std/net/clock.mt +6 -6
  62. data/std/net/discovery.mt +7 -7
  63. data/std/net/lobby.mt +10 -10
  64. data/std/net/manager.mt +1 -1
  65. data/std/net/mux.mt +4 -4
  66. data/std/net/nat.mt +4 -4
  67. data/std/net/packet.mt +3 -3
  68. data/std/net/punch.mt +3 -3
  69. data/std/net/rpc.mt +3 -3
  70. data/std/net/session.mt +15 -15
  71. data/std/net/stun.mt +4 -4
  72. data/std/net/turn.mt +4 -4
  73. data/std/oauth2.mt +12 -12
  74. data/std/path.mt +8 -8
  75. data/std/process.mt +4 -4
  76. data/std/raylib/packed_assets.mt +5 -5
  77. data/std/serialize.mt +1 -1
  78. data/std/tar.mt +10 -10
  79. data/std/terminal.mt +18 -18
  80. data/std/tls.mt +8 -8
  81. data/std/uri.mt +1 -1
  82. data/std/utility.mt +1 -1
  83. metadata +7 -9
  84. data/lib/milk_tea/core/compile_time/const_eval.rb +0 -182
  85. data/lib/milk_tea/core/module_loader/errors.rb +0 -23
  86. data/lib/milk_tea/dap/backends/lldb_dap.rb +0 -156
  87. /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: 12f7a371c9cb4162851cefb9277292455904b6e0bedcba05312770f00d724481
4
- data.tar.gz: 4d73b7c4ef5c45abc731edbfd6e465067230b6772669ad8952a75247cd10e2ad
3
+ metadata.gz: e2f84289b1bcb599ca81cbada0b1e1a925d9962ffe186471b84026f372fb358b
4
+ data.tar.gz: 566b98b015518f583e31fa2f0a75753c3b0abfd5a4047210945b3209c3caf1f3
5
5
  SHA512:
6
- metadata.gz: d434a523bf7e5678512af400a5054765d62976778d27c1d647203fa03a39145509648d82d32d56d02218da1324d66717b1eb1da75543ea513e49fcc2e9fc5410
7
- data.tar.gz: d2fa42eb284fd01df9492fa772d69c46853712862dd6f8d4f1f0f20aa8c40fc996abf821763175419cb58abb2ffe2da94a750e30b8c47d7b0c9ae9c31ec75f75
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
- - `if` supports an inline single-statement form: `if cond: stmt else: stmt`. The body is a single statement on the same line instead of an indented block.
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>
@@ -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 always have an explicit backing type.
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 a fixed integer backing type.
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
@@ -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
- Forms:
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
- `return` is not allowed inside defer blocks.
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 (`+` `-` `*` `/`), bitwise (`&` `|` `^` `~`), shift (`<<` `>>`), and compound assignment. Lane access via compile-time constant index `[i]`. Lowers to GCC/Clang `__attribute__((__vector_size__))`.
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 `+` `-` `*` `/`, 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.
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, use `null` instead of `zero[ptr[T]]`
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
@@ -3,7 +3,7 @@
3
3
  require "pathname"
4
4
 
5
5
  module MilkTea
6
- VERSION = "0.3.14"
6
+ VERSION = "0.3.17"
7
7
 
8
8
  def self.root
9
9
  @root ||= Pathname.new(File.expand_path("../..", __dir__))
@@ -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(:expression, :body, :line, :column, :length) do
216
- def initialize(expression:, body:, line: nil, column: nil, length: nil) = super
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
@@ -15,4 +15,4 @@ module MilkTea
15
15
  def each(&block) = @bindings.each(&block)
16
16
  def each_with_object(init, &block) = @bindings.each_with_object(init, &block)
17
17
  end
18
- end
18
+ end
@@ -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 "compile_time/const_eval"
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 = stmt.body ? build_block(stmt.body, next_id, break_target:, continue_target:) : next_id
216
- expression_reads, expression_reads_info = stmt.expression ? read_identifiers_with_sites(stmt.expression) : [Set.new, []]
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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../compile_time/const_eval"
3
+ require_relative "../compile_time"
4
4
 
5
5
  module MilkTea
6
6
  module ControlFlow
@@ -3,7 +3,7 @@
3
3
  require_relative "types/predicates"
4
4
 
5
5
  module MilkTea
6
- module CompatibilityHelpers
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
@@ -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:,