syntax-lab/src/languages/lisp/syntax.ts
2026-04-25 17:44:05 +02:00

152 lines
4.7 KiB
TypeScript

import type { CodePointSpan } from 'source-region';
import type { ParseError } from './parse_errors';
export type ConcreteInfo = { span: CodePointSpan };
export type ConcreteError = ConcreteErrorNode[] // Convention: can't be empty.
export type ConcreteErrorNode = {
span: CodePointSpan,
error: ParseError,
panickedOver?: CodePointSpan,
}
export namespace ConcreteError {
export function single(node: ConcreteErrorNode): ConcreteError {
return [node];
}
}
export type DelimiterToken =
| { tag: "open-paren", span: CodePointSpan }
| { tag: "close-paren", span: CodePointSpan }
| { tag: "open-bracket", span: CodePointSpan }
| { tag: "close-bracket", span: CodePointSpan }
export namespace DelimiterToken {
export function openParen(span: CodePointSpan): DelimiterToken {
return { tag: "open-paren", span };
}
export function closeParen(span: CodePointSpan): DelimiterToken {
return { tag: "close-paren", span };
}
export function openBracket(span: CodePointSpan): DelimiterToken {
return { tag: "open-bracket", span };
}
export function closeBracket(span: CodePointSpan): DelimiterToken {
return { tag: "close-bracket", span };
}
}
export type Program<Info, Error> = {
tag: "program",
expressions: Expr<Info, Error>[],
error?: Error,
} & Info
export type Expr<Info, Error> =
| Literal<Info, Error>
| List<Info, Error>
| { tag: "error-expression", error: Error } & Info // This is for errors that don't really correspond to any sort of node. Unknown errors.
export type List<Info, Error> =
{ tag: "list", open: DelimiterToken, items: ListItem<Info, Error>[], close?: DelimiterToken, error?: Error } & Info
export type ListItem<Info, Error> =
| Expr<Info, Error>
| { tag: "error-list-separator", error: Error } & Info
export type Literal<Info, Error> =
// === number ===
| { tag: "number", value: number } & Info
| { tag: "error-number", error: Error } & Info
// === identifier ===
| { tag: "identifier", value: Identifier } & Info
| { tag: "error-identifier", error: Error } & Info
export type Identifier = string
export namespace Program {
export function make<Info, Error>(
expressions: Expr<Info, Error>[],
info: Info,
error?: Error,
): Program<Info, Error> {
return error === undefined
? { tag: "program", expressions, ...info }
: { tag: "program", expressions, error, ...info };
}
export function show<Info, Error>(program: Program<Info, Error>): string {
return program.expressions.map(Expr.show).join(" ");
}
}
export namespace Expr {
export function number(value: number, span: CodePointSpan): Expr<ConcreteInfo, ConcreteError> {
return { tag: "number", value, span };
}
export function errorNumber(error: ConcreteError, span: CodePointSpan): Expr<ConcreteInfo, ConcreteError> {
return { tag: "error-number", error, span };
}
export function identifier(value: Identifier, span: CodePointSpan): Expr<ConcreteInfo, ConcreteError> {
return { tag: "identifier", value, span };
}
export function errorIdentifier(error: ConcreteError, span: CodePointSpan): Expr<ConcreteInfo, ConcreteError> {
return { tag: "error-identifier", error, span };
}
export function list(
open: DelimiterToken,
items: ListItem<ConcreteInfo, ConcreteError>[],
span: CodePointSpan,
close?: DelimiterToken,
error?: ConcreteError,
): Expr<ConcreteInfo, ConcreteError> {
return { tag: "list", open, items, close, error, span };
}
export function errorExpression(error: ConcreteError, span: CodePointSpan): Expr<ConcreteInfo, ConcreteError> {
return { tag: "error-expression", error, span };
}
export function show<Info, Error>(expr: Expr<Info, Error>): string {
switch (expr.tag) {
case "number":
return `${expr.value}`;
case "identifier":
return expr.value;
case "error-number":
return "<error-number>";
case "error-identifier":
return "<error-identifier>";
case "error-expression":
return "<error-expression>";
case "list":
return showList(expr);
}
}
function showList<Info, Error>(list: List<Info, Error>): string {
const open = list.open.tag === "open-bracket" ? "[" : "(";
const close = list.open.tag === "open-bracket" ? "]" : ")";
const sep = list.open.tag === "open-bracket" ? ", " : " ";
return `${open}${list.items.map(ListItem.show).join(sep)}${close}`;
}
}
export namespace ListItem {
export function errorSeparator(error: ConcreteError, span: CodePointSpan): ListItem<ConcreteInfo, ConcreteError> {
return { tag: "error-list-separator", error, span };
}
export function show<Info, Error>(item: ListItem<Info, Error>): string {
if (item.tag === "error-list-separator") return "<error-separator>";
return Expr.show(item);
}
}