@jpoly1219/context-extractor 0.1.2 → 0.2.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.
@@ -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
+ }