You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
299 lines
6.8 KiB
TypeScript
299 lines
6.8 KiB
TypeScript
type LiteralValue =
|
|
| { kind: 'int', value: number }
|
|
| { kind: 'float', value: number }
|
|
| { kind: 'string', value: string };
|
|
|
|
// Literals and Variables
|
|
|
|
export type Literal = {
|
|
kind: 'literal'
|
|
value: LiteralValue
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
export type Variable = {
|
|
kind: 'variable'
|
|
name: string
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
export type Constructor = {
|
|
kind: 'constructor'
|
|
name: string
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
// Functions
|
|
|
|
export type Lambda = {
|
|
kind: 'lambda'
|
|
params: string[]
|
|
body: AST
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
export type Apply = {
|
|
kind: 'apply'
|
|
func: AST
|
|
args: AST[]
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
// Bindings
|
|
|
|
export type Let = {
|
|
kind: 'let'
|
|
name: string
|
|
value: AST
|
|
body: AST
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
// Matching
|
|
|
|
export type Match = {
|
|
kind: 'match'
|
|
expr: AST
|
|
cases: MatchCase[]
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
export type MatchCase = {
|
|
pattern: Pattern
|
|
result: AST
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
export type Pattern =
|
|
| { kind: 'wildcard' }
|
|
| { kind: 'var', name: string }
|
|
| { kind: 'literal', value: number | string }
|
|
| { kind: 'constructor', name: string, args: Pattern[] }
|
|
| { kind: 'list', elements: Pattern[] }
|
|
| { kind: 'list-spread', head: Pattern[], spread: string }
|
|
| { kind: 'record', fields: { [key: string]: Pattern } }
|
|
|
|
// Data Structures
|
|
|
|
export type ListSpread = {
|
|
kind: 'list-spread'
|
|
spread: AST;
|
|
line?: number;
|
|
column?: number;
|
|
start?: number;
|
|
}
|
|
|
|
export type List = {
|
|
kind: 'list'
|
|
elements: (AST | ListSpread)[]
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
export type Record = {
|
|
kind: 'record'
|
|
entries: Array<
|
|
| { kind: 'field', key: string, value: AST }
|
|
| { kind: 'spread', expr: AST }
|
|
>
|
|
// fields: { [key: string]: AST }
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
export type RecordAccess = {
|
|
kind: 'record-access'
|
|
record: AST
|
|
field: string
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
export type RecordUpdate = {
|
|
kind: 'record-update'
|
|
record: AST
|
|
updates: { [key: string]: AST }
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
// Top-level constructs
|
|
|
|
export type Definition = {
|
|
kind: 'definition'
|
|
name: string
|
|
body: AST
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
export type Rebind = {
|
|
kind: 'rebind'
|
|
target: AST
|
|
value: AST
|
|
line?: number
|
|
column?: number
|
|
start?: number
|
|
}
|
|
|
|
export type AST =
|
|
| Literal
|
|
| Variable
|
|
| Constructor
|
|
| Lambda
|
|
| Apply
|
|
| Let
|
|
| Match
|
|
| Record
|
|
| RecordAccess
|
|
| RecordUpdate
|
|
| List
|
|
| ListSpread
|
|
| Definition
|
|
| Rebind
|
|
|
|
export function prettyPrint(ast: AST, indent = 0): string {
|
|
const i = ' '.repeat(indent);
|
|
|
|
switch (ast.kind) {
|
|
case 'literal': {
|
|
const val = ast.value;
|
|
if (val.kind === 'string') {
|
|
return `${i}"${val.value}"`;
|
|
}
|
|
return `${i}${val.value}`;
|
|
}
|
|
|
|
case 'variable':
|
|
case 'constructor':
|
|
return ast.name;
|
|
|
|
case 'apply':
|
|
const func = prettyPrint(ast.func, 0);
|
|
const args = ast.args.map(a => {
|
|
const printed = prettyPrint(a, 0);
|
|
if (a.kind === 'lambda' || a.kind === 'match' || a.kind === 'let' || a.kind === 'rebind') {
|
|
return `(${printed})`;
|
|
}
|
|
return printed;
|
|
}).join(' ');
|
|
|
|
return `(${func} ${args})`
|
|
|
|
case 'let':
|
|
return `${ast.name} = ${prettyPrint(ast.value, indent + 1)};\n${i}${prettyPrint(ast.body, indent)}`
|
|
|
|
case 'list':
|
|
const elems = ast.elements.map(e => prettyPrint(e, 0)).join(', ');
|
|
return `[${elems}]`;
|
|
|
|
case 'record':
|
|
const parts = ast.entries.map(entry =>
|
|
entry.kind === 'spread'
|
|
? `...${prettyPrint(entry.expr, )}`
|
|
: `${entry.key} = ${prettyPrint(entry.value, 0)}`
|
|
);
|
|
return `{ ${parts.join(', ')} }`;
|
|
|
|
case 'lambda': {
|
|
const params = ast.params.join(' ');
|
|
const body = prettyPrint(ast.body, indent + 1);
|
|
const isComplex = ast.body.kind === 'match' || ast.body.kind === 'let';
|
|
if (isComplex) {
|
|
return `${params} \\\n${body}`
|
|
|
|
}
|
|
return `${params} \\ ${body}`
|
|
}
|
|
|
|
case 'record-access':
|
|
return `${prettyPrint(ast.record, 0)}.${ast.field}`;
|
|
|
|
case 'record-update': {
|
|
const updates = Object.entries(ast.updates)
|
|
.map(([k, v]) => `${k} = ${prettyPrint(v, 0)}`)
|
|
.join(', ');
|
|
return `${prettyPrint(ast.record, 0)}.{ ${updates} }`
|
|
}
|
|
|
|
case 'match':
|
|
const expr = prettyPrint(ast.expr, 0);
|
|
const cases = ast.cases
|
|
.map(c => `${i}| ${prettyPrintPattern(c.pattern)} \\ ${prettyPrint(c.result, indent + 1)}`)
|
|
.join('\n');
|
|
return `${expr}\n${cases}`;
|
|
|
|
case 'rebind':
|
|
return `${prettyPrint(ast.target, 0)} := ${prettyPrint(ast.value, 0)}`;
|
|
|
|
case 'list-spread':
|
|
return `...${prettyPrint(ast.spread, 0)}`;
|
|
|
|
case 'definition':
|
|
return `${ast.name} = ${prettyPrint(ast.body, indent)};`;
|
|
|
|
default:
|
|
return `Unknown AST kind: ${i}${(ast as any).kind}`
|
|
}
|
|
}
|
|
|
|
function prettyPrintPattern(pattern: Pattern): string {
|
|
switch (pattern.kind) {
|
|
case 'wildcard':
|
|
return '_';
|
|
|
|
case 'var':
|
|
return pattern.name;
|
|
|
|
case 'literal':
|
|
return JSON.stringify(pattern.value);
|
|
|
|
case 'constructor':
|
|
if (pattern.args.length === 0) {
|
|
return pattern.name;
|
|
}
|
|
const args = pattern.args.map(prettyPrintPattern).join(' ');
|
|
return `(${pattern.name} ${args})`;
|
|
|
|
case 'list':
|
|
const elems = pattern.elements.map(prettyPrintPattern).join(', ');
|
|
return `[${elems}]`;
|
|
|
|
case 'list-spread':
|
|
const head = pattern.head.map(prettyPrintPattern).join(', ');
|
|
return head.length > 0
|
|
? `[${head}, ...${pattern.spread}]`
|
|
: `[...${pattern.spread}]`;
|
|
|
|
case 'record':
|
|
const fields = Object.entries(pattern.fields)
|
|
.map(([k, p]) => `${k} = ${prettyPrintPattern(p)}`)
|
|
.join(', ');
|
|
|
|
return `{${fields}}`;
|
|
default:
|
|
return `Unknown AST kind: ${(pattern as any).kind}`
|
|
}
|
|
}
|