qsharp-lang 0.1.0-dev.1
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/LICENSE.txt +21 -0
- package/README.md +74 -0
- package/dist/browser.d.ts +26 -0
- package/dist/browser.js +156 -0
- package/dist/cancellation.d.ts +10 -0
- package/dist/cancellation.js +31 -0
- package/dist/compiler/common.d.ts +31 -0
- package/dist/compiler/common.js +47 -0
- package/dist/compiler/compiler.d.ts +28 -0
- package/dist/compiler/compiler.js +62 -0
- package/dist/compiler/events.d.ts +54 -0
- package/dist/compiler/events.js +92 -0
- package/dist/compiler/worker-browser.d.ts +1 -0
- package/dist/compiler/worker-browser.js +43 -0
- package/dist/compiler/worker-node.d.ts +1 -0
- package/dist/compiler/worker-node.js +41 -0
- package/dist/compiler/worker-proxy.d.ts +7 -0
- package/dist/compiler/worker-proxy.js +16 -0
- package/dist/debug-service/debug-service.d.ts +35 -0
- package/dist/debug-service/debug-service.js +136 -0
- package/dist/debug-service/worker-browser.d.ts +1 -0
- package/dist/debug-service/worker-browser.js +32 -0
- package/dist/debug-service/worker-node.d.ts +1 -0
- package/dist/debug-service/worker-node.js +30 -0
- package/dist/debug-service/worker-proxy.d.ts +7 -0
- package/dist/debug-service/worker-proxy.js +22 -0
- package/dist/katas-content.generated.d.ts +61 -0
- package/dist/katas-content.generated.js +2499 -0
- package/dist/katas.d.ts +55 -0
- package/dist/katas.js +16 -0
- package/dist/language-service/language-service.d.ts +48 -0
- package/dist/language-service/language-service.js +85 -0
- package/dist/language-service/worker-browser.d.ts +1 -0
- package/dist/language-service/worker-browser.js +32 -0
- package/dist/language-service/worker-node.d.ts +1 -0
- package/dist/language-service/worker-node.js +30 -0
- package/dist/language-service/worker-proxy.d.ts +6 -0
- package/dist/language-service/worker-proxy.js +20 -0
- package/dist/log.d.ts +33 -0
- package/dist/log.js +92 -0
- package/dist/main.d.ts +11 -0
- package/dist/main.js +82 -0
- package/dist/samples.generated.d.ts +6 -0
- package/dist/samples.generated.js +62 -0
- package/dist/vsdiagnostic.d.ts +27 -0
- package/dist/vsdiagnostic.js +117 -0
- package/dist/worker-proxy.d.ts +95 -0
- package/dist/worker-proxy.js +226 -0
- package/lib/node/qsc_wasm.cjs +1010 -0
- package/lib/node/qsc_wasm.d.cts +266 -0
- package/lib/node/qsc_wasm_bg.wasm +0 -0
- package/lib/web/qsc_wasm.d.ts +328 -0
- package/lib/web/qsc_wasm.js +1026 -0
- package/lib/web/qsc_wasm_bg.wasm +0 -0
- package/package.json +35 -0
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Microsoft Corporation.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE
|
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# qsharp npm module
|
|
2
|
+
|
|
3
|
+
This package contains the qsharp compiler and language service functionality shipped for consumption via npm.
|
|
4
|
+
|
|
5
|
+
The source is written in TypeScript, which is compiled to ECMAScript modules in the ./dist directory.
|
|
6
|
+
The wasm binaries from the Rust builds are copied to the ./lib directory.
|
|
7
|
+
|
|
8
|
+
Consuming browser projects should import from this module and use a bundler to create their
|
|
9
|
+
own JavaScript bundle, and also copy the wasm file to their project and provide the URL
|
|
10
|
+
to it when calling the `loadWasmModule` method so it may be located and loaded.
|
|
11
|
+
|
|
12
|
+
## Node and browser support
|
|
13
|
+
|
|
14
|
+
wasm-pack generates different files for the browser and Node.js environments. The wasm is slightly
|
|
15
|
+
different, and the loader code is quite different. This can be seen in `./lib/web/qsc_wasm.cjs`
|
|
16
|
+
and `./lib/node/qsc_wasm.js` files respectively. Specifically, the web environment loads the wasm
|
|
17
|
+
file using async web APIs such as `fetch` with a URI, and Node.js uses `require` to load the `fs` module
|
|
18
|
+
and calls to `readFileSync`. Once the wasm module is loaded however, the exported APIs are used
|
|
19
|
+
in a similar manner.
|
|
20
|
+
|
|
21
|
+
To support using this npm package from both environments, the package uses "conditional exports"
|
|
22
|
+
<https://nodejs.org/dist/latest-v18.x/docs/api/packages.html#conditional-exports> to expose one
|
|
23
|
+
entry point for Node.js, and another for browsers. The distinct entry points uses their respective
|
|
24
|
+
loader to load the wasm module for the platform, and then expose functionality that uses the
|
|
25
|
+
loaded module via common code.
|
|
26
|
+
|
|
27
|
+
When bundling for the web, bundlers such as esbuild will automatically use the default entry point,
|
|
28
|
+
whereas when loaded as a Node.js module, it will use the "node" entry point.
|
|
29
|
+
|
|
30
|
+
Note that TypeScript seems to add the ['import', 'types', 'node'] conditions by default when
|
|
31
|
+
searching the Node.js `exports`, and so will always find the 'node' export before the 'default'
|
|
32
|
+
export. To resolve this, a 'browser' condition was added (which is same as 'default' but earlier
|
|
33
|
+
than 'node') and the tsconfig compiler option `"customConditions": ["browser"]` should be added
|
|
34
|
+
(requires TypeScript 5.0 or later). esbuild also adds the 'browser' condition when bundling for
|
|
35
|
+
the browser (see <https://esbuild.github.io/api/#how-conditions-work>).
|
|
36
|
+
|
|
37
|
+
## Design
|
|
38
|
+
|
|
39
|
+
This package provides two services, the compiler and the language service.
|
|
40
|
+
|
|
41
|
+
The API for using these services is similar whether using a browser or Node.js,
|
|
42
|
+
and whether running in the main thread or a worker thread. You instantiate the service
|
|
43
|
+
and call operations on it which complete in the order called.
|
|
44
|
+
|
|
45
|
+
All operations return a Promise which resolves then the operation is complete. Some operations
|
|
46
|
+
may also emit events, such as debug messages or state dumps as they are processed. The service
|
|
47
|
+
itself can also emit events which can be subscribed to using `addEventListener`.
|
|
48
|
+
|
|
49
|
+
See the Q# playground code at <https://github.com/microsoft/qsharp/tree/main/playground> for
|
|
50
|
+
an example of code that uses this package. The unit tests at
|
|
51
|
+
<https://github.com/microsoft/qsharp/tree/main/npm/test> are also a good reference.
|
|
52
|
+
|
|
53
|
+
Promises, Events, and Cancellation are based on JavaScript or Web standards, or the VS Code API:
|
|
54
|
+
|
|
55
|
+
- Promises <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises>
|
|
56
|
+
- EventTarget <https://developer.mozilla.org/en-US/docs/Web/API/EventTarget>
|
|
57
|
+
- Event <https://developer.mozilla.org/en-US/docs/Web/API/Event/Event>
|
|
58
|
+
- VS Code API for CancellationToken <https://code.visualstudio.com/api/references/vscode-api#CancellationToken>
|
|
59
|
+
|
|
60
|
+
The standard Web APIs for custom events were added to Node.js in v16.17. <https://nodejs.org/dist/v16.17.0/docs/api/events.html>, but behind an experimental flag. As CustomEvent is not on
|
|
61
|
+
the global by default until v19 or later, the code will use Event with a 'detail'
|
|
62
|
+
property manually set until v20 is in common use.
|
|
63
|
+
|
|
64
|
+
The VS Code implementation for cancellation tokens is viewable in their source code
|
|
65
|
+
at <src/vs/base/common/cancellation.ts>. This code uses a simplified version of that API.
|
|
66
|
+
|
|
67
|
+
## Testing
|
|
68
|
+
|
|
69
|
+
Node.js tests can be run via `node --test` (see
|
|
70
|
+
<https://nodejs.org/dist/latest-v18.x/docs/api/test.html#test-runner-execution-model>).
|
|
71
|
+
|
|
72
|
+
The test module was also added to Node.js v16.17.0, and Electron 22 (which VS Code plans to move to
|
|
73
|
+
in first half of 2023) includes v16.17.1, so v16.17 should be our minimum Node.js
|
|
74
|
+
version supported (it shipped in Aug 2022).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ICompiler, ICompilerWorker } from "./compiler/compiler.js";
|
|
2
|
+
import { IDebugService, IDebugServiceWorker } from "./debug-service/debug-service.js";
|
|
3
|
+
import { ILanguageService, ILanguageServiceWorker, qsharpLibraryUriScheme } from "./language-service/language-service.js";
|
|
4
|
+
import { LogLevel, log } from "./log.js";
|
|
5
|
+
export { qsharpLibraryUriScheme };
|
|
6
|
+
export declare function loadWasmModule(uriOrBuffer: string | ArrayBuffer): Promise<void>;
|
|
7
|
+
export declare function getLibrarySourceContent(path: string): Promise<string | undefined>;
|
|
8
|
+
export declare function getDebugService(): Promise<IDebugService>;
|
|
9
|
+
export declare function getDebugServiceWorker(workerArg: string | Worker): IDebugServiceWorker;
|
|
10
|
+
export declare function getCompiler(): Promise<ICompiler>;
|
|
11
|
+
export declare function getCompilerWorker(workerArg: string | Worker): ICompilerWorker;
|
|
12
|
+
export declare function getLanguageService(): Promise<ILanguageService>;
|
|
13
|
+
export declare function getLanguageServiceWorker(workerArg: string | Worker): ILanguageServiceWorker;
|
|
14
|
+
export { type Dump, type ShotResult } from "./compiler/common.js";
|
|
15
|
+
export { type CompilerState } from "./compiler/compiler.js";
|
|
16
|
+
export { QscEventTarget } from "./compiler/events.js";
|
|
17
|
+
export { getAllKatas, getExerciseSources, getKata, type ContentItem, type Example, type Exercise, type ExplainedSolution, type ExplainedSolutionItem, type Kata, type KataSection, type Lesson, type LessonItem, type Question, } from "./katas.js";
|
|
18
|
+
export { default as samples } from "./samples.generated.js";
|
|
19
|
+
export { type VSDiagnostic } from "./vsdiagnostic.js";
|
|
20
|
+
export { log, type LogLevel };
|
|
21
|
+
export type { ICompilerWorker, ICompiler };
|
|
22
|
+
export type { ILanguageServiceWorker, ILanguageService };
|
|
23
|
+
export type { IDebugServiceWorker, IDebugService };
|
|
24
|
+
export type { IBreakpointSpan, IStackFrame } from "../lib/web/qsc_wasm.js";
|
|
25
|
+
export { type IStructStepResult, StepResultId } from "../lib/web/qsc_wasm.js";
|
|
26
|
+
export { type LanguageServiceEvent } from "./language-service/language-service.js";
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
// This module is the entry point for browser environments. For Node.js environment,
|
|
4
|
+
// the "./main.js" module is the entry point.
|
|
5
|
+
import initWasm, * as wasm from "../lib/web/qsc_wasm.js";
|
|
6
|
+
import { Compiler } from "./compiler/compiler.js";
|
|
7
|
+
import { createCompilerProxy } from "./compiler/worker-proxy.js";
|
|
8
|
+
import { QSharpDebugService, } from "./debug-service/debug-service.js";
|
|
9
|
+
import { createDebugServiceProxy } from "./debug-service/worker-proxy.js";
|
|
10
|
+
import { QSharpLanguageService, qsharpLibraryUriScheme, } from "./language-service/language-service.js";
|
|
11
|
+
import { createLanguageServiceProxy } from "./language-service/worker-proxy.js";
|
|
12
|
+
import { log } from "./log.js";
|
|
13
|
+
export { qsharpLibraryUriScheme };
|
|
14
|
+
// Create once. A module is stateless and can be efficiently passed to WebWorkers.
|
|
15
|
+
let wasmModule = null;
|
|
16
|
+
let wasmModulePromise = null;
|
|
17
|
+
// Used to track if an instance is already instantiated
|
|
18
|
+
let wasmInstancePromise = null;
|
|
19
|
+
async function wasmLoader(uriOrBuffer) {
|
|
20
|
+
if (typeof uriOrBuffer === "string") {
|
|
21
|
+
log.info("Fetching wasm module from %s", uriOrBuffer);
|
|
22
|
+
performance.mark("fetch-wasm-start");
|
|
23
|
+
const wasmRequst = await fetch(uriOrBuffer);
|
|
24
|
+
const wasmBuffer = await wasmRequst.arrayBuffer();
|
|
25
|
+
const fetchTiming = performance.measure("fetch-wasm", "fetch-wasm-start");
|
|
26
|
+
log.logTelemetry({
|
|
27
|
+
id: "fetch-wasm",
|
|
28
|
+
data: {
|
|
29
|
+
duration: fetchTiming.duration,
|
|
30
|
+
uri: uriOrBuffer,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
wasmModule = await WebAssembly.compile(wasmBuffer);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
log.info("Compiling wasm module from provided buffer");
|
|
37
|
+
wasmModule = await WebAssembly.compile(uriOrBuffer);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function loadWasmModule(uriOrBuffer) {
|
|
41
|
+
// Only initiate if not already in flight, to avoid race conditions
|
|
42
|
+
if (!wasmModulePromise) {
|
|
43
|
+
wasmModulePromise = wasmLoader(uriOrBuffer);
|
|
44
|
+
}
|
|
45
|
+
return wasmModulePromise;
|
|
46
|
+
}
|
|
47
|
+
async function instantiateWasm() {
|
|
48
|
+
// Ensure loading the module has been initiated, and wait for it.
|
|
49
|
+
if (!wasmModulePromise)
|
|
50
|
+
throw "Wasm module must be loaded first";
|
|
51
|
+
await wasmModulePromise;
|
|
52
|
+
if (!wasmModule)
|
|
53
|
+
throw "Wasm module failed to load";
|
|
54
|
+
if (wasmInstancePromise) {
|
|
55
|
+
// Either in flight or already complete. The prior request will do the init,
|
|
56
|
+
// so just wait on that.
|
|
57
|
+
await wasmInstancePromise;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Set the promise to signal this is in flight, then wait on the result.
|
|
61
|
+
wasmInstancePromise = initWasm(wasmModule);
|
|
62
|
+
await wasmInstancePromise;
|
|
63
|
+
// Once ready, set up logging and telemetry as soon as possible after instantiating
|
|
64
|
+
wasm.initLogging(log.logWithLevel, log.getLogLevel());
|
|
65
|
+
log.onLevelChanged = (level) => wasm.setLogLevel(level);
|
|
66
|
+
}
|
|
67
|
+
export async function getLibrarySourceContent(path) {
|
|
68
|
+
await instantiateWasm();
|
|
69
|
+
return wasm.get_library_source_content(path);
|
|
70
|
+
}
|
|
71
|
+
export async function getDebugService() {
|
|
72
|
+
await instantiateWasm();
|
|
73
|
+
return new QSharpDebugService(wasm);
|
|
74
|
+
}
|
|
75
|
+
// Create the debugger inside a WebWorker and proxy requests.
|
|
76
|
+
// If the Worker was already created via other means and is ready to receive
|
|
77
|
+
// messages, then the worker may be passed in and it will be initialized.
|
|
78
|
+
export function getDebugServiceWorker(workerArg) {
|
|
79
|
+
if (!wasmModule)
|
|
80
|
+
throw "Wasm module must be loaded first";
|
|
81
|
+
// Create or use the WebWorker
|
|
82
|
+
const worker = typeof workerArg === "string" ? new Worker(workerArg) : workerArg;
|
|
83
|
+
// Send it the Wasm module to instantiate
|
|
84
|
+
worker.postMessage({
|
|
85
|
+
type: "init",
|
|
86
|
+
wasmModule,
|
|
87
|
+
qscLogLevel: log.getLogLevel(),
|
|
88
|
+
});
|
|
89
|
+
// If you lose the 'this' binding, some environments have issues
|
|
90
|
+
const postMessage = worker.postMessage.bind(worker);
|
|
91
|
+
const onTerminate = () => worker.terminate();
|
|
92
|
+
// Create the proxy which will forward method calls to the worker
|
|
93
|
+
const proxy = createDebugServiceProxy(postMessage, onTerminate);
|
|
94
|
+
// Let proxy handle response and event messages from the worker
|
|
95
|
+
worker.onmessage = (ev) => proxy.onMsgFromWorker(ev.data);
|
|
96
|
+
return proxy;
|
|
97
|
+
}
|
|
98
|
+
export async function getCompiler() {
|
|
99
|
+
await instantiateWasm();
|
|
100
|
+
return new Compiler(wasm);
|
|
101
|
+
}
|
|
102
|
+
// Create the compiler inside a WebWorker and proxy requests.
|
|
103
|
+
// If the Worker was already created via other means and is ready to receive
|
|
104
|
+
// messages, then the worker may be passed in and it will be initialized.
|
|
105
|
+
export function getCompilerWorker(workerArg) {
|
|
106
|
+
if (!wasmModule)
|
|
107
|
+
throw "Wasm module must be loaded first";
|
|
108
|
+
// Create or use the WebWorker
|
|
109
|
+
const worker = typeof workerArg === "string" ? new Worker(workerArg) : workerArg;
|
|
110
|
+
// Send it the Wasm module to instantiate
|
|
111
|
+
worker.postMessage({
|
|
112
|
+
type: "init",
|
|
113
|
+
wasmModule,
|
|
114
|
+
qscLogLevel: log.getLogLevel(),
|
|
115
|
+
});
|
|
116
|
+
// If you lose the 'this' binding, some environments have issues
|
|
117
|
+
const postMessage = worker.postMessage.bind(worker);
|
|
118
|
+
const onTerminate = () => worker.terminate();
|
|
119
|
+
// Create the proxy which will forward method calls to the worker
|
|
120
|
+
const proxy = createCompilerProxy(postMessage, onTerminate);
|
|
121
|
+
// Let proxy handle response and event messages from the worker
|
|
122
|
+
worker.onmessage = (ev) => proxy.onMsgFromWorker(ev.data);
|
|
123
|
+
return proxy;
|
|
124
|
+
}
|
|
125
|
+
export async function getLanguageService() {
|
|
126
|
+
await instantiateWasm();
|
|
127
|
+
return new QSharpLanguageService(wasm);
|
|
128
|
+
}
|
|
129
|
+
// Create the compiler inside a WebWorker and proxy requests.
|
|
130
|
+
// If the Worker was already created via other means and is ready to receive
|
|
131
|
+
// messages, then the worker may be passed in and it will be initialized.
|
|
132
|
+
export function getLanguageServiceWorker(workerArg) {
|
|
133
|
+
if (!wasmModule)
|
|
134
|
+
throw "Wasm module must be loaded first";
|
|
135
|
+
// Create or use the WebWorker
|
|
136
|
+
const worker = typeof workerArg === "string" ? new Worker(workerArg) : workerArg;
|
|
137
|
+
// Send it the Wasm module to instantiate
|
|
138
|
+
worker.postMessage({
|
|
139
|
+
type: "init",
|
|
140
|
+
wasmModule,
|
|
141
|
+
qscLogLevel: log.getLogLevel(),
|
|
142
|
+
});
|
|
143
|
+
// If you lose the 'this' binding, some environments have issues
|
|
144
|
+
const postMessage = worker.postMessage.bind(worker);
|
|
145
|
+
const onTerminate = () => worker.terminate();
|
|
146
|
+
// Create the proxy which will forward method calls to the worker
|
|
147
|
+
const proxy = createLanguageServiceProxy(postMessage, onTerminate);
|
|
148
|
+
// Let proxy handle response and event messages from the worker
|
|
149
|
+
worker.onmessage = (ev) => proxy.onMsgFromWorker(ev.data);
|
|
150
|
+
return proxy;
|
|
151
|
+
}
|
|
152
|
+
export { QscEventTarget } from "./compiler/events.js";
|
|
153
|
+
export { getAllKatas, getExerciseSources, getKata, } from "./katas.js";
|
|
154
|
+
export { default as samples } from "./samples.generated.js";
|
|
155
|
+
export { log };
|
|
156
|
+
export { StepResultId } from "../lib/web/qsc_wasm.js";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface CancellationToken {
|
|
2
|
+
readonly isCancellationRequested: boolean;
|
|
3
|
+
readonly onCancellationRequested: (listener: (e: any) => any) => void;
|
|
4
|
+
}
|
|
5
|
+
export declare class CancellationTokenSource {
|
|
6
|
+
private _token;
|
|
7
|
+
constructor(parent?: CancellationToken);
|
|
8
|
+
get token(): CancellationToken;
|
|
9
|
+
cancel(): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
class InternalToken {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.isCancellationRequested = false;
|
|
6
|
+
this.eventTarget = new EventTarget();
|
|
7
|
+
}
|
|
8
|
+
onCancellationRequested(listener) {
|
|
9
|
+
this.eventTarget.addEventListener("cancelled", listener);
|
|
10
|
+
}
|
|
11
|
+
cancel() {
|
|
12
|
+
if (this.isCancellationRequested)
|
|
13
|
+
return; // Only fires once
|
|
14
|
+
this.isCancellationRequested = true;
|
|
15
|
+
this.eventTarget.dispatchEvent(new Event("cancelled"));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export class CancellationTokenSource {
|
|
19
|
+
constructor(parent) {
|
|
20
|
+
// There are some optimizations you can do here to lazily allocate, but keeping it simple for now.
|
|
21
|
+
this._token = new InternalToken();
|
|
22
|
+
if (parent)
|
|
23
|
+
parent.onCancellationRequested(() => this.cancel());
|
|
24
|
+
}
|
|
25
|
+
get token() {
|
|
26
|
+
return this._token;
|
|
27
|
+
}
|
|
28
|
+
cancel() {
|
|
29
|
+
this._token.cancel();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { VSDiagnostic } from "../vsdiagnostic.js";
|
|
2
|
+
export type Dump = {
|
|
3
|
+
[index: string]: [number, number];
|
|
4
|
+
};
|
|
5
|
+
export type Result = {
|
|
6
|
+
success: true;
|
|
7
|
+
value: string;
|
|
8
|
+
} | {
|
|
9
|
+
success: false;
|
|
10
|
+
value: VSDiagnostic;
|
|
11
|
+
};
|
|
12
|
+
interface DumpMsg {
|
|
13
|
+
type: "DumpMachine";
|
|
14
|
+
state: Dump;
|
|
15
|
+
}
|
|
16
|
+
interface MessageMsg {
|
|
17
|
+
type: "Message";
|
|
18
|
+
message: string;
|
|
19
|
+
}
|
|
20
|
+
interface ResultMsg {
|
|
21
|
+
type: "Result";
|
|
22
|
+
result: Result;
|
|
23
|
+
}
|
|
24
|
+
type EventMsg = ResultMsg | DumpMsg | MessageMsg;
|
|
25
|
+
export declare function eventStringToMsg(msg: string): EventMsg | null;
|
|
26
|
+
export type ShotResult = {
|
|
27
|
+
success: boolean;
|
|
28
|
+
result: string | VSDiagnostic;
|
|
29
|
+
events: Array<MessageMsg | DumpMsg>;
|
|
30
|
+
};
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
function outputAsResult(msg) {
|
|
4
|
+
try {
|
|
5
|
+
const obj = JSON.parse(msg);
|
|
6
|
+
if (obj?.type == "Result" && typeof obj.success == "boolean") {
|
|
7
|
+
return {
|
|
8
|
+
type: "Result",
|
|
9
|
+
result: {
|
|
10
|
+
success: obj.success,
|
|
11
|
+
value: obj.result,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function outputAsMessage(msg) {
|
|
22
|
+
try {
|
|
23
|
+
const obj = JSON.parse(msg);
|
|
24
|
+
if (obj?.type == "Message" && typeof obj.message == "string") {
|
|
25
|
+
return obj;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
function outputAsDump(msg) {
|
|
34
|
+
try {
|
|
35
|
+
const obj = JSON.parse(msg);
|
|
36
|
+
if (obj?.type == "DumpMachine" && typeof obj.state == "object") {
|
|
37
|
+
return obj;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
export function eventStringToMsg(msg) {
|
|
46
|
+
return outputAsResult(msg) || outputAsMessage(msg) || outputAsDump(msg);
|
|
47
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { VSDiagnostic } from "../vsdiagnostic.js";
|
|
2
|
+
import { IServiceProxy, ServiceState } from "../worker-proxy.js";
|
|
3
|
+
import { IQscEventTarget } from "./events.js";
|
|
4
|
+
type Wasm = typeof import("../../lib/node/qsc_wasm.cjs");
|
|
5
|
+
export interface ICompiler {
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated use the language service for errors and other editor features.
|
|
8
|
+
*/
|
|
9
|
+
checkCode(code: string): Promise<VSDiagnostic[]>;
|
|
10
|
+
getHir(code: string): Promise<string>;
|
|
11
|
+
run(code: string, expr: string, shots: number, eventHandler: IQscEventTarget): Promise<void>;
|
|
12
|
+
checkExerciseSolution(user_code: string, exercise_sources: string[], eventHandler: IQscEventTarget): Promise<boolean>;
|
|
13
|
+
}
|
|
14
|
+
export type ICompilerWorker = ICompiler & IServiceProxy;
|
|
15
|
+
export type CompilerState = ServiceState;
|
|
16
|
+
export declare class Compiler implements ICompiler {
|
|
17
|
+
private wasm;
|
|
18
|
+
constructor(wasm: Wasm);
|
|
19
|
+
/**
|
|
20
|
+
* @deprecated use the language service for errors and other editor features.
|
|
21
|
+
*/
|
|
22
|
+
checkCode(code: string): Promise<VSDiagnostic[]>;
|
|
23
|
+
getHir(code: string): Promise<string>;
|
|
24
|
+
run(code: string, expr: string, shots: number, eventHandler: IQscEventTarget): Promise<void>;
|
|
25
|
+
checkExerciseSolution(user_code: string, exercise_sources: string[], eventHandler: IQscEventTarget): Promise<boolean>;
|
|
26
|
+
}
|
|
27
|
+
export declare function onCompilerEvent(msg: string, eventTarget: IQscEventTarget): void;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { log } from "../log.js";
|
|
4
|
+
import { mapDiagnostics } from "../vsdiagnostic.js";
|
|
5
|
+
import { eventStringToMsg } from "./common.js";
|
|
6
|
+
import { makeEvent } from "./events.js";
|
|
7
|
+
export class Compiler {
|
|
8
|
+
constructor(wasm) {
|
|
9
|
+
log.info("Constructing a Compiler instance");
|
|
10
|
+
this.wasm = wasm;
|
|
11
|
+
globalThis.qscGitHash = this.wasm.git_hash();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated use the language service for errors and other editor features.
|
|
15
|
+
*/
|
|
16
|
+
async checkCode(code) {
|
|
17
|
+
let diags = [];
|
|
18
|
+
const languageService = new this.wasm.LanguageService((uri, version, errors) => {
|
|
19
|
+
diags = errors;
|
|
20
|
+
});
|
|
21
|
+
languageService.update_document("code", 1, code, true /* exe */);
|
|
22
|
+
return mapDiagnostics(diags, code);
|
|
23
|
+
}
|
|
24
|
+
async getHir(code) {
|
|
25
|
+
return this.wasm.get_hir(code);
|
|
26
|
+
}
|
|
27
|
+
async run(code, expr, shots, eventHandler) {
|
|
28
|
+
// All results are communicated as events, but if there is a compiler error (e.g. an invalid
|
|
29
|
+
// entry expression or similar), it may throw on run. The caller should expect this promise
|
|
30
|
+
// may reject without all shots running or events firing.
|
|
31
|
+
this.wasm.run(code, expr, (msg) => onCompilerEvent(msg, eventHandler), shots);
|
|
32
|
+
}
|
|
33
|
+
async checkExerciseSolution(user_code, exercise_sources, eventHandler) {
|
|
34
|
+
const success = this.wasm.check_exercise_solution(user_code, exercise_sources, (msg) => onCompilerEvent(msg, eventHandler));
|
|
35
|
+
return success;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function onCompilerEvent(msg, eventTarget) {
|
|
39
|
+
const qscMsg = eventStringToMsg(msg);
|
|
40
|
+
if (!qscMsg) {
|
|
41
|
+
log.error("Unknown event message: %s", msg);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let qscEvent;
|
|
45
|
+
const msgType = qscMsg.type;
|
|
46
|
+
switch (msgType) {
|
|
47
|
+
case "Message":
|
|
48
|
+
qscEvent = makeEvent("Message", qscMsg.message);
|
|
49
|
+
break;
|
|
50
|
+
case "DumpMachine":
|
|
51
|
+
qscEvent = makeEvent("DumpMachine", qscMsg.state);
|
|
52
|
+
break;
|
|
53
|
+
case "Result":
|
|
54
|
+
qscEvent = makeEvent("Result", qscMsg.result);
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
log.never(msgType);
|
|
58
|
+
throw "Unexpected message type";
|
|
59
|
+
}
|
|
60
|
+
log.debug("worker dispatching event " + JSON.stringify(qscEvent));
|
|
61
|
+
eventTarget.dispatchEvent(qscEvent);
|
|
62
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ShotResult, Dump, Result } from "./common.js";
|
|
2
|
+
import { TelemetryEvent } from "../log.js";
|
|
3
|
+
import { IServiceEventTarget } from "../worker-proxy.js";
|
|
4
|
+
export type QscEventData = {
|
|
5
|
+
type: "Message";
|
|
6
|
+
detail: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: "DumpMachine";
|
|
9
|
+
detail: Dump;
|
|
10
|
+
} | {
|
|
11
|
+
type: "Result";
|
|
12
|
+
detail: Result;
|
|
13
|
+
} | {
|
|
14
|
+
type: "telemetry-event";
|
|
15
|
+
detail: TelemetryEvent;
|
|
16
|
+
};
|
|
17
|
+
export type QscEvents = Event & QscEventData;
|
|
18
|
+
export type QscEvent<T extends QscEvents["type"]> = Extract<QscEvents, {
|
|
19
|
+
type: T;
|
|
20
|
+
}>;
|
|
21
|
+
export type IQscEventTarget = IServiceEventTarget<QscEventData>;
|
|
22
|
+
export declare function makeEvent<E extends QscEvents>(type: E["type"], detail: E["detail"]): E;
|
|
23
|
+
type QscUiEvents = QscEvents | (Event & {
|
|
24
|
+
type: "uiResultsRefresh";
|
|
25
|
+
detail: undefined;
|
|
26
|
+
});
|
|
27
|
+
export declare class QscEventTarget implements IQscEventTarget {
|
|
28
|
+
private eventTarget;
|
|
29
|
+
private results;
|
|
30
|
+
private shotActive;
|
|
31
|
+
private animationFrameId;
|
|
32
|
+
private supportsUiRefresh;
|
|
33
|
+
addEventListener<T extends QscUiEvents["type"]>(type: T, listener: (event: Extract<QscEvents, {
|
|
34
|
+
type: T;
|
|
35
|
+
}>) => void): void;
|
|
36
|
+
removeEventListener<T extends QscUiEvents["type"]>(type: T, listener: (event: Extract<QscEvents, {
|
|
37
|
+
type: T;
|
|
38
|
+
}>) => void): void;
|
|
39
|
+
dispatchEvent(event: QscUiEvents): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* @param captureEvents Set to true if this instance should record events internally
|
|
42
|
+
*/
|
|
43
|
+
constructor(captureEvents: boolean);
|
|
44
|
+
private onMessage;
|
|
45
|
+
private onDumpMachine;
|
|
46
|
+
private onResult;
|
|
47
|
+
private ensureActiveShot;
|
|
48
|
+
private queueUiRefresh;
|
|
49
|
+
private onUiRefresh;
|
|
50
|
+
getResults(): ShotResult[];
|
|
51
|
+
resultCount(): number;
|
|
52
|
+
clearResults(): void;
|
|
53
|
+
}
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
import { log } from "../log.js";
|
|
4
|
+
// Convenience method that also provides type safety
|
|
5
|
+
export function makeEvent(type, detail) {
|
|
6
|
+
const event = new Event(type);
|
|
7
|
+
event.detail = detail;
|
|
8
|
+
return event;
|
|
9
|
+
}
|
|
10
|
+
function makeResultObj() {
|
|
11
|
+
return { success: false, result: "", events: [] };
|
|
12
|
+
}
|
|
13
|
+
export class QscEventTarget {
|
|
14
|
+
// Overrides for the base EventTarget methods to limit to expected event types
|
|
15
|
+
addEventListener(type, listener) {
|
|
16
|
+
this.eventTarget.addEventListener(type, listener);
|
|
17
|
+
}
|
|
18
|
+
removeEventListener(type, listener) {
|
|
19
|
+
this.eventTarget.removeEventListener(type, listener);
|
|
20
|
+
}
|
|
21
|
+
dispatchEvent(event) {
|
|
22
|
+
if (log.getLogLevel() >= 4)
|
|
23
|
+
log.debug("Dispatching event: %o", event);
|
|
24
|
+
return this.eventTarget.dispatchEvent(event);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @param captureEvents Set to true if this instance should record events internally
|
|
28
|
+
*/
|
|
29
|
+
constructor(captureEvents) {
|
|
30
|
+
this.eventTarget = new EventTarget();
|
|
31
|
+
this.results = [];
|
|
32
|
+
this.shotActive = false;
|
|
33
|
+
this.animationFrameId = 0;
|
|
34
|
+
this.supportsUiRefresh = false;
|
|
35
|
+
this.supportsUiRefresh =
|
|
36
|
+
typeof globalThis.requestAnimationFrame === "function";
|
|
37
|
+
if (captureEvents) {
|
|
38
|
+
this.addEventListener("Message", (ev) => this.onMessage(ev.detail));
|
|
39
|
+
this.addEventListener("DumpMachine", (ev) => this.onDumpMachine(ev.detail));
|
|
40
|
+
this.addEventListener("Result", (ev) => this.onResult(ev.detail));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
onMessage(msg) {
|
|
44
|
+
this.ensureActiveShot();
|
|
45
|
+
const shotIdx = this.results.length - 1;
|
|
46
|
+
this.results[shotIdx].events.push({ type: "Message", message: msg });
|
|
47
|
+
this.queueUiRefresh();
|
|
48
|
+
}
|
|
49
|
+
onDumpMachine(dump) {
|
|
50
|
+
this.ensureActiveShot();
|
|
51
|
+
const shotIdx = this.results.length - 1;
|
|
52
|
+
this.results[shotIdx].events.push({ type: "DumpMachine", state: dump });
|
|
53
|
+
this.queueUiRefresh();
|
|
54
|
+
}
|
|
55
|
+
onResult(result) {
|
|
56
|
+
this.ensureActiveShot();
|
|
57
|
+
const shotIdx = this.results.length - 1;
|
|
58
|
+
this.results[shotIdx].success = result.success;
|
|
59
|
+
this.results[shotIdx].result = result.value;
|
|
60
|
+
this.shotActive = false;
|
|
61
|
+
this.queueUiRefresh();
|
|
62
|
+
}
|
|
63
|
+
ensureActiveShot() {
|
|
64
|
+
if (!this.shotActive) {
|
|
65
|
+
this.results.push(makeResultObj());
|
|
66
|
+
this.shotActive = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
queueUiRefresh() {
|
|
70
|
+
if (this.supportsUiRefresh && !this.animationFrameId) {
|
|
71
|
+
this.animationFrameId = requestAnimationFrame(() => {
|
|
72
|
+
this.onUiRefresh();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
onUiRefresh() {
|
|
77
|
+
this.animationFrameId = 0;
|
|
78
|
+
const uiRefreshEvent = new Event("uiResultsRefresh");
|
|
79
|
+
this.dispatchEvent(uiRefreshEvent);
|
|
80
|
+
}
|
|
81
|
+
getResults() {
|
|
82
|
+
return this.results;
|
|
83
|
+
}
|
|
84
|
+
resultCount() {
|
|
85
|
+
// May be one less than length if the last is still in flight
|
|
86
|
+
return this.shotActive ? this.results.length - 1 : this.results.length;
|
|
87
|
+
}
|
|
88
|
+
clearResults() {
|
|
89
|
+
this.results = [];
|
|
90
|
+
this.shotActive = false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function messageHandler(e: MessageEvent): void;
|