@jpoly1219/context-extractor 0.1.2 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,394 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.OcamlDriver = void 0;
27
+ const fs = __importStar(require("fs"));
28
+ const path = __importStar(require("path"));
29
+ const child_process_1 = require("child_process");
30
+ const ocaml_type_checker_1 = require("./ocaml-type-checker");
31
+ const utils_1 = require("./utils");
32
+ const ocamlParser = require("../src/ocaml-utils/_build/default/test_parser.bc.js");
33
+ class OcamlDriver {
34
+ constructor() {
35
+ this.typeChecker = new ocaml_type_checker_1.OcamlTypeChecker();
36
+ }
37
+ async init(lspClient, sketchPath) {
38
+ const capabilities = {
39
+ 'textDocument': {
40
+ 'codeAction': { 'dynamicRegistration': true },
41
+ 'codeLens': { 'dynamicRegistration': true },
42
+ 'colorProvider': { 'dynamicRegistration': true },
43
+ 'completion': {
44
+ 'completionItem': {
45
+ 'commitCharactersSupport': true,
46
+ 'documentationFormat': ['markdown', 'plaintext'],
47
+ 'snippetSupport': true
48
+ },
49
+ 'completionItemKind': {
50
+ 'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
51
+ },
52
+ 'contextSupport': true,
53
+ 'dynamicRegistration': true
54
+ },
55
+ 'definition': { 'dynamicRegistration': true },
56
+ 'documentHighlight': { 'dynamicRegistration': true },
57
+ 'documentLink': { 'dynamicRegistration': true },
58
+ 'documentSymbol': {
59
+ 'dynamicRegistration': true,
60
+ 'symbolKind': {
61
+ 'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
62
+ }
63
+ },
64
+ 'formatting': { 'dynamicRegistration': true },
65
+ 'hover': {
66
+ 'contentFormat': ['markdown', 'plaintext'],
67
+ 'dynamicRegistration': true
68
+ },
69
+ 'implementation': { 'dynamicRegistration': true },
70
+ // 'inlayhint': { 'dynamicRegistration': true },
71
+ 'onTypeFormatting': { 'dynamicRegistration': true },
72
+ 'publishDiagnostics': { 'relatedInformation': true },
73
+ 'rangeFormatting': { 'dynamicRegistration': true },
74
+ 'references': { 'dynamicRegistration': true },
75
+ 'rename': { 'dynamicRegistration': true },
76
+ 'signatureHelp': {
77
+ 'dynamicRegistration': true,
78
+ 'signatureInformation': { 'documentationFormat': ['markdown', 'plaintext'] }
79
+ },
80
+ 'synchronization': {
81
+ 'didSave': true,
82
+ 'dynamicRegistration': true,
83
+ 'willSave': true,
84
+ 'willSaveWaitUntil': true
85
+ },
86
+ 'typeDefinition': { 'dynamicRegistration': true, 'linkSupport': true },
87
+ // 'typeHierarchy': { 'dynamicRegistration': true }
88
+ },
89
+ 'workspace': {
90
+ 'applyEdit': true,
91
+ 'configuration': true,
92
+ 'didChangeConfiguration': { 'dynamicRegistration': true },
93
+ 'didChangeWatchedFiles': { 'dynamicRegistration': true },
94
+ 'executeCommand': { 'dynamicRegistration': true },
95
+ 'symbol': {
96
+ 'dynamicRegistration': true,
97
+ 'symbolKind': {
98
+ 'valueSet': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
99
+ }
100
+ }, 'workspaceEdit': { 'documentChanges': true },
101
+ 'workspaceFolders': true
102
+ },
103
+ 'general': {
104
+ 'positionEncodings': ['utf-8']
105
+ },
106
+ };
107
+ const rootPath = path.dirname(sketchPath);
108
+ const rootUri = `file://${rootPath}`;
109
+ const workspaceFolders = [{ 'name': 'context-extractor', 'uri': rootUri }];
110
+ await lspClient.initialize({
111
+ processId: process.pid,
112
+ capabilities: capabilities,
113
+ trace: 'off',
114
+ rootUri: null,
115
+ workspaceFolders: workspaceFolders,
116
+ initializationOptions: {
117
+ preferences: {
118
+ includeInlayVariableTypeHints: true
119
+ }
120
+ }
121
+ });
122
+ }
123
+ async getHoleContext(lspClient, sketchFilePath) {
124
+ const sketchDir = path.dirname(sketchFilePath);
125
+ const sketchFileContent = fs.readFileSync(sketchFilePath, "utf8");
126
+ // Sync client and server by notifying that the client has opened all the files inside the target directory.
127
+ fs.readdirSync(sketchDir).map(fileName => {
128
+ if (fs.lstatSync(path.join(sketchDir, fileName)).isFile()) {
129
+ const langType = (() => {
130
+ switch (fileName) {
131
+ case "dune":
132
+ return "dune";
133
+ case "dune-project":
134
+ return "dune-project";
135
+ case ".ocamlformat":
136
+ return ".ocamlformat";
137
+ default:
138
+ return "ocaml";
139
+ }
140
+ })();
141
+ lspClient.didOpen({
142
+ textDocument: {
143
+ uri: `file://${sketchDir}/${fileName}`,
144
+ languageId: langType,
145
+ text: fs.readFileSync(`${sketchDir}/${fileName}`).toString("ascii"),
146
+ version: 1
147
+ }
148
+ });
149
+ }
150
+ });
151
+ // Get hole context.
152
+ const holeCtx = (await lspClient.ocamlMerlinCallCompatible({
153
+ uri: `file://${sketchFilePath}`,
154
+ command: "holes",
155
+ args: [],
156
+ resultAsSexp: false
157
+ }));
158
+ const sketchSymbol = await lspClient.documentSymbol({
159
+ textDocument: {
160
+ uri: `file://${sketchFilePath}`
161
+ }
162
+ });
163
+ return {
164
+ fullHoverResult: "", //
165
+ functionName: "_", // _
166
+ functionTypeSpan: JSON.parse(holeCtx.result).value[0].type, // model * action -> model
167
+ linePosition: JSON.parse(holeCtx.result).value[0].start.line, // hole's line
168
+ characterPosition: JSON.parse(holeCtx.result).value[0].start.col, // hole's character
169
+ holeTypeDefLinePos: 3, //
170
+ holeTypeDefCharPos: 0, // "
171
+ range: sketchSymbol[0].location.range,
172
+ source: `file://${sketchFilePath}`
173
+ };
174
+ }
175
+ async extractRelevantTypes(lspClient, fullHoverResult, typeName, startLine, endLine, foundSoFar, currentFile) {
176
+ if (!foundSoFar.has(typeName)) {
177
+ foundSoFar.set(typeName, { typeSpan: fullHoverResult, sourceFile: currentFile.slice(7) });
178
+ // outputFile.write(`${fullHoverResult};\n`);
179
+ const content = fs.readFileSync(currentFile.slice(7), "utf8");
180
+ for (let linePos = startLine; linePos <= endLine; ++linePos) {
181
+ const numOfCharsInLine = parseInt((0, child_process_1.execSync)(`wc -m <<< "${content.split("\n")[linePos]}"`, { shell: "/bin/bash" }).toString());
182
+ for (let charPos = 0; charPos < numOfCharsInLine; ++charPos) {
183
+ try {
184
+ const typeDefinitionResult = await lspClient.typeDefinition({
185
+ textDocument: {
186
+ uri: currentFile
187
+ },
188
+ position: {
189
+ character: charPos,
190
+ line: linePos
191
+ }
192
+ });
193
+ if (typeDefinitionResult && typeDefinitionResult instanceof Array && typeDefinitionResult.length != 0) {
194
+ // Use documentSymbol instead of hover.
195
+ // This prevents type alias "squashing" done by tsserver.
196
+ // This also allows for grabbing the entire definition range and not just the symbol range.
197
+ // PERF: feels like this could be memoized to improve performance.
198
+ const documentSymbolResult = await lspClient.documentSymbol({
199
+ textDocument: {
200
+ uri: typeDefinitionResult[0].uri
201
+ }
202
+ });
203
+ // grab if the line number of typeDefinitionResult and documentSymbolResult matches
204
+ // FIX: This overwrites older definitions if the lines are the same. Especially for type constructors, such as playlist_state.
205
+ // Generally the one that comes first is the largest, but this could be dependent on the source code.
206
+ const dsMap = documentSymbolResult.reduce((m, obj) => {
207
+ const newSymbol = obj;
208
+ const existing = m.get(newSymbol.location.range.start.line);
209
+ if (existing) {
210
+ // Compare range between existing doucment symbol and the current symbol.
211
+ if (existing.end.line - existing.start.line >= newSymbol.location.range.end.line - newSymbol.location.range.start.line) {
212
+ return m;
213
+ }
214
+ else if (existing.end.character - existing.start.character >= newSymbol.location.range.end.character - newSymbol.location.range.start.character) {
215
+ return m;
216
+ }
217
+ }
218
+ m.set(obj.location.range.start.line, obj.location.range);
219
+ return m;
220
+ }, new Map());
221
+ const matchingSymbolRange = dsMap.get(typeDefinitionResult[0].range.start.line);
222
+ if (matchingSymbolRange) {
223
+ const snippetInRange = (0, utils_1.extractSnippet)(fs.readFileSync(typeDefinitionResult[0].uri.slice(7)).toString("utf8"), matchingSymbolRange.start, matchingSymbolRange.end);
224
+ // TODO: this can potentially be its own method. the driver would require some way to get type context.
225
+ // potentially, this type checker can be its own class.
226
+ const identifier = this.typeChecker.getIdentifierFromDecl(snippetInRange);
227
+ await this.extractRelevantTypes(lspClient, snippetInRange, identifier, matchingSymbolRange.start.line, matchingSymbolRange.end.line, foundSoFar, typeDefinitionResult[0].uri);
228
+ }
229
+ }
230
+ }
231
+ catch (err) {
232
+ console.log(`${err}`);
233
+ }
234
+ }
235
+ }
236
+ }
237
+ return foundSoFar;
238
+ }
239
+ async extractRelevantHeaders(lspClient,
240
+ // preludeFilePath: string,
241
+ sources, relevantTypes, holeType) {
242
+ const relevantContext = new Set();
243
+ for (const source of sources) {
244
+ const headerTypeSpans = await this.extractHeaderTypeSpans(lspClient, source);
245
+ const targetTypes = this.generateTargetTypes(holeType, relevantTypes, source);
246
+ try {
247
+ for (const hts of headerTypeSpans) {
248
+ const recursiveChildTypes = ocamlParser.parse(hts.typeSpan);
249
+ if (recursiveChildTypes.some((rct) => targetTypes.has(rct))) {
250
+ relevantContext.add({ typeSpan: (hts.identifier + " : " + hts.typeSpan), sourceFile: source });
251
+ continue;
252
+ }
253
+ this.extractRelevantHeadersHelper(hts.typeSpan, targetTypes, relevantTypes, relevantContext, hts.snippet, source);
254
+ }
255
+ }
256
+ catch (err) {
257
+ return new Set();
258
+ console.log(err);
259
+ }
260
+ }
261
+ return relevantContext;
262
+ }
263
+ async extractHeaderTypeSpans(lspClient, preludeFilePath) {
264
+ const docSymbols = await lspClient.documentSymbol({
265
+ textDocument: {
266
+ uri: `file://${preludeFilePath}`,
267
+ }
268
+ });
269
+ if (docSymbols && docSymbols.length > 0) {
270
+ const headerTypeSpans = [];
271
+ const content = fs.readFileSync(preludeFilePath).toString("utf8");
272
+ for (const docSymbol of docSymbols) {
273
+ const ds = docSymbol;
274
+ const snippet = (0, utils_1.extractSnippet)(content, ds.location.range.start, ds.location.range.end);
275
+ const isVar = content.split("\n")[ds.location.range.start.line].slice(0, 3) === "let" ? true : false;
276
+ if (isVar) {
277
+ const symbolHoverResult = await lspClient.hover({
278
+ textDocument: {
279
+ uri: `file://${preludeFilePath}`
280
+ },
281
+ position: {
282
+ line: ds.location.range.start.line,
283
+ character: ds.location.range.start.character + 5
284
+ }
285
+ });
286
+ if (symbolHoverResult) {
287
+ const formattedHoverResult = symbolHoverResult.contents.value.split("\n").reduce((acc, curr) => {
288
+ if (curr != "" && curr != "```ocaml" && curr != "```") {
289
+ return acc + curr;
290
+ }
291
+ else {
292
+ return acc;
293
+ }
294
+ }, "");
295
+ headerTypeSpans.push({ identifier: ds.name, typeSpan: formattedHoverResult, snippet: snippet });
296
+ }
297
+ }
298
+ }
299
+ return headerTypeSpans;
300
+ }
301
+ return [];
302
+ }
303
+ generateTargetTypes(holeType, relevantTypes, preludeFilePath) {
304
+ const targetTypesSet = new Set();
305
+ this.generateTargetTypesHelper(relevantTypes, holeType, targetTypesSet);
306
+ targetTypesSet.add(holeType);
307
+ return targetTypesSet;
308
+ }
309
+ generateTargetTypesHelper(relevantTypes, currType, targetTypes) {
310
+ const constituentTypes = ocamlParser.parse(currType);
311
+ for (const ct of constituentTypes) {
312
+ targetTypes.add(ct);
313
+ if (relevantTypes.has(ct)) {
314
+ const definition = relevantTypes.get(ct).typeSpan.split("=")[1].trim();
315
+ this.generateTargetTypesHelper(relevantTypes, definition, targetTypes);
316
+ }
317
+ }
318
+ }
319
+ // resursive helper for extractRelevantContext
320
+ // checks for nested type equivalence
321
+ extractRelevantHeadersHelper(typeSpan, targetTypes, relevantTypes, relevantContext, snippet, source) {
322
+ targetTypes.forEach(typ => {
323
+ if (this.isTypeEquivalent(typeSpan, typ, relevantTypes)) {
324
+ relevantContext.add({ typeSpan: snippet, sourceFile: source });
325
+ }
326
+ const [ptyp_desc, ...components] = ocamlParser.getComponents(typ);
327
+ if (this.typeChecker.isFunction(ptyp_desc)) {
328
+ const rettype = components[1];
329
+ this.extractRelevantHeadersHelper(rettype, targetTypes, relevantTypes, relevantContext, snippet, source);
330
+ }
331
+ else if (this.typeChecker.isTuple(ptyp_desc)) {
332
+ components.forEach(element => {
333
+ this.extractRelevantHeadersHelper(element, targetTypes, relevantTypes, relevantContext, snippet, source);
334
+ });
335
+ }
336
+ });
337
+ }
338
+ // two types are equivalent if they have the same normal forms
339
+ isTypeEquivalent(t1, t2, relevantTypes) {
340
+ const normT1 = this.normalize(t1, relevantTypes);
341
+ const normT2 = this.normalize(t2, relevantTypes);
342
+ return normT1 === normT2;
343
+ }
344
+ // return the normal form given a type span and a set of relevant types
345
+ normalize(typeSpan, relevantTypes) {
346
+ let normalForm = "";
347
+ // pattern matching for typeSpan
348
+ if (this.typeChecker.isPrimitive(typeSpan)) {
349
+ return typeSpan;
350
+ }
351
+ else if (this.typeChecker.isFunction(typeSpan)) {
352
+ }
353
+ else if (this.typeChecker.isTuple(typeSpan)) {
354
+ const elements = this.typeChecker.parseTypeArrayString(typeSpan);
355
+ elements.forEach((element, i) => {
356
+ normalForm += this.normalize(element, relevantTypes);
357
+ if (i < elements.length - 1) {
358
+ normalForm += " * ";
359
+ }
360
+ });
361
+ return normalForm;
362
+ }
363
+ else if (this.typeChecker.isUnion(typeSpan)) {
364
+ const elements = typeSpan.split(" | ");
365
+ elements.forEach((element, i) => {
366
+ normalForm += "(";
367
+ normalForm += this.normalize(element, relevantTypes);
368
+ normalForm += ")";
369
+ if (i < elements.length - 1) {
370
+ normalForm += " | ";
371
+ }
372
+ });
373
+ return normalForm;
374
+ }
375
+ else if (this.typeChecker.isArray(typeSpan)) {
376
+ const element = typeSpan.split("[]")[0];
377
+ normalForm += this.normalize(element, relevantTypes);
378
+ normalForm += "[]";
379
+ return normalForm;
380
+ }
381
+ else if (this.typeChecker.isTypeAlias(typeSpan)) {
382
+ const typ = relevantTypes.get(typeSpan).typeSpan.split(" = ")[1];
383
+ if (typ === undefined) {
384
+ return typeSpan;
385
+ }
386
+ normalForm += this.normalize(typ, relevantTypes);
387
+ return normalForm;
388
+ }
389
+ else {
390
+ return typeSpan;
391
+ }
392
+ }
393
+ }
394
+ exports.OcamlDriver = OcamlDriver;
@@ -0,0 +1,52 @@
1
+ import { TypeChecker } from "./types";
2
+ export declare class OcamlTypeChecker implements TypeChecker {
3
+ getIdentifierFromDecl(typeDecl: string): string;
4
+ getTypeContextFromDecl(typeDecl: string): {
5
+ identifier: string;
6
+ span: string;
7
+ } | null;
8
+ checkPrimitive(typeDecl: string): {
9
+ identifier: string;
10
+ span: string;
11
+ interestingIndex: number;
12
+ } | null;
13
+ checkImports(typeDecl: string): {
14
+ identifier: string;
15
+ span: string;
16
+ interestingIndex: number;
17
+ } | null;
18
+ checkModule(typeDecl: string): {
19
+ identifier: string;
20
+ span: string;
21
+ interestingIndex: number;
22
+ } | null;
23
+ checkObject(typeDecl: string): {
24
+ identifier: string;
25
+ span: string;
26
+ interestingIndex: number;
27
+ } | null;
28
+ checkUnion(typeDecl: string): {
29
+ identifier: string;
30
+ span: string;
31
+ interestingIndex: number;
32
+ } | null;
33
+ checkFunction(typeDecl: string): {
34
+ identifier: string;
35
+ span: string;
36
+ interestingIndex: number;
37
+ } | null;
38
+ checkHole(typeDecl: string): {
39
+ identifier: string;
40
+ span: string;
41
+ } | null;
42
+ checkParameter(typeDecl: string): null;
43
+ isTuple(typeSpan: string): boolean;
44
+ isUnion(typeSpan: string): boolean;
45
+ isArray(typeSpan: string): boolean;
46
+ isObject(typeSpan: string): boolean;
47
+ isFunction(typeSpan: string): boolean;
48
+ isPrimitive(typeSpan: string): boolean;
49
+ isTypeAlias(typeSpan: string): boolean;
50
+ escapeQuotes(typeSpan: string): string;
51
+ parseTypeArrayString(typeStr: string): string[];
52
+ }