1. 先写 diagnostic.ts 的核心模块
对照你原来的 diagnostic.ts 和 issue 里的模板,你需要做以下几件事:
1. 先写 diagnostic.ts 的核心模块
issue 里引用了 diagnostic.infer_expr 和 diagnostic.context,说明你需要一个独立的诊断模块,负责遍历 AST 收集错误。对你的 utlc 项目来说,错误来源主要是 parse 阶段:
// diagnostic.ts
import { parser, syntax, token } from "../sexp";
import { exp } from "./parse_expr";
export type error = { range: range; message: string };
type position = { line: number; character: number };
type range = { start: position; end: position };
export type context = {
report: error[];
text: string;
};
const get_range = (syn: syntax): range => {
// 和 parse_expr 里一样,从 syntax 节点取位置
switch (syn.tag) {
case syntax.tag.atom:
case syntax.tag.lone: {
const leaf = (syn as syntax.atom).leaf;
return token.to_range(leaf);
}
case syntax.tag.group:
case syntax.tag.mismatch: {
const children = (syn as syntax.group).children;
const first = children[0]! as syntax.atom;
const last = children[children.length - 1]! as syntax.atom;
return {
start: token.to_start_position(first.leaf),
end: token.to_end_position(last.leaf),
};
}
}
};
export const infer_expr = (ctx: context, syn: syntax): exp | undefined => {
try {
return exp.parse(syn, ctx.text);
} catch {
ctx.report.push({ range: get_range(syn), message: "Parse error" });
return undefined;
}
};
2. 然后写 vscode_diagnostic.ts(或直接叫 diagnostic_provider.ts)
直接套 issue 的模板,把 diagnostic.infer_expr 换成上面写的:
import { parser } from "../sexp";
import * as vscode from "vscode";
import { Diagnostic, Range, Position, DiagnosticSeverity as Severity } from "vscode";
import * as diagnostic from "./diagnostic";
export { make };
type Provider = (event: vscode.TextDocumentChangeEvent) => void;
enum Scheme {
file = "file",
output = "output",
}
type error = diagnostic.error;
const error: {
readonly to_diagnostic: (self: error) => Diagnostic;
} = {
to_diagnostic: self => {
let { range: { start, end }, message } = self;
const pstart = new Position(start.line, start.character);
const pend = new Position(end.line, end.character);
const range = new Range(pstart, pend);
return new Diagnostic(range, message, Severity.Error);
},
};
const handler = (
out: vscode.OutputChannel,
event: vscode.TextDocumentChangeEvent,
dia: vscode.DiagnosticCollection,
): void => {
const text = event.document.getText();
const prs = parser.make(text);
const syns = parser.parse(prs);
const ctx: diagnostic.context = { report: [], text };
if (syns.length > 0) {
diagnostic.infer_expr(ctx, syns[0]!);
}
dia.set(event.document.uri, ctx.report.map(error.to_diagnostic));
};
const manager = vscode.languages.createDiagnosticCollection("utlc");
const make = (out: vscode.OutputChannel): Provider => {
return e => {
if (e.contentChanges.length === 0) return;
if (e.document.uri.scheme === Scheme.output) return;
if (e.document.languageId !== "utlc") return;
handler(out, e, manager);
};
};
3. 最后更新 extension.ts
照 issue 的模板直接加一行:
import * as diagnostic from "./vscode_diagnostic"; // 或你命名的文件
const activate = (context: vscode.ExtensionContext) => {
let out = vscode.window.createOutputChannel("utlc", "log");
// ... 已有的 cmpl 等
let diag = vscode.workspace.onDidChangeTextDocument(diagnostic.make(out));
context.subscriptions.push(cmpl, diag, out);
};
关键设计决策
你需要确认一点:错误粒度。现在的方案是整个表达式 parse 失败才报一个错,位置指向顶层节点。如果你希望做到子表达式级别的精确报错(比如 (lambda) 缺 body 时精确指向括号内部),那就需要把 parse_expr.ts 里的 impossible 改成带 range 的错误类型,然后在 infer_expr 里 catch 后取出精确位置。
你现在想要哪种粒度?
评论