141 lines
5.3 KiB
TypeScript
141 lines
5.3 KiB
TypeScript
import { ParseError } from "src/lang/parser/parser";
|
|
import { renderSpan, SourceText } from "src/lang/parser/source_text";
|
|
import { DisplayLineViews } from "./LineView";
|
|
|
|
export function formatErrorMesage(err: ParseError): string {
|
|
switch (err.tag) {
|
|
case "UnexpectedCharacter":
|
|
return `Unexpected character: ${formatChar(err.char)}`;
|
|
|
|
case "UnexpectedEOF":
|
|
return "Unexpected end of file.";
|
|
|
|
case "ExpectedNumber":
|
|
return "Expected a number here.";
|
|
|
|
case "InvalidNumber":
|
|
switch (err.reason) {
|
|
case "NotFinite":
|
|
return "Number is too large or invalid.";
|
|
case "MissingFractionalDigits":
|
|
return "Invalid number format (missing fractional digits?).";
|
|
}
|
|
|
|
case "InvalidEscape":
|
|
switch (err.reason.tag) {
|
|
case "UnknownEscapeSequence": return `Unknown escape sequence: \\${formatChar(err.reason.char)}`;
|
|
case "UnicodeMissingBrace": return "Unicode escape missing opening brace '{'.";
|
|
case "UnicodeNoDigits": return "Unicode escape missing hex digits.";
|
|
case "UnicodeUnclosed": return "Unicode escape missing closing brace '}'.";
|
|
case "UnicodeOverflow": return `Unicode code point ${err.reason.value.toString(16)} is out of bounds.`;
|
|
}
|
|
|
|
case "InvalidIdentifier": {
|
|
let identifierKind = "";
|
|
switch (err.kind) {
|
|
case "variable_use":
|
|
identifierKind = "variable name";
|
|
break;
|
|
case "field_name":
|
|
identifierKind = "field name ";
|
|
break;
|
|
case "tag_construction":
|
|
identifierKind = "tag";
|
|
break;
|
|
case "function_call":
|
|
identifierKind = "function name";
|
|
break;
|
|
case "signal_read":
|
|
identifierKind = "signal name";
|
|
break;
|
|
case "pattern_binding":
|
|
identifierKind = "pattern variable";
|
|
break;
|
|
}
|
|
|
|
let reason = "";
|
|
switch (err.reason.tag) {
|
|
case "Empty":
|
|
reason = "It's empty";
|
|
break;
|
|
case "StartsWithDigit":
|
|
reason = "Can't start with a digit"
|
|
break;
|
|
case "IsKeyword":
|
|
reason = "I'ts a keyword";
|
|
break;
|
|
}
|
|
return `Invalid ${identifierKind} '${err.text}' ${reason}.`;
|
|
}
|
|
|
|
case "UnexpectedIdentifier":
|
|
return `Unexpected identifier encountered '${err.identifier}'`;
|
|
|
|
case "UnexpectedToken":
|
|
return `Unexpected token. Expected: ${err.expected}`;
|
|
|
|
case "UnexpectedTokenWhileParsingSequence":
|
|
return `Unexpected token in sequence. Expected delimiter ${formatChar(err.expectedDelimiter)} or terminator ${formatChar(err.expectedTerminator)}, but found ${formatChar(err.received)}.`;
|
|
|
|
// Context specific errors
|
|
case "ExpectedExpression": return "Expected an expression here.";
|
|
case "ExpectedSignalExpression": return "Expected a signal expression here.";
|
|
case "ExpectedFieldAssignmentSymbol": return "Expected '=' for field assignment.";
|
|
case "ExpectedPatternAssignmentSymbol": return "Expected '=' for pattern assignment.";
|
|
case "ExpectedPatternBindingSymbol": return "Expected '.' for pattern binding.";
|
|
case "ExpectedFunctionCallStart": return "Expected '(' to start function call.";
|
|
case "ExpectedRecordOpen": return "Expected '(' to start record.";
|
|
case "ExpectedLetBlockOpen": return "Expected '{' to start let-block.";
|
|
case "ExpectedLetBlockClose": return "Expected '}' to close let-block.";
|
|
case "ExpectedLetSignalBlockOpen": return "Expected '{' to start let-signal-block.";
|
|
case "ExpectedLetSignalBlockClose": return "Expected '}' to close let-signal-block.";
|
|
case "ExpectedMatchBlockOpen": return "Expected '{' to start match-block.";
|
|
case "ExpectedMatchBlockClose": return "Expected '}' to close match-block.";
|
|
case "ExpectedLambdaBlockOpen": return "Expected '{' to start lambda body.";
|
|
case "ExpectedLambdaBlockClose": return "Expected '}' to close lambda body.";
|
|
case "ExpectedApplyStart": return "Expected '(' after 'apply'.";
|
|
case "ExpectedApplySeparator": return "Expected '!' inside 'apply'.";
|
|
case "UnexpectedTagPattern": return "Unexpected tag pattern (expected product pattern).";
|
|
case "ExpectedPattern": return "Expected a pattern here.";
|
|
case "ExpectedRecordPatternOpen": return "Expected a ':(' at start of record pattern here.";
|
|
case "ExpectedRecordField": return "Expected a field name in record pattern.";
|
|
}
|
|
}
|
|
|
|
// Helper to safely print code points (handling special chars like \n)
|
|
function formatChar(cp: number | undefined): string {
|
|
// Handle EOF (undefined) or invalid numbers safely
|
|
if (cp === undefined || Number.isNaN(cp)) {
|
|
return "EOF";
|
|
}
|
|
|
|
const s = String.fromCodePoint(cp);
|
|
|
|
if (s === '\n') return "\\n";
|
|
if (s === '\r') return "\\r";
|
|
if (s === '\t') return "\\t";
|
|
|
|
return `'${s}'`;
|
|
}
|
|
|
|
export function ShowParseError(props: { text: SourceText, err: ParseError }) {
|
|
const msg = () => formatErrorMesage(props.err);
|
|
const views = () => renderSpan(props.text, props.err.span, 3);
|
|
|
|
|
|
// Parse Error: Expected '(' to start function call.
|
|
// 1 | +(23, x)
|
|
// ^
|
|
|
|
return (
|
|
<div>
|
|
<div>
|
|
<span style={{ color: "#ff5555", "font-weight": "bold" }}>Parse Error: </span>
|
|
<span style={{ "font-weight": "bold" }}>{msg()}</span>
|
|
</div>
|
|
|
|
<DisplayLineViews views={views()} />
|
|
</div>
|
|
);
|
|
}
|
|
|