mirror of
https://github.com/Noratrieb/riverdelta.git
synced 2026-01-16 17:35:02 +01:00
parse more stuff
This commit is contained in:
parent
4e95bc05a3
commit
cc2a9aeca8
5 changed files with 206 additions and 40 deletions
44
src/ast.ts
44
src/ast.ts
|
|
@ -13,16 +13,18 @@ export type FunctionDef = {
|
||||||
name: string;
|
name: string;
|
||||||
args: FunctionArg[];
|
args: FunctionArg[];
|
||||||
body: Expr;
|
body: Expr;
|
||||||
|
returnType?: Type;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FunctionArg = {
|
export type FunctionArg = {
|
||||||
name: string;
|
name: string;
|
||||||
|
type: Type;
|
||||||
span: Span;
|
span: Span;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ExprKind =
|
export type ExprKind =
|
||||||
| { kind: "empty" }
|
| { kind: "empty" }
|
||||||
| { kind: "let"; name: string; rhs: Expr; after: Expr }
|
| { kind: "let"; name: string; type?: Type; rhs: Expr; after: Expr }
|
||||||
| { kind: "block"; exprs: Expr[] }
|
| { kind: "block"; exprs: Expr[] }
|
||||||
| {
|
| {
|
||||||
kind: "literal";
|
kind: "literal";
|
||||||
|
|
@ -39,14 +41,20 @@ export type ExprKind =
|
||||||
rhs: Expr;
|
rhs: Expr;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
kind: "unary",
|
kind: "unary";
|
||||||
unaryKind: UnaryKind,
|
unaryKind: UnaryKind;
|
||||||
rhs: Expr,
|
rhs: Expr;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
kind: "call",
|
kind: "call";
|
||||||
lhs: Expr,
|
lhs: Expr;
|
||||||
args: Expr[],
|
args: Expr[];
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
kind: "if";
|
||||||
|
cond: Expr;
|
||||||
|
then: Expr;
|
||||||
|
else?: Expr;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Expr = ExprKind & {
|
export type Expr = ExprKind & {
|
||||||
|
|
@ -112,5 +120,23 @@ export function binaryExprPrecedenceClass(k: BinaryKind): number {
|
||||||
return cls;
|
return cls;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UnaryKind = '!' | '-';
|
export type UnaryKind = "!" | "-";
|
||||||
export const UNARY_KINDS: UnaryKind[] = ['!', '-'];
|
export const UNARY_KINDS: UnaryKind[] = ["!", "-"];
|
||||||
|
|
||||||
|
export type TypeKind =
|
||||||
|
| {
|
||||||
|
kind: "ident";
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
kind: "list";
|
||||||
|
elem: Type;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
kind: "tuple";
|
||||||
|
elems: Type[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Type = TypeKind & {
|
||||||
|
span: Span;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,13 @@ import { parse } from "./parser";
|
||||||
import { printAst } from "./printer";
|
import { printAst } from "./printer";
|
||||||
|
|
||||||
const input = `
|
const input = `
|
||||||
function main() = (
|
function main(argv: [String]): uwu = (
|
||||||
print("Hello, world!");
|
print("Hello, world!");
|
||||||
"uwu";
|
let a: [String] = 0 in
|
||||||
|
let b = 1 in
|
||||||
|
if 0 then 0 else (
|
||||||
|
if 1 == 1 then 1 else "what"
|
||||||
|
;"meow")
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
||||||
18
src/lexer.ts
18
src/lexer.ts
|
|
@ -4,9 +4,15 @@ export type DatalessToken =
|
||||||
| "function"
|
| "function"
|
||||||
| "let"
|
| "let"
|
||||||
| "in"
|
| "in"
|
||||||
|
| "if"
|
||||||
|
| "then"
|
||||||
|
| "else"
|
||||||
| "("
|
| "("
|
||||||
| ")"
|
| ")"
|
||||||
|
| "["
|
||||||
|
| "]"
|
||||||
| ";"
|
| ";"
|
||||||
|
| ":"
|
||||||
| ","
|
| ","
|
||||||
| "="
|
| "="
|
||||||
| "+"
|
| "+"
|
||||||
|
|
@ -47,7 +53,10 @@ export type BaseToken = { kind: Token["kind"] };
|
||||||
const SINGLE_PUNCT: string[] = [
|
const SINGLE_PUNCT: string[] = [
|
||||||
"(",
|
"(",
|
||||||
")",
|
")",
|
||||||
|
"[",
|
||||||
|
"]",
|
||||||
";",
|
";",
|
||||||
|
":",
|
||||||
",",
|
",",
|
||||||
"+",
|
"+",
|
||||||
"-",
|
"-",
|
||||||
|
|
@ -193,7 +202,14 @@ function isWhitespace(char: string): boolean {
|
||||||
return char === " " || char === "\t" || char === "\n" || char === "\r";
|
return char === " " || char === "\t" || char === "\n" || char === "\r";
|
||||||
}
|
}
|
||||||
|
|
||||||
const keywords = new Set<string>(["function", "let", "in"]);
|
const keywords = new Set<string>([
|
||||||
|
"function",
|
||||||
|
"let",
|
||||||
|
"in",
|
||||||
|
"if",
|
||||||
|
"then",
|
||||||
|
"else",
|
||||||
|
]);
|
||||||
function isKeyword(kw: string): DatalessToken | undefined {
|
function isKeyword(kw: string): DatalessToken | undefined {
|
||||||
return keywords.has(kw) ? (kw as DatalessToken) : undefined;
|
return keywords.has(kw) ? (kw as DatalessToken) : undefined;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
130
src/parser.ts
130
src/parser.ts
|
|
@ -4,13 +4,15 @@ import {
|
||||||
BinaryKind,
|
BinaryKind,
|
||||||
COMPARISON_KINDS,
|
COMPARISON_KINDS,
|
||||||
Expr,
|
Expr,
|
||||||
|
FunctionArg,
|
||||||
FunctionDef,
|
FunctionDef,
|
||||||
Item,
|
Item,
|
||||||
LOGICAL_KINDS,
|
LOGICAL_KINDS,
|
||||||
|
Type,
|
||||||
UNARY_KINDS,
|
UNARY_KINDS,
|
||||||
UnaryKind,
|
UnaryKind,
|
||||||
} from "./ast";
|
} from "./ast";
|
||||||
import { CompilerError, todo } from "./error";
|
import { CompilerError, Span, todo } from "./error";
|
||||||
import { BaseToken, Token, TokenIdent } from "./lexer";
|
import { BaseToken, Token, TokenIdent } from "./lexer";
|
||||||
|
|
||||||
type Parser<T> = (t: Token[]) => [Token[], T];
|
type Parser<T> = (t: Token[]) => [Token[], T];
|
||||||
|
|
@ -35,7 +37,33 @@ function parseItem(t: Token[]): [Token[], Item] {
|
||||||
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
||||||
|
|
||||||
[t] = expectNext(t, "(");
|
[t] = expectNext(t, "(");
|
||||||
|
|
||||||
|
const args: FunctionArg[] = [];
|
||||||
|
let first = true;
|
||||||
|
while (next(t)[1]?.kind !== ")") {
|
||||||
|
if (!first) {
|
||||||
|
[t] = expectNext(t, ",");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
|
||||||
|
let name;
|
||||||
|
[t, name] = expectNext<TokenIdent & { span: Span }>(t, "identifier");
|
||||||
|
[t] = expectNext(t, ":");
|
||||||
|
let type;
|
||||||
|
[t, type] = parseType(t);
|
||||||
|
|
||||||
|
args.push({ name: name.ident, type, span: name.span });
|
||||||
|
}
|
||||||
|
|
||||||
[t] = expectNext(t, ")");
|
[t] = expectNext(t, ")");
|
||||||
|
|
||||||
|
let colon;
|
||||||
|
let returnType = undefined;
|
||||||
|
[t, colon] = eat(t, ":");
|
||||||
|
if (colon) {
|
||||||
|
[t, returnType] = parseType(t);
|
||||||
|
}
|
||||||
|
|
||||||
[t] = expectNext(t, "=");
|
[t] = expectNext(t, "=");
|
||||||
|
|
||||||
let body;
|
let body;
|
||||||
|
|
@ -45,7 +73,8 @@ function parseItem(t: Token[]): [Token[], Item] {
|
||||||
|
|
||||||
const def: FunctionDef = {
|
const def: FunctionDef = {
|
||||||
name: name.ident,
|
name: name.ident,
|
||||||
args: [],
|
args,
|
||||||
|
returnType,
|
||||||
body,
|
body,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -57,7 +86,10 @@ function parseItem(t: Token[]): [Token[], Item] {
|
||||||
|
|
||||||
function parseExpr(t: Token[]): [Token[], Expr] {
|
function parseExpr(t: Token[]): [Token[], Expr] {
|
||||||
/*
|
/*
|
||||||
EXPR = { "let" NAME "=" EXPR "in" EXPR | COMPARISON }
|
EXPR = { LET | COMPARISON | IF }
|
||||||
|
|
||||||
|
LET = "let" NAME { ":" TYPE } "=" EXPR "in" EXPR
|
||||||
|
IF = "if" EXPR "then" EXPR { "else" EXPR }
|
||||||
|
|
||||||
// The precende here is pretty arbitrary since we forbid mixing of operators
|
// The precende here is pretty arbitrary since we forbid mixing of operators
|
||||||
// with different precedence classes anyways.
|
// with different precedence classes anyways.
|
||||||
|
|
@ -79,17 +111,46 @@ function parseExpr(t: Token[]): [Token[], Expr] {
|
||||||
const [, peak] = next(t);
|
const [, peak] = next(t);
|
||||||
|
|
||||||
if (peak.kind === "let") {
|
if (peak.kind === "let") {
|
||||||
[t] = next(t);
|
[t] = expectNext(t, "let");
|
||||||
let name;
|
let name;
|
||||||
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
[t, name] = expectNext<TokenIdent>(t, "identifier");
|
||||||
expectNext(t, "=");
|
|
||||||
|
let type = undefined;
|
||||||
|
let colon;
|
||||||
|
[t, colon] = eat(t, ":");
|
||||||
|
if (colon) {
|
||||||
|
[t, type] = parseType(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
[t] = expectNext(t, "=");
|
||||||
let rhs;
|
let rhs;
|
||||||
[t, rhs] = parseExpr(t);
|
[t, rhs] = parseExpr(t);
|
||||||
expectNext(t, "in");
|
[t] = expectNext(t, "in");
|
||||||
let after;
|
let after;
|
||||||
[t, after] = parseExpr(t);
|
[t, after] = parseExpr(t);
|
||||||
|
|
||||||
return [t, { kind: "let", name: name.ident, rhs, after, span: t[0].span }];
|
return [
|
||||||
|
t,
|
||||||
|
{ kind: "let", name: name.ident, type, rhs, after, span: t[0].span },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (peak.kind === "if") {
|
||||||
|
[t] = expectNext(t, "if");
|
||||||
|
let cond;
|
||||||
|
[t, cond] = parseExpr(t);
|
||||||
|
[t] = expectNext(t, "then");
|
||||||
|
let then;
|
||||||
|
[t, then] = parseExpr(t);
|
||||||
|
|
||||||
|
let elseTok;
|
||||||
|
[t, elseTok] = eat(t, "else");
|
||||||
|
let elsePart = undefined;
|
||||||
|
if (elseTok) {
|
||||||
|
[t, elsePart] = parseExpr(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [t, { kind: "if", cond, then, else: elsePart, span: peak.span }];
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseExprComparison(t);
|
return parseExprComparison(t);
|
||||||
|
|
@ -227,6 +288,45 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
|
||||||
return [startT, { kind: "empty", span: tok.span }];
|
return [startT, { kind: "empty", span: tok.span }];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseType(t: Token[]): [Token[], Type] {
|
||||||
|
let tok;
|
||||||
|
[t, tok] = next(t);
|
||||||
|
|
||||||
|
switch (tok.kind) {
|
||||||
|
case "identifier": {
|
||||||
|
return [t, { kind: "ident", value: tok.ident, span: tok.span }];
|
||||||
|
}
|
||||||
|
case "[": {
|
||||||
|
let elem;
|
||||||
|
[t, elem] = parseType(t);
|
||||||
|
[t] = expectNext(t, "]");
|
||||||
|
return [t, { kind: "list", elem, span: tok.span }];
|
||||||
|
}
|
||||||
|
case "(": {
|
||||||
|
let first = true;
|
||||||
|
const elems = [];
|
||||||
|
while (next(t)[1]?.kind !== ")") {
|
||||||
|
if (!first) {
|
||||||
|
[t] = expectNext(t, ",");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
let type;
|
||||||
|
[t, type] = parseType(t);
|
||||||
|
elems.push(type);
|
||||||
|
}
|
||||||
|
[t] = expectNext(t, ")");
|
||||||
|
|
||||||
|
return [t, { kind: "tuple", elems, span: tok.span }];
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new CompilerError(
|
||||||
|
`unexpected token: \`${tok.kind}\`, expected type`,
|
||||||
|
tok.span
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
function eat<T extends BaseToken>(
|
function eat<T extends BaseToken>(
|
||||||
|
|
@ -246,8 +346,10 @@ function expectNext<T extends BaseToken>(
|
||||||
): [Token[], T] {
|
): [Token[], T] {
|
||||||
let tok;
|
let tok;
|
||||||
[t, tok] = next(t);
|
[t, tok] = next(t);
|
||||||
const token = expectToken(kind, tok);
|
if (tok.kind !== kind) {
|
||||||
return [t, token];
|
throw new CompilerError(`expected ${kind}, found ${tok.kind}`, tok.span);
|
||||||
|
}
|
||||||
|
return [t, tok as unknown as T];
|
||||||
}
|
}
|
||||||
|
|
||||||
function next(t: Token[]): [Token[], Token] {
|
function next(t: Token[]): [Token[], Token] {
|
||||||
|
|
@ -270,13 +372,3 @@ function maybeNextT(t: Token[]): [Token[], Token | undefined] {
|
||||||
function unexpectedToken(token: Token): never {
|
function unexpectedToken(token: Token): never {
|
||||||
throw new CompilerError("unexpected token", token.span);
|
throw new CompilerError("unexpected token", token.span);
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectToken<T extends BaseToken>(kind: T["kind"], token: Token): T {
|
|
||||||
if (token.kind !== kind) {
|
|
||||||
throw new CompilerError(
|
|
||||||
`expected ${kind}, found ${token.kind}`,
|
|
||||||
token.span
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return token as unknown as T;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Expr, FunctionDef, Item } from "./ast";
|
import { Expr, FunctionDef, Item, Type } from "./ast";
|
||||||
|
|
||||||
export function printAst(ast: Item[]): string {
|
export function printAst(ast: Item[]): string {
|
||||||
return ast.map(printItem).join("\n");
|
return ast.map(printItem).join("\n");
|
||||||
|
|
@ -13,8 +13,11 @@ function printItem(item: Item): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
function printFunction(func: FunctionDef): string {
|
function printFunction(func: FunctionDef): string {
|
||||||
const args = func.args.map(({ name }) => name).join(", ");
|
const args = func.args
|
||||||
return `function ${func.name}(${args}) = ${printExpr(func.body, 0)}`;
|
.map(({ name, type }) => `${name}: ${printType(type)}`)
|
||||||
|
.join(", ");
|
||||||
|
const ret = func.returnType ? `: ${printType(func.returnType)}` : "";
|
||||||
|
return `function ${func.name}(${args})${ret} = ${printExpr(func.body, 0)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function printExpr(expr: Expr, indent: number): string {
|
function printExpr(expr: Expr, indent: number): string {
|
||||||
|
|
@ -23,9 +26,12 @@ function printExpr(expr: Expr, indent: number): string {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
case "let": {
|
case "let": {
|
||||||
return `let ${expr.name} = ${printExpr(expr.rhs, 1)} in${linebreak(
|
const type = expr.type ? `: ${printType(expr.type)}` : "";
|
||||||
indent
|
|
||||||
)}`;
|
return `let ${expr.name}${type} = ${printExpr(
|
||||||
|
expr.rhs,
|
||||||
|
indent + 1
|
||||||
|
)} in${linebreak(indent)}${printExpr(expr.after, indent)}`;
|
||||||
}
|
}
|
||||||
case "block": {
|
case "block": {
|
||||||
const exprs = expr.exprs.map((expr) => printExpr(expr, indent + 1));
|
const exprs = expr.exprs.map((expr) => printExpr(expr, indent + 1));
|
||||||
|
|
@ -35,8 +41,10 @@ function printExpr(expr: Expr, indent: number): string {
|
||||||
}
|
}
|
||||||
const shortExprs =
|
const shortExprs =
|
||||||
exprs.map((s) => s.length).reduce((a, b) => a + b, 0) < 40;
|
exprs.map((s) => s.length).reduce((a, b) => a + b, 0) < 40;
|
||||||
|
|
||||||
|
const alreadyHasTrailingSpace =
|
||||||
|
expr.exprs[exprs.length - 1]?.kind === "empty";
|
||||||
if (shortExprs) {
|
if (shortExprs) {
|
||||||
const alreadyHasTrailingSpace = expr.exprs[exprs.length - 1]?.kind === "empty";
|
|
||||||
const trailingSpace = alreadyHasTrailingSpace ? "" : " ";
|
const trailingSpace = alreadyHasTrailingSpace ? "" : " ";
|
||||||
return `( ${exprs.join("; ")}${trailingSpace})`;
|
return `( ${exprs.join("; ")}${trailingSpace})`;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -84,6 +92,26 @@ function printExpr(expr: Expr, indent: number): string {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "if": {
|
||||||
|
const elsePart = expr.else
|
||||||
|
? ` else ${printExpr(expr.else, indent + 1)}`
|
||||||
|
: "";
|
||||||
|
return `if ${printExpr(expr.cond, indent + 1)} then ${printExpr(
|
||||||
|
expr.then,
|
||||||
|
indent + 1
|
||||||
|
)}${elsePart}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printType(type: Type): string {
|
||||||
|
switch (type.kind) {
|
||||||
|
case "ident":
|
||||||
|
return type.value;
|
||||||
|
case "list":
|
||||||
|
return `[${printType(type.elem)}]`;
|
||||||
|
case "tuple":
|
||||||
|
return `(${type.elems.map(printType).join(", ")})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue