numbl 0.4.7 → 0.4.8
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/dist-cli/cli.js +238 -9
- package/dist-lib/lib.d.ts +4 -0
- package/dist-lib/lib.js +937 -6
- package/dist-lib/numbl-core/executors/jit/hostHelpers.d.ts +4 -0
- package/dist-lib/numbl-core/fileIOAdapter.d.ts +13 -0
- package/dist-lib/numbl-core/jit/builtins/defs/math/rand.d.ts +2 -0
- package/dist-lib/numbl-core/jitDeclineDiagnostics.d.ts +32 -0
- package/dist-lib/numbl-core/jsUserFunctions.d.ts +13 -0
- package/dist-lib/numbl-core/version.d.ts +1 -1
- package/dist-lib/vfs/BrowserFileIOAdapter.d.ts +54 -0
- package/dist-lib/vfs/BrowserSystemAdapter.d.ts +27 -0
- package/dist-lib/vfs/VirtualFileSystem.d.ts +66 -0
- package/dist-site-viewer/assets/{index-RucHpf4b.js → index-C-wfkZK0.js} +339 -336
- package/dist-site-viewer/assets/{numbl-worker-IO39kohI.js → numbl-worker-Bnbz2rMQ.js} +87 -87
- package/dist-site-viewer/index.html +1 -1
- package/package.json +1 -1
package/dist-lib/lib.js
CHANGED
|
@@ -55132,6 +55132,8 @@ var SPECIAL_BUILTIN_NAMES = [
|
|
|
55132
55132
|
"delete",
|
|
55133
55133
|
"rmdir",
|
|
55134
55134
|
"movefile",
|
|
55135
|
+
"copyfile",
|
|
55136
|
+
"fileattrib",
|
|
55135
55137
|
"unzip",
|
|
55136
55138
|
"dir",
|
|
55137
55139
|
"warning",
|
|
@@ -55141,6 +55143,7 @@ var SPECIAL_BUILTIN_NAMES = [
|
|
|
55141
55143
|
"userpath",
|
|
55142
55144
|
"getenv",
|
|
55143
55145
|
"setenv",
|
|
55146
|
+
"maxNumCompThreads",
|
|
55144
55147
|
"pwd",
|
|
55145
55148
|
"cd",
|
|
55146
55149
|
"ode45",
|
|
@@ -56082,6 +56085,65 @@ function registerSpecialBuiltins(rt) {
|
|
|
56082
56085
|
RTV.char("")
|
|
56083
56086
|
];
|
|
56084
56087
|
});
|
|
56088
|
+
registerSpecial("copyfile", (nargout, args) => {
|
|
56089
|
+
const io = requireFileIO();
|
|
56090
|
+
if (!io.copyfile)
|
|
56091
|
+
throw new RuntimeError("copyfile is not available in this environment");
|
|
56092
|
+
const margs = args.map((a) => ensureRuntimeValue(a));
|
|
56093
|
+
if (margs.length < 1)
|
|
56094
|
+
throw new RuntimeError("copyfile requires at least 1 argument");
|
|
56095
|
+
const source = toString(margs[0]);
|
|
56096
|
+
const destination = margs.length >= 2 ? toString(margs[1]) : rt.system?.cwd() ?? ".";
|
|
56097
|
+
let force = false;
|
|
56098
|
+
if (margs.length >= 3) {
|
|
56099
|
+
const third = toString(margs[2]);
|
|
56100
|
+
if (third.toLowerCase() === "f") force = true;
|
|
56101
|
+
}
|
|
56102
|
+
const ok = io.copyfile(source, destination, force);
|
|
56103
|
+
if (nargout === 0) {
|
|
56104
|
+
if (!ok)
|
|
56105
|
+
throw new RuntimeError(
|
|
56106
|
+
`copyfile: cannot copy '${source}' to '${destination}'`
|
|
56107
|
+
);
|
|
56108
|
+
return void 0;
|
|
56109
|
+
}
|
|
56110
|
+
return nargout <= 1 ? RTV.num(ok ? 1 : 0) : [
|
|
56111
|
+
RTV.num(ok ? 1 : 0),
|
|
56112
|
+
RTV.char(ok ? "" : `Cannot copy '${source}' to '${destination}'`),
|
|
56113
|
+
RTV.char("")
|
|
56114
|
+
];
|
|
56115
|
+
});
|
|
56116
|
+
registerSpecial("fileattrib", (nargout, args) => {
|
|
56117
|
+
const io = requireFileIO();
|
|
56118
|
+
if (!io.fileattrib)
|
|
56119
|
+
throw new RuntimeError("fileattrib is not available in this environment");
|
|
56120
|
+
const margs = args.map((a) => ensureRuntimeValue(a));
|
|
56121
|
+
if (margs.length < 1)
|
|
56122
|
+
throw new RuntimeError("fileattrib requires at least 1 argument");
|
|
56123
|
+
const p2 = toString(margs[0]);
|
|
56124
|
+
const info = io.fileattrib(p2);
|
|
56125
|
+
if (nargout === 0) {
|
|
56126
|
+
if (!info)
|
|
56127
|
+
throw new RuntimeError(
|
|
56128
|
+
`fileattrib: cannot find '${p2}': No such file or directory`
|
|
56129
|
+
);
|
|
56130
|
+
return void 0;
|
|
56131
|
+
}
|
|
56132
|
+
const values = info ? RTV.struct(
|
|
56133
|
+
/* @__PURE__ */ new Map([
|
|
56134
|
+
["Name", RTV.char(info.Name)],
|
|
56135
|
+
["archive", RTV.num(0)],
|
|
56136
|
+
["system", RTV.num(0)],
|
|
56137
|
+
["hidden", RTV.num(0)],
|
|
56138
|
+
["directory", RTV.num(info.directory ? 1 : 0)],
|
|
56139
|
+
["UserRead", RTV.num(info.UserRead ? 1 : 0)],
|
|
56140
|
+
["UserWrite", RTV.num(info.UserWrite ? 1 : 0)],
|
|
56141
|
+
["UserExecute", RTV.num(info.UserExecute ? 1 : 0)]
|
|
56142
|
+
])
|
|
56143
|
+
) : RTV.char(`No such file or directory: ${p2}`);
|
|
56144
|
+
if (nargout <= 1) return RTV.num(info ? 1 : 0);
|
|
56145
|
+
return [RTV.num(info ? 1 : 0), values, RTV.char("")];
|
|
56146
|
+
});
|
|
56085
56147
|
registerSpecial("unzip", (nargout, args) => {
|
|
56086
56148
|
const io = requireFileIO();
|
|
56087
56149
|
if (!io.unzip)
|
|
@@ -56407,6 +56469,7 @@ function registerSpecialBuiltins(rt) {
|
|
|
56407
56469
|
}
|
|
56408
56470
|
return RTV.char(sys?.getEnv(toString(args[0])) ?? "");
|
|
56409
56471
|
});
|
|
56472
|
+
registerSpecial("maxNumCompThreads", () => RTV.num(1));
|
|
56410
56473
|
registerSpecialVoid("setenv", (args) => {
|
|
56411
56474
|
const sys = rt.system;
|
|
56412
56475
|
if (args.length === 2) {
|
|
@@ -62056,6 +62119,13 @@ function defaultClassInstanceVertcat(rows) {
|
|
|
62056
62119
|
}
|
|
62057
62120
|
|
|
62058
62121
|
// src/numbl-core/jsUserFunctions.ts
|
|
62122
|
+
function callHandle(handle, args, nargout = 1) {
|
|
62123
|
+
const rt = getCurrentRuntime();
|
|
62124
|
+
if (!rt) {
|
|
62125
|
+
throw new RuntimeError("callHandle: no active runtime to invoke handle");
|
|
62126
|
+
}
|
|
62127
|
+
return rt.index(handle, args, nargout);
|
|
62128
|
+
}
|
|
62059
62129
|
function funcNameFromFile(fileName) {
|
|
62060
62130
|
const base = fileName.split("/").pop();
|
|
62061
62131
|
return base.replace(/\.numbl\.js$/, "");
|
|
@@ -62101,6 +62171,8 @@ function instantiateWasm(wasmData) {
|
|
|
62101
62171
|
const moduleImports = WebAssembly.Module.imports(wasmModule);
|
|
62102
62172
|
const importObject = {};
|
|
62103
62173
|
const neededModules = new Set(moduleImports.map((i) => i.module));
|
|
62174
|
+
const callbacks = /* @__PURE__ */ new Map();
|
|
62175
|
+
let nextCbId = 1;
|
|
62104
62176
|
if (neededModules.has("wasi_snapshot_preview1")) {
|
|
62105
62177
|
importObject.wasi_snapshot_preview1 = {
|
|
62106
62178
|
fd_write: () => 0,
|
|
@@ -62120,10 +62192,36 @@ function instantiateWasm(wasmData) {
|
|
|
62120
62192
|
if (neededModules.has("env")) {
|
|
62121
62193
|
importObject.env = {
|
|
62122
62194
|
emscripten_notify_memory_growth: () => {
|
|
62195
|
+
},
|
|
62196
|
+
// Scalar callback: WASM calls back into a registered handle with one
|
|
62197
|
+
// f64 and receives an f64. Exceptions thrown here (incl. a missing id)
|
|
62198
|
+
// propagate out through the WASM call into the apply.
|
|
62199
|
+
numbl_cb_d: (id, x) => {
|
|
62200
|
+
const fn = callbacks.get(id);
|
|
62201
|
+
if (!fn) {
|
|
62202
|
+
throw new RuntimeError(
|
|
62203
|
+
`numbl_cb_d: no callback registered for id ${id}`
|
|
62204
|
+
);
|
|
62205
|
+
}
|
|
62206
|
+
return fn(x);
|
|
62123
62207
|
}
|
|
62124
62208
|
};
|
|
62125
62209
|
}
|
|
62126
|
-
|
|
62210
|
+
const instance = new WebAssembly.Instance(
|
|
62211
|
+
wasmModule,
|
|
62212
|
+
importObject
|
|
62213
|
+
);
|
|
62214
|
+
instance.callbacks = {
|
|
62215
|
+
add(fn) {
|
|
62216
|
+
const id = nextCbId++;
|
|
62217
|
+
callbacks.set(id, fn);
|
|
62218
|
+
return id;
|
|
62219
|
+
},
|
|
62220
|
+
remove(id) {
|
|
62221
|
+
callbacks.delete(id);
|
|
62222
|
+
}
|
|
62223
|
+
};
|
|
62224
|
+
return instance;
|
|
62127
62225
|
}
|
|
62128
62226
|
function resolveBindings(file, directives, getWasmInstance, nativeBridge) {
|
|
62129
62227
|
const wasmInstance = directives.wasm ? getWasmInstance(directives.wasm) : void 0;
|
|
@@ -62198,6 +62296,8 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge) {
|
|
|
62198
62296
|
"wasm",
|
|
62199
62297
|
"native",
|
|
62200
62298
|
"importJS",
|
|
62299
|
+
"callHandle",
|
|
62300
|
+
"toNumber",
|
|
62201
62301
|
libFile.source
|
|
62202
62302
|
);
|
|
62203
62303
|
const exports = factory(
|
|
@@ -62207,7 +62307,9 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge) {
|
|
|
62207
62307
|
dummyRegister,
|
|
62208
62308
|
wasmInstance,
|
|
62209
62309
|
nativeLib,
|
|
62210
|
-
importJS
|
|
62310
|
+
importJS,
|
|
62311
|
+
callHandle,
|
|
62312
|
+
toNumber
|
|
62211
62313
|
);
|
|
62212
62314
|
libCache.set(name, exports);
|
|
62213
62315
|
return exports;
|
|
@@ -62245,6 +62347,8 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge) {
|
|
|
62245
62347
|
"wasm",
|
|
62246
62348
|
"native",
|
|
62247
62349
|
"importJS",
|
|
62350
|
+
"callHandle",
|
|
62351
|
+
"toNumber",
|
|
62248
62352
|
file.source
|
|
62249
62353
|
);
|
|
62250
62354
|
factory(
|
|
@@ -62254,7 +62358,9 @@ function loadJsUserFunctions(jsFiles, wasmFiles, nativeBridge) {
|
|
|
62254
62358
|
registerFn,
|
|
62255
62359
|
wasmInstance,
|
|
62256
62360
|
nativeLib,
|
|
62257
|
-
importJS
|
|
62361
|
+
importJS,
|
|
62362
|
+
callHandle,
|
|
62363
|
+
toNumber
|
|
62258
62364
|
);
|
|
62259
62365
|
if (!builtin) {
|
|
62260
62366
|
throw new Error(
|
|
@@ -63874,6 +63980,15 @@ __export(interpreterExec_exports, {
|
|
|
63874
63980
|
writeLValueBase: () => writeLValueBase
|
|
63875
63981
|
});
|
|
63876
63982
|
|
|
63983
|
+
// src/numbl-core/jitDeclineDiagnostics.ts
|
|
63984
|
+
var lastDecline = null;
|
|
63985
|
+
function recordJitDecline(d) {
|
|
63986
|
+
lastDecline = d;
|
|
63987
|
+
}
|
|
63988
|
+
function getLastJitDecline() {
|
|
63989
|
+
return lastDecline;
|
|
63990
|
+
}
|
|
63991
|
+
|
|
63877
63992
|
// src/numbl-core/runtime/cow.ts
|
|
63878
63993
|
function cowCopy(v) {
|
|
63879
63994
|
if (isRuntimeTensor(v)) {
|
|
@@ -64176,14 +64291,16 @@ function execStmtInner(stmt) {
|
|
|
64176
64291
|
case "Directive": {
|
|
64177
64292
|
if (stmt.directive === "assert_jit") {
|
|
64178
64293
|
const wantC = stmt.args.includes("c");
|
|
64294
|
+
const decline = getLastJitDecline();
|
|
64295
|
+
const why = decline ? ` Most recent JIT decline (${decline.where}, ${decline.kind}): ${decline.message}` : ` (no JIT decline reason was recorded \u2014 the unit may have been declined before lowering, e.g. an unsupported input type.)`;
|
|
64179
64296
|
if (this.optimization === "1") {
|
|
64180
64297
|
throw new RuntimeError(
|
|
64181
|
-
`%!numbl:assert_jit: expected the enclosing loop/function/script to be JS-JIT-compiled at --opt 1, but it ran in the interpreter
|
|
64298
|
+
`%!numbl:assert_jit: expected the enclosing loop/function/script to be JS-JIT-compiled at --opt 1, but it ran in the interpreter.${why} (Run with --opt 0 to silence.)`
|
|
64182
64299
|
);
|
|
64183
64300
|
}
|
|
64184
64301
|
if (this.optimization === "2" && wantC) {
|
|
64185
64302
|
throw new RuntimeError(
|
|
64186
|
-
`%!numbl:assert_jit c: expected the enclosing loop/function/script to be C-JIT-compiled at --opt 2, but it ran in the interpreter
|
|
64303
|
+
`%!numbl:assert_jit c: expected the enclosing loop/function/script to be C-JIT-compiled at --opt 2, but it ran in the interpreter.${why} (Run with --opt 0 to silence.)`
|
|
64187
64304
|
);
|
|
64188
64305
|
}
|
|
64189
64306
|
}
|
|
@@ -77094,6 +77211,30 @@ var sin = defineUnaryRealMath({
|
|
|
77094
77211
|
complex: { cFnComplex: "mtoc2_csin", jsFnComplex: cSin }
|
|
77095
77212
|
});
|
|
77096
77213
|
|
|
77214
|
+
// src/numbl-core/jit/builtins/defs/math/rand.ts
|
|
77215
|
+
var rand = {
|
|
77216
|
+
name: "rand",
|
|
77217
|
+
transfer(argTypes, nargout) {
|
|
77218
|
+
if (nargout > 1) {
|
|
77219
|
+
throw new UnsupportedConstruct(
|
|
77220
|
+
`'rand' does not support multi-output (nargout=${nargout})`
|
|
77221
|
+
);
|
|
77222
|
+
}
|
|
77223
|
+
if (argTypes.length !== 0) {
|
|
77224
|
+
throw new UnsupportedConstruct(
|
|
77225
|
+
`JS-JIT 'rand' supports only the scalar form rand() so far (got ${argTypes.length} arg(s)); matrix/seed forms run in the interpreter`
|
|
77226
|
+
);
|
|
77227
|
+
}
|
|
77228
|
+
return [scalarDouble("nonneg")];
|
|
77229
|
+
},
|
|
77230
|
+
emitJs() {
|
|
77231
|
+
return "$rand()";
|
|
77232
|
+
},
|
|
77233
|
+
call() {
|
|
77234
|
+
return [rngRandom()];
|
|
77235
|
+
}
|
|
77236
|
+
};
|
|
77237
|
+
|
|
77097
77238
|
// src/numbl-core/jit/builtins/defs/math/tan.ts
|
|
77098
77239
|
var tan = defineUnaryRealMath({
|
|
77099
77240
|
name: "tan",
|
|
@@ -82477,6 +82618,7 @@ for (const b of [
|
|
|
82477
82618
|
stdBuiltin,
|
|
82478
82619
|
min,
|
|
82479
82620
|
max,
|
|
82621
|
+
rand,
|
|
82480
82622
|
any,
|
|
82481
82623
|
all,
|
|
82482
82624
|
zeros,
|
|
@@ -88262,12 +88404,13 @@ function emitJsProgram(prog, opts = {}) {
|
|
|
88262
88404
|
const wrapperLines = [];
|
|
88263
88405
|
if (opts.exposeSpec !== void 0) {
|
|
88264
88406
|
wrapperLines.push(
|
|
88265
|
-
`return function ($h) { globalThis.$write = $h.write; globalThis.$plotDispatch = $h.plotDispatch; return ${opts.exposeSpec}; };`
|
|
88407
|
+
`return function ($h) { globalThis.$write = $h.write; globalThis.$plotDispatch = $h.plotDispatch; globalThis.$rand = $h.rand; return ${opts.exposeSpec}; };`
|
|
88266
88408
|
);
|
|
88267
88409
|
} else {
|
|
88268
88410
|
wrapperLines.push("function run($h) {");
|
|
88269
88411
|
wrapperLines.push(" globalThis.$write = $h.write;");
|
|
88270
88412
|
wrapperLines.push(" globalThis.$plotDispatch = $h.plotDispatch;");
|
|
88413
|
+
wrapperLines.push(" globalThis.$rand = $h.rand;");
|
|
88271
88414
|
const locals = collectAssignedLocals(prog.topLevelStmts);
|
|
88272
88415
|
if (locals.length > 0) {
|
|
88273
88416
|
wrapperLines.push(` let ${locals.join(", ")};`);
|
|
@@ -90304,6 +90447,7 @@ function getOrCreateSession(interp) {
|
|
|
90304
90447
|
function buildHostHelpers(rt) {
|
|
90305
90448
|
return {
|
|
90306
90449
|
write: (s) => rt.output(s),
|
|
90450
|
+
rand: () => rngRandom(),
|
|
90307
90451
|
plotDispatch: (name, args) => {
|
|
90308
90452
|
const runtimeArgs = args.map((a) => jitToNumbl(a));
|
|
90309
90453
|
const handled = dispatchPlotBuiltin(
|
|
@@ -90396,6 +90540,11 @@ var jitCallExecutor = {
|
|
|
90396
90540
|
return { specFn };
|
|
90397
90541
|
} catch (e) {
|
|
90398
90542
|
if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
|
|
90543
|
+
recordJitDecline({
|
|
90544
|
+
message: e.message,
|
|
90545
|
+
kind: e.constructor.name,
|
|
90546
|
+
where: "jit-call"
|
|
90547
|
+
});
|
|
90399
90548
|
return null;
|
|
90400
90549
|
}
|
|
90401
90550
|
throw e;
|
|
@@ -90522,6 +90671,11 @@ var jitLoopExecutor = {
|
|
|
90522
90671
|
return { specFn };
|
|
90523
90672
|
} catch (e) {
|
|
90524
90673
|
if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
|
|
90674
|
+
recordJitDecline({
|
|
90675
|
+
message: e.message,
|
|
90676
|
+
kind: e.constructor.name,
|
|
90677
|
+
where: "jit-loop"
|
|
90678
|
+
});
|
|
90525
90679
|
return null;
|
|
90526
90680
|
}
|
|
90527
90681
|
throw e;
|
|
@@ -90662,6 +90816,11 @@ var jitTopLevelExecutor = {
|
|
|
90662
90816
|
return { specFn, nargout };
|
|
90663
90817
|
} catch (e) {
|
|
90664
90818
|
if (e instanceof UnsupportedConstruct || e instanceof TypeError2) {
|
|
90819
|
+
recordJitDecline({
|
|
90820
|
+
message: e.message,
|
|
90821
|
+
kind: e.constructor.name,
|
|
90822
|
+
where: "jit-top-level"
|
|
90823
|
+
});
|
|
90665
90824
|
return null;
|
|
90666
90825
|
}
|
|
90667
90826
|
throw e;
|
|
@@ -91325,9 +91484,781 @@ async function load() {
|
|
|
91325
91484
|
setDelaunayBackend((points, dim) => qhull.delaunay(points, dim).facets);
|
|
91326
91485
|
setConvexHullBackend((points, dim) => qhull.convexHull(points, dim).facets);
|
|
91327
91486
|
}
|
|
91487
|
+
|
|
91488
|
+
// src/vfs/VirtualFileSystem.ts
|
|
91489
|
+
var VirtualFileSystem = class {
|
|
91490
|
+
files = /* @__PURE__ */ new Map();
|
|
91491
|
+
directories = /* @__PURE__ */ new Set();
|
|
91492
|
+
cwd = "/project";
|
|
91493
|
+
// Change tracking
|
|
91494
|
+
createdFiles = /* @__PURE__ */ new Set();
|
|
91495
|
+
modifiedFiles = /* @__PURE__ */ new Set();
|
|
91496
|
+
deletedFiles = /* @__PURE__ */ new Set();
|
|
91497
|
+
/** Clear change tracking. Call after populating the VFS with initial files. */
|
|
91498
|
+
clearChangeTracking() {
|
|
91499
|
+
this.createdFiles.clear();
|
|
91500
|
+
this.modifiedFiles.clear();
|
|
91501
|
+
this.deletedFiles.clear();
|
|
91502
|
+
}
|
|
91503
|
+
/** Normalize a path to absolute form. */
|
|
91504
|
+
normalizePath(p2) {
|
|
91505
|
+
if (!p2.startsWith("/")) {
|
|
91506
|
+
p2 = this.cwd + "/" + p2;
|
|
91507
|
+
}
|
|
91508
|
+
const parts = p2.split("/");
|
|
91509
|
+
const resolved = [];
|
|
91510
|
+
for (const part of parts) {
|
|
91511
|
+
if (part === "" || part === ".") continue;
|
|
91512
|
+
if (part === "..") {
|
|
91513
|
+
if (resolved.length > 0) resolved.pop();
|
|
91514
|
+
} else {
|
|
91515
|
+
resolved.push(part);
|
|
91516
|
+
}
|
|
91517
|
+
}
|
|
91518
|
+
return "/" + resolved.join("/");
|
|
91519
|
+
}
|
|
91520
|
+
getCwd() {
|
|
91521
|
+
return this.cwd;
|
|
91522
|
+
}
|
|
91523
|
+
setCwd(dir) {
|
|
91524
|
+
this.cwd = this.normalizePath(dir);
|
|
91525
|
+
}
|
|
91526
|
+
readFile(path) {
|
|
91527
|
+
const norm2 = this.normalizePath(path);
|
|
91528
|
+
const file = this.files.get(norm2);
|
|
91529
|
+
if (!file) throw new Error(`File not found: ${path}`);
|
|
91530
|
+
return file.content;
|
|
91531
|
+
}
|
|
91532
|
+
writeFile(path, content) {
|
|
91533
|
+
const norm2 = this.normalizePath(path);
|
|
91534
|
+
const existed = this.files.has(norm2);
|
|
91535
|
+
this.files.set(norm2, { content, mtimeMs: Date.now() });
|
|
91536
|
+
this.ensureParentDirs(norm2);
|
|
91537
|
+
if (this.deletedFiles.has(norm2)) {
|
|
91538
|
+
this.deletedFiles.delete(norm2);
|
|
91539
|
+
this.modifiedFiles.add(norm2);
|
|
91540
|
+
} else if (!existed) {
|
|
91541
|
+
this.createdFiles.add(norm2);
|
|
91542
|
+
} else if (!this.createdFiles.has(norm2)) {
|
|
91543
|
+
this.modifiedFiles.add(norm2);
|
|
91544
|
+
}
|
|
91545
|
+
}
|
|
91546
|
+
deleteFile(path) {
|
|
91547
|
+
const norm2 = this.normalizePath(path);
|
|
91548
|
+
if (!this.files.has(norm2)) return false;
|
|
91549
|
+
this.files.delete(norm2);
|
|
91550
|
+
if (this.createdFiles.has(norm2)) {
|
|
91551
|
+
this.createdFiles.delete(norm2);
|
|
91552
|
+
this.modifiedFiles.delete(norm2);
|
|
91553
|
+
} else {
|
|
91554
|
+
this.modifiedFiles.delete(norm2);
|
|
91555
|
+
this.deletedFiles.add(norm2);
|
|
91556
|
+
}
|
|
91557
|
+
return true;
|
|
91558
|
+
}
|
|
91559
|
+
exists(path) {
|
|
91560
|
+
const norm2 = this.normalizePath(path);
|
|
91561
|
+
if (this.files.has(norm2)) return "file";
|
|
91562
|
+
if (this.directories.has(norm2)) return "dir";
|
|
91563
|
+
const prefix = norm2 + "/";
|
|
91564
|
+
for (const key of this.files.keys()) {
|
|
91565
|
+
if (key.startsWith(prefix)) return "dir";
|
|
91566
|
+
}
|
|
91567
|
+
return null;
|
|
91568
|
+
}
|
|
91569
|
+
fileSize(path) {
|
|
91570
|
+
const norm2 = this.normalizePath(path);
|
|
91571
|
+
const file = this.files.get(norm2);
|
|
91572
|
+
return file ? file.content.length : 0;
|
|
91573
|
+
}
|
|
91574
|
+
mkdir(dirPath) {
|
|
91575
|
+
const norm2 = this.normalizePath(dirPath);
|
|
91576
|
+
this.directories.add(norm2);
|
|
91577
|
+
this.ensureParentDirs(norm2 + "/placeholder");
|
|
91578
|
+
return true;
|
|
91579
|
+
}
|
|
91580
|
+
/** Move/rename a file or directory tree. Returns true on success. */
|
|
91581
|
+
movefile(source, destination) {
|
|
91582
|
+
const srcNorm = this.normalizePath(source);
|
|
91583
|
+
const srcType = this.exists(srcNorm);
|
|
91584
|
+
if (srcType === null) return false;
|
|
91585
|
+
let dstNorm = this.normalizePath(destination);
|
|
91586
|
+
const dstType = this.exists(dstNorm);
|
|
91587
|
+
if (dstType === "dir") {
|
|
91588
|
+
const lastSlash = srcNorm.lastIndexOf("/");
|
|
91589
|
+
const baseName = lastSlash >= 0 ? srcNorm.slice(lastSlash + 1) : srcNorm;
|
|
91590
|
+
dstNorm = (dstNorm === "/" ? "" : dstNorm) + "/" + baseName;
|
|
91591
|
+
}
|
|
91592
|
+
if (srcType === "dir" && this.exists(dstNorm) === "file") return false;
|
|
91593
|
+
if (srcType === "file") {
|
|
91594
|
+
const content = this.files.get(srcNorm).content;
|
|
91595
|
+
if (this.exists(dstNorm) === "file") this.deleteFile(dstNorm);
|
|
91596
|
+
this.writeFile(dstNorm, content);
|
|
91597
|
+
this.deleteFile(srcNorm);
|
|
91598
|
+
return true;
|
|
91599
|
+
}
|
|
91600
|
+
const srcPrefix = srcNorm + "/";
|
|
91601
|
+
const filesToMove = [];
|
|
91602
|
+
for (const [path, file] of this.files) {
|
|
91603
|
+
if (path === srcNorm || path.startsWith(srcPrefix)) {
|
|
91604
|
+
const rest = path === srcNorm ? "" : path.slice(srcNorm.length);
|
|
91605
|
+
filesToMove.push({
|
|
91606
|
+
from: path,
|
|
91607
|
+
to: dstNorm + rest,
|
|
91608
|
+
content: file.content
|
|
91609
|
+
});
|
|
91610
|
+
}
|
|
91611
|
+
}
|
|
91612
|
+
const dirsToMove = [];
|
|
91613
|
+
for (const dir of this.directories) {
|
|
91614
|
+
if (dir === srcNorm || dir.startsWith(srcPrefix)) {
|
|
91615
|
+
const rest = dir === srcNorm ? "" : dir.slice(srcNorm.length);
|
|
91616
|
+
dirsToMove.push({ from: dir, to: dstNorm + rest });
|
|
91617
|
+
}
|
|
91618
|
+
}
|
|
91619
|
+
for (const { from } of filesToMove) this.deleteFile(from);
|
|
91620
|
+
for (const { from } of dirsToMove) this.directories.delete(from);
|
|
91621
|
+
this.directories.add(dstNorm);
|
|
91622
|
+
for (const { to } of dirsToMove) this.directories.add(to);
|
|
91623
|
+
for (const { to, content } of filesToMove) this.writeFile(to, content);
|
|
91624
|
+
return true;
|
|
91625
|
+
}
|
|
91626
|
+
/** Copy a file or directory tree (source is left in place). True on success. */
|
|
91627
|
+
copyfile(source, destination) {
|
|
91628
|
+
const srcNorm = this.normalizePath(source);
|
|
91629
|
+
const srcType = this.exists(srcNorm);
|
|
91630
|
+
if (srcType === null) return false;
|
|
91631
|
+
let dstNorm = this.normalizePath(destination);
|
|
91632
|
+
if (this.exists(dstNorm) === "dir") {
|
|
91633
|
+
const lastSlash = srcNorm.lastIndexOf("/");
|
|
91634
|
+
const baseName = lastSlash >= 0 ? srcNorm.slice(lastSlash + 1) : srcNorm;
|
|
91635
|
+
dstNorm = (dstNorm === "/" ? "" : dstNorm) + "/" + baseName;
|
|
91636
|
+
}
|
|
91637
|
+
if (srcType === "dir" && this.exists(dstNorm) === "file") return false;
|
|
91638
|
+
if (srcType === "file") {
|
|
91639
|
+
const content = this.files.get(srcNorm).content;
|
|
91640
|
+
if (this.exists(dstNorm) === "file") this.deleteFile(dstNorm);
|
|
91641
|
+
this.writeFile(dstNorm, content);
|
|
91642
|
+
return true;
|
|
91643
|
+
}
|
|
91644
|
+
const srcPrefix = srcNorm + "/";
|
|
91645
|
+
for (const [path, file] of [...this.files]) {
|
|
91646
|
+
if (path === srcNorm || path.startsWith(srcPrefix)) {
|
|
91647
|
+
const rest = path === srcNorm ? "" : path.slice(srcNorm.length);
|
|
91648
|
+
this.writeFile(dstNorm + rest, file.content);
|
|
91649
|
+
}
|
|
91650
|
+
}
|
|
91651
|
+
for (const dir of [...this.directories]) {
|
|
91652
|
+
if (dir === srcNorm || dir.startsWith(srcPrefix)) {
|
|
91653
|
+
const rest = dir === srcNorm ? "" : dir.slice(srcNorm.length);
|
|
91654
|
+
this.directories.add(dstNorm + rest);
|
|
91655
|
+
}
|
|
91656
|
+
}
|
|
91657
|
+
this.directories.add(dstNorm);
|
|
91658
|
+
return true;
|
|
91659
|
+
}
|
|
91660
|
+
/** Resolve a path to its (normalized) absolute path + attributes, or null. */
|
|
91661
|
+
fileattrib(path) {
|
|
91662
|
+
const norm2 = this.normalizePath(path);
|
|
91663
|
+
const t = this.exists(norm2);
|
|
91664
|
+
if (t === null) return null;
|
|
91665
|
+
return {
|
|
91666
|
+
Name: norm2,
|
|
91667
|
+
directory: t === "dir",
|
|
91668
|
+
UserRead: true,
|
|
91669
|
+
UserWrite: true,
|
|
91670
|
+
UserExecute: t === "dir"
|
|
91671
|
+
};
|
|
91672
|
+
}
|
|
91673
|
+
rmdir(dirPath, recursive) {
|
|
91674
|
+
const norm2 = this.normalizePath(dirPath);
|
|
91675
|
+
if (this.exists(norm2) !== "dir") return false;
|
|
91676
|
+
if (recursive) {
|
|
91677
|
+
const prefix = norm2 + "/";
|
|
91678
|
+
for (const key of [...this.files.keys()]) {
|
|
91679
|
+
if (key.startsWith(prefix)) {
|
|
91680
|
+
this.deleteFile(key);
|
|
91681
|
+
}
|
|
91682
|
+
}
|
|
91683
|
+
for (const dir of [...this.directories]) {
|
|
91684
|
+
if (dir === norm2 || dir.startsWith(prefix)) {
|
|
91685
|
+
this.directories.delete(dir);
|
|
91686
|
+
}
|
|
91687
|
+
}
|
|
91688
|
+
} else {
|
|
91689
|
+
const prefix = norm2 + "/";
|
|
91690
|
+
for (const key of this.files.keys()) {
|
|
91691
|
+
if (key.startsWith(prefix)) return false;
|
|
91692
|
+
}
|
|
91693
|
+
this.directories.delete(norm2);
|
|
91694
|
+
}
|
|
91695
|
+
return true;
|
|
91696
|
+
}
|
|
91697
|
+
/** List entries in a directory. */
|
|
91698
|
+
listDir(dirPath) {
|
|
91699
|
+
const norm2 = this.normalizePath(dirPath);
|
|
91700
|
+
const prefix = norm2 === "/" ? "/" : norm2 + "/";
|
|
91701
|
+
const results = [];
|
|
91702
|
+
const now = Date.now();
|
|
91703
|
+
results.push({
|
|
91704
|
+
name: ".",
|
|
91705
|
+
folder: norm2,
|
|
91706
|
+
bytes: 0,
|
|
91707
|
+
isdir: true,
|
|
91708
|
+
mtimeMs: now
|
|
91709
|
+
});
|
|
91710
|
+
results.push({
|
|
91711
|
+
name: "..",
|
|
91712
|
+
folder: norm2,
|
|
91713
|
+
bytes: 0,
|
|
91714
|
+
isdir: true,
|
|
91715
|
+
mtimeMs: now
|
|
91716
|
+
});
|
|
91717
|
+
const seen = /* @__PURE__ */ new Set();
|
|
91718
|
+
for (const [path, file] of this.files) {
|
|
91719
|
+
if (!path.startsWith(prefix)) continue;
|
|
91720
|
+
const rest = path.slice(prefix.length);
|
|
91721
|
+
const slashIdx = rest.indexOf("/");
|
|
91722
|
+
if (slashIdx === -1) {
|
|
91723
|
+
results.push({
|
|
91724
|
+
name: rest,
|
|
91725
|
+
folder: norm2,
|
|
91726
|
+
bytes: file.content.length,
|
|
91727
|
+
isdir: false,
|
|
91728
|
+
mtimeMs: file.mtimeMs
|
|
91729
|
+
});
|
|
91730
|
+
} else {
|
|
91731
|
+
const dirName = rest.slice(0, slashIdx);
|
|
91732
|
+
if (!seen.has(dirName)) {
|
|
91733
|
+
seen.add(dirName);
|
|
91734
|
+
results.push({
|
|
91735
|
+
name: dirName,
|
|
91736
|
+
folder: norm2,
|
|
91737
|
+
bytes: 0,
|
|
91738
|
+
isdir: true,
|
|
91739
|
+
mtimeMs: now
|
|
91740
|
+
});
|
|
91741
|
+
}
|
|
91742
|
+
}
|
|
91743
|
+
}
|
|
91744
|
+
for (const dir of this.directories) {
|
|
91745
|
+
if (!dir.startsWith(prefix)) continue;
|
|
91746
|
+
const rest = dir.slice(prefix.length);
|
|
91747
|
+
if (!rest.includes("/") && rest.length > 0 && !seen.has(rest)) {
|
|
91748
|
+
seen.add(rest);
|
|
91749
|
+
results.push({
|
|
91750
|
+
name: rest,
|
|
91751
|
+
folder: norm2,
|
|
91752
|
+
bytes: 0,
|
|
91753
|
+
isdir: true,
|
|
91754
|
+
mtimeMs: now
|
|
91755
|
+
});
|
|
91756
|
+
}
|
|
91757
|
+
}
|
|
91758
|
+
return results;
|
|
91759
|
+
}
|
|
91760
|
+
/** List all file paths in the VFS. */
|
|
91761
|
+
allFiles() {
|
|
91762
|
+
return [...this.files.keys()];
|
|
91763
|
+
}
|
|
91764
|
+
/** Get the changes since the VFS was created. */
|
|
91765
|
+
getChanges() {
|
|
91766
|
+
const created = [];
|
|
91767
|
+
const modified = [];
|
|
91768
|
+
for (const path of this.createdFiles) {
|
|
91769
|
+
const file = this.files.get(path);
|
|
91770
|
+
if (file) created.push({ path, content: file.content });
|
|
91771
|
+
}
|
|
91772
|
+
for (const path of this.modifiedFiles) {
|
|
91773
|
+
const file = this.files.get(path);
|
|
91774
|
+
if (file) modified.push({ path, content: file.content });
|
|
91775
|
+
}
|
|
91776
|
+
return {
|
|
91777
|
+
created,
|
|
91778
|
+
modified,
|
|
91779
|
+
deleted: [...this.deletedFiles]
|
|
91780
|
+
};
|
|
91781
|
+
}
|
|
91782
|
+
ensureParentDirs(path) {
|
|
91783
|
+
const parts = path.split("/").filter(Boolean);
|
|
91784
|
+
for (let i = 1; i < parts.length; i++) {
|
|
91785
|
+
const dir = "/" + parts.slice(0, i).join("/");
|
|
91786
|
+
this.directories.add(dir);
|
|
91787
|
+
}
|
|
91788
|
+
}
|
|
91789
|
+
};
|
|
91790
|
+
|
|
91791
|
+
// src/vfs/BrowserFileIOAdapter.ts
|
|
91792
|
+
import { unzipSync } from "fflate";
|
|
91793
|
+
var TEXT_DECODER = new TextDecoder("utf-8");
|
|
91794
|
+
var TEXT_ENCODER = new TextEncoder();
|
|
91795
|
+
var READ_CHUNK_SIZE = 8192;
|
|
91796
|
+
var BrowserFileIOAdapter = class {
|
|
91797
|
+
constructor(vfs) {
|
|
91798
|
+
this.vfs = vfs;
|
|
91799
|
+
}
|
|
91800
|
+
nextFid = 3;
|
|
91801
|
+
// 0=stdin, 1=stdout, 2=stderr reserved
|
|
91802
|
+
openFiles = /* @__PURE__ */ new Map();
|
|
91803
|
+
fopen(filename, permission) {
|
|
91804
|
+
const perm = permission.replace(/b/g, "");
|
|
91805
|
+
const path = this.vfs.normalizePath(filename);
|
|
91806
|
+
try {
|
|
91807
|
+
let data;
|
|
91808
|
+
let pos = 0;
|
|
91809
|
+
if (perm === "r" || perm === "r+") {
|
|
91810
|
+
if (this.vfs.exists(filename) !== "file") return -1;
|
|
91811
|
+
data = new Uint8Array(this.vfs.readFile(filename));
|
|
91812
|
+
} else if (perm === "w" || perm === "w+") {
|
|
91813
|
+
data = new Uint8Array(0);
|
|
91814
|
+
} else if (perm === "a" || perm === "a+") {
|
|
91815
|
+
if (this.vfs.exists(filename) === "file") {
|
|
91816
|
+
data = new Uint8Array(this.vfs.readFile(filename));
|
|
91817
|
+
pos = data.length;
|
|
91818
|
+
} else {
|
|
91819
|
+
data = new Uint8Array(0);
|
|
91820
|
+
}
|
|
91821
|
+
} else {
|
|
91822
|
+
return -1;
|
|
91823
|
+
}
|
|
91824
|
+
const fid = this.nextFid++;
|
|
91825
|
+
this.openFiles.set(fid, {
|
|
91826
|
+
path,
|
|
91827
|
+
permission: perm,
|
|
91828
|
+
lastError: "",
|
|
91829
|
+
buffer: "",
|
|
91830
|
+
eof: false,
|
|
91831
|
+
pos,
|
|
91832
|
+
data,
|
|
91833
|
+
dataLen: data.length,
|
|
91834
|
+
dirty: perm === "w" || perm === "a"
|
|
91835
|
+
// w starts dirty (truncated or new)
|
|
91836
|
+
});
|
|
91837
|
+
return fid;
|
|
91838
|
+
} catch {
|
|
91839
|
+
return -1;
|
|
91840
|
+
}
|
|
91841
|
+
}
|
|
91842
|
+
fclose(fidOrAll) {
|
|
91843
|
+
if (fidOrAll === "all") {
|
|
91844
|
+
for (const [, entry2] of this.openFiles) {
|
|
91845
|
+
this.flushEntry(entry2);
|
|
91846
|
+
}
|
|
91847
|
+
this.openFiles.clear();
|
|
91848
|
+
return 0;
|
|
91849
|
+
}
|
|
91850
|
+
const entry = this.openFiles.get(fidOrAll);
|
|
91851
|
+
if (!entry) return -1;
|
|
91852
|
+
this.flushEntry(entry);
|
|
91853
|
+
this.openFiles.delete(fidOrAll);
|
|
91854
|
+
return 0;
|
|
91855
|
+
}
|
|
91856
|
+
fgetl(fid) {
|
|
91857
|
+
const entry = this.getEntry(fid);
|
|
91858
|
+
if (!entry) return -1;
|
|
91859
|
+
return this.readLine(entry, false);
|
|
91860
|
+
}
|
|
91861
|
+
fgets(fid) {
|
|
91862
|
+
const entry = this.getEntry(fid);
|
|
91863
|
+
if (!entry) return -1;
|
|
91864
|
+
return this.readLine(entry, true);
|
|
91865
|
+
}
|
|
91866
|
+
fileread(filename) {
|
|
91867
|
+
const data = this.vfs.readFile(filename);
|
|
91868
|
+
return TEXT_DECODER.decode(data);
|
|
91869
|
+
}
|
|
91870
|
+
feof(fid) {
|
|
91871
|
+
const entry = this.getEntry(fid);
|
|
91872
|
+
if (!entry) return 1;
|
|
91873
|
+
if (entry.buffer.length > 0) return 0;
|
|
91874
|
+
if (entry.eof) return 1;
|
|
91875
|
+
if (entry.pos >= entry.dataLen) {
|
|
91876
|
+
entry.eof = true;
|
|
91877
|
+
return 1;
|
|
91878
|
+
}
|
|
91879
|
+
return 0;
|
|
91880
|
+
}
|
|
91881
|
+
ferror(fid) {
|
|
91882
|
+
const entry = this.getEntry(fid);
|
|
91883
|
+
if (!entry) return "Invalid file identifier";
|
|
91884
|
+
return entry.lastError;
|
|
91885
|
+
}
|
|
91886
|
+
fwrite(fid, text) {
|
|
91887
|
+
const entry = this.getEntry(fid);
|
|
91888
|
+
if (!entry) throw new Error(`Invalid file identifier: ${fid}`);
|
|
91889
|
+
const bytes = TEXT_ENCODER.encode(text);
|
|
91890
|
+
this.writeBytes(entry, bytes);
|
|
91891
|
+
entry.lastError = "";
|
|
91892
|
+
}
|
|
91893
|
+
freadBytes(fid, count) {
|
|
91894
|
+
const entry = this.getEntry(fid);
|
|
91895
|
+
if (!entry) throw new Error(`Invalid file identifier: ${fid}`);
|
|
91896
|
+
if (entry.buffer.length > 0) {
|
|
91897
|
+
entry.buffer = "";
|
|
91898
|
+
}
|
|
91899
|
+
const available = entry.dataLen - entry.pos;
|
|
91900
|
+
const toRead = Math.min(count, available);
|
|
91901
|
+
if (toRead <= 0) {
|
|
91902
|
+
entry.eof = true;
|
|
91903
|
+
return new Uint8Array(0);
|
|
91904
|
+
}
|
|
91905
|
+
const result = new Uint8Array(toRead);
|
|
91906
|
+
result.set(entry.data.subarray(entry.pos, entry.pos + toRead));
|
|
91907
|
+
entry.pos += toRead;
|
|
91908
|
+
if (entry.pos >= entry.dataLen) entry.eof = true;
|
|
91909
|
+
entry.lastError = "";
|
|
91910
|
+
return result;
|
|
91911
|
+
}
|
|
91912
|
+
fwriteBytes(fid, data) {
|
|
91913
|
+
const entry = this.getEntry(fid);
|
|
91914
|
+
if (!entry) throw new Error(`Invalid file identifier: ${fid}`);
|
|
91915
|
+
this.writeBytes(entry, data);
|
|
91916
|
+
entry.lastError = "";
|
|
91917
|
+
return data.length;
|
|
91918
|
+
}
|
|
91919
|
+
fseek(fid, offset, origin) {
|
|
91920
|
+
const entry = this.getEntry(fid);
|
|
91921
|
+
if (!entry) return -1;
|
|
91922
|
+
entry.buffer = "";
|
|
91923
|
+
entry.eof = false;
|
|
91924
|
+
if (origin === -1) {
|
|
91925
|
+
entry.pos = offset;
|
|
91926
|
+
} else if (origin === 0) {
|
|
91927
|
+
entry.pos += offset;
|
|
91928
|
+
} else {
|
|
91929
|
+
entry.pos = entry.dataLen + offset;
|
|
91930
|
+
}
|
|
91931
|
+
return 0;
|
|
91932
|
+
}
|
|
91933
|
+
ftell(fid) {
|
|
91934
|
+
const entry = this.getEntry(fid);
|
|
91935
|
+
if (!entry) return -1;
|
|
91936
|
+
return entry.pos;
|
|
91937
|
+
}
|
|
91938
|
+
scanDirectory(dirPath) {
|
|
91939
|
+
const norm2 = this.vfs.normalizePath(dirPath);
|
|
91940
|
+
const prefix = norm2 === "/" ? "/" : norm2 + "/";
|
|
91941
|
+
const results = [];
|
|
91942
|
+
for (const filePath of this.vfs.allFiles()) {
|
|
91943
|
+
if (!filePath.startsWith(prefix)) continue;
|
|
91944
|
+
const relativePath = filePath.slice(prefix.length);
|
|
91945
|
+
if (!relativePath) continue;
|
|
91946
|
+
const segments = relativePath.split("/");
|
|
91947
|
+
const dirs = segments.slice(0, -1);
|
|
91948
|
+
const allSpecial = dirs.every(
|
|
91949
|
+
(d) => d.startsWith("@") || d.startsWith("+") || d === "private"
|
|
91950
|
+
);
|
|
91951
|
+
if (!allSpecial) continue;
|
|
91952
|
+
if (!relativePath.endsWith(".m") && !relativePath.endsWith(".numbl.js") && !relativePath.endsWith(".wasm")) {
|
|
91953
|
+
continue;
|
|
91954
|
+
}
|
|
91955
|
+
try {
|
|
91956
|
+
const content = this.vfs.readFile(filePath);
|
|
91957
|
+
if (relativePath.endsWith(".wasm")) {
|
|
91958
|
+
results.push({
|
|
91959
|
+
name: filePath,
|
|
91960
|
+
source: "",
|
|
91961
|
+
data: new Uint8Array(content)
|
|
91962
|
+
});
|
|
91963
|
+
} else {
|
|
91964
|
+
results.push({
|
|
91965
|
+
name: filePath,
|
|
91966
|
+
source: TEXT_DECODER.decode(content)
|
|
91967
|
+
});
|
|
91968
|
+
}
|
|
91969
|
+
} catch {
|
|
91970
|
+
}
|
|
91971
|
+
}
|
|
91972
|
+
return results;
|
|
91973
|
+
}
|
|
91974
|
+
resolvePath(dirPath) {
|
|
91975
|
+
return this.vfs.normalizePath(dirPath);
|
|
91976
|
+
}
|
|
91977
|
+
existsPath(path) {
|
|
91978
|
+
return this.vfs.exists(path);
|
|
91979
|
+
}
|
|
91980
|
+
mkdir(dirPath) {
|
|
91981
|
+
return this.vfs.mkdir(dirPath);
|
|
91982
|
+
}
|
|
91983
|
+
tempdir() {
|
|
91984
|
+
return "/tmp";
|
|
91985
|
+
}
|
|
91986
|
+
userpath() {
|
|
91987
|
+
return "/system";
|
|
91988
|
+
}
|
|
91989
|
+
deleteFile(pattern) {
|
|
91990
|
+
const norm2 = this.vfs.normalizePath(pattern);
|
|
91991
|
+
if (norm2.includes("*") || norm2.includes("?")) {
|
|
91992
|
+
const lastSlash = norm2.lastIndexOf("/");
|
|
91993
|
+
const dir = norm2.slice(0, lastSlash);
|
|
91994
|
+
const globPat = norm2.slice(lastSlash + 1);
|
|
91995
|
+
const re = new RegExp(
|
|
91996
|
+
"^" + globPat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
91997
|
+
);
|
|
91998
|
+
const entries = this.vfs.listDir(dir);
|
|
91999
|
+
for (const entry of entries) {
|
|
92000
|
+
if (!entry.isdir && re.test(entry.name)) {
|
|
92001
|
+
this.vfs.deleteFile(dir + "/" + entry.name);
|
|
92002
|
+
}
|
|
92003
|
+
}
|
|
92004
|
+
} else {
|
|
92005
|
+
this.vfs.deleteFile(norm2);
|
|
92006
|
+
}
|
|
92007
|
+
}
|
|
92008
|
+
rmdir(dirPath, recursive) {
|
|
92009
|
+
return this.vfs.rmdir(dirPath, recursive);
|
|
92010
|
+
}
|
|
92011
|
+
movefile(source, destination) {
|
|
92012
|
+
return this.vfs.movefile(source, destination);
|
|
92013
|
+
}
|
|
92014
|
+
copyfile(source, destination) {
|
|
92015
|
+
return this.vfs.copyfile(source, destination);
|
|
92016
|
+
}
|
|
92017
|
+
fileattrib(path) {
|
|
92018
|
+
return this.vfs.fileattrib(path);
|
|
92019
|
+
}
|
|
92020
|
+
listDir(dirPath) {
|
|
92021
|
+
const norm2 = this.vfs.normalizePath(dirPath);
|
|
92022
|
+
if (dirPath.includes("**")) {
|
|
92023
|
+
return this.listDirRecursive(dirPath);
|
|
92024
|
+
}
|
|
92025
|
+
if (dirPath.includes("*") || dirPath.includes("?")) {
|
|
92026
|
+
return this.listDirGlob(dirPath);
|
|
92027
|
+
}
|
|
92028
|
+
const type = this.vfs.exists(dirPath);
|
|
92029
|
+
if (type === "file") {
|
|
92030
|
+
const lastSlash = norm2.lastIndexOf("/");
|
|
92031
|
+
const name = lastSlash >= 0 ? norm2.slice(lastSlash + 1) : norm2;
|
|
92032
|
+
const folder = lastSlash >= 0 ? norm2.slice(0, lastSlash) : "/";
|
|
92033
|
+
return [
|
|
92034
|
+
{
|
|
92035
|
+
name,
|
|
92036
|
+
folder,
|
|
92037
|
+
bytes: this.vfs.fileSize(dirPath),
|
|
92038
|
+
isdir: false,
|
|
92039
|
+
mtimeMs: Date.now()
|
|
92040
|
+
}
|
|
92041
|
+
];
|
|
92042
|
+
}
|
|
92043
|
+
return this.vfs.listDir(dirPath);
|
|
92044
|
+
}
|
|
92045
|
+
websave(url, filename, options) {
|
|
92046
|
+
url = filterUrl(url);
|
|
92047
|
+
const method = options?.requestMethod?.toUpperCase() || "GET";
|
|
92048
|
+
const xhr = new XMLHttpRequest();
|
|
92049
|
+
xhr.open(method, url, false);
|
|
92050
|
+
if (options?.timeout) xhr.timeout = Math.round(options.timeout * 1e3);
|
|
92051
|
+
if (options?.username && options?.password) {
|
|
92052
|
+
xhr.setRequestHeader(
|
|
92053
|
+
"Authorization",
|
|
92054
|
+
"Basic " + btoa(`${options.username}:${options.password}`)
|
|
92055
|
+
);
|
|
92056
|
+
}
|
|
92057
|
+
if (options?.keyName && options?.keyValue) {
|
|
92058
|
+
xhr.setRequestHeader(options.keyName, options.keyValue);
|
|
92059
|
+
}
|
|
92060
|
+
xhr.responseType = "arraybuffer";
|
|
92061
|
+
xhr.send();
|
|
92062
|
+
if (xhr.status < 200 || xhr.status >= 300) {
|
|
92063
|
+
throw new Error(`websave: HTTP ${xhr.status} for ${url}`);
|
|
92064
|
+
}
|
|
92065
|
+
const data = new Uint8Array(xhr.response);
|
|
92066
|
+
this.vfs.writeFile(filename, data);
|
|
92067
|
+
}
|
|
92068
|
+
webread(url, options) {
|
|
92069
|
+
url = filterUrl(url);
|
|
92070
|
+
const method = options?.requestMethod?.toUpperCase() || "GET";
|
|
92071
|
+
const xhr = new XMLHttpRequest();
|
|
92072
|
+
xhr.open(method, url, false);
|
|
92073
|
+
if (options?.timeout) xhr.timeout = Math.round(options.timeout * 1e3);
|
|
92074
|
+
if (options?.username && options?.password) {
|
|
92075
|
+
xhr.setRequestHeader(
|
|
92076
|
+
"Authorization",
|
|
92077
|
+
"Basic " + btoa(`${options.username}:${options.password}`)
|
|
92078
|
+
);
|
|
92079
|
+
}
|
|
92080
|
+
if (options?.keyName && options?.keyValue) {
|
|
92081
|
+
xhr.setRequestHeader(options.keyName, options.keyValue);
|
|
92082
|
+
}
|
|
92083
|
+
xhr.send();
|
|
92084
|
+
if (xhr.status < 200 || xhr.status >= 300) {
|
|
92085
|
+
throw new Error(`webread: HTTP ${xhr.status} for ${url}`);
|
|
92086
|
+
}
|
|
92087
|
+
return xhr.responseText;
|
|
92088
|
+
}
|
|
92089
|
+
unzip(zipfilename, outputfolder) {
|
|
92090
|
+
zipfilename = filterUrl(zipfilename);
|
|
92091
|
+
const zipData = this.vfs.readFile(zipfilename);
|
|
92092
|
+
this.vfs.mkdir(outputfolder);
|
|
92093
|
+
const files = unzipSync(zipData);
|
|
92094
|
+
const extracted = [];
|
|
92095
|
+
for (const [entryName, fileData] of Object.entries(files)) {
|
|
92096
|
+
if (entryName.endsWith("/")) {
|
|
92097
|
+
this.vfs.mkdir(outputfolder + "/" + entryName);
|
|
92098
|
+
continue;
|
|
92099
|
+
}
|
|
92100
|
+
const outPath = outputfolder + "/" + entryName;
|
|
92101
|
+
const lastSlash = outPath.lastIndexOf("/");
|
|
92102
|
+
if (lastSlash > 0) {
|
|
92103
|
+
this.vfs.mkdir(outPath.slice(0, lastSlash));
|
|
92104
|
+
}
|
|
92105
|
+
this.vfs.writeFile(outPath, fileData);
|
|
92106
|
+
extracted.push(this.vfs.normalizePath(outPath));
|
|
92107
|
+
}
|
|
92108
|
+
return extracted;
|
|
92109
|
+
}
|
|
92110
|
+
/** Get the VFS changes for syncing back to the main thread. */
|
|
92111
|
+
getChanges() {
|
|
92112
|
+
for (const [, entry] of this.openFiles) {
|
|
92113
|
+
this.flushEntry(entry);
|
|
92114
|
+
}
|
|
92115
|
+
return this.vfs.getChanges();
|
|
92116
|
+
}
|
|
92117
|
+
getEntry(fid) {
|
|
92118
|
+
return this.openFiles.get(fid);
|
|
92119
|
+
}
|
|
92120
|
+
flushEntry(entry) {
|
|
92121
|
+
if (entry.dirty) {
|
|
92122
|
+
const finalData = entry.dataLen === entry.data.length ? entry.data : entry.data.subarray(0, entry.dataLen);
|
|
92123
|
+
this.vfs.writeFile(entry.path, finalData);
|
|
92124
|
+
entry.dirty = false;
|
|
92125
|
+
}
|
|
92126
|
+
}
|
|
92127
|
+
writeBytes(entry, bytes) {
|
|
92128
|
+
const needed = entry.pos + bytes.length;
|
|
92129
|
+
if (needed > entry.data.length) {
|
|
92130
|
+
const newSize = Math.max(needed, entry.data.length * 2, 256);
|
|
92131
|
+
const newData = new Uint8Array(newSize);
|
|
92132
|
+
newData.set(entry.data.subarray(0, entry.dataLen));
|
|
92133
|
+
entry.data = newData;
|
|
92134
|
+
}
|
|
92135
|
+
entry.data.set(bytes, entry.pos);
|
|
92136
|
+
entry.pos += bytes.length;
|
|
92137
|
+
if (entry.pos > entry.dataLen) {
|
|
92138
|
+
entry.dataLen = entry.pos;
|
|
92139
|
+
}
|
|
92140
|
+
entry.dirty = true;
|
|
92141
|
+
}
|
|
92142
|
+
readLine(entry, keepNewline) {
|
|
92143
|
+
while (!entry.eof) {
|
|
92144
|
+
const nlIdx2 = entry.buffer.indexOf("\n");
|
|
92145
|
+
if (nlIdx2 !== -1) break;
|
|
92146
|
+
const available = entry.dataLen - entry.pos;
|
|
92147
|
+
if (available <= 0) {
|
|
92148
|
+
entry.eof = true;
|
|
92149
|
+
break;
|
|
92150
|
+
}
|
|
92151
|
+
const toRead = Math.min(READ_CHUNK_SIZE, available);
|
|
92152
|
+
const chunk = entry.data.subarray(entry.pos, entry.pos + toRead);
|
|
92153
|
+
entry.pos += toRead;
|
|
92154
|
+
entry.buffer += TEXT_DECODER.decode(chunk, { stream: true });
|
|
92155
|
+
}
|
|
92156
|
+
if (entry.buffer.length === 0) {
|
|
92157
|
+
return -1;
|
|
92158
|
+
}
|
|
92159
|
+
const nlIdx = entry.buffer.indexOf("\n");
|
|
92160
|
+
if (nlIdx !== -1) {
|
|
92161
|
+
const line2 = keepNewline ? entry.buffer.slice(0, nlIdx + 1) : entry.buffer.slice(0, nlIdx);
|
|
92162
|
+
entry.buffer = entry.buffer.slice(nlIdx + 1);
|
|
92163
|
+
return line2;
|
|
92164
|
+
}
|
|
92165
|
+
const line = entry.buffer;
|
|
92166
|
+
entry.buffer = "";
|
|
92167
|
+
return line;
|
|
92168
|
+
}
|
|
92169
|
+
listDirGlob(pattern) {
|
|
92170
|
+
const norm2 = this.vfs.normalizePath(pattern);
|
|
92171
|
+
const lastSlash = norm2.lastIndexOf("/");
|
|
92172
|
+
const dir = lastSlash >= 0 ? norm2.slice(0, lastSlash) : "/";
|
|
92173
|
+
const globPat = lastSlash >= 0 ? norm2.slice(lastSlash + 1) : norm2;
|
|
92174
|
+
const re = new RegExp(
|
|
92175
|
+
"^" + globPat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
92176
|
+
);
|
|
92177
|
+
const entries = this.vfs.listDir(dir);
|
|
92178
|
+
return entries.filter(
|
|
92179
|
+
(e) => e.name !== "." && e.name !== ".." && re.test(e.name)
|
|
92180
|
+
);
|
|
92181
|
+
}
|
|
92182
|
+
listDirRecursive(pattern) {
|
|
92183
|
+
const idx = pattern.indexOf("**");
|
|
92184
|
+
let baseDir = pattern.slice(0, idx);
|
|
92185
|
+
if (baseDir.endsWith("/")) baseDir = baseDir.slice(0, -1);
|
|
92186
|
+
if (!baseDir) baseDir = ".";
|
|
92187
|
+
const results = [];
|
|
92188
|
+
const walkDir = (dir) => {
|
|
92189
|
+
const entries = this.vfs.listDir(dir);
|
|
92190
|
+
results.push(...entries);
|
|
92191
|
+
for (const entry of entries) {
|
|
92192
|
+
if (entry.isdir && entry.name !== "." && entry.name !== "..") {
|
|
92193
|
+
walkDir(entry.folder + "/" + entry.name);
|
|
92194
|
+
}
|
|
92195
|
+
}
|
|
92196
|
+
};
|
|
92197
|
+
walkDir(this.vfs.normalizePath(baseDir));
|
|
92198
|
+
return results;
|
|
92199
|
+
}
|
|
92200
|
+
};
|
|
92201
|
+
function filterUrl(url) {
|
|
92202
|
+
if (/^https:\/\/github\.com\/.+\/releases\/download\/.+/.test(url)) {
|
|
92203
|
+
url = url.replace(
|
|
92204
|
+
"https://github.com/",
|
|
92205
|
+
"https://mip-cors-proxy.figurl.workers.dev/gh/"
|
|
92206
|
+
);
|
|
92207
|
+
url += url.includes("?") ? "&" : "?";
|
|
92208
|
+
url += "t=" + Date.now();
|
|
92209
|
+
}
|
|
92210
|
+
return url;
|
|
92211
|
+
}
|
|
92212
|
+
|
|
92213
|
+
// src/vfs/BrowserSystemAdapter.ts
|
|
92214
|
+
var BrowserSystemAdapter = class {
|
|
92215
|
+
vars = /* @__PURE__ */ new Map();
|
|
92216
|
+
vfs = null;
|
|
92217
|
+
// Used only when no VFS is attached (e.g. tests that don't need files).
|
|
92218
|
+
fallbackCwd = "/";
|
|
92219
|
+
constructor(vfs) {
|
|
92220
|
+
if (vfs) this.vfs = vfs;
|
|
92221
|
+
}
|
|
92222
|
+
/** Attach (or replace) the VFS used as the cwd source of truth. */
|
|
92223
|
+
setVfs(vfs) {
|
|
92224
|
+
this.vfs = vfs;
|
|
92225
|
+
}
|
|
92226
|
+
getEnv(name) {
|
|
92227
|
+
return this.vars.get(name);
|
|
92228
|
+
}
|
|
92229
|
+
getAllEnv() {
|
|
92230
|
+
const result = {};
|
|
92231
|
+
for (const [k, v] of this.vars) {
|
|
92232
|
+
result[k] = v;
|
|
92233
|
+
}
|
|
92234
|
+
return result;
|
|
92235
|
+
}
|
|
92236
|
+
setEnv(name, value) {
|
|
92237
|
+
this.vars.set(name, value);
|
|
92238
|
+
}
|
|
92239
|
+
cwd() {
|
|
92240
|
+
return this.vfs ? this.vfs.getCwd() : this.fallbackCwd;
|
|
92241
|
+
}
|
|
92242
|
+
chdir(dir) {
|
|
92243
|
+
if (this.vfs) {
|
|
92244
|
+
this.vfs.setCwd(dir);
|
|
92245
|
+
} else {
|
|
92246
|
+
this.fallbackCwd = dir.startsWith("/") ? dir : this.fallbackCwd.replace(/\/$/, "") + "/" + dir;
|
|
92247
|
+
}
|
|
92248
|
+
}
|
|
92249
|
+
platform() {
|
|
92250
|
+
return "linux";
|
|
92251
|
+
}
|
|
92252
|
+
arch() {
|
|
92253
|
+
return "x64";
|
|
92254
|
+
}
|
|
92255
|
+
};
|
|
91328
92256
|
export {
|
|
92257
|
+
BrowserFileIOAdapter,
|
|
92258
|
+
BrowserSystemAdapter,
|
|
91329
92259
|
RTV,
|
|
91330
92260
|
RuntimeError,
|
|
92261
|
+
VirtualFileSystem,
|
|
91331
92262
|
displayValue,
|
|
91332
92263
|
executeCode,
|
|
91333
92264
|
loadQhullNodeBackend as loadDelaunayBackend,
|