pyret-embed 0.1.6 → 0.1.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/README.md +22 -0
- package/dist/build/web/css/editor.css +21 -0
- package/dist/build/web/editor.embed.html +35 -2
- package/dist/build/web/editor.html +34 -1
- package/dist/build/web/js/beforeBlocks.js +110 -8
- package/dist/build/web/js/beforePyret.js +205 -21
- package/dist/build/web/js/cpo-main-ts.js +862 -0
- package/dist/build/web/js/cpo-main.jarr +2 -2
- package/dist/build/web/js/cpo-main.jarr.gz.js +0 -0
- package/dist/build/web/js/cpo-main.jarr.js +2 -2
- package/dist/build/web/js/cpo-main.jarr.min +2 -2
- package/dist/build/web/js/events.js +24 -1
- package/dist/build/web/js/repl-ui.js +7 -2
- package/dist/build/web/js/ts-compiler-lib.js +585 -0
- package/dist/build/web/js/vega.min.js +1 -1
- package/dist/build/web/views/editor.html +34 -1
- package/dist/build/web/views/editor.selfcontained.html +285 -24
- package/dist/pyret.js +1 -1
- package/package.json +2 -1
- package/types/pyret.d.ts +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/build/web/js/cpo-main.jarr
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 39419095 bytes
|
|
4
|
+
sha256: 463aeca97884e60038f806d6ace1f16095634fe41c3632d707fde19ef0697108
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/build/web/js/cpo-main.jarr.js
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 39419095 bytes
|
|
4
|
+
sha256: 463aeca97884e60038f806d6ace1f16095634fe41c3632d707fde19ef0697108
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/dist/build/web/js/cpo-main.jarr.min
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 36463335 bytes
|
|
4
|
+
sha256: 4fac69dd8fa65ffa6118e45ae2fb72232f6f2336d7c8dd8de84c12d8c126e346
|
|
@@ -156,7 +156,22 @@ function makeEvents(config) {
|
|
|
156
156
|
await window.RUN_CODE(state.definitionsAtLastRun);
|
|
157
157
|
}
|
|
158
158
|
else if (state.definitionsAtLastRun === false) {
|
|
159
|
-
|
|
159
|
+
// "Never run": the empty run below establishes the REPL baseline. Two
|
|
160
|
+
// audiences depend on it -- interaction replay (the loop after this
|
|
161
|
+
// branch assumes a runtime to replay against), and hosts that show the
|
|
162
|
+
// interactions pane, whose prompt only appears at a run's completion
|
|
163
|
+
// (repl-ui's showPrompt) -- pyret-embed's default reset state relies on
|
|
164
|
+
// that for a live prompt at boot.
|
|
165
|
+
//
|
|
166
|
+
// A host that needs neither can send warmStart: false to skip it.
|
|
167
|
+
// While this run is in flight the editor LOOKS ready, but a Run click
|
|
168
|
+
// is silently swallowed and edits race the contents install below --
|
|
169
|
+
// so a host whose pane hides interactions (the vscode cpo editor) pays
|
|
170
|
+
// boot latency plus a race window for an invisible effect. Interaction
|
|
171
|
+
// replay always gets its baseline regardless of the flag.
|
|
172
|
+
if (state.warmStart !== false || (state.interactionsSinceLastRun || []).length > 0) {
|
|
173
|
+
await window.RUN_CODE("");
|
|
174
|
+
}
|
|
160
175
|
}
|
|
161
176
|
const interactions = state.interactionsSinceLastRun;
|
|
162
177
|
for(let i = 0; i < interactions.length; i += 1) {
|
|
@@ -164,6 +179,11 @@ function makeEvents(config) {
|
|
|
164
179
|
}
|
|
165
180
|
editorUpdate(state.editorContents);
|
|
166
181
|
replUpdate(state.replContents);
|
|
182
|
+
// The host-fed settle point (see EXPECTS_HOST_RESET in beforePyret.js):
|
|
183
|
+
// everything above -- the awaited warm-start run included -- is done and
|
|
184
|
+
// the host's contents are installed. RECEIVED_RESET (commSetup) marks
|
|
185
|
+
// receipt, which is the START of that window; this marks its end.
|
|
186
|
+
window.EDITOR_CONTENTS_SETTLED = true;
|
|
167
187
|
}
|
|
168
188
|
|
|
169
189
|
config.CPO.events.onLoad(async function () {
|
|
@@ -328,6 +348,9 @@ function makeEvents(config) {
|
|
|
328
348
|
});
|
|
329
349
|
CPO.loadProgram(toLoad).then((text) => {
|
|
330
350
|
editorUpdate(text);
|
|
351
|
+
// The share-link flavor of a host reset settles here (it bypasses
|
|
352
|
+
// reset(), so the end of reset() can't cover it).
|
|
353
|
+
window.EDITOR_CONTENTS_SETTLED = true;
|
|
331
354
|
})
|
|
332
355
|
.catch((e) => {
|
|
333
356
|
console.error("Error loading initial state from share link: ", e, link);
|
|
@@ -865,6 +865,10 @@
|
|
|
865
865
|
running = true;
|
|
866
866
|
output.empty();
|
|
867
867
|
promptContainer.hide();
|
|
868
|
+
// This gained stop() after having been just hide() for a while,
|
|
869
|
+
// because we found cases where queued up fadeIns needed to be
|
|
870
|
+
// canceled to avoid showing the prompt while running.
|
|
871
|
+
promptContainer.stop(true, true).hide();
|
|
868
872
|
lastEditorRun = uiOptions.cm || null;
|
|
869
873
|
setWhileRunning();
|
|
870
874
|
|
|
@@ -917,7 +921,7 @@
|
|
|
917
921
|
var echoCM = CodeMirror.fromTextArea(echo[0], { readOnly: true });
|
|
918
922
|
echoCM.setValue(code);
|
|
919
923
|
CM.setValue("");
|
|
920
|
-
promptContainer.hide();
|
|
924
|
+
promptContainer.stop(true, true).hide();
|
|
921
925
|
setWhileRunning();
|
|
922
926
|
interactionsCount++;
|
|
923
927
|
var thisName = 'interactions://' + interactionsCount;
|
|
@@ -1005,7 +1009,8 @@
|
|
|
1005
1009
|
runCode: runMainCode,
|
|
1006
1010
|
runner: runner,
|
|
1007
1011
|
focus: function() { CM.focus(); },
|
|
1008
|
-
stop: onBreak
|
|
1012
|
+
stop: onBreak,
|
|
1013
|
+
isRunning: function() { return running; }
|
|
1009
1014
|
};
|
|
1010
1015
|
}
|
|
1011
1016
|
|
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Support library for running code.pyret.org on the TypeScript compiler
|
|
3
|
+
(lang/src/ts-compiler, bundled onto the page as window.PyretTSCompiler).
|
|
4
|
+
|
|
5
|
+
This module adapts between the TS compiler's plain-JS world and the
|
|
6
|
+
Pyret runtime world that the rest of CPO (repl-ui, error-ui, check-ui)
|
|
7
|
+
expects:
|
|
8
|
+
|
|
9
|
+
- builtin Loadables/locators for the TS compiler, built from the
|
|
10
|
+
same staticModules record the page already carries;
|
|
11
|
+
- an async, context-aware module finder for the dependency
|
|
12
|
+
protocols CPO supports ((url-)file imports resolve relative to the
|
|
13
|
+
importing module via the embedding host's fs/path RPCs, exactly
|
|
14
|
+
like the stock finder);
|
|
15
|
+
- a ReplExecutor that runs compiled programs through the builtin
|
|
16
|
+
load-lib's run-program, so results are genuine load-lib
|
|
17
|
+
ModuleResults and render exactly as they do today;
|
|
18
|
+
- a bridge that converts TS compile errors (with their renderReason/
|
|
19
|
+
renderFancyReason methods returning TS ErrorDisplay trees) into
|
|
20
|
+
Pyret objects whose render-(fancy-)reason methods return genuine
|
|
21
|
+
builtin://error-display values, so error-ui/output-ui render them
|
|
22
|
+
unchanged;
|
|
23
|
+
- a re-thrower that converts TS parse errors into the same Pyret
|
|
24
|
+
parse exceptions the stock parser raises.
|
|
25
|
+
*/
|
|
26
|
+
({
|
|
27
|
+
requires: [
|
|
28
|
+
{ "import-type": "builtin", name: "load-lib" },
|
|
29
|
+
{ "import-type": "builtin", name: "either" },
|
|
30
|
+
{ "import-type": "builtin", name: "error-display" },
|
|
31
|
+
{ "import-type": "builtin", name: "ast" }
|
|
32
|
+
],
|
|
33
|
+
nativeRequires: [],
|
|
34
|
+
provides: {},
|
|
35
|
+
theModule: function(runtime, namespace, uri, loadLib, eitherLib, errorDisplayLib, astLib) {
|
|
36
|
+
var gf = runtime.getField;
|
|
37
|
+
var gmf = function(m, f) { return gf(gf(m, "values"), f); };
|
|
38
|
+
// NOTE: runtime.ffi is not fully populated until the ffi builtin
|
|
39
|
+
// loads, which can happen after this module instantiates; always
|
|
40
|
+
// access it lazily.
|
|
41
|
+
|
|
42
|
+
function tsCompiler() {
|
|
43
|
+
if(!window.PyretTSCompiler) {
|
|
44
|
+
throw new Error("The TypeScript compiler bundle (ts-compiler.gz.js) is not loaded on this page");
|
|
45
|
+
}
|
|
46
|
+
return window.PyretTSCompiler;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------
|
|
50
|
+
// Srcloc conversions
|
|
51
|
+
// ---------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
function tsLocToPyret(loc) {
|
|
54
|
+
if(!loc) { return runtime.makeSrcloc(["unknown location"]); }
|
|
55
|
+
if(loc.$name === "builtin") {
|
|
56
|
+
return runtime.makeSrcloc([loc.moduleName]);
|
|
57
|
+
}
|
|
58
|
+
return runtime.makeSrcloc([
|
|
59
|
+
loc.source,
|
|
60
|
+
loc.startLine, loc.startColumn, loc.startChar,
|
|
61
|
+
loc.endLine, loc.endColumn, loc.endChar
|
|
62
|
+
]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function pyretLocToTs(pyLoc) {
|
|
66
|
+
var T = tsCompiler();
|
|
67
|
+
if(runtime.hasField(pyLoc, "source")) {
|
|
68
|
+
return new T.srcloc.Srcloc(
|
|
69
|
+
gf(pyLoc, "source"),
|
|
70
|
+
runtime.num_to_fixnum(gf(pyLoc, "start-line")),
|
|
71
|
+
runtime.num_to_fixnum(gf(pyLoc, "start-column")),
|
|
72
|
+
runtime.num_to_fixnum(gf(pyLoc, "start-char")),
|
|
73
|
+
runtime.num_to_fixnum(gf(pyLoc, "end-line")),
|
|
74
|
+
runtime.num_to_fixnum(gf(pyLoc, "end-column")),
|
|
75
|
+
runtime.num_to_fixnum(gf(pyLoc, "end-char")));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
return new T.srcloc.Builtin(gf(pyLoc, "module-name"));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------
|
|
83
|
+
// ErrorDisplay conversion: TS error-display tree -> Pyret
|
|
84
|
+
// builtin://error-display values
|
|
85
|
+
// ---------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
var ED = gf(errorDisplayLib, "values");
|
|
88
|
+
function edc(name) { return gf(ED, name); }
|
|
89
|
+
|
|
90
|
+
function convertEmbed(val) {
|
|
91
|
+
// Pyret numbers/strings/booleans are plain JS values at runtime, so
|
|
92
|
+
// primitives can be embedded directly; anything else from the TS
|
|
93
|
+
// world is shown via its toString (the TS port's data classes print
|
|
94
|
+
// like the Pyret originals).
|
|
95
|
+
if(typeof val === "number" || typeof val === "string" || typeof val === "boolean") {
|
|
96
|
+
return edc("embed").app(val);
|
|
97
|
+
}
|
|
98
|
+
return edc("text").app(String(val));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function convertED(ed) {
|
|
102
|
+
switch(ed.$name) {
|
|
103
|
+
case "paragraph":
|
|
104
|
+
return edc("paragraph").app(runtime.ffi.makeList(ed.contents.map(convertED)));
|
|
105
|
+
case "bulleted-sequence":
|
|
106
|
+
return edc("bulleted-sequence").app(runtime.ffi.makeList(ed.contents.map(convertED)));
|
|
107
|
+
case "v-sequence":
|
|
108
|
+
return edc("v-sequence").app(runtime.ffi.makeList(ed.contents.map(convertED)));
|
|
109
|
+
case "h-sequence":
|
|
110
|
+
return edc("h-sequence").app(runtime.ffi.makeList(ed.contents.map(convertED)), ed.sep);
|
|
111
|
+
case "h-sequence-sep":
|
|
112
|
+
return edc("h-sequence-sep").app(runtime.ffi.makeList(ed.contents.map(convertED)), ed.sep, ed.last);
|
|
113
|
+
case "embed":
|
|
114
|
+
return convertEmbed(ed.val);
|
|
115
|
+
case "text":
|
|
116
|
+
return edc("text").app(ed.str);
|
|
117
|
+
case "loc":
|
|
118
|
+
return edc("loc").app(tsLocToPyret(ed.loc));
|
|
119
|
+
case "maybe-stack-loc":
|
|
120
|
+
return edc("maybe-stack-loc").app(
|
|
121
|
+
ed.n,
|
|
122
|
+
ed.userFramesOnly,
|
|
123
|
+
runtime.makeFunction(function(pyLoc) {
|
|
124
|
+
return convertED(ed.contentsWithLoc(pyretLocToTs(pyLoc)));
|
|
125
|
+
}, "contents-with-loc"),
|
|
126
|
+
convertED(ed.contentsWithoutLoc));
|
|
127
|
+
case "code":
|
|
128
|
+
return edc("code").app(convertED(ed.contents));
|
|
129
|
+
case "cmcode":
|
|
130
|
+
return edc("cmcode").app(tsLocToPyret(ed.loc));
|
|
131
|
+
case "loc-display":
|
|
132
|
+
return edc("loc-display").app(tsLocToPyret(ed.loc), ed.style, convertED(ed.contents));
|
|
133
|
+
case "optional":
|
|
134
|
+
return edc("optional").app(convertED(ed.contents));
|
|
135
|
+
case "highlight":
|
|
136
|
+
return edc("highlight").app(
|
|
137
|
+
convertED(ed.contents),
|
|
138
|
+
runtime.ffi.makeList(ed.locs.map(tsLocToPyret)),
|
|
139
|
+
ed.color);
|
|
140
|
+
default:
|
|
141
|
+
console.error("Unknown TS ErrorDisplay variant: ", ed);
|
|
142
|
+
return edc("text").app(String(ed));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// A TS CompileError -> a Pyret object renderable by error-ui. The
|
|
147
|
+
// render methods are called off the data the TS error carries, and
|
|
148
|
+
// produce genuine error-display values.
|
|
149
|
+
function bridgeCompileError(tsError) {
|
|
150
|
+
return runtime.makeObject({
|
|
151
|
+
"render-fancy-reason": runtime.makeMethod0(function(_self) {
|
|
152
|
+
return convertED(tsError.renderFancyReason());
|
|
153
|
+
}),
|
|
154
|
+
"render-reason": runtime.makeMethod0(function(_self) {
|
|
155
|
+
return convertED(tsError.renderReason());
|
|
156
|
+
})
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// The `left` payload from the TS repl is a list whose elements are
|
|
161
|
+
// either CompileResult errs (with .problems) or raw problem lists
|
|
162
|
+
// (from make-standalone); normalize to the shape repl-ui's
|
|
163
|
+
// displayResult expects: a Pyret list of records with a `problems`
|
|
164
|
+
// list of renderable errors.
|
|
165
|
+
function bridgeCompileErrors(errs) {
|
|
166
|
+
var asRecords = errs.map(function(e) {
|
|
167
|
+
var problems;
|
|
168
|
+
if(e && Array.isArray(e.problems)) { problems = e.problems; }
|
|
169
|
+
else { problems = [e]; }
|
|
170
|
+
return runtime.makeObject({
|
|
171
|
+
"problems": runtime.ffi.makeList(problems.map(bridgeCompileError))
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
return runtime.ffi.makeList(asRecords);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------
|
|
178
|
+
// Parse errors: re-throw the TS parser's structured errors through
|
|
179
|
+
// the runtime ffi, producing the same Pyret exceptions the stock
|
|
180
|
+
// parse-pyret module raises. MUST be called on the Pyret stack.
|
|
181
|
+
// ---------------------------------------------------------------
|
|
182
|
+
|
|
183
|
+
function isTsParseError(e) {
|
|
184
|
+
return e && e.name === "PyretParseError" && typeof e.kind === "string";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function tsCheckOpToPyret(op) {
|
|
188
|
+
var mkOp = gmf(astLib, op.$name);
|
|
189
|
+
var l = tsLocToPyret(op.l);
|
|
190
|
+
if(typeof op.op === "string") { return mkOp.app(l, op.op); }
|
|
191
|
+
return mkOp.app(l);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function throwPyretParseError(e) {
|
|
195
|
+
var loc = tsLocToPyret(e.loc);
|
|
196
|
+
switch(e.kind) {
|
|
197
|
+
case "parse-error-next-token":
|
|
198
|
+
return runtime.ffi.throwParseErrorNextToken(loc, e.nextToken);
|
|
199
|
+
case "parse-error-eof":
|
|
200
|
+
return runtime.ffi.throwParseErrorEOF(loc);
|
|
201
|
+
case "parse-error-unterminated-string":
|
|
202
|
+
return runtime.ffi.throwParseErrorUnterminatedString(loc);
|
|
203
|
+
case "parse-error-bad-number":
|
|
204
|
+
return runtime.ffi.throwParseErrorBadNumber(loc);
|
|
205
|
+
case "parse-error-bad-operator":
|
|
206
|
+
return runtime.ffi.throwParseErrorBadOper(loc);
|
|
207
|
+
case "parse-error-bad-check-operator":
|
|
208
|
+
return runtime.ffi.throwParseErrorBadCheckOper(tsCheckOpToPyret(e.op));
|
|
209
|
+
case "parse-error-colon-colon":
|
|
210
|
+
return runtime.ffi.throwParseErrorColonColon(loc, e.nextToken);
|
|
211
|
+
case "parse-error-bad-app":
|
|
212
|
+
return runtime.ffi.throwParseErrorBadApp(tsLocToPyret(e.funLoc), tsLocToPyret(e.argsLoc));
|
|
213
|
+
case "parse-error-bad-fun-header":
|
|
214
|
+
return runtime.ffi.throwParseErrorBadFunHeader(tsLocToPyret(e.funLoc || e.loc), tsLocToPyret(e.argsLoc || e.loc));
|
|
215
|
+
default:
|
|
216
|
+
return runtime.ffi.throwMessageException(String(e.message || e));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------
|
|
221
|
+
// Builtin modules: loadables and locators from staticModules
|
|
222
|
+
// ---------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
function makeBuiltinSupport(staticModules) {
|
|
225
|
+
var T = tsCompiler();
|
|
226
|
+
var CS = T.compileStructs;
|
|
227
|
+
var loadables = new Map();
|
|
228
|
+
var locators = {};
|
|
229
|
+
|
|
230
|
+
Object.keys(staticModules).forEach(function(modUri) {
|
|
231
|
+
if(modUri.indexOf("builtin://") !== 0) { return; }
|
|
232
|
+
var name = modUri.slice("builtin://".length);
|
|
233
|
+
var m = staticModules[modUri];
|
|
234
|
+
var raw = T.builtinModules.builtinRawLocatorFromModule(
|
|
235
|
+
function() { return m; },
|
|
236
|
+
function() { return JSON.stringify(m); });
|
|
237
|
+
var loadable = null;
|
|
238
|
+
function getLoadable() {
|
|
239
|
+
if(loadable === null) {
|
|
240
|
+
var provs = CS.providesFromRawProvides(modUri, {
|
|
241
|
+
uri: modUri,
|
|
242
|
+
values: raw.getRawValueProvides(),
|
|
243
|
+
aliases: raw.getRawAliasProvides(),
|
|
244
|
+
datatypes: raw.getRawDatatypeProvides(),
|
|
245
|
+
modules: raw.getRawModuleProvides()
|
|
246
|
+
});
|
|
247
|
+
loadable = new CS.ModuleAsString(provs, CS.noBuiltins, CS.computedNone,
|
|
248
|
+
CS.ok(new T.jsOfPyret.CCPString("")));
|
|
249
|
+
}
|
|
250
|
+
return loadable;
|
|
251
|
+
}
|
|
252
|
+
loadables.set(modUri, getLoadable());
|
|
253
|
+
locators[modUri] = {
|
|
254
|
+
getUncached: function() { return undefined; },
|
|
255
|
+
needsCompile: function(_provides) { return false; },
|
|
256
|
+
getModifiedTime: function() { return 0; },
|
|
257
|
+
getOptions: function(options) {
|
|
258
|
+
var o = Object.assign({}, options);
|
|
259
|
+
o.checks = "none";
|
|
260
|
+
return o;
|
|
261
|
+
},
|
|
262
|
+
getModule: function() {
|
|
263
|
+
throw new Error("Should never fetch source for builtin module " + modUri);
|
|
264
|
+
},
|
|
265
|
+
getExtraImports: function() { return CS.standardImports; },
|
|
266
|
+
getDependencies: function() {
|
|
267
|
+
return raw.getRawDependencies().map(CS.makeDep);
|
|
268
|
+
},
|
|
269
|
+
getNativeModules: function() {
|
|
270
|
+
return raw.getRawNativeModules().map(function(n) { return new CS.Requirejs(n); });
|
|
271
|
+
},
|
|
272
|
+
getGlobals: function() { return CS.standardGlobals; },
|
|
273
|
+
uri: function() { return modUri; },
|
|
274
|
+
name: function() { return name; },
|
|
275
|
+
setCompiled: function(_loadable, _provides) { return; },
|
|
276
|
+
getCompiled: function() { return getLoadable(); }
|
|
277
|
+
};
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return { loadables: loadables, locators: locators };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------
|
|
284
|
+
// Module finder. The TS compiler's dependency chase is async
|
|
285
|
+
// (CL.compileWorklist awaits each finder result), so modules are
|
|
286
|
+
// located and loaded on demand -- fetch for url imports, the
|
|
287
|
+
// embedding host's fs/path RPCs for (url-)file imports -- with NO
|
|
288
|
+
// prefetch pass. Resolution is context-aware, mirroring the stock
|
|
289
|
+
// finder (cpo-main.js makeFindModule / 913740d28eb): a (url-)file
|
|
290
|
+
// import resolves relative to the directory of the importing
|
|
291
|
+
// module, threaded as a "load-path", and all path arithmetic goes
|
|
292
|
+
// through the same host path RPC the running program uses (the
|
|
293
|
+
// filesystem-internal builtin), so imports and runtime file ops
|
|
294
|
+
// resolve a given path identically.
|
|
295
|
+
// ---------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
function maybeAppendSlash(s) {
|
|
298
|
+
if(s.endsWith("/")) { return s; }
|
|
299
|
+
return s + "/";
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function urlResolve(path, base) {
|
|
303
|
+
return new URL(path, base).href;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// The same wire calls filesystem-internal.js makes; that module is
|
|
307
|
+
// the single source of truth for path semantics in embedded hosts.
|
|
308
|
+
function hostRpc(module, method, args) {
|
|
309
|
+
if(!window.MESSAGES) {
|
|
310
|
+
return Promise.reject(new Error(
|
|
311
|
+
"This import needs an embedding host (vscode / embed) that provides '" +
|
|
312
|
+
module + "." + method + "' over RPC"));
|
|
313
|
+
}
|
|
314
|
+
return window.MESSAGES.sendRpc(module, method, args);
|
|
315
|
+
}
|
|
316
|
+
function hostReadFileString(p) {
|
|
317
|
+
return hostRpc('fs', 'readFile', [p]).then(function(contents) {
|
|
318
|
+
// The RPC delivers whatever the host's fs produced after a
|
|
319
|
+
// structured clone: a string, a Uint8Array (vscode), or a
|
|
320
|
+
// JSONified Buffer. Decode without assuming a Buffer global --
|
|
321
|
+
// this module's scope inside the jarr doesn't have one.
|
|
322
|
+
if(typeof contents === "string") { return contents; }
|
|
323
|
+
if(contents instanceof Uint8Array) { return new TextDecoder("utf-8").decode(contents); }
|
|
324
|
+
if(contents && contents.type === "Buffer" && Array.isArray(contents.data)) {
|
|
325
|
+
return new TextDecoder("utf-8").decode(new Uint8Array(contents.data));
|
|
326
|
+
}
|
|
327
|
+
if(Array.isArray(contents)) { return new TextDecoder("utf-8").decode(new Uint8Array(contents)); }
|
|
328
|
+
return new TextDecoder("utf-8").decode(new Uint8Array(contents));
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
function hostExists(p) {
|
|
332
|
+
return hostRpc('fs', 'stat', [p]).then(function() { return true; }, function(e) {
|
|
333
|
+
if(String(e).includes("EntryNotFound")) { return false; }
|
|
334
|
+
throw e;
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function getLoadPath(context) {
|
|
339
|
+
if(context && typeof context["load-path"] === "string") { return context["load-path"]; }
|
|
340
|
+
return ".";
|
|
341
|
+
}
|
|
342
|
+
// get-real-path: an absolute REL is honored as-is, otherwise it is
|
|
343
|
+
// joined onto the importer's load-path (both via the host path RPC).
|
|
344
|
+
function getRealPath(context, rel) {
|
|
345
|
+
return hostRpc('path', 'isAbsolute', [rel]).then(function(abs) {
|
|
346
|
+
if(abs) { return rel; }
|
|
347
|
+
return hostRpc('path', 'join', [getLoadPath(context), rel]);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
// For a dependency, compute a Promise of { path, context }: the local
|
|
351
|
+
// path to load (null for non-local protocols) and the context to
|
|
352
|
+
// thread to that module's own imports. Unlike the stock finder we
|
|
353
|
+
// skip the path RPCs entirely when a url-file import will load
|
|
354
|
+
// remotely (all-remote mode, or no embedding host) -- observable
|
|
355
|
+
// behavior is identical, but the plain-web editor never touches RPCs.
|
|
356
|
+
function dependencyResolveInfo(context, dep, urlFileMode) {
|
|
357
|
+
var CS = tsCompiler().compileStructs;
|
|
358
|
+
var rel = null;
|
|
359
|
+
if(CS.isDependency(dep)) {
|
|
360
|
+
if(dep.protocol === "file" || dep.protocol === "js-file") { rel = dep.arguments[0]; }
|
|
361
|
+
else if(dep.protocol === "url-file" && urlFileMode !== "all-remote" && window.MESSAGES) {
|
|
362
|
+
rel = dep.arguments[1];
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if(rel === null) {
|
|
366
|
+
// builtin/gdrive/url and remote url-file: nothing local to track,
|
|
367
|
+
// thread the importer's context through unchanged.
|
|
368
|
+
return Promise.resolve({ path: null, context: context });
|
|
369
|
+
}
|
|
370
|
+
return getRealPath(context, rel).then(function(realPath) {
|
|
371
|
+
return hostRpc('path', 'dirname', [realPath]).then(function(dir) {
|
|
372
|
+
return { path: realPath, context: { "load-path": dir } };
|
|
373
|
+
});
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Register a loaded source with CPO's document map so error
|
|
378
|
+
// highlighting (cmcode) can render srclocs into it.
|
|
379
|
+
function registerDocument(uri, text) {
|
|
380
|
+
if(window.CPO && CPO.documents && typeof CodeMirror !== "undefined" && !CPO.documents.has(uri)) {
|
|
381
|
+
CPO.documents.set(uri, new CodeMirror.Doc(text, "pyret"));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function fetchText(url) {
|
|
386
|
+
return fetch(url).then(function(response) {
|
|
387
|
+
if(!response.ok) {
|
|
388
|
+
throw new Error("Failed to load " + url + ": " + response.status);
|
|
389
|
+
}
|
|
390
|
+
return response.text();
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function makeTextLocator(uri, text) {
|
|
395
|
+
var T = tsCompiler();
|
|
396
|
+
var CS = T.compileStructs;
|
|
397
|
+
var CL = T.compileLib;
|
|
398
|
+
var self = {
|
|
399
|
+
getUncached: function() { return undefined; },
|
|
400
|
+
needsCompile: function(_provides) { return true; },
|
|
401
|
+
getModifiedTime: function() { return 0; },
|
|
402
|
+
getOptions: function(options) { return options; },
|
|
403
|
+
getModule: function() { return new CL.PyretString(text); },
|
|
404
|
+
getExtraImports: function() { return CS.standardImports; },
|
|
405
|
+
getDependencies: function() {
|
|
406
|
+
return CL.getStandardDependencies(self.getModule(), uri);
|
|
407
|
+
},
|
|
408
|
+
getNativeModules: function() { return []; },
|
|
409
|
+
getGlobals: function() { return CS.standardGlobals; },
|
|
410
|
+
uri: function() { return uri; },
|
|
411
|
+
name: function() { return uri; },
|
|
412
|
+
setCompiled: function(_loadable, _provides) { return; },
|
|
413
|
+
getCompiled: function() { return undefined; }
|
|
414
|
+
};
|
|
415
|
+
return self;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function makeFinderFactory(builtinSupport, urlFileMode) {
|
|
419
|
+
return function makeFinder() {
|
|
420
|
+
var locatorCache = {};
|
|
421
|
+
return function finder(context, dep) {
|
|
422
|
+
var T = tsCompiler();
|
|
423
|
+
var CS = T.compileStructs;
|
|
424
|
+
var CL = T.compileLib;
|
|
425
|
+
function located(uri, locator, ctx) {
|
|
426
|
+
locatorCache[uri] = locator;
|
|
427
|
+
return new CL.Located(locator, ctx);
|
|
428
|
+
}
|
|
429
|
+
if(CS.isBuiltin(dep)) {
|
|
430
|
+
var builtinLocator = builtinSupport.locators["builtin://" + dep.modname];
|
|
431
|
+
if(!builtinLocator) {
|
|
432
|
+
throw new Error("Unknown module: " + dep.modname);
|
|
433
|
+
}
|
|
434
|
+
return located("builtin://" + dep.modname, builtinLocator, context);
|
|
435
|
+
}
|
|
436
|
+
return dependencyResolveInfo(context, dep, urlFileMode).then(function(info) {
|
|
437
|
+
function textLocated(uri, textPromise, ctx) {
|
|
438
|
+
if(locatorCache[uri]) {
|
|
439
|
+
return new CL.Located(locatorCache[uri], ctx);
|
|
440
|
+
}
|
|
441
|
+
return textPromise.then(function(text) {
|
|
442
|
+
registerDocument(uri, text);
|
|
443
|
+
return located(uri, makeTextLocator(uri, text), ctx);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
function localFileLocated(fullUrlForDocs, ctx) {
|
|
447
|
+
return hostRpc('path', 'resolve', [info.path]).then(function(realpath) {
|
|
448
|
+
var uri = "file://" + realpath;
|
|
449
|
+
return textLocated(uri, hostReadFileString(info.path).then(function(text) {
|
|
450
|
+
if(fullUrlForDocs) { registerDocument(fullUrlForDocs, text); }
|
|
451
|
+
return text;
|
|
452
|
+
}), ctx);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if(dep.protocol === "file") {
|
|
456
|
+
return localFileLocated(null, info.context);
|
|
457
|
+
}
|
|
458
|
+
if(dep.protocol === "url") {
|
|
459
|
+
var url = dep.arguments[0];
|
|
460
|
+
return textLocated(url, fetchText(url), context);
|
|
461
|
+
}
|
|
462
|
+
if(dep.protocol === "url-file") {
|
|
463
|
+
var fullUrl = urlResolve(dep.arguments[1], maybeAppendSlash(dep.arguments[0]));
|
|
464
|
+
if(info.path === null) {
|
|
465
|
+
// all-remote (or no embedding host)
|
|
466
|
+
return textLocated(fullUrl, fetchText(fullUrl), context);
|
|
467
|
+
}
|
|
468
|
+
if(urlFileMode === "all-local") {
|
|
469
|
+
return localFileLocated(fullUrl, info.context);
|
|
470
|
+
}
|
|
471
|
+
// local-if-present
|
|
472
|
+
return hostExists(info.path).then(function(exists) {
|
|
473
|
+
if(exists) { return localFileLocated(fullUrl, info.context); }
|
|
474
|
+
return textLocated(fullUrl, fetchText(fullUrl), context);
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
throw new Error("The import protocol '" + dep.protocol +
|
|
478
|
+
"' is not supported when running with the TypeScript compiler: " +
|
|
479
|
+
dep.protocol + "://" + dep.arguments.join(":"));
|
|
480
|
+
});
|
|
481
|
+
};
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ---------------------------------------------------------------
|
|
486
|
+
// Executor over load-lib: runs compiled program text in the realm
|
|
487
|
+
// ---------------------------------------------------------------
|
|
488
|
+
|
|
489
|
+
function makeExecutor(pyRuntime) {
|
|
490
|
+
var runProgramPy = gmf(loadLib, "run-program");
|
|
491
|
+
return {
|
|
492
|
+
run: function(realm, programJsSource, _options) {
|
|
493
|
+
return new Promise(function(resolve, reject) {
|
|
494
|
+
runtime.runThunk(function() {
|
|
495
|
+
return runProgramPy.app(
|
|
496
|
+
pyRuntime,
|
|
497
|
+
realm,
|
|
498
|
+
programJsSource,
|
|
499
|
+
runtime.makeObject({ "checks": "main" }),
|
|
500
|
+
runtime.ffi.makeList([]));
|
|
501
|
+
}, function(result) {
|
|
502
|
+
if(runtime.isSuccessResult(result)) {
|
|
503
|
+
resolve(result.result);
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
// An error escaping run-program itself (not the
|
|
507
|
+
// program): surface the whole FailureResult.
|
|
508
|
+
reject(result);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
},
|
|
513
|
+
isSuccessResult: function(moduleResult) {
|
|
514
|
+
return moduleResult.val.runtime.isSuccessResult(moduleResult.val.result);
|
|
515
|
+
},
|
|
516
|
+
getResultRealm: function(moduleResult) {
|
|
517
|
+
return moduleResult.val.realm;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ---------------------------------------------------------------
|
|
523
|
+
// Result plumbing: convert the TS repl's Either (or thrown error)
|
|
524
|
+
// into the same runtime-result-wrapped Pyret Either that repl-ui's
|
|
525
|
+
// displayResult consumes.
|
|
526
|
+
// ---------------------------------------------------------------
|
|
527
|
+
|
|
528
|
+
var pyLeft = gmf(eitherLib, "left");
|
|
529
|
+
var pyRight = gmf(eitherLib, "right");
|
|
530
|
+
|
|
531
|
+
function resolveWithEither(deferred, either) {
|
|
532
|
+
runtime.runThunk(function() {
|
|
533
|
+
if(either.$name === "left") {
|
|
534
|
+
return pyLeft.app(bridgeCompileErrors(either.v));
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
return pyRight.app(either.v);
|
|
538
|
+
}
|
|
539
|
+
}, function(result) {
|
|
540
|
+
deferred.resolve(result);
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function resolveWithError(deferred, err) {
|
|
545
|
+
// A FailureResult rejected out of the executor passes through
|
|
546
|
+
// unchanged (it is already what displayResult expects).
|
|
547
|
+
if(err && runtime.isFailureResult && runtime.isFailureResult(err)) {
|
|
548
|
+
deferred.resolve(err);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
runtime.runThunk(function() {
|
|
552
|
+
if(isTsParseError(err)) {
|
|
553
|
+
return throwPyretParseError(err);
|
|
554
|
+
}
|
|
555
|
+
console.error("Error while compiling with the TS compiler: ", err);
|
|
556
|
+
return runtime.ffi.throwMessageException(String((err && err.message) || err));
|
|
557
|
+
}, function(result) {
|
|
558
|
+
deferred.resolve(result);
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/*
|
|
563
|
+
Report a run that was stopped before it ever became a Pyret computation.
|
|
564
|
+
(This happens for stops during the TS compiler's compile)
|
|
565
|
+
*/
|
|
566
|
+
function resolveWithUserBreak(deferred) {
|
|
567
|
+
runtime.runThunk(function() {
|
|
568
|
+
return runtime.raise(runtime.ffi.userBreak);
|
|
569
|
+
}, function(result) {
|
|
570
|
+
deferred.resolve(result);
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return runtime.makeJSModuleReturn({
|
|
575
|
+
makeBuiltinSupport: makeBuiltinSupport,
|
|
576
|
+
makeFinderFactory: makeFinderFactory,
|
|
577
|
+
makeExecutor: makeExecutor,
|
|
578
|
+
resolveWithEither: resolveWithEither,
|
|
579
|
+
resolveWithError: resolveWithError,
|
|
580
|
+
resolveWithUserBreak: resolveWithUserBreak,
|
|
581
|
+
bridgeCompileErrors: bridgeCompileErrors,
|
|
582
|
+
tsCompiler: tsCompiler
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
})
|