|
| 1 | +// SPDX-License-Identifier: EPL-2.0 |
| 2 | +//! Java surface for `java.util.Collections` static methods (D-526). |
| 3 | +//! |
| 4 | +//! Backend: impl-only |
| 5 | +//! Impl deps: none |
| 6 | +//! Clojure peer: clojure.core/sort, clojure.core/reverse, clojure.core/max, clojure.core/min |
| 7 | +//! |
| 8 | +//! The common static surface: `emptyList` / `singletonList` (fresh |
| 9 | +//! java.util.ArrayList host instances — the JVM's IMMUTABLE list types are |
| 10 | +//! deliberately not mirrored; cljw has no unmodifiable wrapper, and no cljw |
| 11 | +//! consumer depends on the mutation-throw) / `sort` + `reverse` (in-place on |
| 12 | +//! an ArrayList) / `max` + `min` (natural `compare` over a vector / Java |
| 13 | +//! array / ArrayList). `unmodifiable*` / `shuffle` / `synchronized*` are |
| 14 | +//! intentionally omitted until a real consumer needs them (dead-interop |
| 15 | +//! lesson) — an unmodifiable view faked as a mutable list would be a lie. |
| 16 | + |
| 17 | +const std = @import("std"); |
| 18 | +const host_api = @import("../_host_api.zig"); |
| 19 | +const type_descriptor = @import("../../type_descriptor.zig"); |
| 20 | +const Value = @import("../../value/value.zig").Value; |
| 21 | +const Runtime = @import("../../runtime.zig").Runtime; |
| 22 | +const Env = @import("../../env.zig").Env; |
| 23 | +const SourceLocation = @import("../../error/info.zig").SourceLocation; |
| 24 | +const error_catalog = @import("../../error/catalog.zig"); |
| 25 | +const array_list_surface = @import("ArrayList.zig"); |
| 26 | +const java_array = @import("../../collection/java_array.zig"); |
| 27 | +const vector_mod = @import("../../collection/vector.zig"); |
| 28 | +const compare_mod = @import("../../compare.zig"); |
| 29 | + |
| 30 | +/// `(java.util.Collections/emptyList)` — a fresh empty ArrayList (see the |
| 31 | +/// module doc's immutability note). JVM ref: Collections#emptyList. |
| 32 | +fn emptyList(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 33 | + try error_catalog.checkArity("java.util.Collections/emptyList", args, 0, loc); |
| 34 | + return array_list_surface.initArrayList(rt, env, &.{}, loc); |
| 35 | +} |
| 36 | + |
| 37 | +/// `(java.util.Collections/singletonList x)` — a fresh 1-element ArrayList. |
| 38 | +fn singletonList(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 39 | + try error_catalog.checkArity("java.util.Collections/singletonList", args, 1, loc); |
| 40 | + const vec = try vector_mod.fromSlice(rt, args[0..1]); |
| 41 | + return array_list_surface.initArrayList(rt, env, &.{vec}, loc); |
| 42 | +} |
| 43 | + |
| 44 | +fn expectArrayList(v: Value, fn_name: []const u8, loc: SourceLocation) !void { |
| 45 | + if (!array_list_surface.isArrayList(v)) |
| 46 | + return error_catalog.raise(.type_arg_invalid, loc, .{ .fn_name = fn_name, .expected = "a java.util.ArrayList", .actual = @tagName(v.tag()) }); |
| 47 | +} |
| 48 | + |
| 49 | +const SortCtx = struct { |
| 50 | + rt: *Runtime, |
| 51 | + loc: SourceLocation, |
| 52 | + err: ?anyerror = null, |
| 53 | +}; |
| 54 | + |
| 55 | +fn lessThan(ctx: *SortCtx, a: Value, b: Value) bool { |
| 56 | + if (ctx.err != null) return false; |
| 57 | + const ord = compare_mod.valueCompare(ctx.rt, a, b, ctx.loc) catch |e| { |
| 58 | + ctx.err = e; |
| 59 | + return false; |
| 60 | + }; |
| 61 | + return ord == .lt; |
| 62 | +} |
| 63 | + |
| 64 | +/// `(java.util.Collections/sort list)` — IN-PLACE natural-order stable sort |
| 65 | +/// of an ArrayList; returns nil (void in Java). |
| 66 | +fn sortFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 67 | + _ = env; |
| 68 | + try error_catalog.checkArity("java.util.Collections/sort", args, 1, loc); |
| 69 | + try expectArrayList(args[0], "java.util.Collections/sort", loc); |
| 70 | + var ctx: SortCtx = .{ .rt = rt, .loc = loc }; |
| 71 | + std.mem.sort(Value, array_list_surface.itemsOf(args[0]), &ctx, lessThan); |
| 72 | + if (ctx.err) |e| return e; |
| 73 | + return .nil_val; |
| 74 | +} |
| 75 | + |
| 76 | +/// `(java.util.Collections/reverse list)` — IN-PLACE reversal of an |
| 77 | +/// ArrayList; returns nil (void in Java). |
| 78 | +fn reverseFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 79 | + _ = rt; |
| 80 | + _ = env; |
| 81 | + try error_catalog.checkArity("java.util.Collections/reverse", args, 1, loc); |
| 82 | + try expectArrayList(args[0], "java.util.Collections/reverse", loc); |
| 83 | + std.mem.reverse(Value, array_list_surface.itemsOf(args[0])); |
| 84 | + return .nil_val; |
| 85 | +} |
| 86 | + |
| 87 | +/// The realized element slice of a vector / Java array / ArrayList arg, or |
| 88 | +/// a type error. Vectors need a scratch copy (trie, not flat); the caller |
| 89 | +/// frees it via `owned`. |
| 90 | +const Elems = struct { items: []const Value, owned: ?[]Value = null }; |
| 91 | +fn elemsOf(rt: *Runtime, v: Value, fn_name: []const u8, loc: SourceLocation) !Elems { |
| 92 | + if (v.tag() == .vector) { |
| 93 | + const n = vector_mod.count(v); |
| 94 | + const buf = try rt.gpa.alloc(Value, n); |
| 95 | + var i: u32 = 0; |
| 96 | + while (i < n) : (i += 1) buf[i] = vector_mod.nth(v, i); |
| 97 | + return .{ .items = buf, .owned = buf }; |
| 98 | + } |
| 99 | + if (java_array.isArray(v)) return .{ .items = java_array.asArray(v).items() }; |
| 100 | + if (array_list_surface.isArrayList(v)) return .{ .items = array_list_surface.itemsOf(v) }; |
| 101 | + return error_catalog.raise(.type_arg_invalid, loc, .{ .fn_name = fn_name, .expected = "a vector, Java array, or ArrayList", .actual = @tagName(v.tag()) }); |
| 102 | +} |
| 103 | + |
| 104 | +fn extremum(rt: *Runtime, args: []const Value, comptime fn_name: []const u8, comptime want: std.math.Order, loc: SourceLocation) anyerror!Value { |
| 105 | + try error_catalog.checkArity(fn_name, args, 1, loc); |
| 106 | + const es = try elemsOf(rt, args[0], fn_name, loc); |
| 107 | + defer if (es.owned) |b| rt.gpa.free(b); |
| 108 | + if (es.items.len == 0) |
| 109 | + return error_catalog.raise(.type_arg_invalid, loc, .{ .fn_name = fn_name, .expected = "a non-empty collection", .actual = "empty collection" }); |
| 110 | + var best = es.items[0]; |
| 111 | + for (es.items[1..]) |e| { |
| 112 | + if (try compare_mod.valueCompare(rt, e, best, loc) == want) best = e; |
| 113 | + } |
| 114 | + return best; |
| 115 | +} |
| 116 | + |
| 117 | +/// `(java.util.Collections/max coll)` — natural-order maximum. |
| 118 | +fn maxFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 119 | + _ = env; |
| 120 | + return extremum(rt, args, "java.util.Collections/max", .gt, loc); |
| 121 | +} |
| 122 | + |
| 123 | +/// `(java.util.Collections/min coll)` — natural-order minimum. |
| 124 | +fn minFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value { |
| 125 | + _ = env; |
| 126 | + return extremum(rt, args, "java.util.Collections/min", .lt, loc); |
| 127 | +} |
| 128 | + |
| 129 | +fn initCollections(td: *type_descriptor.TypeDescriptor, gpa: std.mem.Allocator) anyerror!void { |
| 130 | + if (td.method_table.len != 0) return; // idempotent re-run |
| 131 | + const specs = .{ |
| 132 | + .{ "emptyList", &emptyList }, |
| 133 | + .{ "singletonList", &singletonList }, |
| 134 | + .{ "sort", &sortFn }, |
| 135 | + .{ "reverse", &reverseFn }, |
| 136 | + .{ "max", &maxFn }, |
| 137 | + .{ "min", &minFn }, |
| 138 | + }; |
| 139 | + const entries = try gpa.alloc(type_descriptor.TypeDescriptor.MethodEntry, specs.len); |
| 140 | + inline for (specs, 0..) |spec, i| { |
| 141 | + entries[i] = .{ |
| 142 | + .protocol_name = "", |
| 143 | + .method_name = try gpa.dupe(u8, spec[0]), |
| 144 | + .method_val = Value.initBuiltinFn(spec[1]), |
| 145 | + }; |
| 146 | + } |
| 147 | + td.method_table = entries; |
| 148 | +} |
| 149 | + |
| 150 | +pub const ___HOST_EXTENSION: host_api.Extension = .{ |
| 151 | + .cljw_ns = "cljw.java.util.Collections", |
| 152 | + .descriptor = &descriptor, |
| 153 | + .init = &initCollections, |
| 154 | +}; |
| 155 | + |
| 156 | +var descriptor: type_descriptor.TypeDescriptor = .{ |
| 157 | + .fqcn = "cljw.java.util.Collections", |
| 158 | + .kind = .native, |
| 159 | + .field_layout = null, |
| 160 | + .protocol_impls = &.{}, |
| 161 | + .method_table = &.{}, |
| 162 | + .parent = null, |
| 163 | + .meta = .nil_val, |
| 164 | +}; |
0 commit comments