pi-codex-auto-review 0.1.4 → 0.2.0
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.
- package/NOTICE +4 -2
- package/README.ko.md +85 -0
- package/README.md +51 -52
- package/dist/approval-commands.d.ts +3 -3
- package/dist/approval-commands.js +112 -58
- package/dist/approval-commands.js.map +1 -1
- package/dist/approval-settings.d.ts +1 -1
- package/dist/approval-settings.js +17 -10
- package/dist/approval-settings.js.map +1 -1
- package/dist/approvals.d.ts +9 -9
- package/dist/approvals.js +195 -75
- package/dist/approvals.js.map +1 -1
- package/dist/audit.d.ts +1 -1
- package/dist/audit.js +37 -13
- package/dist/audit.js.map +1 -1
- package/dist/cli.js +60 -31
- package/dist/cli.js.map +1 -1
- package/dist/context-files.d.ts +2 -2
- package/dist/context-files.js +40 -19
- package/dist/context-files.js.map +1 -1
- package/dist/contracts.d.ts +39 -39
- package/dist/contracts.js +117 -51
- package/dist/contracts.js.map +1 -1
- package/dist/index.d.ts +9 -9
- package/dist/index.js +145 -48
- package/dist/index.js.map +1 -1
- package/dist/pi-host.js +3 -3
- package/dist/pi-host.js.map +1 -1
- package/dist/policy/domains.js +17 -7
- package/dist/policy/domains.js.map +1 -1
- package/dist/policy/index.d.ts +7 -7
- package/dist/policy/index.js +379 -141
- package/dist/policy/index.js.map +1 -1
- package/dist/policy/paths.js +31 -18
- package/dist/policy/paths.js.map +1 -1
- package/dist/policy/rule-engine.d.ts +2 -0
- package/dist/policy/rule-engine.js +283 -0
- package/dist/policy/rule-engine.js.map +1 -0
- package/dist/policy/rule-types.d.ts +30 -0
- package/dist/policy/rule-types.js +2 -0
- package/dist/policy/rule-types.js.map +1 -0
- package/dist/policy/rules-sync.d.ts +1 -0
- package/dist/policy/rules-sync.js +14 -0
- package/dist/policy/rules-sync.js.map +1 -0
- package/dist/policy/rules-worker.d.ts +1 -0
- package/dist/policy/rules-worker.js +14 -0
- package/dist/policy/rules-worker.js.map +1 -0
- package/dist/policy/rules.d.ts +4 -31
- package/dist/policy/rules.js +126 -38
- package/dist/policy/rules.js.map +1 -1
- package/dist/policy/shell.js +70 -41
- package/dist/policy/shell.js.map +1 -1
- package/dist/policy/starlark/builtins.d.ts +6 -0
- package/dist/policy/starlark/builtins.js +764 -0
- package/dist/policy/starlark/builtins.js.map +1 -0
- package/dist/policy/starlark/evaluator.d.ts +18 -0
- package/dist/policy/starlark/evaluator.js +518 -0
- package/dist/policy/starlark/evaluator.js.map +1 -0
- package/dist/policy/starlark/lexer.d.ts +14 -0
- package/dist/policy/starlark/lexer.js +189 -0
- package/dist/policy/starlark/lexer.js.map +1 -0
- package/dist/policy/starlark/parser.d.ts +140 -0
- package/dist/policy/starlark/parser.js +508 -0
- package/dist/policy/starlark/parser.js.map +1 -0
- package/dist/policy/starlark/validation.d.ts +4 -0
- package/dist/policy/starlark/validation.js +183 -0
- package/dist/policy/starlark/validation.js.map +1 -0
- package/dist/policy/starlark/values.d.ts +46 -0
- package/dist/policy/starlark/values.js +363 -0
- package/dist/policy/starlark/values.js.map +1 -0
- package/dist/reports.d.ts +1 -1
- package/dist/reports.js +66 -30
- package/dist/reports.js.map +1 -1
- package/dist/review/context.d.ts +3 -3
- package/dist/review/context.js +207 -71
- package/dist/review/context.js.map +1 -1
- package/dist/review/investigation.d.ts +2 -2
- package/dist/review/investigation.js +57 -16
- package/dist/review/investigation.js.map +1 -1
- package/dist/review/lifecycle.d.ts +1 -1
- package/dist/review/lifecycle.js +70 -27
- package/dist/review/lifecycle.js.map +1 -1
- package/dist/review/policy.js +10 -5
- package/dist/review/policy.js.map +1 -1
- package/dist/review/upstream-policy.js +1 -1
- package/dist/review/upstream-policy.js.map +1 -1
- package/dist/review/user-input.d.ts +3 -3
- package/dist/review/user-input.js +39 -16
- package/dist/review/user-input.js.map +1 -1
- package/dist/reviewer.d.ts +8 -8
- package/dist/reviewer.js +253 -75
- package/dist/reviewer.js.map +1 -1
- package/dist/signals.js +24 -9
- package/dist/signals.js.map +1 -1
- package/dist/startup.d.ts +4 -4
- package/dist/startup.js +139 -41
- package/dist/startup.js.map +1 -1
- package/dist/tools/controller.d.ts +16 -12
- package/dist/tools/controller.js +432 -121
- package/dist/tools/controller.js.map +1 -1
- package/dist/tools/environment.js +3 -1
- package/dist/tools/environment.js.map +1 -1
- package/dist/tools/executor.d.ts +1 -1
- package/dist/tools/executor.js +45 -28
- package/dist/tools/executor.js.map +1 -1
- package/dist/tools/external.d.ts +8 -8
- package/dist/tools/external.js +22 -11
- package/dist/tools/external.js.map +1 -1
- package/dist/tools/mcp.d.ts +6 -6
- package/dist/tools/mcp.js +208 -80
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/mutation-queue.js.map +1 -1
- package/docs/publishing.ko.md +114 -0
- package/docs/publishing.md +49 -47
- package/docs/security/dependencies.ko.md +54 -0
- package/docs/security/dependencies.md +27 -26
- package/docs/testing/auto-review-protection.ko.md +158 -0
- package/docs/testing/auto-review-protection.md +104 -99
- package/docs/usage.ko.md +242 -0
- package/docs/usage.md +125 -121
- package/package.json +106 -95
- package/dist/native/darwin-arm64/artifact.json +0 -5
- package/dist/native/darwin-arm64/pi-guard-execpolicy +0 -0
- package/dist/native/linux-x64/artifact.json +0 -5
- package/dist/native/linux-x64/pi-guard-execpolicy +0 -0
- package/native/execpolicy/LICENSE +0 -201
- package/native/execpolicy/NOTICE +0 -6
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
import { compare, Dict, display, equal, integer, Range, repr, StringIterator, sequence, Tuple, text, trimWhitespace, truth, typeName, } from "./values.js";
|
|
2
|
+
export function bindArguments(name, args, keywords, spec) {
|
|
3
|
+
if (args.length > spec.length)
|
|
4
|
+
throw new Error(`${name}: too many arguments`);
|
|
5
|
+
const remaining = new Map(keywords);
|
|
6
|
+
const result = spec.map((parameter, index) => {
|
|
7
|
+
const key = typeof parameter === "string" ? parameter : parameter[0];
|
|
8
|
+
if (index < args.length) {
|
|
9
|
+
if (remaining.has(key))
|
|
10
|
+
throw new Error(`${name}: repeated argument ${key}`);
|
|
11
|
+
return args[index];
|
|
12
|
+
}
|
|
13
|
+
if (remaining.has(key)) {
|
|
14
|
+
const value = remaining.get(key);
|
|
15
|
+
remaining.delete(key);
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
if (typeof parameter !== "string")
|
|
19
|
+
return parameter[1];
|
|
20
|
+
throw new Error(`${name}: missing argument ${key}`);
|
|
21
|
+
});
|
|
22
|
+
if (remaining.size)
|
|
23
|
+
throw new Error(`${name}: unexpected argument ${remaining.keys().next().value}`);
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
function callable(name, spec, run) {
|
|
27
|
+
return {
|
|
28
|
+
kind: "function",
|
|
29
|
+
name,
|
|
30
|
+
call: (args, keywords) => run(bindArguments(name, args, keywords, spec)),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function invoke(target, args, keywords = new Map()) {
|
|
34
|
+
if (!target ||
|
|
35
|
+
typeof target !== "object" ||
|
|
36
|
+
!("kind" in target) ||
|
|
37
|
+
target.kind !== "function")
|
|
38
|
+
throw new Error("Expected a function");
|
|
39
|
+
return target.call(args, keywords);
|
|
40
|
+
}
|
|
41
|
+
const stringAttributes = [
|
|
42
|
+
"capitalize",
|
|
43
|
+
"codepoints",
|
|
44
|
+
"count",
|
|
45
|
+
"elems",
|
|
46
|
+
"endswith",
|
|
47
|
+
"find",
|
|
48
|
+
"format",
|
|
49
|
+
"index",
|
|
50
|
+
"isalnum",
|
|
51
|
+
"isalpha",
|
|
52
|
+
"isdigit",
|
|
53
|
+
"islower",
|
|
54
|
+
"isspace",
|
|
55
|
+
"istitle",
|
|
56
|
+
"isupper",
|
|
57
|
+
"join",
|
|
58
|
+
"lower",
|
|
59
|
+
"lstrip",
|
|
60
|
+
"partition",
|
|
61
|
+
"removeprefix",
|
|
62
|
+
"removesuffix",
|
|
63
|
+
"replace",
|
|
64
|
+
"rfind",
|
|
65
|
+
"rindex",
|
|
66
|
+
"rpartition",
|
|
67
|
+
"rsplit",
|
|
68
|
+
"rstrip",
|
|
69
|
+
"split",
|
|
70
|
+
"splitlines",
|
|
71
|
+
"startswith",
|
|
72
|
+
"strip",
|
|
73
|
+
"title",
|
|
74
|
+
"upper",
|
|
75
|
+
];
|
|
76
|
+
function attributes(value) {
|
|
77
|
+
if (typeof value === "string")
|
|
78
|
+
return stringAttributes;
|
|
79
|
+
if (Array.isArray(value))
|
|
80
|
+
return [
|
|
81
|
+
"append",
|
|
82
|
+
"clear",
|
|
83
|
+
"extend",
|
|
84
|
+
"index",
|
|
85
|
+
"insert",
|
|
86
|
+
"pop",
|
|
87
|
+
"remove",
|
|
88
|
+
];
|
|
89
|
+
if (value instanceof Dict)
|
|
90
|
+
return [
|
|
91
|
+
"clear",
|
|
92
|
+
"get",
|
|
93
|
+
"items",
|
|
94
|
+
"keys",
|
|
95
|
+
"pop",
|
|
96
|
+
"popitem",
|
|
97
|
+
"setdefault",
|
|
98
|
+
"update",
|
|
99
|
+
"values",
|
|
100
|
+
];
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
export function standardGlobals(budget, mutate) {
|
|
104
|
+
const globals = new Map();
|
|
105
|
+
const add = (name, spec, run) => globals.set(name, callable(name, spec, run));
|
|
106
|
+
add("len", ["x"], ([value]) => BigInt(typeof value === "string"
|
|
107
|
+
? [...value].length
|
|
108
|
+
: value instanceof Dict
|
|
109
|
+
? value.size
|
|
110
|
+
: sequence(value).length));
|
|
111
|
+
add("str", [["x", ""]], ([value]) => budget.string(display(value)));
|
|
112
|
+
add("repr", ["x"], ([value]) => budget.string(repr(value)));
|
|
113
|
+
add("type", ["x"], ([value]) => typeName(value));
|
|
114
|
+
add("bool", [["x", false]], ([value]) => truth(value));
|
|
115
|
+
add("list", [["x", new Tuple([])]], ([value]) => [...sequence(value)]);
|
|
116
|
+
add("tuple", [["x", new Tuple([])]], ([value]) => new Tuple([...sequence(value)]));
|
|
117
|
+
add("int", [
|
|
118
|
+
["x", 0n],
|
|
119
|
+
["base", 10n],
|
|
120
|
+
], ([value, base]) => {
|
|
121
|
+
if (typeof value === "bigint")
|
|
122
|
+
return value;
|
|
123
|
+
if (typeof value === "boolean")
|
|
124
|
+
return value ? 1n : 0n;
|
|
125
|
+
if (typeof value === "number") {
|
|
126
|
+
if (!Number.isFinite(value))
|
|
127
|
+
throw new Error("Invalid integer");
|
|
128
|
+
return BigInt(Math.trunc(value));
|
|
129
|
+
}
|
|
130
|
+
const raw = text(value), radix = integer(base);
|
|
131
|
+
if (radix !== 0 && (radix < 2 || radix > 36))
|
|
132
|
+
throw new Error("Invalid integer base");
|
|
133
|
+
let body = raw.toLowerCase(), sign = 1n;
|
|
134
|
+
if (/^[+-]/.test(body)) {
|
|
135
|
+
if (body[0] === "-")
|
|
136
|
+
sign = -1n;
|
|
137
|
+
body = body.slice(1);
|
|
138
|
+
}
|
|
139
|
+
let actual = radix;
|
|
140
|
+
if (actual === 0)
|
|
141
|
+
actual = body.startsWith("0x")
|
|
142
|
+
? 16
|
|
143
|
+
: body.startsWith("0o")
|
|
144
|
+
? 8
|
|
145
|
+
: body.startsWith("0b")
|
|
146
|
+
? 2
|
|
147
|
+
: 10;
|
|
148
|
+
if ((actual === 16 && body.startsWith("0x")) ||
|
|
149
|
+
(actual === 8 && body.startsWith("0o")) ||
|
|
150
|
+
(actual === 2 && body.startsWith("0b")))
|
|
151
|
+
body = body.slice(2);
|
|
152
|
+
if (!body)
|
|
153
|
+
throw new Error("Invalid integer");
|
|
154
|
+
let result = 0n;
|
|
155
|
+
for (const char of body) {
|
|
156
|
+
budget.tick();
|
|
157
|
+
const digit = "0123456789abcdefghijklmnopqrstuvwxyz".indexOf(char);
|
|
158
|
+
if (digit < 0 || digit >= actual)
|
|
159
|
+
throw new Error("Invalid integer");
|
|
160
|
+
result = result * BigInt(actual) + BigInt(digit);
|
|
161
|
+
}
|
|
162
|
+
return result * sign;
|
|
163
|
+
});
|
|
164
|
+
add("float", [["x", 0n]], ([value]) => {
|
|
165
|
+
if (typeof value === "number" ||
|
|
166
|
+
typeof value === "bigint" ||
|
|
167
|
+
typeof value === "boolean")
|
|
168
|
+
return Number(value);
|
|
169
|
+
const raw = text(value).toLowerCase();
|
|
170
|
+
if (/\p{White_Space}/u.test(raw))
|
|
171
|
+
throw new Error("Invalid float");
|
|
172
|
+
if (/^[+-]?nan$/.test(raw))
|
|
173
|
+
return Number.NaN;
|
|
174
|
+
if (/^\+?inf(?:inity)?$/.test(raw))
|
|
175
|
+
return Infinity;
|
|
176
|
+
if (/^-inf(?:inity)?$/.test(raw))
|
|
177
|
+
return -Infinity;
|
|
178
|
+
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/.test(raw))
|
|
179
|
+
throw new Error("Invalid float");
|
|
180
|
+
return Number(raw);
|
|
181
|
+
});
|
|
182
|
+
add("hash", ["x"], ([value]) => {
|
|
183
|
+
const raw = text(value);
|
|
184
|
+
let hash = 0;
|
|
185
|
+
for (let index = 0; index < raw.length; index++)
|
|
186
|
+
hash = (Math.imul(hash, 31) + raw.charCodeAt(index)) | 0;
|
|
187
|
+
return BigInt(hash);
|
|
188
|
+
});
|
|
189
|
+
add("ord", ["x"], ([value]) => {
|
|
190
|
+
const chars = [...text(value)];
|
|
191
|
+
if (chars.length !== 1)
|
|
192
|
+
throw new Error("ord expects one character");
|
|
193
|
+
return BigInt(chars[0].codePointAt(0) ?? 0);
|
|
194
|
+
});
|
|
195
|
+
add("chr", ["x"], ([value]) => {
|
|
196
|
+
const point = integer(value);
|
|
197
|
+
if (point < 0 ||
|
|
198
|
+
point > 0x10ffff ||
|
|
199
|
+
(point >= 0xd800 && point <= 0xdfff))
|
|
200
|
+
throw new Error("Invalid codepoint");
|
|
201
|
+
return String.fromCodePoint(point);
|
|
202
|
+
});
|
|
203
|
+
add("dir", ["x"], ([value]) => [...attributes(value)]);
|
|
204
|
+
add("hasattr", ["x", "name"], ([value, name]) => attributes(value).includes(text(name)));
|
|
205
|
+
globals.set("getattr", {
|
|
206
|
+
kind: "function",
|
|
207
|
+
name: "getattr",
|
|
208
|
+
call(args, keywords) {
|
|
209
|
+
if (keywords.size || args.length < 2 || args.length > 3)
|
|
210
|
+
throw new Error("getattr expects two or three arguments");
|
|
211
|
+
const name = text(args[1]);
|
|
212
|
+
if (attributes(args[0]).includes(name))
|
|
213
|
+
return attribute(args[0], name, budget, mutate);
|
|
214
|
+
if (args.length === 3)
|
|
215
|
+
return args[2];
|
|
216
|
+
throw new Error(`Missing attribute ${name}`);
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
add("abs", ["x"], ([value]) => {
|
|
220
|
+
if (typeof value === "bigint")
|
|
221
|
+
return value < 0n ? -value : value;
|
|
222
|
+
if (typeof value === "number")
|
|
223
|
+
return Math.abs(value);
|
|
224
|
+
throw new Error("Expected number");
|
|
225
|
+
});
|
|
226
|
+
add("all", ["iterable"], ([value]) => sequence(value).every(truth));
|
|
227
|
+
add("any", ["iterable"], ([value]) => sequence(value).some(truth));
|
|
228
|
+
add("enumerate", ["iterable", ["start", 0n]], ([value, start]) => sequence(value).map((item, index) => new Tuple([BigInt(integer(start) + index), item])));
|
|
229
|
+
add("reversed", ["sequence"], ([value]) => [...sequence(value)].reverse());
|
|
230
|
+
add("sorted", ["iterable", ["key", null], ["reverse", false]], ([value, key, reverse]) => {
|
|
231
|
+
if (typeof reverse !== "boolean")
|
|
232
|
+
throw new Error("reverse must be bool");
|
|
233
|
+
return sequence(value)
|
|
234
|
+
.map((item) => ({
|
|
235
|
+
item,
|
|
236
|
+
key: key === null ? item : invoke(key, [item]),
|
|
237
|
+
}))
|
|
238
|
+
.sort((a, b) => {
|
|
239
|
+
budget.tick();
|
|
240
|
+
return compare(a.key, b.key) * (reverse ? -1 : 1);
|
|
241
|
+
})
|
|
242
|
+
.map((entry) => entry.item);
|
|
243
|
+
});
|
|
244
|
+
globals.set("range", {
|
|
245
|
+
kind: "function",
|
|
246
|
+
name: "range",
|
|
247
|
+
call(args, keywords) {
|
|
248
|
+
if (keywords.size || args.length < 1 || args.length > 3)
|
|
249
|
+
throw new Error("range expects one to three positional integers");
|
|
250
|
+
const [start, stop, step] = args.length === 1
|
|
251
|
+
? [0, integer(args[0]), 1]
|
|
252
|
+
: [
|
|
253
|
+
integer(args[0]),
|
|
254
|
+
integer(args[1]),
|
|
255
|
+
args.length === 3 ? integer(args[2]) : 1,
|
|
256
|
+
];
|
|
257
|
+
if (step === 0)
|
|
258
|
+
throw new Error("range step cannot be zero");
|
|
259
|
+
const length = Math.max(0, Math.ceil((stop - start) / step));
|
|
260
|
+
budget.size(length);
|
|
261
|
+
return new Range(start, stop, step);
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
globals.set("zip", {
|
|
265
|
+
kind: "function",
|
|
266
|
+
name: "zip",
|
|
267
|
+
call(args, keywords) {
|
|
268
|
+
if (keywords.size)
|
|
269
|
+
throw new Error("zip accepts positional arguments");
|
|
270
|
+
const lists = args.map(sequence);
|
|
271
|
+
const length = lists.length
|
|
272
|
+
? Math.min(...lists.map((items) => items.length))
|
|
273
|
+
: 0;
|
|
274
|
+
budget.size(length);
|
|
275
|
+
return Array.from({ length }, (_, index) => new Tuple(lists.map((items) => items[index])));
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
for (const name of ["min", "max"])
|
|
279
|
+
globals.set(name, {
|
|
280
|
+
kind: "function",
|
|
281
|
+
name,
|
|
282
|
+
call(args, keywords) {
|
|
283
|
+
const key = keywords.get("key") ?? null;
|
|
284
|
+
if ([...keywords.keys()].some((key) => key !== "key"))
|
|
285
|
+
throw new Error("Unexpected comparison argument");
|
|
286
|
+
const values = args.length === 1 ? sequence(args[0]) : args;
|
|
287
|
+
if (!values.length)
|
|
288
|
+
throw new Error(`${name} of empty sequence`);
|
|
289
|
+
let chosen = values[0], chosenKey = key === null ? chosen : invoke(key, [chosen]);
|
|
290
|
+
for (const value of values.slice(1)) {
|
|
291
|
+
budget.tick();
|
|
292
|
+
const candidate = key === null ? value : invoke(key, [value]);
|
|
293
|
+
if (compare(candidate, chosenKey) *
|
|
294
|
+
(name === "min" ? 1 : -1) <
|
|
295
|
+
0) {
|
|
296
|
+
chosen = value;
|
|
297
|
+
chosenKey = candidate;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return chosen;
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
globals.set("dict", {
|
|
304
|
+
kind: "function",
|
|
305
|
+
name: "dict",
|
|
306
|
+
call(args, keywords) {
|
|
307
|
+
if (args.length > 1)
|
|
308
|
+
throw new Error("dict takes at most one positional argument");
|
|
309
|
+
const result = new Dict();
|
|
310
|
+
if (args.length)
|
|
311
|
+
for (const entry of args[0] instanceof Dict
|
|
312
|
+
? args[0].entries()
|
|
313
|
+
: sequence(args[0]).map(sequence)) {
|
|
314
|
+
if (entry.length !== 2)
|
|
315
|
+
throw new Error("dict entry must have two values");
|
|
316
|
+
result.set(entry[0], entry[1]);
|
|
317
|
+
}
|
|
318
|
+
for (const [key, value] of keywords)
|
|
319
|
+
result.set(key, value);
|
|
320
|
+
budget.size(result.size);
|
|
321
|
+
return result;
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
globals.set("fail", {
|
|
325
|
+
kind: "function",
|
|
326
|
+
name: "fail",
|
|
327
|
+
call(args) {
|
|
328
|
+
throw new Error(args.map(display).join(" "));
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
globals.set("print", {
|
|
332
|
+
kind: "function",
|
|
333
|
+
name: "print",
|
|
334
|
+
call(args, keywords) {
|
|
335
|
+
if ([...keywords.keys()].some((key) => key !== "sep"))
|
|
336
|
+
throw new Error("Unexpected print argument");
|
|
337
|
+
budget.string(args.map(display).join(text(keywords.get("sep") ?? " ")));
|
|
338
|
+
return null;
|
|
339
|
+
},
|
|
340
|
+
});
|
|
341
|
+
return globals;
|
|
342
|
+
}
|
|
343
|
+
export function attribute(value, name, budget, mutate) {
|
|
344
|
+
const method = (spec, run) => callable(name, spec, run);
|
|
345
|
+
if (Array.isArray(value)) {
|
|
346
|
+
switch (name) {
|
|
347
|
+
case "append":
|
|
348
|
+
return method(["item"], ([item]) => {
|
|
349
|
+
mutate(value);
|
|
350
|
+
budget.size(value.length + 1);
|
|
351
|
+
value.push(item);
|
|
352
|
+
return null;
|
|
353
|
+
});
|
|
354
|
+
case "extend":
|
|
355
|
+
return method(["items"], ([items]) => {
|
|
356
|
+
mutate(value);
|
|
357
|
+
const extra = sequence(items);
|
|
358
|
+
budget.size(value.length + extra.length);
|
|
359
|
+
value.push(...extra);
|
|
360
|
+
return null;
|
|
361
|
+
});
|
|
362
|
+
case "insert":
|
|
363
|
+
return method(["index", "item"], ([index, item]) => {
|
|
364
|
+
mutate(value);
|
|
365
|
+
budget.size(value.length + 1);
|
|
366
|
+
value.splice(integer(index), 0, item);
|
|
367
|
+
return null;
|
|
368
|
+
});
|
|
369
|
+
case "pop":
|
|
370
|
+
return method([["index", -1n]], ([index]) => {
|
|
371
|
+
mutate(value);
|
|
372
|
+
const at = integer(index), position = at < 0 ? value.length + at : at;
|
|
373
|
+
if (position < 0 || position >= value.length)
|
|
374
|
+
throw new Error("pop index out of range");
|
|
375
|
+
return value.splice(position, 1)[0];
|
|
376
|
+
});
|
|
377
|
+
case "remove":
|
|
378
|
+
return method(["item"], ([item]) => {
|
|
379
|
+
mutate(value);
|
|
380
|
+
const index = value.findIndex((existing) => equal(existing, item));
|
|
381
|
+
if (index < 0)
|
|
382
|
+
throw new Error("Value not in list");
|
|
383
|
+
value.splice(index, 1);
|
|
384
|
+
return null;
|
|
385
|
+
});
|
|
386
|
+
case "clear":
|
|
387
|
+
return method([], () => {
|
|
388
|
+
mutate(value);
|
|
389
|
+
value.length = 0;
|
|
390
|
+
return null;
|
|
391
|
+
});
|
|
392
|
+
case "index":
|
|
393
|
+
return method(["item", ["start", 0n], ["end", BigInt(value.length)]], ([item, start, end]) => {
|
|
394
|
+
for (let index = Math.max(0, integer(start)); index < Math.min(value.length, integer(end)); index++)
|
|
395
|
+
if (equal(value[index], item))
|
|
396
|
+
return BigInt(index);
|
|
397
|
+
throw new Error("Value not in list");
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (value instanceof Dict) {
|
|
402
|
+
switch (name) {
|
|
403
|
+
case "get":
|
|
404
|
+
return method(["key", ["default", null]], ([key, fallback]) => value.has(key) ? value.get(key) : fallback);
|
|
405
|
+
case "keys":
|
|
406
|
+
return method([], () => value.entries().map(([key]) => key));
|
|
407
|
+
case "values":
|
|
408
|
+
return method([], () => value.entries().map(([, item]) => item));
|
|
409
|
+
case "items":
|
|
410
|
+
return method([], () => value.entries().map((entry) => new Tuple(entry)));
|
|
411
|
+
case "clear":
|
|
412
|
+
return method([], () => {
|
|
413
|
+
mutate(value);
|
|
414
|
+
value.clear();
|
|
415
|
+
return null;
|
|
416
|
+
});
|
|
417
|
+
case "setdefault":
|
|
418
|
+
return method(["key", ["default", null]], ([key, fallback]) => {
|
|
419
|
+
mutate(value);
|
|
420
|
+
if (!value.has(key)) {
|
|
421
|
+
budget.size(value.size + 1);
|
|
422
|
+
value.set(key, fallback);
|
|
423
|
+
}
|
|
424
|
+
return value.get(key);
|
|
425
|
+
});
|
|
426
|
+
case "pop":
|
|
427
|
+
return {
|
|
428
|
+
kind: "function",
|
|
429
|
+
name,
|
|
430
|
+
call(args, keywords) {
|
|
431
|
+
if (keywords.size || args.length < 1 || args.length > 2)
|
|
432
|
+
throw new Error("Invalid dict.pop arguments");
|
|
433
|
+
mutate(value);
|
|
434
|
+
if (!value.has(args[0])) {
|
|
435
|
+
if (args.length === 2)
|
|
436
|
+
return args[1];
|
|
437
|
+
throw new Error("Missing dictionary key");
|
|
438
|
+
}
|
|
439
|
+
const result = value.get(args[0]);
|
|
440
|
+
value.delete(args[0]);
|
|
441
|
+
return result;
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
case "popitem":
|
|
445
|
+
return method([], () => {
|
|
446
|
+
mutate(value);
|
|
447
|
+
const first = value.entries()[0];
|
|
448
|
+
if (!first)
|
|
449
|
+
throw new Error("Empty dictionary");
|
|
450
|
+
value.delete(first[0]);
|
|
451
|
+
return new Tuple(first);
|
|
452
|
+
});
|
|
453
|
+
case "update":
|
|
454
|
+
return {
|
|
455
|
+
kind: "function",
|
|
456
|
+
name,
|
|
457
|
+
call(args, keywords) {
|
|
458
|
+
mutate(value);
|
|
459
|
+
if (args.length > 1)
|
|
460
|
+
throw new Error("dict.update takes at most one positional argument");
|
|
461
|
+
if (args.length)
|
|
462
|
+
for (const entry of args[0] instanceof Dict
|
|
463
|
+
? args[0].entries()
|
|
464
|
+
: sequence(args[0]).map(sequence)) {
|
|
465
|
+
if (entry.length !== 2)
|
|
466
|
+
throw new Error("Invalid dictionary entry");
|
|
467
|
+
value.set(entry[0], entry[1]);
|
|
468
|
+
}
|
|
469
|
+
for (const [key, item] of keywords)
|
|
470
|
+
value.set(key, item);
|
|
471
|
+
budget.size(value.size);
|
|
472
|
+
return null;
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (typeof value === "string")
|
|
478
|
+
return stringMethod(value, name, budget);
|
|
479
|
+
throw new Error(`${typeName(value)} has no attribute ${name}`);
|
|
480
|
+
}
|
|
481
|
+
function stringMethod(value, name, budget) {
|
|
482
|
+
const method = (spec, run) => callable(name, spec, run);
|
|
483
|
+
switch (name) {
|
|
484
|
+
case "lower":
|
|
485
|
+
return method([], () => value.toLowerCase());
|
|
486
|
+
case "upper":
|
|
487
|
+
return method([], () => value.toUpperCase());
|
|
488
|
+
case "capitalize":
|
|
489
|
+
return method([], () => value.slice(0, 1).toUpperCase() +
|
|
490
|
+
value.slice(1).toLowerCase());
|
|
491
|
+
case "join":
|
|
492
|
+
return method(["elements"], ([items]) => budget.string(sequence(items).map(text).join(value)));
|
|
493
|
+
case "elems":
|
|
494
|
+
return method([], () => new StringIterator([...value]));
|
|
495
|
+
case "codepoints":
|
|
496
|
+
return method([], () => new StringIterator([...value].map((char) => BigInt(char.codePointAt(0) ?? 0))));
|
|
497
|
+
case "isalnum":
|
|
498
|
+
return method([], () => /^[\p{L}\p{N}]+$/u.test(value));
|
|
499
|
+
case "isalpha":
|
|
500
|
+
return method([], () => /^\p{L}+$/u.test(value));
|
|
501
|
+
case "isdigit":
|
|
502
|
+
return method([], () => /^\p{Nd}+$/u.test(value));
|
|
503
|
+
case "isspace":
|
|
504
|
+
return method([], () => /^\p{White_Space}+$/u.test(value));
|
|
505
|
+
case "islower":
|
|
506
|
+
return method([], () => /\p{Ll}/u.test(value) && !/\p{Lu}|\p{Lt}/u.test(value));
|
|
507
|
+
case "isupper":
|
|
508
|
+
return method([], () => /\p{Lu}/u.test(value) && !/\p{Ll}|\p{Lt}/u.test(value));
|
|
509
|
+
case "title":
|
|
510
|
+
return method([], () => value.replace(/\p{L}+/gu, (word) => word.slice(0, 1).toUpperCase() +
|
|
511
|
+
word.slice(1).toLowerCase()));
|
|
512
|
+
case "istitle":
|
|
513
|
+
return method([], () => /\p{L}/u.test(value) &&
|
|
514
|
+
value ===
|
|
515
|
+
value.replace(/\p{L}+/gu, (word) => word.slice(0, 1).toUpperCase() +
|
|
516
|
+
word.slice(1).toLowerCase()));
|
|
517
|
+
case "removeprefix":
|
|
518
|
+
return method(["prefix"], ([prefix]) => value.startsWith(text(prefix))
|
|
519
|
+
? value.slice(text(prefix).length)
|
|
520
|
+
: value);
|
|
521
|
+
case "removesuffix":
|
|
522
|
+
return method(["suffix"], ([suffix]) => text(suffix) && value.endsWith(text(suffix))
|
|
523
|
+
? value.slice(0, -text(suffix).length)
|
|
524
|
+
: value);
|
|
525
|
+
case "partition":
|
|
526
|
+
case "rpartition":
|
|
527
|
+
return method(["sep"], ([separator]) => {
|
|
528
|
+
const sep = text(separator);
|
|
529
|
+
if (!sep)
|
|
530
|
+
throw new Error("Empty separator");
|
|
531
|
+
const index = name === "partition"
|
|
532
|
+
? value.indexOf(sep)
|
|
533
|
+
: value.lastIndexOf(sep);
|
|
534
|
+
return new Tuple(index < 0
|
|
535
|
+
? name === "partition"
|
|
536
|
+
? [value, "", ""]
|
|
537
|
+
: ["", "", value]
|
|
538
|
+
: [
|
|
539
|
+
value.slice(0, index),
|
|
540
|
+
sep,
|
|
541
|
+
value.slice(index + sep.length),
|
|
542
|
+
]);
|
|
543
|
+
});
|
|
544
|
+
case "splitlines":
|
|
545
|
+
return method([["keepends", false]], ([keepends]) => {
|
|
546
|
+
if (typeof keepends !== "boolean")
|
|
547
|
+
throw new Error("keepends must be bool");
|
|
548
|
+
const lines = value.match(/[^\r\n]*(?:\r\n|\r|\n|$)/g) ?? [];
|
|
549
|
+
if (lines.at(-1) === "")
|
|
550
|
+
lines.pop();
|
|
551
|
+
return keepends
|
|
552
|
+
? lines
|
|
553
|
+
: lines.map((line) => line.replace(/(?:\r\n|\r|\n)$/, ""));
|
|
554
|
+
});
|
|
555
|
+
case "strip":
|
|
556
|
+
case "lstrip":
|
|
557
|
+
case "rstrip":
|
|
558
|
+
return method([["chars", null]], ([chars]) => {
|
|
559
|
+
const trim = (char) => chars === null
|
|
560
|
+
? /\p{White_Space}/u.test(char)
|
|
561
|
+
: text(chars).includes(char);
|
|
562
|
+
let start = 0, end = value.length;
|
|
563
|
+
if (name !== "rstrip")
|
|
564
|
+
while (start < end && trim(value[start]))
|
|
565
|
+
start++;
|
|
566
|
+
if (name !== "lstrip")
|
|
567
|
+
while (end > start && trim(value[end - 1]))
|
|
568
|
+
end--;
|
|
569
|
+
return value.slice(start, end);
|
|
570
|
+
});
|
|
571
|
+
case "startswith":
|
|
572
|
+
case "endswith":
|
|
573
|
+
return method([
|
|
574
|
+
name === "startswith" ? "prefix" : "suffix",
|
|
575
|
+
["start", 0n],
|
|
576
|
+
["end", BigInt(value.length)],
|
|
577
|
+
], ([part, start, end]) => {
|
|
578
|
+
const section = value.slice(integer(start), integer(end)), parts = part instanceof Tuple ? part.items : [part];
|
|
579
|
+
return parts.some((item) => name === "startswith"
|
|
580
|
+
? section.startsWith(text(item))
|
|
581
|
+
: section.endsWith(text(item)));
|
|
582
|
+
});
|
|
583
|
+
case "split":
|
|
584
|
+
case "rsplit":
|
|
585
|
+
return method([
|
|
586
|
+
["sep", null],
|
|
587
|
+
["maxsplit", -1n],
|
|
588
|
+
], ([separator, maxsplit]) => {
|
|
589
|
+
const limit = integer(maxsplit), sep = separator === null ? null : text(separator);
|
|
590
|
+
if (sep === "")
|
|
591
|
+
throw new Error("Empty separator");
|
|
592
|
+
if (sep === null && limit >= 0) {
|
|
593
|
+
const words = [
|
|
594
|
+
...value.matchAll(/[^\p{White_Space}]+/gu),
|
|
595
|
+
];
|
|
596
|
+
if (!words.length)
|
|
597
|
+
return [];
|
|
598
|
+
if (words.length <= limit + 1)
|
|
599
|
+
return words.map((match) => match[0]);
|
|
600
|
+
return name === "split"
|
|
601
|
+
? [
|
|
602
|
+
...words
|
|
603
|
+
.slice(0, limit)
|
|
604
|
+
.map((match) => match[0]),
|
|
605
|
+
value.slice(words[limit].index),
|
|
606
|
+
]
|
|
607
|
+
: [
|
|
608
|
+
value.slice(0, (words[words.length - limit - 1].index ??
|
|
609
|
+
0) +
|
|
610
|
+
words[words.length - limit - 1][0]
|
|
611
|
+
.length),
|
|
612
|
+
...words
|
|
613
|
+
.slice(words.length - limit)
|
|
614
|
+
.map((match) => match[0]),
|
|
615
|
+
];
|
|
616
|
+
}
|
|
617
|
+
const parts = sep === null
|
|
618
|
+
? trimWhitespace(value)
|
|
619
|
+
.split(/\p{White_Space}+/u)
|
|
620
|
+
.filter(Boolean)
|
|
621
|
+
: value.split(sep);
|
|
622
|
+
budget.size(parts.length);
|
|
623
|
+
if (limit < 0 || parts.length <= limit + 1)
|
|
624
|
+
return parts;
|
|
625
|
+
return name === "split"
|
|
626
|
+
? [
|
|
627
|
+
...parts.slice(0, limit),
|
|
628
|
+
parts.slice(limit).join(sep ?? " "),
|
|
629
|
+
]
|
|
630
|
+
: [
|
|
631
|
+
parts
|
|
632
|
+
.slice(0, parts.length - limit)
|
|
633
|
+
.join(sep ?? " "),
|
|
634
|
+
...parts.slice(parts.length - limit),
|
|
635
|
+
];
|
|
636
|
+
});
|
|
637
|
+
case "replace":
|
|
638
|
+
return method(["old", "new", ["count", -1n]], ([old, replacement, count]) => {
|
|
639
|
+
const from = text(old), to = text(replacement), limit = integer(count);
|
|
640
|
+
let used = 0;
|
|
641
|
+
if (from === "") {
|
|
642
|
+
const chars = [...value];
|
|
643
|
+
return budget.string(chars
|
|
644
|
+
.map((char) => (limit < 0 || used++ < limit
|
|
645
|
+
? to
|
|
646
|
+
: "") + char)
|
|
647
|
+
.join("") +
|
|
648
|
+
(limit < 0 || used < limit ? to : ""));
|
|
649
|
+
}
|
|
650
|
+
return budget.string(value
|
|
651
|
+
.split(from)
|
|
652
|
+
.reduce((result, part, index) => index === 0
|
|
653
|
+
? part
|
|
654
|
+
: result +
|
|
655
|
+
(limit < 0 || used++ < limit
|
|
656
|
+
? to
|
|
657
|
+
: from) +
|
|
658
|
+
part, ""));
|
|
659
|
+
});
|
|
660
|
+
case "find":
|
|
661
|
+
case "rfind":
|
|
662
|
+
case "index":
|
|
663
|
+
case "rindex":
|
|
664
|
+
case "count":
|
|
665
|
+
return method(["sub", ["start", 0n], ["end", BigInt(value.length)]], ([part, start, end]) => {
|
|
666
|
+
const offset = integer(start) < 0
|
|
667
|
+
? Math.max(0, value.length + integer(start))
|
|
668
|
+
: integer(start), section = value.slice(offset, integer(end)), needle = text(part);
|
|
669
|
+
if (name === "count")
|
|
670
|
+
return BigInt(needle === ""
|
|
671
|
+
? section.length + 1
|
|
672
|
+
: section.split(needle).length - 1);
|
|
673
|
+
const found = name.startsWith("r")
|
|
674
|
+
? section.lastIndexOf(needle)
|
|
675
|
+
: section.indexOf(needle);
|
|
676
|
+
if (found < 0 && name.endsWith("index"))
|
|
677
|
+
throw new Error("Substring not found");
|
|
678
|
+
return BigInt(found < 0 ? -1 : offset + found);
|
|
679
|
+
});
|
|
680
|
+
case "format":
|
|
681
|
+
return {
|
|
682
|
+
kind: "function",
|
|
683
|
+
name,
|
|
684
|
+
call(args, keywords) {
|
|
685
|
+
let automatic = 0;
|
|
686
|
+
return budget.string(value.replace(/\{\{|\}\}|\{([^{}]*)\}/g, (match, field) => {
|
|
687
|
+
if (match === "{{")
|
|
688
|
+
return "{";
|
|
689
|
+
if (match === "}}")
|
|
690
|
+
return "}";
|
|
691
|
+
const conversion = field.endsWith("!r")
|
|
692
|
+
? "r"
|
|
693
|
+
: field.endsWith("!s")
|
|
694
|
+
? "s"
|
|
695
|
+
: undefined;
|
|
696
|
+
const raw = conversion
|
|
697
|
+
? field.slice(0, -2)
|
|
698
|
+
: field;
|
|
699
|
+
const key = raw === "" ? String(automatic++) : raw;
|
|
700
|
+
const item = /^\d+$/.test(key)
|
|
701
|
+
? args[Number(key)]
|
|
702
|
+
: keywords.get(key);
|
|
703
|
+
if (item === undefined)
|
|
704
|
+
throw new Error(`Missing format argument ${key}`);
|
|
705
|
+
return conversion === "r"
|
|
706
|
+
? repr(item)
|
|
707
|
+
: display(item);
|
|
708
|
+
}));
|
|
709
|
+
},
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
throw new Error(`string has no attribute ${name}`);
|
|
713
|
+
}
|
|
714
|
+
export function percentFormat(format, value, budget) {
|
|
715
|
+
if (/%(?![%srdxXofeEgG])/u.test(format))
|
|
716
|
+
throw new Error("Unsupported Starlark percent format");
|
|
717
|
+
const values = value instanceof Tuple ? value.items : [value];
|
|
718
|
+
let index = 0;
|
|
719
|
+
const output = format.replace(/%%|%(?:\(([^)]+)\))?([#0 +-]*)(\d*)(?:\.(\d+))?([srdxXofeEgG])/g, (match, key, flags, width, precision, kind) => {
|
|
720
|
+
if (match === "%%")
|
|
721
|
+
return "%";
|
|
722
|
+
const item = key
|
|
723
|
+
? value instanceof Dict
|
|
724
|
+
? value.get(key)
|
|
725
|
+
: undefined
|
|
726
|
+
: values[index++];
|
|
727
|
+
if (item === undefined)
|
|
728
|
+
throw new Error("Missing format value");
|
|
729
|
+
let result;
|
|
730
|
+
if (kind === "s")
|
|
731
|
+
result = display(item);
|
|
732
|
+
else if (kind === "r")
|
|
733
|
+
result = repr(item);
|
|
734
|
+
else if (["d", "x", "X", "o"].includes(kind)) {
|
|
735
|
+
if (typeof item !== "bigint")
|
|
736
|
+
throw new Error("Expected integer format value");
|
|
737
|
+
result = item.toString(kind === "d" ? 10 : kind === "o" ? 8 : 16);
|
|
738
|
+
if (kind === "X")
|
|
739
|
+
result = result.toUpperCase();
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
if (typeof item !== "bigint" && typeof item !== "number")
|
|
743
|
+
throw new Error("Expected numeric format value");
|
|
744
|
+
const digits = precision ? Number(precision) : 6;
|
|
745
|
+
if (digits > 100)
|
|
746
|
+
throw new Error("Excessive format precision");
|
|
747
|
+
result =
|
|
748
|
+
kind.toLowerCase() === "f"
|
|
749
|
+
? Number(item).toFixed(digits)
|
|
750
|
+
: kind.toLowerCase() === "e"
|
|
751
|
+
? Number(item).toExponential(digits)
|
|
752
|
+
: String(item);
|
|
753
|
+
}
|
|
754
|
+
const length = Number(width || 0);
|
|
755
|
+
budget.size(length);
|
|
756
|
+
return flags.includes("-")
|
|
757
|
+
? result.padEnd(length, " ")
|
|
758
|
+
: result.padStart(length, flags.includes("0") ? "0" : " ");
|
|
759
|
+
});
|
|
760
|
+
if (!(value instanceof Dict) && index !== values.length)
|
|
761
|
+
throw new Error("Unused format values");
|
|
762
|
+
return budget.string(output);
|
|
763
|
+
}
|
|
764
|
+
//# sourceMappingURL=builtins.js.map
|