@tracecode/harness 0.5.0 → 0.6.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/CHANGELOG.md +23 -0
- package/LICENSE +67 -80
- package/README.md +31 -4
- package/dist/browser.cjs +974 -19
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +3 -2
- package/dist/browser.d.ts +3 -2
- package/dist/browser.js +974 -19
- package/dist/browser.js.map +1 -1
- package/dist/cli.cjs +24 -0
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +24 -0
- package/dist/cli.js.map +1 -1
- package/dist/core.cjs +611 -4
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +20 -6
- package/dist/core.d.ts +20 -6
- package/dist/core.js +605 -4
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +1055 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1049 -52
- package/dist/index.js.map +1 -1
- package/dist/internal/browser.cjs +211 -0
- package/dist/internal/browser.cjs.map +1 -1
- package/dist/internal/browser.d.cts +62 -6
- package/dist/internal/browser.d.ts +62 -6
- package/dist/internal/browser.js +210 -0
- package/dist/internal/browser.js.map +1 -1
- package/dist/javascript.cjs +37 -19
- package/dist/javascript.cjs.map +1 -1
- package/dist/javascript.d.cts +2 -2
- package/dist/javascript.d.ts +2 -2
- package/dist/javascript.js +37 -19
- package/dist/javascript.js.map +1 -1
- package/dist/python.cjs +14 -14
- package/dist/python.cjs.map +1 -1
- package/dist/python.d.cts +8 -8
- package/dist/python.d.ts +8 -8
- package/dist/python.js +14 -14
- package/dist/python.js.map +1 -1
- package/dist/{runtime-types-DtaaAhHL.d.ts → runtime-types-89nchXlY.d.cts} +8 -4
- package/dist/{runtime-types--lBQ6rYu.d.cts → runtime-types-CCQ-ZLc9.d.ts} +8 -4
- package/dist/{types-DwIYM3Ku.d.cts → types-zyvpJKCi.d.cts} +1 -0
- package/dist/{types-DwIYM3Ku.d.ts → types-zyvpJKCi.d.ts} +1 -0
- package/package.json +12 -6
- package/workers/java/.build/classes/harness/browser/JavaRewriteLibrary.class +0 -0
- package/workers/java/java-worker.js +712 -0
- package/workers/java/src/harness/browser/JavaRewriteLibrary.java +54 -0
- package/workers/javascript/javascript-worker.js +343 -63
- package/workers/python/generated-python-harness-snippets.js +3 -3
- package/workers/python/pyodide-worker.js +419 -68
- package/workers/python/runtime-core.js +52 -3
- package/workers/vendor/java-browser-spike-helper.jar +0 -0
- package/workers/vendor/java-practice-rewriter.jar +0 -0
- package/workers/vendor/java-rewrite-bridge.jar +0 -0
- package/workers/vendor/javaparser-core-3.25.10.jar +0 -0
- package/workers/vendor/jdk.compiler-17.jar +0 -0
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
const CHEERPJ_LOADER_URL = 'https://cjrtnc.leaningtech.com/4.2/loader.js';
|
|
2
|
+
const HELPER_JAR_PATH = '/app/workers/vendor/java-browser-spike-helper.jar';
|
|
3
|
+
const JDK17_COMPILER_JAR_PATH = '/app/workers/vendor/jdk.compiler-17.jar';
|
|
4
|
+
const REWRITER_JAR_PATH = '/app/workers/vendor/java-practice-rewriter.jar';
|
|
5
|
+
const REWRITER_BRIDGE_JAR_PATH = '/app/workers/vendor/java-rewrite-bridge.jar';
|
|
6
|
+
const JAVAPARSER_JAR_PATH = '/app/workers/vendor/javaparser-core-3.25.10.jar';
|
|
7
|
+
const FULL_CLASSPATH = [
|
|
8
|
+
HELPER_JAR_PATH,
|
|
9
|
+
JDK17_COMPILER_JAR_PATH,
|
|
10
|
+
REWRITER_JAR_PATH,
|
|
11
|
+
REWRITER_BRIDGE_JAR_PATH,
|
|
12
|
+
JAVAPARSER_JAR_PATH,
|
|
13
|
+
].join(':');
|
|
14
|
+
const DEFAULT_COMPILER_DEBUG_PROFILE = 'full';
|
|
15
|
+
const DEFAULT_MAX_STORED_EVENTS = 50_000;
|
|
16
|
+
const IDLE_TIMEOUT_MS = 90_000;
|
|
17
|
+
|
|
18
|
+
let workerReadyPromise = null;
|
|
19
|
+
let idleTimer = null;
|
|
20
|
+
let queue = Promise.resolve();
|
|
21
|
+
let helperLibraryPromise = null;
|
|
22
|
+
let compileLibraryClassPromise = null;
|
|
23
|
+
let rewriteLibraryClassPromise = null;
|
|
24
|
+
let idleGeneration = 0;
|
|
25
|
+
let hostWarmupPromise = null;
|
|
26
|
+
let initLoadTimeMs = null;
|
|
27
|
+
|
|
28
|
+
function postMessageResponse(message) {
|
|
29
|
+
self.postMessage(message);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resetIdleTimer() {
|
|
33
|
+
idleGeneration += 1;
|
|
34
|
+
const generation = idleGeneration;
|
|
35
|
+
if (idleTimer !== null) {
|
|
36
|
+
clearTimeout(idleTimer);
|
|
37
|
+
}
|
|
38
|
+
idleTimer = setTimeout(() => {
|
|
39
|
+
if (generation !== idleGeneration) return;
|
|
40
|
+
postMessageResponse({ type: 'idle-timeout' });
|
|
41
|
+
self.close();
|
|
42
|
+
}, IDLE_TIMEOUT_MS);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function assertSupportedExecutionStyle(executionStyle) {
|
|
46
|
+
if (executionStyle !== 'function' && executionStyle !== 'solution-method' && executionStyle !== 'ops-class') {
|
|
47
|
+
throw new Error(`Java worker does not support execution style "${executionStyle}".`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveMaxStoredEvents(options = {}) {
|
|
52
|
+
const fromStored = Number(options.maxStoredEvents);
|
|
53
|
+
if (Number.isFinite(fromStored) && fromStored > 0) {
|
|
54
|
+
return Math.floor(fromStored);
|
|
55
|
+
}
|
|
56
|
+
const fromTraceSteps = Number(options.maxTraceSteps);
|
|
57
|
+
if (Number.isFinite(fromTraceSteps) && fromTraceSteps > 0) {
|
|
58
|
+
return Math.floor(fromTraceSteps);
|
|
59
|
+
}
|
|
60
|
+
return DEFAULT_MAX_STORED_EVENTS;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isRecord(value) {
|
|
64
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isListNodeShape(value) {
|
|
68
|
+
if (!isRecord(value)) return false;
|
|
69
|
+
if (!('val' in value || 'value' in value)) return false;
|
|
70
|
+
if ('next' in value) return true;
|
|
71
|
+
return typeof value.__id__ === 'string' && value.__id__.startsWith('list-');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isTreeNodeShape(value) {
|
|
75
|
+
if (!isRecord(value)) return false;
|
|
76
|
+
if (!('val' in value || 'value' in value)) return false;
|
|
77
|
+
if ('left' in value || 'right' in value) return true;
|
|
78
|
+
return typeof value.__id__ === 'string' && value.__id__.startsWith('tree-');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function detectFeatures(source, input) {
|
|
82
|
+
const values = Object.values(input ?? {});
|
|
83
|
+
return {
|
|
84
|
+
hasList: /\bListNode\b/.test(source) || values.some((value) => isListNodeShape(value)),
|
|
85
|
+
hasTree: /\bTreeNode\b/.test(source) || values.some((value) => isTreeNodeShape(value)),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function toJavaScalarLiteral(value) {
|
|
90
|
+
if (value === null) return 'null';
|
|
91
|
+
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
|
|
92
|
+
if (typeof value === 'string') return JSON.stringify(value);
|
|
93
|
+
throw new Error(`Unsupported scalar literal: ${JSON.stringify(value)}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function toJavaArrayLiteral(value) {
|
|
97
|
+
if (value.length === 0) return 'new int[] {}';
|
|
98
|
+
if (value.every((entry) => typeof entry === 'number' && Number.isInteger(entry))) {
|
|
99
|
+
return `new int[] { ${value.map((entry) => String(entry)).join(', ')} }`;
|
|
100
|
+
}
|
|
101
|
+
if (value.every((entry) => typeof entry === 'number')) {
|
|
102
|
+
return `new double[] { ${value.map((entry) => String(entry)).join(', ')} }`;
|
|
103
|
+
}
|
|
104
|
+
if (value.every((entry) => typeof entry === 'string')) {
|
|
105
|
+
return `new String[] { ${value.map((entry) => JSON.stringify(entry)).join(', ')} }`;
|
|
106
|
+
}
|
|
107
|
+
if (value.every((entry) => Array.isArray(entry))) {
|
|
108
|
+
return `new int[][] { ${value.map((entry) => toJavaArrayLiteral(entry)).join(', ')} }`;
|
|
109
|
+
}
|
|
110
|
+
throw new Error(`Unsupported array literal: ${JSON.stringify(value)}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function stripGenericType(typeSource) {
|
|
114
|
+
return typeSource.replace(/\s+/g, '');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function extractTypeArguments(typeSource) {
|
|
118
|
+
const normalized = stripGenericType(typeSource);
|
|
119
|
+
const start = normalized.indexOf('<');
|
|
120
|
+
const end = normalized.lastIndexOf('>');
|
|
121
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const body = normalized.slice(start + 1, end);
|
|
126
|
+
const parts = [];
|
|
127
|
+
let depth = 0;
|
|
128
|
+
let current = '';
|
|
129
|
+
for (const ch of body) {
|
|
130
|
+
if (ch === '<') depth += 1;
|
|
131
|
+
if (ch === '>') depth -= 1;
|
|
132
|
+
if (ch === ',' && depth === 0) {
|
|
133
|
+
parts.push(current);
|
|
134
|
+
current = '';
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
current += ch;
|
|
138
|
+
}
|
|
139
|
+
if (current) {
|
|
140
|
+
parts.push(current);
|
|
141
|
+
}
|
|
142
|
+
return parts.map((part) => part.trim()).filter(Boolean);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function toJavaTypedArrayLiteral(value, expectedType) {
|
|
146
|
+
const normalized = stripGenericType(expectedType);
|
|
147
|
+
if (!normalized.endsWith('[]')) {
|
|
148
|
+
return toJavaArrayLiteral(value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const elementType = normalized.slice(0, -2);
|
|
152
|
+
if (value.every((entry) => Array.isArray(entry))) {
|
|
153
|
+
return `new ${normalized} { ${value
|
|
154
|
+
.map((entry) => toJavaTypedArrayLiteral(entry, elementType))
|
|
155
|
+
.join(', ')} }`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (elementType === 'int' && value.every((entry) => typeof entry === 'number' && Number.isInteger(entry))) {
|
|
159
|
+
return `new int[] { ${value.map((entry) => String(entry)).join(', ')} }`;
|
|
160
|
+
}
|
|
161
|
+
if (elementType === 'double' && value.every((entry) => typeof entry === 'number')) {
|
|
162
|
+
return `new double[] { ${value.map((entry) => String(entry)).join(', ')} }`;
|
|
163
|
+
}
|
|
164
|
+
if (elementType === 'boolean' && value.every((entry) => typeof entry === 'boolean')) {
|
|
165
|
+
return `new boolean[] { ${value.map((entry) => String(entry)).join(', ')} }`;
|
|
166
|
+
}
|
|
167
|
+
if (elementType === 'String' && value.every((entry) => typeof entry === 'string')) {
|
|
168
|
+
return `new String[] { ${value.map((entry) => JSON.stringify(entry)).join(', ')} }`;
|
|
169
|
+
}
|
|
170
|
+
if (elementType === 'char' && value.every((entry) => typeof entry === 'string' && entry.length === 1)) {
|
|
171
|
+
return `new char[] { ${value
|
|
172
|
+
.map((entry) => `'${String(entry).replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`)
|
|
173
|
+
.join(', ')} }`;
|
|
174
|
+
}
|
|
175
|
+
if (elementType === 'long' && value.every((entry) => typeof entry === 'number' && Number.isInteger(entry))) {
|
|
176
|
+
return `new long[] { ${value.map((entry) => `${String(entry)}L`).join(', ')} }`;
|
|
177
|
+
}
|
|
178
|
+
if (elementType === 'Object') {
|
|
179
|
+
return `new Object[] { ${value.map((entry) => buildJavaExpression(entry)).join(', ')} }`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return `new ${normalized} { ${value.map((entry) => buildJavaExpression(entry, elementType)).join(', ')} }`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function toJavaListLiteral(value, expectedType) {
|
|
186
|
+
const [elementType = 'Object'] = extractTypeArguments(expectedType);
|
|
187
|
+
return `java.util.List.of(${value.map((entry) => buildJavaExpression(entry, elementType)).join(', ')})`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function listLiteral(value) {
|
|
191
|
+
const rawVal = value.val ?? value.value ?? 0;
|
|
192
|
+
const next = value.next;
|
|
193
|
+
return `list(${toJavaScalarLiteral(rawVal)}, ${next ? listLiteral(next) : 'null'})`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function listGraphExpression(head) {
|
|
197
|
+
const nodes = [];
|
|
198
|
+
const indexByNode = new Map();
|
|
199
|
+
const indexById = new Map();
|
|
200
|
+
const nextIndices = [];
|
|
201
|
+
const pendingRefs = [];
|
|
202
|
+
|
|
203
|
+
const visit = (node) => {
|
|
204
|
+
if (!node) return -1;
|
|
205
|
+
if (indexByNode.has(node)) return indexByNode.get(node);
|
|
206
|
+
|
|
207
|
+
const index = nodes.length;
|
|
208
|
+
nodes.push(node);
|
|
209
|
+
indexByNode.set(node, index);
|
|
210
|
+
nextIndices[index] = -1;
|
|
211
|
+
|
|
212
|
+
if (typeof node.__id__ === 'string') {
|
|
213
|
+
indexById.set(node.__id__, index);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const next = node.next;
|
|
217
|
+
if (isRecord(next) && !Array.isArray(next)) {
|
|
218
|
+
if (typeof next.__ref__ === 'string') {
|
|
219
|
+
pendingRefs.push({ sourceIndex: index, targetId: next.__ref__ });
|
|
220
|
+
} else {
|
|
221
|
+
nextIndices[index] = visit(next);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return index;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
visit(head);
|
|
229
|
+
|
|
230
|
+
for (const pendingRef of pendingRefs) {
|
|
231
|
+
const targetIndex = indexById.get(pendingRef.targetId);
|
|
232
|
+
if (targetIndex !== undefined) {
|
|
233
|
+
nextIndices[pendingRef.sourceIndex] = targetIndex;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const values = nodes.map((node) => {
|
|
238
|
+
const rawVal = node.val ?? node.value ?? 0;
|
|
239
|
+
if (typeof rawVal !== 'number' || !Number.isInteger(rawVal)) {
|
|
240
|
+
throw new Error(`Unsupported list node value: ${JSON.stringify(rawVal)}`);
|
|
241
|
+
}
|
|
242
|
+
return rawVal;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return `buildList(new int[] { ${values.join(', ')} }, new int[] { ${nextIndices.join(', ')} })`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function listExpression(value) {
|
|
249
|
+
return `TraceHooks.reindexListIds(${listGraphExpression(value)})`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function treeExpression(value) {
|
|
253
|
+
const rawVal = value.val ?? value.value ?? 0;
|
|
254
|
+
const left = value.left ? treeExpression(value.left) : 'null';
|
|
255
|
+
const right = value.right ? treeExpression(value.right) : 'null';
|
|
256
|
+
return `tree(${toJavaScalarLiteral(rawVal)}, ${left}, ${right})`;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function buildJavaExpression(value, expectedType) {
|
|
260
|
+
const normalizedType = expectedType ? stripGenericType(expectedType) : null;
|
|
261
|
+
if (Array.isArray(value)) {
|
|
262
|
+
if (normalizedType?.startsWith('List<')) {
|
|
263
|
+
return toJavaListLiteral(value, normalizedType);
|
|
264
|
+
}
|
|
265
|
+
if (normalizedType?.endsWith('[]')) {
|
|
266
|
+
return toJavaTypedArrayLiteral(value, normalizedType);
|
|
267
|
+
}
|
|
268
|
+
return toJavaArrayLiteral(value);
|
|
269
|
+
}
|
|
270
|
+
if (isRecord(value) && normalizedType === 'ListNode') return listExpression(value);
|
|
271
|
+
if (isRecord(value) && normalizedType === 'TreeNode') return treeExpression(value);
|
|
272
|
+
if (isListNodeShape(value)) return listExpression(value);
|
|
273
|
+
if (isTreeNodeShape(value)) return treeExpression(value);
|
|
274
|
+
return toJavaScalarLiteral(value);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function buildHelperMethods(features) {
|
|
278
|
+
const members = [];
|
|
279
|
+
if (features.hasList) {
|
|
280
|
+
members.push(`
|
|
281
|
+
private static ListNode list(int val, ListNode next) {
|
|
282
|
+
ListNode node = new ListNode(val);
|
|
283
|
+
node.next = next;
|
|
284
|
+
return node;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private static ListNode buildList(int[] values, int[] nextIndices) {
|
|
288
|
+
if (values.length == 0) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
ListNode[] nodes = new ListNode[values.length];
|
|
292
|
+
for (int i = 0; i < values.length; i++) {
|
|
293
|
+
nodes[i] = new ListNode(values[i]);
|
|
294
|
+
}
|
|
295
|
+
for (int i = 0; i < values.length; i++) {
|
|
296
|
+
int nextIndex = nextIndices[i];
|
|
297
|
+
nodes[i].next = nextIndex >= 0 ? nodes[nextIndex] : null;
|
|
298
|
+
}
|
|
299
|
+
return nodes[0];
|
|
300
|
+
}`);
|
|
301
|
+
}
|
|
302
|
+
if (features.hasTree) {
|
|
303
|
+
members.push(`
|
|
304
|
+
private static TreeNode tree(int val, TreeNode left, TreeNode right) {
|
|
305
|
+
TreeNode node = new TreeNode(val);
|
|
306
|
+
node.left = left;
|
|
307
|
+
node.right = right;
|
|
308
|
+
return node;
|
|
309
|
+
}`);
|
|
310
|
+
}
|
|
311
|
+
return members.join('\n');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function extractMethodParameters(source, methodName) {
|
|
315
|
+
const compact = source.replace(/\s+/g, ' ');
|
|
316
|
+
const escapedMethod = methodName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
317
|
+
const match = compact.match(new RegExp(`\\b${escapedMethod}\\s*\\(([^)]*)\\)`));
|
|
318
|
+
if (!match || !match[1] || !match[1].trim()) {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return match[1]
|
|
323
|
+
.split(',')
|
|
324
|
+
.map((segment) => segment.trim())
|
|
325
|
+
.filter(Boolean)
|
|
326
|
+
.map((segment) => {
|
|
327
|
+
const lastSpace = segment.lastIndexOf(' ');
|
|
328
|
+
if (lastSpace === -1) {
|
|
329
|
+
return { type: segment, name: segment };
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
type: segment.slice(0, lastSpace).trim(),
|
|
333
|
+
name: segment.slice(lastSpace + 1).trim(),
|
|
334
|
+
};
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function indentBlock(source, spaces = 2) {
|
|
339
|
+
const prefix = ' '.repeat(spaces);
|
|
340
|
+
return source
|
|
341
|
+
.split('\n')
|
|
342
|
+
.map((line) => (line.trim().length === 0 ? '' : `${prefix}${line}`))
|
|
343
|
+
.join('\n');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function normalizeFunctionSource(source) {
|
|
347
|
+
if (/\bpackage\s+[A-Za-z_][A-Za-z0-9_.]*\s*;/.test(source)) {
|
|
348
|
+
throw new Error('Java function style should not declare a package; the harness manages package isolation.');
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (/\bclass\s+Solution\b/.test(source)) {
|
|
352
|
+
return source;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (/\b(class|interface|enum|record)\b/.test(source)) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
'Java function style currently expects a bare method fragment or a class named Solution containing the target method.'
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const lines = source.split('\n');
|
|
362
|
+
const importLines = [];
|
|
363
|
+
const bodyLines = [];
|
|
364
|
+
let inImportPrelude = true;
|
|
365
|
+
|
|
366
|
+
for (const line of lines) {
|
|
367
|
+
const trimmed = line.trim();
|
|
368
|
+
if (inImportPrelude && (trimmed === '' || trimmed.startsWith('import '))) {
|
|
369
|
+
importLines.push(line);
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
inImportPrelude = false;
|
|
373
|
+
bodyLines.push(line);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const importBlock = importLines.join('\n').trim();
|
|
377
|
+
const body = bodyLines.join('\n').trim();
|
|
378
|
+
if (!body) {
|
|
379
|
+
throw new Error('Java function style requires a method fragment.');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return `${importBlock ? `${importBlock}\n\n` : ''}class Solution {\n${indentBlock(body, 2)}\n}`;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function normalizeJavaRequest(payload) {
|
|
386
|
+
if (payload.executionStyle !== 'function') {
|
|
387
|
+
return payload;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
...payload,
|
|
392
|
+
code: normalizeFunctionSource(payload.code),
|
|
393
|
+
executionStyle: 'solution-method',
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function buildExportsSource(source, functionName, executionStyle, input) {
|
|
398
|
+
const features = detectFeatures(source, input);
|
|
399
|
+
const helperMethods = buildHelperMethods(features);
|
|
400
|
+
|
|
401
|
+
if (executionStyle === 'ops-class') {
|
|
402
|
+
const operations = Array.isArray(input.operations) ? input.operations : [];
|
|
403
|
+
const argumentsList = Array.isArray(input.arguments) ? input.arguments : [];
|
|
404
|
+
const lines = [
|
|
405
|
+
` ${functionName} instance = null;`,
|
|
406
|
+
' java.util.List<Object> out = new java.util.ArrayList<>();',
|
|
407
|
+
];
|
|
408
|
+
|
|
409
|
+
operations.forEach((operation, index) => {
|
|
410
|
+
const args = Array.isArray(argumentsList[index]) ? argumentsList[index] : [];
|
|
411
|
+
if (index === 0) {
|
|
412
|
+
lines.push(` instance = new ${functionName}(${args.map((arg) => buildJavaExpression(arg)).join(', ')});`);
|
|
413
|
+
lines.push(' out.add(null);');
|
|
414
|
+
} else {
|
|
415
|
+
lines.push(` out.add(instance.${String(operation)}(${args.map((arg) => buildJavaExpression(arg)).join(', ')}));`);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
return `public class Exports {
|
|
420
|
+
${helperMethods}
|
|
421
|
+
|
|
422
|
+
public static String run() {
|
|
423
|
+
${lines.join('\n')}
|
|
424
|
+
return TraceHooks.serializeResult(out);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
`;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const parameters = extractMethodParameters(source, functionName);
|
|
431
|
+
const invocationArgs = (parameters.length > 0 ? parameters.map((parameter) => parameter.name) : Object.keys(input))
|
|
432
|
+
.map((key, index) => {
|
|
433
|
+
const type = parameters[index] ? parameters[index].type : undefined;
|
|
434
|
+
return buildJavaExpression(input[key], type);
|
|
435
|
+
})
|
|
436
|
+
.join(', ');
|
|
437
|
+
|
|
438
|
+
return `public class Exports {
|
|
439
|
+
${helperMethods}
|
|
440
|
+
|
|
441
|
+
public static String run() {
|
|
442
|
+
Solution solution = new Solution();
|
|
443
|
+
Object result = solution.${functionName}(${invocationArgs});
|
|
444
|
+
return TraceHooks.serializeResult(result);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function buildPackageName(messageId) {
|
|
451
|
+
return `harness.user.job${String(messageId).replace(/[^A-Za-z0-9]/g, '')}`;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function buildExportsClassName(messageId) {
|
|
455
|
+
return `Exports${String(messageId).replace(/[^A-Za-z0-9]/g, '')}`;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async function ensureReady() {
|
|
459
|
+
if (!workerReadyPromise) {
|
|
460
|
+
workerReadyPromise = (async () => {
|
|
461
|
+
const startedAt = performance.now();
|
|
462
|
+
self.importScripts(CHEERPJ_LOADER_URL);
|
|
463
|
+
if (typeof self.cheerpjInit !== 'function') {
|
|
464
|
+
throw new Error('CheerpJ loader did not expose cheerpjInit');
|
|
465
|
+
}
|
|
466
|
+
await self.cheerpjInit({ version: 17, status: 'none' });
|
|
467
|
+
if (
|
|
468
|
+
typeof self.cheerpjRunLibrary !== 'function' ||
|
|
469
|
+
typeof self.cheerpOSAddStringFile !== 'function'
|
|
470
|
+
) {
|
|
471
|
+
throw new Error('CheerpJ runtime APIs are unavailable in the worker');
|
|
472
|
+
}
|
|
473
|
+
initLoadTimeMs = performance.now() - startedAt;
|
|
474
|
+
})();
|
|
475
|
+
}
|
|
476
|
+
await workerReadyPromise;
|
|
477
|
+
resetIdleTimer();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function getHelperLibrary() {
|
|
481
|
+
if (!helperLibraryPromise) {
|
|
482
|
+
helperLibraryPromise = self.cheerpjRunLibrary(FULL_CLASSPATH);
|
|
483
|
+
}
|
|
484
|
+
return helperLibraryPromise;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function getCompileLibraryClass() {
|
|
488
|
+
if (!compileLibraryClassPromise) {
|
|
489
|
+
compileLibraryClassPromise = (async () => {
|
|
490
|
+
const library = await getHelperLibrary();
|
|
491
|
+
return library.spike.browser.BrowserCompileAndTraceLibrary;
|
|
492
|
+
})();
|
|
493
|
+
}
|
|
494
|
+
return compileLibraryClassPromise;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
async function getRewriteLibraryClass() {
|
|
498
|
+
if (!rewriteLibraryClassPromise) {
|
|
499
|
+
rewriteLibraryClassPromise = (async () => {
|
|
500
|
+
const library = await getHelperLibrary();
|
|
501
|
+
return library.harness.browser.JavaRewriteLibrary;
|
|
502
|
+
})();
|
|
503
|
+
}
|
|
504
|
+
return rewriteLibraryClassPromise;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
async function warmHost() {
|
|
508
|
+
if (!hostWarmupPromise) {
|
|
509
|
+
hostWarmupPromise = (async () => {
|
|
510
|
+
const libraryClass = await getCompileLibraryClass();
|
|
511
|
+
const sourcePath = '/str/ExportsTracecodeWarmup.java';
|
|
512
|
+
const classesDir = '/files/java-worker/__warm__/classes';
|
|
513
|
+
const warmupSource = `
|
|
514
|
+
package harness.user.warmup;
|
|
515
|
+
|
|
516
|
+
public class ExportsTracecodeWarmup {
|
|
517
|
+
public static String run() {
|
|
518
|
+
return "0";
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
`;
|
|
522
|
+
await self.cheerpOSAddStringFile(sourcePath, warmupSource);
|
|
523
|
+
await libraryClass.compileAndTrace(
|
|
524
|
+
sourcePath,
|
|
525
|
+
classesDir,
|
|
526
|
+
'harness.user.warmup.ExportsTracecodeWarmup',
|
|
527
|
+
HELPER_JAR_PATH,
|
|
528
|
+
DEFAULT_COMPILER_DEBUG_PROFILE
|
|
529
|
+
);
|
|
530
|
+
})();
|
|
531
|
+
}
|
|
532
|
+
await hostWarmupPromise;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async function rewriteSource(payload, requestId) {
|
|
536
|
+
const normalizedPayload = normalizeJavaRequest(payload);
|
|
537
|
+
const rewriteLibraryClass = await getRewriteLibraryClass();
|
|
538
|
+
const exportsClassName = buildExportsClassName(requestId);
|
|
539
|
+
const packageName = buildPackageName(requestId);
|
|
540
|
+
const exportsSource = buildExportsSource(
|
|
541
|
+
normalizedPayload.code,
|
|
542
|
+
normalizedPayload.functionName,
|
|
543
|
+
normalizedPayload.executionStyle,
|
|
544
|
+
normalizedPayload.inputs ?? {}
|
|
545
|
+
);
|
|
546
|
+
return rewriteLibraryClass.rewriteSource(
|
|
547
|
+
normalizedPayload.code,
|
|
548
|
+
normalizedPayload.executionStyle,
|
|
549
|
+
normalizedPayload.functionName,
|
|
550
|
+
exportsSource,
|
|
551
|
+
exportsClassName,
|
|
552
|
+
packageName
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
async function runJavaRequest(payload, requestId) {
|
|
557
|
+
assertSupportedExecutionStyle(payload.executionStyle);
|
|
558
|
+
if (typeof payload.functionName !== 'string' || payload.functionName.trim().length === 0) {
|
|
559
|
+
throw new Error('Java execution requires a non-empty functionName or class entry name.');
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
const totalStart = performance.now();
|
|
563
|
+
const rewriteStart = performance.now();
|
|
564
|
+
const rewrittenSource = await rewriteSource(payload, requestId);
|
|
565
|
+
const rewriteEnd = performance.now();
|
|
566
|
+
|
|
567
|
+
const exportsClassName = buildExportsClassName(requestId);
|
|
568
|
+
const packageName = buildPackageName(requestId);
|
|
569
|
+
const sourcePath = `/str/${exportsClassName}.java`;
|
|
570
|
+
const classesDir = `/files/java-worker/${requestId}/classes`;
|
|
571
|
+
|
|
572
|
+
await self.cheerpOSAddStringFile(sourcePath, rewrittenSource);
|
|
573
|
+
|
|
574
|
+
const compileLibraryClass = await getCompileLibraryClass();
|
|
575
|
+
const libraryCallStart = performance.now();
|
|
576
|
+
const reportText = await compileLibraryClass.compileAndTrace(
|
|
577
|
+
sourcePath,
|
|
578
|
+
classesDir,
|
|
579
|
+
`${packageName}.${exportsClassName}`,
|
|
580
|
+
HELPER_JAR_PATH,
|
|
581
|
+
DEFAULT_COMPILER_DEBUG_PROFILE,
|
|
582
|
+
String(resolveMaxStoredEvents(payload.options))
|
|
583
|
+
);
|
|
584
|
+
const libraryCallEnd = performance.now();
|
|
585
|
+
|
|
586
|
+
const report = JSON.parse(reportText);
|
|
587
|
+
const totalEnd = performance.now();
|
|
588
|
+
const consoleOutput = [report.compilerStdout, report.compilerStderr].filter(
|
|
589
|
+
(entry) => typeof entry === 'string' && entry.trim().length > 0
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
if (report.success !== true) {
|
|
593
|
+
return {
|
|
594
|
+
success: false,
|
|
595
|
+
events: Array.isArray(report.events) ? report.events : [],
|
|
596
|
+
executionTimeMs: totalEnd - totalStart,
|
|
597
|
+
consoleOutput,
|
|
598
|
+
error:
|
|
599
|
+
report.runtimeError ||
|
|
600
|
+
report.compilerStderr ||
|
|
601
|
+
report.compilerStdout ||
|
|
602
|
+
'Java execution failed',
|
|
603
|
+
...(report.traceLimitExceeded !== undefined
|
|
604
|
+
? {
|
|
605
|
+
traceLimitExceeded: Boolean(report.traceLimitExceeded),
|
|
606
|
+
timeoutReason: report.traceLimitExceeded ? 'trace-limit' : undefined,
|
|
607
|
+
droppedEventCount: report.droppedEventCount ?? 0,
|
|
608
|
+
}
|
|
609
|
+
: {}),
|
|
610
|
+
timings: {
|
|
611
|
+
rewriteMs: rewriteEnd - rewriteStart,
|
|
612
|
+
hostCallMs: libraryCallEnd - libraryCallStart,
|
|
613
|
+
totalMs: totalEnd - totalStart,
|
|
614
|
+
},
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return {
|
|
619
|
+
success: true,
|
|
620
|
+
output: report.output ? JSON.parse(report.output) : undefined,
|
|
621
|
+
events: Array.isArray(report.events) ? report.events : [],
|
|
622
|
+
executionTimeMs: totalEnd - totalStart,
|
|
623
|
+
consoleOutput,
|
|
624
|
+
...(report.traceLimitExceeded !== undefined
|
|
625
|
+
? {
|
|
626
|
+
traceLimitExceeded: Boolean(report.traceLimitExceeded),
|
|
627
|
+
timeoutReason: report.traceLimitExceeded ? 'trace-limit' : undefined,
|
|
628
|
+
droppedEventCount: report.droppedEventCount ?? 0,
|
|
629
|
+
}
|
|
630
|
+
: {}),
|
|
631
|
+
timings: {
|
|
632
|
+
rewriteMs: rewriteEnd - rewriteStart,
|
|
633
|
+
hostCallMs: libraryCallEnd - libraryCallStart,
|
|
634
|
+
totalMs: totalEnd - totalStart,
|
|
635
|
+
compileMs: report.compileTimeMs ?? 0,
|
|
636
|
+
classLoadMs: report.classLoadTimeMs ?? 0,
|
|
637
|
+
runMs: report.runTimeMs ?? 0,
|
|
638
|
+
compileCacheHit: report.compileCacheHit ?? false,
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
self.onmessage = (event) => {
|
|
644
|
+
const message = event.data;
|
|
645
|
+
if (!message || typeof message !== 'object') {
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (idleTimer !== null) {
|
|
650
|
+
clearTimeout(idleTimer);
|
|
651
|
+
idleTimer = null;
|
|
652
|
+
}
|
|
653
|
+
idleGeneration += 1;
|
|
654
|
+
|
|
655
|
+
if (message.type === 'terminate') {
|
|
656
|
+
self.close();
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
if (message.type === 'init') {
|
|
661
|
+
queue = queue.then(async () => {
|
|
662
|
+
try {
|
|
663
|
+
await ensureReady();
|
|
664
|
+
await warmHost();
|
|
665
|
+
postMessageResponse({
|
|
666
|
+
id: message.id,
|
|
667
|
+
type: 'init',
|
|
668
|
+
payload: {
|
|
669
|
+
success: true,
|
|
670
|
+
loadTimeMs: Math.round(initLoadTimeMs ?? 0),
|
|
671
|
+
},
|
|
672
|
+
});
|
|
673
|
+
} catch (error) {
|
|
674
|
+
postMessageResponse({
|
|
675
|
+
id: message.id,
|
|
676
|
+
type: 'error',
|
|
677
|
+
payload: { error: error instanceof Error ? error.message : String(error) },
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (
|
|
685
|
+
message.type === 'execute-with-tracing' ||
|
|
686
|
+
message.type === 'execute-code' ||
|
|
687
|
+
message.type === 'execute-code-interview'
|
|
688
|
+
) {
|
|
689
|
+
queue = queue.then(async () => {
|
|
690
|
+
try {
|
|
691
|
+
await ensureReady();
|
|
692
|
+
const result = await runJavaRequest(message.payload, message.id);
|
|
693
|
+
postMessageResponse({
|
|
694
|
+
id: message.id,
|
|
695
|
+
type: message.type,
|
|
696
|
+
payload: result,
|
|
697
|
+
});
|
|
698
|
+
} catch (error) {
|
|
699
|
+
postMessageResponse({
|
|
700
|
+
id: message.id,
|
|
701
|
+
type: 'error',
|
|
702
|
+
payload: { error: error instanceof Error ? error.message : String(error) },
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
queueMicrotask(() => {
|
|
711
|
+
postMessageResponse({ type: 'worker-ready' });
|
|
712
|
+
});
|