forked from tlaplus/vscode-tlaplus
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparseModule.ts
More file actions
135 lines (126 loc) · 5.61 KB
/
Copy pathparseModule.ts
File metadata and controls
135 lines (126 loc) · 5.61 KB
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import * as vscode from 'vscode';
import { DCollection, applyDCollection } from '../diagnostic';
import { TranspilerStdoutParser } from '../parsers/pluscal';
import { SanyData, SanyStdoutParser, getDivergenceType, hasTranslationChecksums } from '../parsers/sany';
import { runPlusCal, runSany, stopProcess, ToolProcessInfo } from '../tla2tools';
import { ToolOutputChannel } from '../outputChannels';
import { LANG_TLAPLUS, pathToModuleName } from '../common';
export const CMD_PARSE_MODULE = 'tlaplus.parse';
const plusCalOutChannel = new ToolOutputChannel('PlusCal');
const sanyOutChannel = new ToolOutputChannel('SANY');
/**
* Parses .tla module:
* - Transpiles PlusCal to TLA+
* - Parses resulting TLA+ specification and checks for syntax errors
*/
export function parseModule(diagnostic: vscode.DiagnosticCollection): void {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('No editor is active, cannot find a TLA+ file to transpile');
return;
}
if (editor.document.languageId !== LANG_TLAPLUS) {
vscode.window.showWarningMessage('File in the active editor is not a TLA+ file, it cannot be transpiled');
return;
}
editor.document.save().then(() => doParseFile(editor.document, diagnostic));
}
const OVERWRITE_PCAL_TRANSL_LABEL = 'Overwrite Translation';
const CANCEL_LABEL = 'Cancel';
const WARN_TLA_MODIFIED =
'The TLA+ translation has been modified manually since its last translation. '
+ 'Click "Overwrite Translation" to replace it with a fresh translation from '
+ 'the PlusCal algorithm or "' + CANCEL_LABEL + '" to keep the modified TLA+ translation.';
async function doParseFile(doc: vscode.TextDocument, diagnostic: vscode.DiagnosticCollection) {
try {
// Only run the pre-translation SANY check if the file has translation markers with checksums.
// This avoids doubling SANY invocations for files without PlusCal or without checksums.
if (hasTranslationChecksums(doc.getText())) {
const initialSpecData = await parseSpec(doc.uri);
const divergence = getDivergenceType(initialSpecData);
// Prompt for overwrite when the root module's TLA+ translation was
// manually edited. This applies to both 'tla' (only translation changed)
// and 'both' (PlusCal also changed) — either way, manual TLA+ edits
// will be lost on re-translation.
const rootType = divergence.get(pathToModuleName(doc.uri.fsPath));
if (rootType === 'tla' || rootType === 'both') {
const choice = await vscode.window.showWarningMessage(
WARN_TLA_MODIFIED,
OVERWRITE_PCAL_TRANSL_LABEL,
CANCEL_LABEL
);
if (choice !== OVERWRITE_PCAL_TRANSL_LABEL) {
// User cancelled — apply initial SANY diagnostics without translating
applyDCollection(initialSpecData.dCollection, diagnostic);
return;
}
}
}
const messages = await transpilePlusCal(doc.uri);
vscode.window.showTextDocument(doc); // To force changes reloading
const specData = await parseSpec(doc.uri);
messages.addAll(specData.dCollection);
applyDCollection(messages, diagnostic);
} catch (err) {
vscode.window.showErrorMessage(`Error parsing file: ${err}`);
}
}
/**
* Transpiles PlusCal code in the current .tla file to TLA+ code in the same file.
*/
export async function transpilePlusCal(fileUri: vscode.Uri, token?: vscode.CancellationToken): Promise<DCollection> {
throwIfCancelled(token);
const procInfo = await runPlusCal(fileUri.fsPath);
plusCalOutChannel.bindTo(procInfo);
const cancellationDisposable = registerCancellation(procInfo, token);
const stdoutParser = new TranspilerStdoutParser(procInfo.mergedOutput, fileUri.fsPath);
try {
const result = await stdoutParser.readAll();
throwIfCancelled(token);
return result;
} finally {
cancellationDisposable?.dispose();
}
}
/**
* Parses the resulting TLA+ spec.
*/
export async function parseSpec(fileUri: vscode.Uri, token?: vscode.CancellationToken): Promise<SanyData> {
throwIfCancelled(token);
// SANY runs on a filesystem path, so only file:// URIs are supported.
// Reject untitled or virtual (e.g. jarfile://) URIs with a clear error
// instead of letting the underlying process fail obscurely on fsPath.
if (fileUri.scheme !== 'file') {
throw new Error(`Cannot parse '${fileUri.toString()}': only files saved on disk are supported.`);
}
const procInfo = await runSany(fileUri.fsPath);
sanyOutChannel.bindTo(procInfo);
const cancellationDisposable = registerCancellation(procInfo, token);
const stdoutParser = new SanyStdoutParser(procInfo.mergedOutput);
try {
const result = await stdoutParser.readAll();
throwIfCancelled(token);
return result;
} finally {
cancellationDisposable?.dispose();
}
}
function throwIfCancelled(token: vscode.CancellationToken | undefined): void {
if (token?.isCancellationRequested) {
throw new vscode.CancellationError();
}
}
function registerCancellation(
procInfo: ToolProcessInfo,
token: vscode.CancellationToken | undefined
): vscode.Disposable | undefined {
if (!token) {
return undefined;
}
const cancel = () => stopProcess(procInfo.process);
if (token.isCancellationRequested) {
cancel();
return undefined;
}
return token.onCancellationRequested(cancel);
}