mt-lang 0.4.23 → 0.4.25
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/.ruby-version +1 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +8 -8
- data/lib/milk_tea/base.rb +1 -1
- data/lib/milk_tea/core/control_flow/builder.rb +12 -0
- data/lib/milk_tea/core/types/layout.rb +90 -19
- data/lib/milk_tea/tooling/cli/commands/std.rb +84 -0
- data/lib/milk_tea/tooling/cli.rb +25 -0
- data/lib/milk_tea/tooling/linter/fix_engine.rb +100 -0
- data/lib/milk_tea/tooling/linter/release_rules.rb +69 -11
- data/lib/milk_tea/tooling/linter/visitors.rb +58 -31
- data/lib/milk_tea/tooling/linter.rb +8 -1
- data/lib/milk_tea/tooling/std_catalog.rb +277 -0
- data/lib/milk_tea/tooling.rb +1 -0
- data/std/async/libuv_runtime.mt +20 -23
- data/std/async/mailbox.mt +2 -2
- data/std/base64.mt +3 -6
- data/std/behavior_tree.mt +1 -6
- data/std/binary.mt +2 -5
- data/std/color.mt +20 -20
- data/std/cookie.mt +1 -4
- data/std/deque.mt +2 -8
- data/std/encoding.mt +1 -4
- data/std/fmt.mt +1 -4
- data/std/graph.mt +5 -1
- data/std/htn.mt +9 -1
- data/std/http/server.mt +1 -4
- data/std/http.mt +14 -17
- data/std/jobs.mt +9 -10
- data/std/json.mt +17 -28
- data/std/linked_map.mt +3 -9
- data/std/map.mt +1 -4
- data/std/net/channel.mt +6 -6
- data/std/net/lobby.mt +3 -3
- data/std/net/manager.mt +0 -1
- data/std/net/mux.mt +1 -1
- data/std/net/packet.mt +4 -1
- data/std/net/punch.mt +3 -2
- data/std/net/rpc.mt +1 -3
- data/std/net/session.mt +12 -25
- data/std/net/sync.mt +3 -3
- data/std/net/turn.mt +7 -3
- data/std/net.mt +145 -159
- data/std/noise.mt +11 -7
- data/std/ordered_map.mt +2 -8
- data/std/ordered_set.mt +3 -5
- data/std/path.mt +1 -4
- data/std/process.mt +7 -18
- data/std/random.mt +2 -2
- data/std/sdl3/runtime.mt +1 -1
- data/std/steering.mt +27 -4
- data/std/string.mt +1 -4
- data/std/tar.mt +8 -10
- data/std/terminal.mt +21 -40
- data/std/thread.mt +3 -4
- data/std/tls.mt +22 -29
- data/std/toml.mt +23 -31
- data/std/uri.mt +2 -1
- data/std/utility.mt +2 -2
- data/std/vec.mt +8 -17
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f4c65f190209c5486b52d39b2ac868b2e2c076ad3ec01206151df16675319cf9
|
|
4
|
+
data.tar.gz: 28bb0127501319fb64f0da085409bb8658b6fd9cd41d8c18c0ac2175608f93dd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '098dc1e2c0303bbf39dcd4402d7f21482c682f83b904799c1c85a9a73207b4c8443c0a98bdee192db9d17a0bf33ec4a96ad31c03a733f28c57880b7601c91d42'
|
|
7
|
+
data.tar.gz: ce0f5ba1ae8e8c6f958ab4cd5e2d0e89c522a9f814d8e5e31a1e8b8b849faafe437caaab098bd3a4d59755cdbf4f12c23d521810e9892c6e039acf27176b7d10
|
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.6
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -5,7 +5,7 @@ GEM
|
|
|
5
5
|
drb (2.2.3)
|
|
6
6
|
fiddle (1.1.8)
|
|
7
7
|
logger (1.7.0)
|
|
8
|
-
minitest (6.0.
|
|
8
|
+
minitest (6.0.6)
|
|
9
9
|
drb (~> 2.0)
|
|
10
10
|
prism (~> 1.5)
|
|
11
11
|
minitest-coverage (1.0.0.b3)
|
|
@@ -15,7 +15,7 @@ GEM
|
|
|
15
15
|
puma (6.6.1)
|
|
16
16
|
nio4r (~> 2.0)
|
|
17
17
|
racc (1.8.1)
|
|
18
|
-
rack (3.2.
|
|
18
|
+
rack (3.2.7)
|
|
19
19
|
rack-protection (4.2.1)
|
|
20
20
|
base64 (>= 0.1.0)
|
|
21
21
|
logger (>= 1.6.0)
|
|
@@ -34,7 +34,7 @@ GEM
|
|
|
34
34
|
rack-protection (= 4.2.1)
|
|
35
35
|
rack-session (>= 2.0.0, < 3)
|
|
36
36
|
tilt (~> 2.0)
|
|
37
|
-
tilt (2.
|
|
37
|
+
tilt (2.9.0)
|
|
38
38
|
tomlrb (2.0.4)
|
|
39
39
|
|
|
40
40
|
PLATFORMS
|
|
@@ -58,25 +58,25 @@ CHECKSUMS
|
|
|
58
58
|
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
|
59
59
|
fiddle (1.1.8) sha256=7fa8ee3627271497f3add5503acdbc3f40b32f610fc1cf49634f083ef3f32eee
|
|
60
60
|
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
61
|
-
minitest (6.0.
|
|
61
|
+
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
|
|
62
62
|
minitest-coverage (1.0.0.b3) sha256=27c8b6b8df1259325dd206baa2b143c594f8a88b7dfd97fd8d3389a0e8a6ad26
|
|
63
63
|
mustermann (3.1.1) sha256=4c6170c7234d5499c345562ba7c7dfe73e1754286dcc1abb053064d66a127198
|
|
64
64
|
nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
|
|
65
65
|
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
66
66
|
puma (6.6.1) sha256=b9b56e4a4ea75d1bfa6d9e1972ee2c9f43d0883f011826d914e8e37b3694ea1e
|
|
67
67
|
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
68
|
-
rack (3.2.
|
|
68
|
+
rack (3.2.7) sha256=93e13e1c24f93556671d85d2d79fa228c3485815c50d7e2f265b5330c6528fb7
|
|
69
69
|
rack-protection (4.2.1) sha256=cf6e2842df8c55f5e4d1a4be015e603e19e9bc3a7178bae58949ccbb58558bac
|
|
70
70
|
rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
|
|
71
71
|
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
|
|
72
72
|
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
|
73
73
|
rexml (3.4.4) sha256=19e0a2c3425dfbf2d4fc1189747bdb2f849b6c5e74180401b15734bc97b5d142
|
|
74
74
|
sinatra (4.2.1) sha256=b7aeb9b11d046b552972ade834f1f9be98b185fa8444480688e3627625377080
|
|
75
|
-
tilt (2.
|
|
75
|
+
tilt (2.9.0) sha256=da5735d0280bba96e9a91041bb14aee435ccad5c17b0fa519249ae543d9aa3a5
|
|
76
76
|
tomlrb (2.0.4) sha256=262f77947ac3ac9b3366a0a5940ecd238300c553e2e14f22009e2afcd2181b99
|
|
77
77
|
|
|
78
78
|
RUBY VERSION
|
|
79
|
-
ruby 4.0.
|
|
79
|
+
ruby 4.0.6
|
|
80
80
|
|
|
81
81
|
BUNDLED WITH
|
|
82
|
-
4.0.
|
|
82
|
+
4.0.16
|
data/lib/milk_tea/base.rb
CHANGED
|
@@ -352,6 +352,12 @@ module MilkTea
|
|
|
352
352
|
read_identifiers(expression.expression, names, reads_info)
|
|
353
353
|
when AST::UnsafeExpr
|
|
354
354
|
read_identifiers(expression.expression, names, reads_info)
|
|
355
|
+
when AST::PrefixCast
|
|
356
|
+
read_identifiers(expression.expression, names, reads_info)
|
|
357
|
+
when AST::DetachExpr
|
|
358
|
+
expression.body.each do |stmt|
|
|
359
|
+
read_identifiers(stmt.expression, names, reads_info) if stmt.is_a?(AST::ExpressionStmt)
|
|
360
|
+
end
|
|
355
361
|
when AST::FormatString
|
|
356
362
|
expression.parts.each do |part|
|
|
357
363
|
read_identifiers(part.expression, names, reads_info) if part.is_a?(AST::FormatExprPart)
|
|
@@ -433,6 +439,12 @@ module MilkTea
|
|
|
433
439
|
end
|
|
434
440
|
when AST::UnsafeExpr
|
|
435
441
|
write_targets_from_expression(expression.expression, line:, writes:, writes_info:)
|
|
442
|
+
when AST::PrefixCast
|
|
443
|
+
write_targets_from_expression(expression.expression, line:, writes:, writes_info:)
|
|
444
|
+
when AST::DetachExpr
|
|
445
|
+
expression.body.each do |stmt|
|
|
446
|
+
write_targets_from_expression(stmt.expression, line:, writes:, writes_info:) if stmt.is_a?(AST::ExpressionStmt)
|
|
447
|
+
end
|
|
436
448
|
when AST::FormatString
|
|
437
449
|
expression.parts.each do |part|
|
|
438
450
|
write_targets_from_expression(part.expression, line:, writes:, writes_info:) if part.is_a?(AST::FormatExprPart)
|
|
@@ -10,11 +10,11 @@ module MilkTea
|
|
|
10
10
|
POINTER_SIZE = Fiddle::SIZEOF_VOIDP
|
|
11
11
|
|
|
12
12
|
def self.size_of(type)
|
|
13
|
-
size_and_alignment(type,
|
|
13
|
+
size_and_alignment(type, nil)&.first
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def self.alignment_of(type)
|
|
17
|
-
size_and_alignment(type,
|
|
17
|
+
size_and_alignment(type, nil)&.last
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def self.offset_of(type, field_name)
|
|
@@ -38,6 +38,14 @@ module MilkTea
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
# Computes [size, alignment] for a type.
|
|
42
|
+
#
|
|
43
|
+
# `stack` carries the aggregates on the current root-to-leaf path,
|
|
44
|
+
# pass-down only (never popped). A field whose type reaches any aggregate
|
|
45
|
+
# on that path participates in a storage cycle, and the C backend embeds
|
|
46
|
+
# exactly those fields as pointers (see
|
|
47
|
+
# CBackend#build_cyclic_aggregate_pairs and the cyclic field emission in
|
|
48
|
+
# type_declaration.rb), so layout sizes back-edge fields as raw pointers.
|
|
41
49
|
def self.size_and_alignment(type, stack)
|
|
42
50
|
case type
|
|
43
51
|
when Types::Primitive
|
|
@@ -48,24 +56,24 @@ module MilkTea
|
|
|
48
56
|
if type.is_a?(Types::Function) || layout_pointer_like_nullable_base?(type.base)
|
|
49
57
|
[POINTER_SIZE, POINTER_SIZE]
|
|
50
58
|
else
|
|
51
|
-
with_stack(type, stack) do
|
|
52
|
-
layout = struct_layout(nullable_opt_layout_fields(type.base), packed: false, alignment: nil, stack:)
|
|
59
|
+
with_stack(type, stack) do |next_stack|
|
|
60
|
+
layout = struct_layout(nullable_opt_layout_fields(type.base), packed: false, alignment: nil, stack: next_stack)
|
|
53
61
|
[layout[:size], layout[:alignment]]
|
|
54
62
|
end
|
|
55
63
|
end
|
|
56
64
|
when Types::StringView, Types::Span, Types::Task, Types::Struct, Types::StructInstance
|
|
57
|
-
with_stack(type, stack) do
|
|
58
|
-
layout = struct_layout(ordered_fields(type), packed: packed_layout?(type), alignment: explicit_alignment(type), stack:)
|
|
65
|
+
with_stack(type, stack) do |next_stack|
|
|
66
|
+
layout = struct_layout(ordered_fields(type), packed: packed_layout?(type), alignment: explicit_alignment(type), stack: next_stack)
|
|
59
67
|
[layout[:size], layout[:alignment]]
|
|
60
68
|
end
|
|
61
69
|
when Types::Union
|
|
62
|
-
with_stack(type, stack) do
|
|
63
|
-
layout = union_layout(ordered_fields(type), packed: packed_layout?(type), alignment: explicit_alignment(type), stack:)
|
|
70
|
+
with_stack(type, stack) do |next_stack|
|
|
71
|
+
layout = union_layout(ordered_fields(type), packed: packed_layout?(type), alignment: explicit_alignment(type), stack: next_stack)
|
|
64
72
|
[layout[:size], layout[:alignment]]
|
|
65
73
|
end
|
|
66
74
|
when Types::Variant, Types::VariantInstance
|
|
67
|
-
with_stack(type, stack) do
|
|
68
|
-
layout = variant_layout(type, stack:)
|
|
75
|
+
with_stack(type, stack) do |next_stack|
|
|
76
|
+
layout = variant_layout(type, stack: next_stack)
|
|
69
77
|
[layout[:size], layout[:alignment]]
|
|
70
78
|
end
|
|
71
79
|
when Types::GenericInstance
|
|
@@ -128,8 +136,8 @@ module MilkTea
|
|
|
128
136
|
when "str_buffer"
|
|
129
137
|
return unless str_buffer_type?(type)
|
|
130
138
|
|
|
131
|
-
with_stack(type, stack) do
|
|
132
|
-
layout = struct_layout(str_buffer_fields(type), packed: false, alignment: nil, stack:)
|
|
139
|
+
with_stack(type, stack) do |next_stack|
|
|
140
|
+
layout = struct_layout(str_buffer_fields(type), packed: false, alignment: nil, stack: next_stack)
|
|
133
141
|
[layout[:size], layout[:alignment]]
|
|
134
142
|
end
|
|
135
143
|
end
|
|
@@ -151,7 +159,7 @@ module MilkTea
|
|
|
151
159
|
|
|
152
160
|
def self.struct_layout(fields, packed:, alignment:, stack:)
|
|
153
161
|
field_infos = fields.map do |field_name, field_type|
|
|
154
|
-
field_layout =
|
|
162
|
+
field_layout = field_size_and_alignment(field_type, stack)
|
|
155
163
|
return nil unless field_layout
|
|
156
164
|
|
|
157
165
|
[field_name, field_layout.first, field_layout.last]
|
|
@@ -162,7 +170,7 @@ module MilkTea
|
|
|
162
170
|
|
|
163
171
|
def self.union_layout(fields, packed:, alignment:, stack:)
|
|
164
172
|
field_infos = fields.map do |_field_name, field_type|
|
|
165
|
-
field_layout =
|
|
173
|
+
field_layout = field_size_and_alignment(field_type, stack)
|
|
166
174
|
return nil unless field_layout
|
|
167
175
|
|
|
168
176
|
field_layout
|
|
@@ -171,6 +179,30 @@ module MilkTea
|
|
|
171
179
|
union_layout_from_layouts(field_infos.map { |size, field_alignment| { size:, alignment: field_alignment } }, packed:, alignment:)
|
|
172
180
|
end
|
|
173
181
|
|
|
182
|
+
# A field whose type reaches any aggregate on the current path is a
|
|
183
|
+
# storage-cycle back edge. The C backend emits such fields as pointers
|
|
184
|
+
# (element pointer for arrays), so they occupy pointer-size storage here.
|
|
185
|
+
def self.field_size_and_alignment(field_type, stack)
|
|
186
|
+
return [POINTER_SIZE, POINTER_SIZE] if cyclic_back_edge?(field_type, stack)
|
|
187
|
+
|
|
188
|
+
size_and_alignment(field_type, stack)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def self.cyclic_back_edge?(field_type, stack)
|
|
192
|
+
return false if stack.nil? || stack.empty?
|
|
193
|
+
|
|
194
|
+
aggregate_reaches_in_progress?(field_type, stack, {})
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def self.aggregate_reaches_in_progress?(type, stack, visited)
|
|
198
|
+
return false if visited.key?(type.object_id)
|
|
199
|
+
|
|
200
|
+
visited[type.object_id] = true
|
|
201
|
+
return true if stack.key?(type)
|
|
202
|
+
|
|
203
|
+
reachability_children(type).any? { |child| aggregate_reaches_in_progress?(child, stack, visited) }
|
|
204
|
+
end
|
|
205
|
+
|
|
174
206
|
def self.struct_layout_from_infos(field_infos, packed:, alignment:)
|
|
175
207
|
offsets = {}
|
|
176
208
|
offset = 0
|
|
@@ -253,13 +285,52 @@ module MilkTea
|
|
|
253
285
|
remainder.zero? ? value : value + alignment - remainder
|
|
254
286
|
end
|
|
255
287
|
|
|
288
|
+
# `stack` is the set of aggregates on the current path, pass-down only.
|
|
289
|
+
# A nil or empty stack means the caller is not inside an aggregate yet.
|
|
290
|
+
# Yields the extended stack; returns nil when `type` is already in progress
|
|
291
|
+
# (callers treat that as unreachable while mirroring backend semantics).
|
|
256
292
|
def self.with_stack(type, stack)
|
|
257
|
-
|
|
293
|
+
next_stack = (stack || {}).dup
|
|
294
|
+
return nil if next_stack.key?(type)
|
|
295
|
+
|
|
296
|
+
next_stack[type] = true
|
|
297
|
+
yield(next_stack)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Structural expansion used for back-edge detection. Mirrors the
|
|
301
|
+
# CBackend's `aggregate_type_dependencies` so pointer-broken cyclic
|
|
302
|
+
# fields agree with the emitted C struct layout.
|
|
303
|
+
def self.reachability_children(type)
|
|
304
|
+
case type
|
|
305
|
+
when Types::Nullable
|
|
306
|
+
[type.base]
|
|
307
|
+
when Types::GenericInstance
|
|
308
|
+
if pointer_type?(type)
|
|
309
|
+
[]
|
|
310
|
+
elsif array_type?(type)
|
|
311
|
+
[array_element_type(type)]
|
|
312
|
+
else
|
|
313
|
+
[]
|
|
314
|
+
end
|
|
315
|
+
when Types::Span
|
|
316
|
+
[type.element_type]
|
|
317
|
+
when Types::Struct, Types::StructInstance, Types::Union
|
|
318
|
+
type.fields.values
|
|
319
|
+
when Types::Variant, Types::VariantInstance
|
|
320
|
+
type.arm_names.flat_map { |name| type.arm(name).values }
|
|
321
|
+
when Types::VariantArmPayload
|
|
322
|
+
type.fields.values
|
|
323
|
+
else
|
|
324
|
+
[]
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def self.pointer_type?(type)
|
|
329
|
+
type.is_a?(Types::GenericInstance) && %w[ptr const_ptr own ref].include?(type.name)
|
|
330
|
+
end
|
|
258
331
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
ensure
|
|
262
|
-
stack.delete(type)
|
|
332
|
+
def self.array_element_type(type)
|
|
333
|
+
type.arguments.first
|
|
263
334
|
end
|
|
264
335
|
end
|
|
265
336
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
class CLI
|
|
5
|
+
module CommandStd
|
|
6
|
+
def std_command
|
|
7
|
+
subcommand = @argv.shift
|
|
8
|
+
|
|
9
|
+
case subcommand
|
|
10
|
+
when "list"
|
|
11
|
+
std_list_command
|
|
12
|
+
when "show"
|
|
13
|
+
std_show_command
|
|
14
|
+
when nil
|
|
15
|
+
@err.puts("missing std subcommand (expected list or show)")
|
|
16
|
+
print_command_help("std", @err)
|
|
17
|
+
1
|
|
18
|
+
else
|
|
19
|
+
@err.puts("unknown std subcommand #{subcommand}")
|
|
20
|
+
print_command_help("std", @err)
|
|
21
|
+
1
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def std_list_command
|
|
26
|
+
as_json = false
|
|
27
|
+
until @argv.empty?
|
|
28
|
+
option = @argv.shift
|
|
29
|
+
case option
|
|
30
|
+
when "--json"
|
|
31
|
+
as_json = true
|
|
32
|
+
else
|
|
33
|
+
@err.puts("unknown std list option #{option}")
|
|
34
|
+
return 1
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
entries = StdCatalog.entries
|
|
39
|
+
if as_json
|
|
40
|
+
@out.puts(JSON.generate(entries.map(&:to_h)))
|
|
41
|
+
return 0
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
print_std_list(entries)
|
|
45
|
+
0
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def print_std_list(entries)
|
|
49
|
+
name_width = entries.map { |entry| entry.name.length }.max
|
|
50
|
+
description_width = entries.map { |entry| entry.description&.length.to_i }.max
|
|
51
|
+
|
|
52
|
+
StdCatalog::CATEGORY_ORDER.each do |category|
|
|
53
|
+
category_entries = entries.select { |entry| entry.category == category }
|
|
54
|
+
next if category_entries.empty?
|
|
55
|
+
|
|
56
|
+
@out.puts("#{category}:")
|
|
57
|
+
category_entries.each do |entry|
|
|
58
|
+
description = entry.description || "(no description)"
|
|
59
|
+
@out.puts(" #{entry.name.ljust(name_width)} #{description.ljust(description_width)} #{entry.path}")
|
|
60
|
+
end
|
|
61
|
+
@out.puts
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def std_show_command
|
|
66
|
+
name = @argv.shift
|
|
67
|
+
unless name
|
|
68
|
+
@err.puts("missing module name")
|
|
69
|
+
print_command_help("std", @err)
|
|
70
|
+
return 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
path = StdCatalog.resolve(name)
|
|
74
|
+
unless path
|
|
75
|
+
@err.puts("std module not found: #{name}")
|
|
76
|
+
return 1
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
@out.write(File.read(path))
|
|
80
|
+
0
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/milk_tea/tooling/cli.rb
CHANGED
|
@@ -33,6 +33,7 @@ module MilkTea
|
|
|
33
33
|
["bindgen", "Generate a binding module from a C header"],
|
|
34
34
|
["cache", "Inspect and manage the build cache"],
|
|
35
35
|
["docs", "Serve the local documentation site"],
|
|
36
|
+
["std", "List standard library modules and print their source"],
|
|
36
37
|
["snapshot", "Render a highlighted HTML snapshot"],
|
|
37
38
|
["lsp", "Start the Language Server Protocol server"],
|
|
38
39
|
["dap", "Start the Debug Adapter Protocol server"],
|
|
@@ -127,6 +128,8 @@ module MilkTea
|
|
|
127
128
|
cache_command
|
|
128
129
|
when "docs"
|
|
129
130
|
docs_command
|
|
131
|
+
when "std"
|
|
132
|
+
std_command
|
|
130
133
|
when "snapshot"
|
|
131
134
|
snapshot_command
|
|
132
135
|
when "lsp"
|
|
@@ -164,6 +167,7 @@ module MilkTea
|
|
|
164
167
|
require_relative "cli/commands/bindgen"
|
|
165
168
|
require_relative "cli/commands/cache"
|
|
166
169
|
require_relative "cli/commands/docs"
|
|
170
|
+
require_relative "cli/commands/std"
|
|
167
171
|
require_relative "cli/commands/snapshot"
|
|
168
172
|
require_relative "cli/commands/lsp"
|
|
169
173
|
require_relative "cli/commands/dap"
|
|
@@ -187,6 +191,7 @@ module MilkTea
|
|
|
187
191
|
include CommandBindgen
|
|
188
192
|
include CommandCache
|
|
189
193
|
include CommandDocs
|
|
194
|
+
include CommandStd
|
|
190
195
|
include CommandSnapshot
|
|
191
196
|
include CommandLsp
|
|
192
197
|
include CommandDap
|
|
@@ -796,6 +801,24 @@ module MilkTea
|
|
|
796
801
|
--open, -o Open the docs in your default browser.
|
|
797
802
|
--port, -p PORT Listen on a specific port (default: random).
|
|
798
803
|
HELP
|
|
804
|
+
"std" => <<~HELP,
|
|
805
|
+
Usage: mtc std list [--json]
|
|
806
|
+
mtc std show MODULE
|
|
807
|
+
|
|
808
|
+
Inspect the Milk Tea standard library shipped in std/.
|
|
809
|
+
|
|
810
|
+
list Print every hand-written standard library module with a short
|
|
811
|
+
description and its path on disk, grouped by category.
|
|
812
|
+
Generated binding modules (std/c/* and imported-binding
|
|
813
|
+
wrappers such as raylib or zstd) are not listed.
|
|
814
|
+
show Print the source of a module. Accepts dotted or slashed names
|
|
815
|
+
(mem.arena, mem/arena) and resolves platform variants such as
|
|
816
|
+
fs to the active platform file. Generated modules stay
|
|
817
|
+
viewable here (for example c.cjson or raylib).
|
|
818
|
+
|
|
819
|
+
Options:
|
|
820
|
+
--json Emit list output as JSON.
|
|
821
|
+
HELP
|
|
799
822
|
"snapshot" => <<~HELP,
|
|
800
823
|
Usage: mtc snapshot INPUT.mt [OPTIONS]
|
|
801
824
|
|
|
@@ -891,6 +914,8 @@ module MilkTea
|
|
|
891
914
|
io.puts(" mtc bindgen MODULE HEADER [-o OUTPUT] [--nullable-report PATH] [--link LIB] [--include HEADER] [--clang PATH] [--clang-arg ARG]")
|
|
892
915
|
io.puts(" mtc cache purge|status")
|
|
893
916
|
io.puts(" mtc docs [--open] [--port PORT]")
|
|
917
|
+
io.puts(" mtc std list [--json]")
|
|
918
|
+
io.puts(" mtc std show MODULE")
|
|
894
919
|
io.puts(" mtc snapshot INPUT.mt [--theme PATH] [-o OUTPUT]")
|
|
895
920
|
io.puts(" mtc lsp [--log-level LEVEL] [--stdio]")
|
|
896
921
|
io.puts(" mtc dap [--backend KIND] [--adapter-path PATH]")
|
|
@@ -17,6 +17,7 @@ module MilkTea
|
|
|
17
17
|
when "redundant-else" then redundant_else_edits(lines, warning)
|
|
18
18
|
when "redundant-return" then redundant_return_edits(lines, warning)
|
|
19
19
|
when "redundant-type-annotation" then redundant_type_annotation_edits(lines, warning)
|
|
20
|
+
when "redundant-unsafe" then redundant_unsafe_edits(lines, warning)
|
|
20
21
|
when "prefer-inline-methods" then prefer_inline_methods_edits(lines, warning)
|
|
21
22
|
when "unused-import" then unused_import_edits(lines, warning)
|
|
22
23
|
when "trailing-list-comma" then trailing_list_comma_edits(lines, warning)
|
|
@@ -302,6 +303,105 @@ module MilkTea
|
|
|
302
303
|
edits
|
|
303
304
|
end
|
|
304
305
|
|
|
306
|
+
# ── redundant-unsafe ───────────────────────────────────────────────────
|
|
307
|
+
# Removes an `unsafe:` wrapper that contains no unsafe operations.
|
|
308
|
+
#
|
|
309
|
+
# Supported shapes:
|
|
310
|
+
# <indent>unsafe:\n<deeper body> (block statement; body is dedented)
|
|
311
|
+
# ... unsafe: expr ... (inline expression on a single line)
|
|
312
|
+
def self.redundant_unsafe_edits(lines, warning)
|
|
313
|
+
return [] unless warning.line
|
|
314
|
+
|
|
315
|
+
line_idx = warning.line - 1
|
|
316
|
+
line = lines[line_idx]
|
|
317
|
+
return [] unless line
|
|
318
|
+
|
|
319
|
+
unsafe_idx = line.index("unsafe:")
|
|
320
|
+
return [] unless unsafe_idx
|
|
321
|
+
|
|
322
|
+
keyword_end = unsafe_idx + "unsafe:".length
|
|
323
|
+
rest = line[keyword_end..]
|
|
324
|
+
|
|
325
|
+
if rest && !rest.strip.empty?
|
|
326
|
+
# Inline expression form: drop the keyword and the whitespace after
|
|
327
|
+
# it, but only when the remainder is balanced on this line.
|
|
328
|
+
remainder = rest.sub(/\A\s+/, "")
|
|
329
|
+
return [] if remainder.empty? || !balanced_line?(remainder)
|
|
330
|
+
|
|
331
|
+
return [FixEdit.new(
|
|
332
|
+
start_line: line_idx,
|
|
333
|
+
start_char: unsafe_idx,
|
|
334
|
+
end_line: line_idx,
|
|
335
|
+
end_char: keyword_end + (rest.length - remainder.length),
|
|
336
|
+
new_text: "",
|
|
337
|
+
)]
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Block statement form: `unsafe:` alone, body indented deeper.
|
|
341
|
+
indent = line[0...unsafe_idx]
|
|
342
|
+
return [] unless indent.match?(/\A *\z/)
|
|
343
|
+
|
|
344
|
+
body_start = line_idx + 1
|
|
345
|
+
body_indent = nil
|
|
346
|
+
last_body = nil
|
|
347
|
+
(body_start...lines.length).each do |i|
|
|
348
|
+
candidate = lines[i]
|
|
349
|
+
if candidate.nil? || candidate.strip.empty?
|
|
350
|
+
last_body = i
|
|
351
|
+
next
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
break unless candidate.start_with?(indent + " ")
|
|
355
|
+
|
|
356
|
+
body_indent ||= candidate[/\A */].length - indent.length
|
|
357
|
+
last_body = i
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
return [] if last_body.nil? || last_body < body_start
|
|
361
|
+
return [] if body_indent.nil? || body_indent <= 0
|
|
362
|
+
return [] unless (body_start..last_body).all? do |i|
|
|
363
|
+
l = lines[i]
|
|
364
|
+
l.nil? || l.strip.empty? || l.start_with?(indent + " " * body_indent)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
replacement = (body_start..last_body).map do |i|
|
|
368
|
+
l = lines[i]
|
|
369
|
+
l.nil? || l.strip.empty? ? "" : l[body_indent..].to_s.chomp
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
if last_body + 1 <= lines.length - 1
|
|
373
|
+
# Consume through the first following line; apply_fix_edits re-emits
|
|
374
|
+
# it as the suffix of a multi-line replacement.
|
|
375
|
+
end_line = last_body + 1
|
|
376
|
+
end_char = 0
|
|
377
|
+
else
|
|
378
|
+
end_line = last_body
|
|
379
|
+
end_char = lines[last_body].to_s.length
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
[FixEdit.new(
|
|
383
|
+
start_line: line_idx,
|
|
384
|
+
start_char: 0,
|
|
385
|
+
end_line:,
|
|
386
|
+
end_char:,
|
|
387
|
+
new_text: replacement.join("\n") + "\n",
|
|
388
|
+
)]
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def self.balanced_line?(text)
|
|
392
|
+
depth = 0
|
|
393
|
+
text.each_char do |char|
|
|
394
|
+
case char
|
|
395
|
+
when "(", "["
|
|
396
|
+
depth += 1
|
|
397
|
+
when ")", "]"
|
|
398
|
+
depth -= 1
|
|
399
|
+
return false if depth.negative?
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
depth.zero?
|
|
403
|
+
end
|
|
404
|
+
|
|
305
405
|
def self.match_paren(line, open_pos)
|
|
306
406
|
depth = 0
|
|
307
407
|
(open_pos...line.length).each do |i|
|
|
@@ -258,10 +258,28 @@ module MilkTea
|
|
|
258
258
|
case expr
|
|
259
259
|
when AST::Call
|
|
260
260
|
expr.arguments.any? do |arg|
|
|
261
|
-
next unless arg.is_a?(AST::Argument)
|
|
261
|
+
next false unless arg.is_a?(AST::Argument)
|
|
262
262
|
|
|
263
|
-
|
|
264
|
-
|
|
263
|
+
own_transfer_identifier?(arg.value, name)
|
|
264
|
+
end
|
|
265
|
+
when AST::PrefixCast
|
|
266
|
+
own_transfer_identifier?(expr.expression, name)
|
|
267
|
+
else
|
|
268
|
+
false
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def own_transfer_identifier?(value, name)
|
|
273
|
+
case value
|
|
274
|
+
when AST::Identifier
|
|
275
|
+
value.name == name
|
|
276
|
+
when AST::PrefixCast
|
|
277
|
+
own_transfer_identifier?(value.expression, name)
|
|
278
|
+
when AST::Call
|
|
279
|
+
value.arguments.any? do |arg|
|
|
280
|
+
next false unless arg.is_a?(AST::Argument)
|
|
281
|
+
|
|
282
|
+
own_transfer_identifier?(arg.value, name)
|
|
265
283
|
end
|
|
266
284
|
else
|
|
267
285
|
false
|
|
@@ -426,15 +444,29 @@ module MilkTea
|
|
|
426
444
|
return false unless expr.is_a?(AST::Call)
|
|
427
445
|
|
|
428
446
|
expr.arguments.any? do |arg|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
447
|
+
named = arg.is_a?(AST::Argument) && !arg.name.nil?
|
|
448
|
+
argument_transfers?(arg.value, name, named)
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# An identifier passed as a named argument transfers ownership (struct
|
|
453
|
+
# field initialization), even when wrapped in a pointer cast
|
|
454
|
+
# (e.g. handle_set_data(h, ptr[void]<-state)). Nested calls are walked
|
|
455
|
+
# so `queue.push(Foo(field = x))` recognizes the inner transfer.
|
|
456
|
+
def argument_transfers?(value, name, named)
|
|
457
|
+
case value
|
|
458
|
+
when AST::Identifier
|
|
459
|
+
named && value.name == name
|
|
460
|
+
when AST::PrefixCast
|
|
461
|
+
inner = value.expression
|
|
462
|
+
inner.is_a?(AST::Identifier) && inner.name == name
|
|
463
|
+
when AST::Call
|
|
464
|
+
value.arguments.any? do |arg|
|
|
465
|
+
nested_named = arg.is_a?(AST::Argument) && !arg.name.nil?
|
|
466
|
+
argument_transfers?(arg.value, name, nested_named)
|
|
437
467
|
end
|
|
468
|
+
else
|
|
469
|
+
false
|
|
438
470
|
end
|
|
439
471
|
end
|
|
440
472
|
|
|
@@ -481,6 +513,9 @@ module MilkTea
|
|
|
481
513
|
return if expr.nil?
|
|
482
514
|
|
|
483
515
|
result << expr.callee.receiver.name if release_call_on_binding?(expr)
|
|
516
|
+
if expr.is_a?(AST::Call) && expr.callee.is_a?(AST::Identifier) && expr.callee.name.start_with?("release_")
|
|
517
|
+
result.merge(release_helper_binding_names(expr))
|
|
518
|
+
end
|
|
484
519
|
|
|
485
520
|
case expr
|
|
486
521
|
when AST::Call
|
|
@@ -496,6 +531,26 @@ module MilkTea
|
|
|
496
531
|
end
|
|
497
532
|
end
|
|
498
533
|
|
|
534
|
+
# std convention: `release_*` helper functions take ownership of the
|
|
535
|
+
# bindings handed to them via ref_of/ptr_of, e.g.
|
|
536
|
+
# `release_string_values(ref_of(values))`. Treat those bindings as
|
|
537
|
+
# released so the leak rule understands the cleanup-helper idiom.
|
|
538
|
+
def release_helper_binding_names(expr)
|
|
539
|
+
names = Set.new
|
|
540
|
+
expr.arguments.each do |arg|
|
|
541
|
+
value = arg.respond_to?(:value) ? arg.value : nil
|
|
542
|
+
next unless value.is_a?(AST::Call)
|
|
543
|
+
next unless value.callee.is_a?(AST::Identifier)
|
|
544
|
+
next unless %w[ref_of ptr_of].include?(value.callee.name)
|
|
545
|
+
|
|
546
|
+
value.arguments.each do |inner|
|
|
547
|
+
binding_name = inner.value
|
|
548
|
+
names << binding_name.name if binding_name.is_a?(AST::Identifier)
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
names
|
|
552
|
+
end
|
|
553
|
+
|
|
499
554
|
def _any_stmt?(stmts, &pred)
|
|
500
555
|
return false if stmts.nil?
|
|
501
556
|
|
|
@@ -620,6 +675,9 @@ module MilkTea
|
|
|
620
675
|
return false unless expr
|
|
621
676
|
|
|
622
677
|
return true if release_call_on_binding?(expr) && expr.callee.receiver.name == name
|
|
678
|
+
return true if expr.is_a?(AST::Call) && expr.callee.is_a?(AST::Identifier) &&
|
|
679
|
+
expr.callee.name.start_with?("release_") &&
|
|
680
|
+
release_helper_binding_names(expr).include?(name)
|
|
623
681
|
|
|
624
682
|
case expr
|
|
625
683
|
when AST::Call
|