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
|
@@ -366,6 +366,10 @@ module MilkTea
|
|
|
366
366
|
end
|
|
367
367
|
def check_redundant_unsafe(node)
|
|
368
368
|
return unless @sema_facts
|
|
369
|
+
# Generic bodies are only fully checked once instantiated elsewhere,
|
|
370
|
+
# so required-unsafe facts are incomplete for them: an unsafe block in
|
|
371
|
+
# a generic body may be required by concrete instances.
|
|
372
|
+
return if generic_function_context?
|
|
369
373
|
return if @sema_facts.required_unsafe_lines.include?(node.line)
|
|
370
374
|
|
|
371
375
|
@warnings << Warning.new(
|
|
@@ -830,43 +834,64 @@ module MilkTea
|
|
|
830
834
|
)
|
|
831
835
|
end
|
|
832
836
|
|
|
837
|
+
# Simulates the actual one-line join: every branch body must be a single
|
|
838
|
+
# physical line with balanced delimiters, and the composed line must fit
|
|
839
|
+
# the column budget. Anything that cannot be joined cleanly is reported
|
|
840
|
+
# as "too wide" so the hint is suppressed.
|
|
833
841
|
def inline_if_too_wide?(statement, stmts)
|
|
834
842
|
return false if statement.branches.empty?
|
|
835
843
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
844
|
+
if_line_idx = Integer(statement.line) - 1
|
|
845
|
+
return true if if_line_idx.negative? || if_line_idx >= @source_lines.length
|
|
846
|
+
|
|
847
|
+
header = @source_lines[if_line_idx].to_s
|
|
848
|
+
return true unless header.strip.start_with?("if ")
|
|
849
|
+
|
|
850
|
+
indent = header[/\A[ \t]*/].to_s
|
|
851
|
+
segments = []
|
|
852
|
+
cur_header = header.strip
|
|
853
|
+
cur_body = nil
|
|
854
|
+
idx = if_line_idx + 1
|
|
855
|
+
while idx < @source_lines.length
|
|
856
|
+
line = @source_lines[idx].to_s.rstrip
|
|
857
|
+
break if line.empty?
|
|
858
|
+
|
|
859
|
+
line_indent = line[/\A[ \t]*/].to_s
|
|
860
|
+
if line_indent == indent && (line.strip == "else:" || line.strip.start_with?("else if "))
|
|
861
|
+
return true if cur_body.nil?
|
|
862
|
+
|
|
863
|
+
segments << [cur_header, cur_body]
|
|
864
|
+
cur_header = line.strip
|
|
865
|
+
cur_body = nil
|
|
866
|
+
idx += 1
|
|
867
|
+
next
|
|
868
|
+
end
|
|
869
|
+
break if line_indent.length <= indent.length
|
|
870
|
+
return true unless cur_body.nil?
|
|
852
871
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
return false unless else_src
|
|
856
|
-
text << else_src
|
|
857
|
-
text << " "
|
|
872
|
+
cur_body = line.strip
|
|
873
|
+
idx += 1
|
|
858
874
|
end
|
|
875
|
+
return true if cur_body.nil?
|
|
876
|
+
return true unless cur_header.strip == "else:"
|
|
859
877
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
text << else_body
|
|
878
|
+
segments << [cur_header, cur_body]
|
|
879
|
+
joined = indent + segments.map { |h, b| "#{h} #{b}" }.join(" ")
|
|
880
|
+
joined.length > 120 || !balanced_inline_line?(joined)
|
|
881
|
+
end
|
|
865
882
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
883
|
+
def balanced_inline_line?(text)
|
|
884
|
+
depth = 0
|
|
885
|
+
text.each_char do |ch|
|
|
886
|
+
case ch
|
|
887
|
+
when "(", "["
|
|
888
|
+
depth += 1
|
|
889
|
+
when ")", "]"
|
|
890
|
+
depth -= 1
|
|
891
|
+
return false if depth.negative?
|
|
892
|
+
end
|
|
893
|
+
end
|
|
894
|
+
depth.zero?
|
|
870
895
|
end
|
|
871
896
|
|
|
872
897
|
def source_line_from(line, column)
|
|
@@ -966,7 +991,9 @@ module MilkTea
|
|
|
966
991
|
struct_type = resolve_expr_type(call)
|
|
967
992
|
return unless struct_type.is_a?(Types::Struct)
|
|
968
993
|
source_type = resolve_expr_type(copied.first.value.receiver)
|
|
969
|
-
|
|
994
|
+
# Compare qualified names: bare names collide across modules
|
|
995
|
+
# (e.g. net::Config vs chan::Config).
|
|
996
|
+
return unless source_type.respond_to?(:to_s) && source_type.to_s == struct_type.to_s
|
|
970
997
|
|
|
971
998
|
field_names = struct_type.fields.keys.map(&:to_s).to_set
|
|
972
999
|
copied_names = copied.map { |arg| arg.name.to_s }.to_set
|
|
@@ -77,6 +77,7 @@ module MilkTea
|
|
|
77
77
|
redundant-else
|
|
78
78
|
redundant-return
|
|
79
79
|
redundant-type-annotation
|
|
80
|
+
redundant-unsafe
|
|
80
81
|
reserved-primitive-name
|
|
81
82
|
trailing-list-comma
|
|
82
83
|
].freeze
|
|
@@ -96,6 +97,7 @@ module MilkTea
|
|
|
96
97
|
"redundant-else" => "Remove redundant else",
|
|
97
98
|
"redundant-return" => "Remove redundant return",
|
|
98
99
|
"redundant-type-annotation" => "Remove redundant type annotation",
|
|
100
|
+
"redundant-unsafe" => "Remove redundant unsafe",
|
|
99
101
|
"prefer-let-else" => "Rewrite as let-else",
|
|
100
102
|
"prefer-var-else" => "Rewrite as var-else",
|
|
101
103
|
"prefer-inline-methods" => "Inline methods into struct",
|
|
@@ -358,7 +360,7 @@ module MilkTea
|
|
|
358
360
|
# redundant-else,
|
|
359
361
|
# redundant-return, reserved-primitive-name,
|
|
360
362
|
# trailing-list-comma.
|
|
361
|
-
# Returns the fixed source (may be identical
|
|
363
|
+
# Returns the fixed source (may be identical to nothing-was-fixable input).
|
|
362
364
|
def self.fix_source(source, path: nil, sema_facts: nil, select: nil, ignore: nil, max_passes: 5, profile: nil)
|
|
363
365
|
pass_limit = [max_passes.to_i, 1].max
|
|
364
366
|
current_source = source
|
|
@@ -446,6 +448,11 @@ module MilkTea
|
|
|
446
448
|
working_context = sema_facts ? nil : best_effort_lint_context(source, path:)
|
|
447
449
|
working_sema_facts = sema_facts || working_context[:facts]
|
|
448
450
|
unresolved_import_paths = sema_facts ? Set.new : working_context[:unresolved_import_paths]
|
|
451
|
+
# With unresolved imports the analysis facts are incomplete (receiver
|
|
452
|
+
# editability, casts, ownership), so hint rules can misfire and fixes
|
|
453
|
+
# would land on false positives. Leave such sources to the author.
|
|
454
|
+
return source if unresolved_import_paths.any?
|
|
455
|
+
|
|
449
456
|
warnings = lint_source(
|
|
450
457
|
source,
|
|
451
458
|
path:,
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MilkTea
|
|
4
|
+
# Discovers the hand-written standard library modules shipped in std/, and
|
|
5
|
+
# resolves module names to source paths for tooling commands.
|
|
6
|
+
#
|
|
7
|
+
# Generated modules (bindgen output under std/c/ and imported-binding
|
|
8
|
+
# wrappers such as raylib or zstd) are excluded from discovery but stay
|
|
9
|
+
# resolvable, so `std show` can still print them.
|
|
10
|
+
class StdCatalog
|
|
11
|
+
Entry = Struct.new(:name, :description, :category, :path, keyword_init: true)
|
|
12
|
+
|
|
13
|
+
GENERATED_HEADER_PREFIX = "# generated by mtc"
|
|
14
|
+
PLATFORM_SUFFIXES = %w[linux windows wasm].freeze
|
|
15
|
+
EXCLUDED_DIRECTORIES = %w[c].freeze
|
|
16
|
+
|
|
17
|
+
CATEGORY_ORDER = [
|
|
18
|
+
"Core & Patterns",
|
|
19
|
+
"Collections",
|
|
20
|
+
"Memory",
|
|
21
|
+
"Text & Formatting",
|
|
22
|
+
"Math & Simulation",
|
|
23
|
+
"Data & Serialization",
|
|
24
|
+
"Concurrency & Time",
|
|
25
|
+
"Files & System",
|
|
26
|
+
"Network",
|
|
27
|
+
"Security",
|
|
28
|
+
"AI & Behavior",
|
|
29
|
+
"Game & Graphics",
|
|
30
|
+
].freeze
|
|
31
|
+
|
|
32
|
+
# name => [category, description]. Must cover every hand-written module
|
|
33
|
+
# discovered under std/; a test enforces that coverage.
|
|
34
|
+
REGISTRY = {
|
|
35
|
+
"asset_pack" => ["Game & Graphics", "MTAP asset pack reader and writer for bundled runtime assets."],
|
|
36
|
+
"async" => ["Concurrency & Time", "Async/await runtime facade over a pluggable event-loop backend."],
|
|
37
|
+
"async.libuv_runtime" => ["Concurrency & Time", "libuv-backed async runtime implementation."],
|
|
38
|
+
"async.mailbox" => ["Concurrency & Time", "Typed mailboxes for message passing between async tasks."],
|
|
39
|
+
"async.runtime" => ["Concurrency & Time", "Async backend selection and event-loop plumbing shared by async code."],
|
|
40
|
+
"base64" => ["Text & Formatting", "Base64 encode and decode."],
|
|
41
|
+
"behavior_tree" => ["AI & Behavior", "Behavior trees with sequence, selector, decorator, and leaf nodes."],
|
|
42
|
+
"binary" => ["Data & Serialization", "Binary reader/writer with explicit endianness and error reporting."],
|
|
43
|
+
"binary_heap" => ["Collections", "Binary heap over a flat array with sift-up/sift-down operations."],
|
|
44
|
+
"bitset" => ["Collections", "Fixed-capacity bit array with set, clear, test, and population count."],
|
|
45
|
+
"blackboard" => ["AI & Behavior", "Typed key-value shared state for AI agents."],
|
|
46
|
+
"box" => ["Core & Patterns", "Explicit single-value heap storage for owned boxed values."],
|
|
47
|
+
"bytes" => ["Data & Serialization", "Byte buffer type with slicing, comparison, and stream I/O."],
|
|
48
|
+
"cli" => ["Files & System", "Command-line parsing helpers for flags, options, and positionals."],
|
|
49
|
+
"cmd" => ["Core & Patterns", "Command pattern with paired do/undo callbacks for undo/redo stacks."],
|
|
50
|
+
"color" => ["Game & Graphics", "RGBA color conversion, blending, and named presets."],
|
|
51
|
+
"cookie" => ["Data & Serialization", "HTTP cookie parsing and serialization."],
|
|
52
|
+
"counter" => ["Collections", "Occurrence counter keyed by value, a counting multiset."],
|
|
53
|
+
"crypto" => ["Security", "Digests, HMAC, and secure random via the platform crypto bindings."],
|
|
54
|
+
"cstring" => ["Text & Formatting", "Null-terminated C string helpers backed by libc memory routines."],
|
|
55
|
+
"ctype" => ["Text & Formatting", "Character classification: alnum, digit, space, and case tests."],
|
|
56
|
+
"curl.runtime" => ["Network", "libcurl runtime glue for handle setup and error reporting."],
|
|
57
|
+
"deque" => ["Collections", "Double-ended queue with amortized O(1) operations at both ends."],
|
|
58
|
+
"encoding" => ["Text & Formatting", "UTF-8 validation, codepoint iteration, and encoding helpers."],
|
|
59
|
+
"env" => ["Files & System", "Environment variable access returning Option values."],
|
|
60
|
+
"errno" => ["Files & System", "errno code constants and error message lookup."],
|
|
61
|
+
"fmt" => ["Text & Formatting", "Value formatting helpers for integers, floats, and fixed buffers."],
|
|
62
|
+
"fs" => ["Files & System", "File and directory operations with platform-specific variants."],
|
|
63
|
+
"fsm" => ["AI & Behavior", "Finite state machines with table-driven transitions and dispatch."],
|
|
64
|
+
"goap" => ["AI & Behavior", "Goal-oriented action planning (GOAP) with forward search."],
|
|
65
|
+
"graph" => ["Game & Graphics", "Weighted graph algorithms such as shortest paths and reachability."],
|
|
66
|
+
"gzip" => ["Data & Serialization", "Gzip and zlib compression and decompression."],
|
|
67
|
+
"hash" => ["Core & Patterns", "Canonical hash, equality, and ordering for common primitive types."],
|
|
68
|
+
"htn" => ["AI & Behavior", "Hierarchical Task Network (HTN) planner."],
|
|
69
|
+
"http" => ["Network", "HTTP client for requests, responses, and chunked bodies."],
|
|
70
|
+
"http.server" => ["Network", "Small HTTP server with routing, static files, and JSON responses."],
|
|
71
|
+
"input" => ["Game & Graphics", "Input action mapping that decouples logical actions from physical devices."],
|
|
72
|
+
"intern" => ["Text & Formatting", "String interning table producing unique identifier handles."],
|
|
73
|
+
"iter" => ["Core & Patterns", "Composable lazy iteration adaptors over sequences."],
|
|
74
|
+
"jobs" => ["Concurrency & Time", "Job scheduler for fan-out parallel work with completion tracking."],
|
|
75
|
+
"json" => ["Data & Serialization", "JSON parsing and serialization over cjson with arena-backed values."],
|
|
76
|
+
"libc" => ["Files & System", "Curated libc facade for memory, string, process, and math functions."],
|
|
77
|
+
"linear_algebra" => ["Math & Simulation", "Vectors and matrices for 2D/3D graphics and physics."],
|
|
78
|
+
"linked_map" => ["Collections", "Hash map with a doubly-linked order list for O(1) reordering."],
|
|
79
|
+
"linked_map_view" => ["Collections", "Read-only snapshot views over linked_map instances."],
|
|
80
|
+
"linked_set" => ["Collections", "Insertion-ordered hash set backed by a linked list."],
|
|
81
|
+
"log" => ["Files & System", "Leveled logging with configurable output sinks."],
|
|
82
|
+
"lru_cache" => ["Collections", "Least-recently-used cache with bounded capacity."],
|
|
83
|
+
"map" => ["Collections", "Chained hash map with pluggable hashing for generic keys and values."],
|
|
84
|
+
"math" => ["Math & Simulation", "Mathematical constants and functions beyond the language builtins."],
|
|
85
|
+
"mem.arena" => ["Memory", "Region allocator: bump allocation with O(1) mark/reset."],
|
|
86
|
+
"mem.endian" => ["Memory", "Byte-swap and endian conversion helpers."],
|
|
87
|
+
"mem.heap" => ["Memory", "General-purpose heap allocator wrapper with alignment control."],
|
|
88
|
+
"mem.pool" => ["Memory", "Fixed-slot pool allocator for uniform-size objects."],
|
|
89
|
+
"mem.ptr" => ["Memory", "Safe pointer load/store wrappers around raw memory access."],
|
|
90
|
+
"mem.stack" => ["Memory", "Stack allocator with mark/release layered on the arena."],
|
|
91
|
+
"mem.tracking" => ["Memory", "Allocation tracker for leak detection and usage diagnostics."],
|
|
92
|
+
"multiset" => ["Collections", "Hash multiset that counts duplicate values."],
|
|
93
|
+
"net" => ["Network", "UDP and TCP sockets, address resolution, and packet I/O."],
|
|
94
|
+
"net.channel" => ["Network", "Multiplexed message channels over a single connection."],
|
|
95
|
+
"net.clock" => ["Network", "Synchronized network clock with ping and offset estimation."],
|
|
96
|
+
"net.discovery" => ["Network", "LAN service discovery via UDP broadcast."],
|
|
97
|
+
"net.lobby" => ["Network", "Lobby listing and joining built on discovery."],
|
|
98
|
+
"net.manager" => ["Network", "Connection and session manager for multiplayer topologies."],
|
|
99
|
+
"net.mux" => ["Network", "Stream multiplexer with reliable and unordered delivery flags."],
|
|
100
|
+
"net.nat" => ["Network", "NAT type detection and address binding via STUN."],
|
|
101
|
+
"net.packet" => ["Network", "Framed packet reading and writing over byte streams."],
|
|
102
|
+
"net.punch" => ["Network", "UDP hole punching for peer-to-peer connections."],
|
|
103
|
+
"net.rpc" => ["Network", "Request/response RPC framing over net channels."],
|
|
104
|
+
"net.session" => ["Network", "Client session state machine with heartbeats and timeouts."],
|
|
105
|
+
"net.stun" => ["Network", "STUN client for public address and port discovery."],
|
|
106
|
+
"net.sync" => ["Network", "State synchronization primitives for replicated values."],
|
|
107
|
+
"net.turn" => ["Network", "TURN relay client for NAT-restricted peers."],
|
|
108
|
+
"noise" => ["Math & Simulation", "Coherent Perlin noise and fractal variants."],
|
|
109
|
+
"oauth2" => ["Data & Serialization", "OAuth2 token requests and refresh flows."],
|
|
110
|
+
"option" => ["Core & Patterns", "Option[T] optional values, some(value) or none, with combinators."],
|
|
111
|
+
"ordered_map" => ["Collections", "Hash map that preserves insertion order."],
|
|
112
|
+
"ordered_set" => ["Collections", "Hash set that preserves insertion order."],
|
|
113
|
+
"parse" => ["Text & Formatting", "Numeric string parsing for integers and floats."],
|
|
114
|
+
"path" => ["Files & System", "Path join, split, and normalization utilities."],
|
|
115
|
+
"pcre2.runtime" => ["Text & Formatting", "Compiled regex matching runtime over the pcre2 bindings."],
|
|
116
|
+
"pool" => ["Collections", "Fixed-capacity reusable object storage."],
|
|
117
|
+
"priority_queue" => ["Collections", "Priority queue backed by a binary heap."],
|
|
118
|
+
"process" => ["Files & System", "Child process spawning with pipes and exit status."],
|
|
119
|
+
"queue" => ["Collections", "First-in first-out queue."],
|
|
120
|
+
"random" => ["Math & Simulation", "PCG pseudo-random number generation with seeding and ranges."],
|
|
121
|
+
"raylib.debug_console" => ["Game & Graphics", "In-game debug console overlay with command history."],
|
|
122
|
+
"raylib.easing" => ["Game & Graphics", "Easing curves for animation interpolation."],
|
|
123
|
+
"raylib.packed_assets" => ["Game & Graphics", "Load images, textures, and audio from assets.mtpack packs."],
|
|
124
|
+
"raylib.runtime" => ["Game & Graphics", "raylib app lifecycle helpers: window loop, asset directory, fatal errors."],
|
|
125
|
+
"raylib.tracy_gpu" => ["Game & Graphics", "Tracy profiler GPU zone calibration for raylib."],
|
|
126
|
+
"result" => ["Core & Patterns", "Result[T, E] success/failure type for recoverable errors."],
|
|
127
|
+
"ring_buffer" => ["Collections", "Fixed-capacity circular buffer with overwrite behavior."],
|
|
128
|
+
"sdl3.runtime" => ["Game & Graphics", "SDL3 app lifecycle helpers and main-loop wrappers."],
|
|
129
|
+
"serialize" => ["Data & Serialization", "Struct field serialization helpers layered on std.binary."],
|
|
130
|
+
"set" => ["Collections", "Hash set of unique values."],
|
|
131
|
+
"signal" => ["Concurrency & Time", "Fixed-capacity observer and publish-subscribe signals."],
|
|
132
|
+
"simd" => ["Math & Simulation", "SIMD load, store, and reduction helpers."],
|
|
133
|
+
"sparse_set" => ["Collections", "Sparse set with dense iteration for integer-keyed data."],
|
|
134
|
+
"spatial" => ["Math & Simulation", "Uniform spatial hash grid for broad-phase queries."],
|
|
135
|
+
"stack" => ["Collections", "Last-in first-out stack."],
|
|
136
|
+
"steering" => ["AI & Behavior", "Craig Reynolds steering behaviors for autonomous motion."],
|
|
137
|
+
"stdio" => ["Files & System", "Stdin, stdout, and stderr FILE wrappers and console I/O."],
|
|
138
|
+
"str" => ["Text & Formatting", "Borrowed string slice helpers: length, compare, search, split."],
|
|
139
|
+
"string" => ["Text & Formatting", "Heap-owned growable string type with builder operations."],
|
|
140
|
+
"sync" => ["Concurrency & Time", "Mutex, condition variable, and once primitives over libuv."],
|
|
141
|
+
"tar" => ["Data & Serialization", "TAR archive reading and writing with 512-byte blocks."],
|
|
142
|
+
"terminal" => ["Files & System", "Terminal size, color, and raw-mode helpers."],
|
|
143
|
+
"thread" => ["Concurrency & Time", "Threads via libuv with join and handle management."],
|
|
144
|
+
"time" => ["Concurrency & Time", "Wall-clock timestamps and monotonic time helpers."],
|
|
145
|
+
"timer" => ["Concurrency & Time", "Countdown and repeating timers."],
|
|
146
|
+
"tls" => ["Security", "TLS client and server streams over libuv handles."],
|
|
147
|
+
"toml" => ["Data & Serialization", "TOML parser for configuration files."],
|
|
148
|
+
"tween" => ["Game & Graphics", "Tweening and easing for smooth value interpolation."],
|
|
149
|
+
"uri" => ["Data & Serialization", "URI parsing and construction, including file and web schemes."],
|
|
150
|
+
"url" => ["Data & Serialization", "URL parsing: scheme, host, path, query, and percent-encoding."],
|
|
151
|
+
"utility" => ["AI & Behavior", "Utility AI with score-based action selection."],
|
|
152
|
+
"vec" => ["Collections", "Growable contiguous array Vec[T], the default dynamic sequence."],
|
|
153
|
+
}.freeze
|
|
154
|
+
|
|
155
|
+
class << self
|
|
156
|
+
def std_root
|
|
157
|
+
File.join(MilkTea.root.to_s, "std")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def entries
|
|
161
|
+
discover
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Resolves a module name ("mem.arena", "mem/arena", "c.cjson", "fs") to a
|
|
165
|
+
# source path. Prefers the active platform variant, matching the
|
|
166
|
+
# compiler's import resolution rule. Returns nil when unresolvable.
|
|
167
|
+
def resolve(name)
|
|
168
|
+
clean = name.to_s.strip
|
|
169
|
+
return nil if clean.empty? || clean.include?("..")
|
|
170
|
+
|
|
171
|
+
relative = clean.tr(".", "/").delete_suffix(".mt")
|
|
172
|
+
root = std_root
|
|
173
|
+
|
|
174
|
+
variant = File.join(root, "#{relative}.#{MilkTea.host_platform}.mt")
|
|
175
|
+
return variant if File.file?(variant)
|
|
176
|
+
|
|
177
|
+
shared = File.join(root, "#{relative}.mt")
|
|
178
|
+
return shared if File.file?(shared)
|
|
179
|
+
|
|
180
|
+
nil
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def discover
|
|
184
|
+
root = std_root
|
|
185
|
+
by_name = {}
|
|
186
|
+
|
|
187
|
+
module_files(root).each do |path|
|
|
188
|
+
add_module(by_name, logical_name(path), path)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
namespace_directories(root).each do |directory|
|
|
192
|
+
namespace = File.basename(directory)
|
|
193
|
+
module_files(directory).each do |path|
|
|
194
|
+
add_module(by_name, "#{namespace}.#{logical_name(path)}", path)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
by_name.values.sort_by(&:name)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def add_module(by_name, name, path)
|
|
202
|
+
return if generated?(path)
|
|
203
|
+
|
|
204
|
+
category, description = REGISTRY.fetch(name, [nil, nil])
|
|
205
|
+
description ||= doc_comment_summary(path)
|
|
206
|
+
entry = by_name[name]
|
|
207
|
+
unless entry
|
|
208
|
+
by_name[name] = Entry.new(name:, description:, category:, path:)
|
|
209
|
+
return
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
entry.path = path if prefer_path?(path, entry.path)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# A shared file beats any variant; among variants the host platform
|
|
216
|
+
# wins, then the first alphabetically.
|
|
217
|
+
def prefer_path?(candidate, current)
|
|
218
|
+
candidate_rank = path_rank(candidate)
|
|
219
|
+
current_rank = path_rank(current)
|
|
220
|
+
order = candidate_rank <=> current_rank
|
|
221
|
+
order.negative? || (order.zero? && candidate < current)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def path_rank(path)
|
|
225
|
+
host = MilkTea.host_platform.to_s
|
|
226
|
+
[platform_variant?(path) ? 1 : 0, platform_of(path) == host ? 0 : 1]
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def module_files(directory)
|
|
230
|
+
Dir.glob(File.join(directory, "*.mt")).sort
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def namespace_directories(root)
|
|
234
|
+
Dir.children(root).map { |child| File.join(root, child) }
|
|
235
|
+
.select { |path| File.directory?(path) }
|
|
236
|
+
.reject { |path| EXCLUDED_DIRECTORIES.include?(File.basename(path)) }
|
|
237
|
+
.sort
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def logical_name(path)
|
|
241
|
+
stem = File.basename(path, ".mt")
|
|
242
|
+
platform = platform_of(path)
|
|
243
|
+
platform ? stem.delete_suffix(".#{platform}") : stem
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def platform_variant?(path)
|
|
247
|
+
PLATFORM_SUFFIXES.include?(platform_of(path))
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def platform_of(path)
|
|
251
|
+
stem = File.basename(path, ".mt")
|
|
252
|
+
suffix = stem[/\.([^.]+)\z/, 1]
|
|
253
|
+
suffix && PLATFORM_SUFFIXES.include?(suffix) ? suffix : nil
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def generated?(path)
|
|
257
|
+
File.open(path, "r") { |file| file.readline.start_with?(GENERATED_HEADER_PREFIX) }
|
|
258
|
+
rescue EOFError, Errno::ENOENT
|
|
259
|
+
false
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def doc_comment_summary(path)
|
|
263
|
+
File.foreach(path) do |line|
|
|
264
|
+
stripped = line.strip
|
|
265
|
+
next if stripped.empty?
|
|
266
|
+
|
|
267
|
+
return stripped.sub(/\A##\s?/, "").rstrip if stripped.start_with?("##")
|
|
268
|
+
|
|
269
|
+
break
|
|
270
|
+
end
|
|
271
|
+
nil
|
|
272
|
+
rescue Errno::ENOENT
|
|
273
|
+
nil
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
data/lib/milk_tea/tooling.rb
CHANGED
|
@@ -15,6 +15,7 @@ require_relative "tooling/error_formatter"
|
|
|
15
15
|
require_relative "tooling/formatter"
|
|
16
16
|
require_relative "tooling/linter"
|
|
17
17
|
require_relative "tooling/docs_app"
|
|
18
|
+
require_relative "tooling/std_catalog"
|
|
18
19
|
require_relative "tooling/project_scaffold"
|
|
19
20
|
require_relative "tooling/toolchain_cli"
|
|
20
21
|
require_relative "tooling/bindgen_cli"
|
data/std/async/libuv_runtime.mt
CHANGED
|
@@ -79,23 +79,22 @@ function work_as_req(work: ptr[NativeWorkRequest]) -> ptr[NativeRequest]:
|
|
|
79
79
|
return unsafe: ptr[NativeRequest]<-work
|
|
80
80
|
|
|
81
81
|
|
|
82
|
-
function close_all_handles_cb(handle: ptr[NativeHandle],
|
|
82
|
+
function close_all_handles_cb(handle: ptr[NativeHandle], _arg: ptr[void]) -> void:
|
|
83
83
|
if libuv.is_closing(handle) == 0:
|
|
84
84
|
libuv.close(handle, close_all_handles_close_cb)
|
|
85
|
-
unsafe: arg
|
|
86
85
|
|
|
87
86
|
|
|
88
|
-
function close_all_handles_close_cb(
|
|
89
|
-
|
|
87
|
+
function close_all_handles_close_cb(_handle: ptr[NativeHandle]) -> void:
|
|
88
|
+
pass
|
|
90
89
|
|
|
91
90
|
|
|
92
|
-
function noop_waiter(
|
|
93
|
-
|
|
91
|
+
function noop_waiter(_frame: ptr[void]) -> void:
|
|
92
|
+
pass
|
|
94
93
|
|
|
95
94
|
|
|
96
95
|
function require_current_runtime() -> Runtime:
|
|
97
96
|
if not current_runtime_active:
|
|
98
|
-
fatal(c"async runtime requires an active runtime; use async.wait or async.run, or call the
|
|
97
|
+
fatal(c"async runtime requires an active runtime; use async.wait or async.run, or call the *_on helpers")
|
|
99
98
|
return current_runtime
|
|
100
99
|
|
|
101
100
|
|
|
@@ -337,20 +336,19 @@ public function sleep_on(runtime: Runtime, timeout: ptr_uint) -> Task[int]:
|
|
|
337
336
|
let loop = live_loop(runtime)
|
|
338
337
|
|
|
339
338
|
let state = heap.must_alloc_zeroed[SleepState](1)
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
state.released = false
|
|
339
|
+
state.ready = false
|
|
340
|
+
state.status = 0
|
|
341
|
+
state.waiter_frame = null
|
|
342
|
+
state.waiter = noop_waiter
|
|
343
|
+
state.waiter_registered = false
|
|
344
|
+
state.timer = null
|
|
345
|
+
state.closing = false
|
|
346
|
+
state.closed = false
|
|
347
|
+
state.released = false
|
|
350
348
|
|
|
351
349
|
let timer_size = libuv.handle_size(libuv.uv_handle_type.UV_TIMER)
|
|
352
350
|
let timer = unsafe: ptr[NativeTimerHandle]<-heap.must_alloc_zeroed_bytes(1, timer_size)
|
|
353
|
-
|
|
351
|
+
state.timer = timer
|
|
354
352
|
|
|
355
353
|
let init_status = libuv.timer_init(loop, timer)
|
|
356
354
|
if init_status != 0:
|
|
@@ -367,11 +365,10 @@ public function sleep_on(runtime: Runtime, timeout: ptr_uint) -> Task[int]:
|
|
|
367
365
|
|
|
368
366
|
let start_status = libuv.timer_start(timer, sleep_timer_fire, timeout, 0)
|
|
369
367
|
if start_status != 0:
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
libuv.close(timer_as_handle(timer), sleep_timer_close)
|
|
368
|
+
state.status = start_status
|
|
369
|
+
state.ready = true
|
|
370
|
+
state.closing = true
|
|
371
|
+
libuv.close(timer_as_handle(timer), sleep_timer_close)
|
|
375
372
|
|
|
376
373
|
return sleep_task(state)
|
|
377
374
|
|
data/std/async/mailbox.mt
CHANGED
|
@@ -45,8 +45,8 @@ function handle_as_async(handle: ptr[NativeHandle]) -> ptr[NativeAsyncHandle]:
|
|
|
45
45
|
return unsafe: ptr[NativeAsyncHandle]<-handle
|
|
46
46
|
|
|
47
47
|
|
|
48
|
-
function mailbox_async_callback(
|
|
49
|
-
|
|
48
|
+
function mailbox_async_callback(_handle: ptr[NativeAsyncHandle]) -> void:
|
|
49
|
+
pass
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
function mailbox_destroy_state[T](state_frame: ptr[void]) -> void:
|
data/std/base64.mt
CHANGED
|
@@ -163,18 +163,15 @@ public function decode(text_value: str) -> Result[bytes.Bytes, Error]:
|
|
|
163
163
|
|
|
164
164
|
let combined = (ulong<-raw0 << 18) | (ulong<-raw1 << 12) | (ulong<-raw2 << 6) | ulong<-raw3
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
read(output + out_index) = ubyte<-( (combined >> 16) & 0xFF)
|
|
166
|
+
read(output + out_index) = ubyte<-( (combined >> 16) & 0xFF)
|
|
168
167
|
out_index += 1
|
|
169
168
|
|
|
170
169
|
if out_index < output_len:
|
|
171
|
-
|
|
172
|
-
read(output + out_index) = ubyte<-( (combined >> 8) & 0xFF)
|
|
170
|
+
read(output + out_index) = ubyte<-( (combined >> 8) & 0xFF)
|
|
173
171
|
out_index += 1
|
|
174
172
|
|
|
175
173
|
if out_index < output_len:
|
|
176
|
-
|
|
177
|
-
read(output + out_index) = ubyte<-(combined & 0xFF)
|
|
174
|
+
read(output + out_index) = ubyte<-(combined & 0xFF)
|
|
178
175
|
out_index += 1
|
|
179
176
|
|
|
180
177
|
return Result[bytes.Bytes, Error].success(value = bytes.Bytes(data = output, len = output_len))
|
data/std/behavior_tree.mt
CHANGED
|
@@ -233,11 +233,8 @@ function tick_node[Context](tree: ref[Tree[Context]], node_id: ptr_uint, context
|
|
|
233
233
|
match child_status:
|
|
234
234
|
Status.success:
|
|
235
235
|
return Status.success
|
|
236
|
-
Status.failure:
|
|
237
|
-
return Status.running
|
|
238
|
-
Status.running:
|
|
236
|
+
Status.failure | Status.running:
|
|
239
237
|
return Status.running
|
|
240
|
-
|
|
241
238
|
NodeKind.until_failure:
|
|
242
239
|
let child_ptr = read(node).children.first() else:
|
|
243
240
|
return Status.failure
|
|
@@ -251,8 +248,6 @@ function tick_node[Context](tree: ref[Tree[Context]], node_id: ptr_uint, context
|
|
|
251
248
|
Status.running:
|
|
252
249
|
return Status.running
|
|
253
250
|
|
|
254
|
-
return Status.failure
|
|
255
|
-
|
|
256
251
|
|
|
257
252
|
extending Node[Context]:
|
|
258
253
|
static function create(kind: NodeKind) -> Node[Context]:
|
data/std/binary.mt
CHANGED
|
@@ -95,10 +95,7 @@ extending Writer:
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
public editable function write_bool(value: bool) -> void:
|
|
98
|
-
if value:
|
|
99
|
-
writer_append_byte(ref_of(this), 1)
|
|
100
|
-
else:
|
|
101
|
-
writer_append_byte(ref_of(this), 0)
|
|
98
|
+
if value: writer_append_byte(ref_of(this), 1) else: writer_append_byte(ref_of(this), 0)
|
|
102
99
|
|
|
103
100
|
|
|
104
101
|
public editable function write_bytes(data: span[ubyte]) -> void:
|
|
@@ -268,7 +265,7 @@ extending Reader:
|
|
|
268
265
|
|
|
269
266
|
public editable function read_span(count: ptr_uint) -> Result[span[ubyte], Error]:
|
|
270
267
|
if count == 0:
|
|
271
|
-
let empty =
|
|
268
|
+
let empty = span[ubyte](data = this.data.data, len = 0)
|
|
272
269
|
return Result[span[ubyte], Error].success(value = empty)
|
|
273
270
|
|
|
274
271
|
reader_check_remaining(ref_of(this), count)?
|