microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
ac22b7ee6ec9d38efb5324d7543b832c55b56f2c

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

npm/src/common.ts

209lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4// Each DumpMachine output is represented as an object where each key is a basis
5// state, e.g., "|3>" and the value is the [real, imag] parts of the complex amplitude.
6export type Dump = {
7 [index: string]: [number, number];
8};
9
10export interface VSDiagnostic {
11 start_pos: number;
12 end_pos: number;
13 message: string;
14 severity: number;
15}
16
17export type Result =
18 | { success: true; value: string }
19 | { success: false; value: VSDiagnostic };
20
21export interface DumpMsg {
22 type: "DumpMachine";
23 state: Dump;
24}
25
26export interface MessageMsg {
27 type: "Message";
28 message: string;
29}
30
31export interface ResultMsg {
32 type: "Result";
33 result: Result;
34}
35
36export type EventMsg = ResultMsg | DumpMsg | MessageMsg;
37
38export function outputAsResult(msg: string): ResultMsg | null {
39 try {
40 const obj = JSON.parse(msg);
41 if (obj?.type == "Result" && typeof obj.success == "boolean") {
42 return {
43 type: "Result",
44 result: {
45 success: obj.success,
46 value: obj.result,
47 },
48 };
49 }
50 } catch {
51 return null;
52 }
53 return null;
54}
55
56export function outputAsMessage(msg: string): MessageMsg | null {
57 try {
58 const obj = JSON.parse(msg);
59 if (obj?.type == "Message" && typeof obj.message == "string") {
60 return obj as MessageMsg;
61 }
62 } catch {
63 return null;
64 }
65 return null;
66}
67
68export function outputAsDump(msg: string): DumpMsg | null {
69 try {
70 const obj = JSON.parse(msg);
71 if (obj?.type == "DumpMachine" && typeof obj.state == "object") {
72 return obj as DumpMsg;
73 }
74 } catch {
75 return null;
76 }
77 return null;
78}
79
80export function eventStringToMsg(msg: string): EventMsg | null {
81 return outputAsResult(msg) || outputAsMessage(msg) || outputAsDump(msg);
82}
83
84export type ShotResult = {
85 success: boolean;
86 result: string | VSDiagnostic;
87 events: Array<MessageMsg | DumpMsg>;
88};
89
90// The QSharp compiler returns positions in utf-8 code unit positions (basically a byte[]
91// index), however VS Code and Monaco handle positions as utf-16 code unit positions
92// (basically JavaScript string index positions). Thus the positions returned from the
93// wasm calls needs to be mapped between the two for editor integration.
94
95/**
96 * @param positions - An array of utf-8 code unit indexes to map to utf-16 code unit indexes
97 * @param source - The source code to do the mapping on
98 * @returns An object where the keys are the utf-8 index and the values are the utf-16 index
99 */
100export function mapUtf8UnitsToUtf16Units(
101 positions: Array<number>,
102 source: string
103): { [index: number]: number } {
104 const result: { [index: number]: number } = {};
105 if (positions.length === 0) return result;
106
107 // Remove any duplicates by converting to a set and back to an array
108 const deduped_pos = [...new Set(positions)];
109
110 // Do one pass through the source, so ensure the indexs are in ascending order
111 const sorted_pos = deduped_pos.sort((a, b) => (a < b ? -1 : 1));
112
113 // Assume that Rust handles utf-8 correctly in strings, and that the UTF-8 code units
114 // per Unicode code point are as per the ranges below:
115 // - 0x000000 to 0x00007F = 1 utf-8 code unit
116 // - 0x000080 to 0x0007FF = 2 utf-8 code units
117 // - 0x000800 to 0x00FFFF = 3 utf-8 code units
118 // - 0x010000 to 0x10FFFF = 4 utf-8 code units
119 //
120 // Also assume the source JavaScript string is valid UTF-16 and all characters
121 // outside the BMP (i.e. > 0xFFFF) are encoded with valid 'surrogate pairs', and
122 // no other UTF-16 code units in the D800 - DFFF range occur.
123
124 // A valid pair must be "high" surrogate (D800–DBFF) then "low" surrogates (DC00–DFFF)
125 function isValidSurrogatePair(first: number, second: number): boolean {
126 if (
127 first < 0xd800 ||
128 first > 0xdbff ||
129 second < 0xdc00 ||
130 second > 0xdfff
131 ) {
132 return false;
133 }
134 return true;
135 }
136
137 let utf16Index = 0;
138 let utf8Index = 0;
139 let posArrayIndex = 0;
140 let nextUtf8Target = sorted_pos[posArrayIndex];
141 for (;;) {
142 // Walk though the source code maintaining a UTF-8 to UTF-16 code unit index mapping.
143 // When the UTF-8 index >= the next searched for index, save that result and increment.
144 // If the end of source or end of searched for positions is reached, then break
145 if (utf8Index >= nextUtf8Target) {
146 result[utf8Index] = utf16Index;
147 if (++posArrayIndex >= sorted_pos.length) break;
148 nextUtf8Target = sorted_pos[posArrayIndex];
149 }
150
151 if (utf16Index >= source.length) break;
152
153 // Get the code unit (not code point) at the source index.
154 const utf16CodeUnit = source.charCodeAt(utf16Index);
155
156 // Advance the utf-8 offset by the correct amount for the utf-16 code unit value.
157 if (utf16CodeUnit < 0x80) {
158 utf8Index += 1;
159 } else if (utf16CodeUnit < 0x800) {
160 utf8Index += 2;
161 } else if (utf16CodeUnit < 0xd800 || utf16CodeUnit > 0xdfff) {
162 // Not a surrogate pair, so one utf-16 code unit over 0x7FF == three utf-8 code utits
163 utf8Index += 3;
164 } else {
165 // Need to consume the extra utf16 code unit for the pair also.
166 const nextCodeUnit = source.charCodeAt(++utf16Index) || 0;
167 if (!isValidSurrogatePair(utf16CodeUnit, nextCodeUnit))
168 throw "Invalid surrogate pair";
169 // Valid utf-16 surrogate pair implies code point over 0xFFFF implies 4 utf-8 code units.
170 utf8Index += 4;
171 }
172 ++utf16Index; // Don't break here if EOF. We need to handle EOF being the final position to resolve.
173 }
174
175 // TODO: May want to have a more configurable error reporting at some point. Avoid throwing here,
176 // and just report and continue.
177 if (posArrayIndex < sorted_pos.length) {
178 console.error(
179 `Failed to map all utf-8 positions to source locations. Remaining positions are: ${sorted_pos.slice(
180 posArrayIndex
181 )}`
182 );
183 }
184
185 return result;
186}
187
188export function mapDiagnostics(
189 diags: VSDiagnostic[],
190 code: string
191): VSDiagnostic[] {
192 // Get a map of the Rust source positions to the JavaScript source positions
193 const positions: number[] = [];
194 diags.forEach((diag) => {
195 positions.push(diag.start_pos);
196 positions.push(diag.end_pos);
197 });
198 const positionMap = mapUtf8UnitsToUtf16Units(positions, code);
199
200 // Return the diagnostics with the positions mapped (or EOF if couldn't resolve)
201 const results = diags.map((diag) => ({
202 ...diag,
203 // The mapped position may well be 0, so need to use ?? rather than ||
204 start_pos: positionMap[diag.start_pos] ?? code.length,
205 end_pos: positionMap[diag.end_pos] ?? code.length,
206 }));
207
208 return results;
209}