| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- import * as vscode from 'vscode';
- const MODULE_HEADER = /\b(DEFINITION\s+)?(IMPLEMENTATION\s+)?MODULE\s+([A-Za-z_][A-Za-z0-9_]*)/i;
- const END_MODULE = /\bEND\s+([A-Za-z_][A-Za-z0-9_]*)\s*(\.)?/gi;
- interface ScanResult {
- diagnostics: vscode.Diagnostic[];
- code: string;
- }
- function positionAt(document: vscode.TextDocument, offset: number): vscode.Position {
- return document.positionAt(Math.max(0, Math.min(offset, document.getText().length)));
- }
- function rangeAt(document: vscode.TextDocument, start: number, end: number): vscode.Range {
- return new vscode.Range(positionAt(document, start), positionAt(document, end));
- }
- function diagnostic(
- document: vscode.TextDocument,
- start: number,
- end: number,
- message: string,
- severity = vscode.DiagnosticSeverity.Error
- ): vscode.Diagnostic {
- const item = new vscode.Diagnostic(rangeAt(document, start, end), message, severity);
- item.source = 'Modula-2';
- return item;
- }
- /**
- * Lightweight diagnostics used until the Coco/R parser is connected.
- *
- * This intentionally checks only constructs that can be recognised without
- * pretending to be a complete Modula-2 parser: unterminated comments/strings,
- * module headers, and the final module END. The real syntax diagnostics will
- * be supplied by the Coco/R parser in the next stage.
- */
- function scan(document: vscode.TextDocument): ScanResult {
- const text = document.getText();
- const diagnostics: vscode.Diagnostic[] = [];
- let code = '';
- let i = 0;
- while (i < text.length) {
- const ch = text[i];
- const next = text[i + 1];
- if (ch === '(' && next === '*') {
- const start = i;
- i += 2;
- let closed = false;
- while (i < text.length) {
- if (text[i] === '*' && text[i + 1] === ')') {
- i += 2;
- closed = true;
- break;
- }
- i++;
- }
- if (!closed) {
- diagnostics.push(diagnostic(document, start, Math.min(start + 2, text.length), 'Unterminated Modula-2 comment.'));
- }
- code += ' ';
- continue;
- }
- if (ch === '"') {
- const start = i;
- i++;
- let closed = false;
- while (i < text.length) {
- if (text[i] === '\\') {
- i += 2;
- continue;
- }
- if (text[i] === '"') {
- i++;
- closed = true;
- break;
- }
- i++;
- }
- if (!closed) {
- diagnostics.push(diagnostic(document, start, Math.min(start + 1, text.length), 'Unterminated string literal.'));
- }
- code += ' ';
- continue;
- }
- code += ch;
- i++;
- }
- const header = MODULE_HEADER.exec(code);
- if (!header) {
- const isDefinition = document.languageId === 'modula2-definition';
- const expected = isDefinition ? 'DEFINITION MODULE' : 'MODULE or IMPLEMENTATION MODULE';
- diagnostics.push(diagnostic(document, 0, Math.min(code.length, 1), `Expected a ${expected} header.`));
- return { diagnostics, code };
- }
- const moduleName = header[3];
- const headerEnd = header.index + header[0].length;
- const semicolon = code.indexOf(';', headerEnd);
- if (semicolon < 0) {
- diagnostics.push(diagnostic(document, headerEnd, Math.min(headerEnd + moduleName.length, code.length), 'Expected ";" after the module header.'));
- }
- END_MODULE.lastIndex = 0;
- let match: RegExpExecArray | null = null;
- let lastEnd: RegExpExecArray | null = null;
- while ((match = END_MODULE.exec(code)) !== null) {
- lastEnd = match;
- }
- if (!lastEnd) {
- diagnostics.push(diagnostic(document, Math.max(0, code.trimEnd().length - 1), code.length, `Expected "END ${moduleName}." at the end of the module.`));
- } else {
- const endName = lastEnd[1];
- const nameStart = lastEnd.index + lastEnd[0].indexOf(endName);
- if (endName.toUpperCase() !== moduleName.toUpperCase()) {
- diagnostics.push(diagnostic(document, nameStart, nameStart + endName.length, `Module name "${moduleName}" expected after END, found "${endName}".`));
- }
- if (!lastEnd[2]) {
- const dot = lastEnd.index + lastEnd[0].length;
- diagnostics.push(diagnostic(document, dot, Math.min(dot + 1, code.length), `Expected "." after END ${endName}.`));
- }
- }
- return { diagnostics, code };
- }
- export function createDiagnostics(context: vscode.ExtensionContext): void {
- const collection = vscode.languages.createDiagnosticCollection('modula2');
- context.subscriptions.push(collection);
- const update = (document: vscode.TextDocument): void => {
- if (document.languageId !== 'modula2' && document.languageId !== 'modula2-definition') {
- return;
- }
- collection.set(document.uri, scan(document).diagnostics);
- };
- for (const document of vscode.workspace.textDocuments) {
- update(document);
- }
- context.subscriptions.push(
- vscode.workspace.onDidOpenTextDocument(update),
- vscode.workspace.onDidChangeTextDocument(event => update(event.document)),
- vscode.workspace.onDidCloseTextDocument(document => collection.delete(document.uri))
- );
- }
|