parse more stuff

This commit is contained in:
nora 2023-07-23 14:39:09 +02:00
parent 4e95bc05a3
commit cc2a9aeca8
5 changed files with 206 additions and 40 deletions

View file

@ -13,16 +13,18 @@ export type FunctionDef = {
name: string;
args: FunctionArg[];
body: Expr;
returnType?: Type;
};
export type FunctionArg = {
name: string;
type: Type;
span: Span;
};
export type ExprKind =
| { 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: "literal";
@ -39,15 +41,21 @@ export type ExprKind =
rhs: Expr;
}
| {
kind: "unary",
unaryKind: UnaryKind,
rhs: Expr,
}
kind: "unary";
unaryKind: UnaryKind;
rhs: Expr;
}
| {
kind: "call",
lhs: Expr,
args: Expr[],
};
kind: "call";
lhs: Expr;
args: Expr[];
}
| {
kind: "if";
cond: Expr;
then: Expr;
else?: Expr;
};
export type Expr = ExprKind & {
span: Span;
@ -112,5 +120,23 @@ export function binaryExprPrecedenceClass(k: BinaryKind): number {
return cls;
}
export type UnaryKind = '!' | '-';
export const UNARY_KINDS: UnaryKind[] = ['!', '-'];
export type 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;
};

View file

@ -4,9 +4,13 @@ import { parse } from "./parser";
import { printAst } from "./printer";
const input = `
function main() = (
function main(argv: [String]): uwu = (
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")
);
`;

View file

@ -4,9 +4,15 @@ export type DatalessToken =
| "function"
| "let"
| "in"
| "if"
| "then"
| "else"
| "("
| ")"
| "["
| "]"
| ";"
| ":"
| ","
| "="
| "+"
@ -47,7 +53,10 @@ export type BaseToken = { kind: Token["kind"] };
const SINGLE_PUNCT: string[] = [
"(",
")",
"[",
"]",
";",
":",
",",
"+",
"-",
@ -193,7 +202,14 @@ function isWhitespace(char: string): boolean {
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 {
return keywords.has(kw) ? (kw as DatalessToken) : undefined;
}

View file

@ -4,13 +4,15 @@ import {
BinaryKind,
COMPARISON_KINDS,
Expr,
FunctionArg,
FunctionDef,
Item,
LOGICAL_KINDS,
Type,
UNARY_KINDS,
UnaryKind,
} from "./ast";
import { CompilerError, todo } from "./error";
import { CompilerError, Span, todo } from "./error";
import { BaseToken, Token, TokenIdent } from "./lexer";
type Parser<T> = (t: Token[]) => [Token[], T];
@ -35,7 +37,33 @@ function parseItem(t: Token[]): [Token[], Item] {
[t, name] = expectNext<TokenIdent>(t, "identifier");
[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, ")");
let colon;
let returnType = undefined;
[t, colon] = eat(t, ":");
if (colon) {
[t, returnType] = parseType(t);
}
[t] = expectNext(t, "=");
let body;
@ -45,7 +73,8 @@ function parseItem(t: Token[]): [Token[], Item] {
const def: FunctionDef = {
name: name.ident,
args: [],
args,
returnType,
body,
};
@ -57,7 +86,10 @@ function parseItem(t: Token[]): [Token[], Item] {
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
// with different precedence classes anyways.
@ -79,17 +111,46 @@ function parseExpr(t: Token[]): [Token[], Expr] {
const [, peak] = next(t);
if (peak.kind === "let") {
[t] = next(t);
[t] = expectNext(t, "let");
let name;
[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;
[t, rhs] = parseExpr(t);
expectNext(t, "in");
[t] = expectNext(t, "in");
let after;
[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);
@ -227,6 +288,45 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
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
function eat<T extends BaseToken>(
@ -246,8 +346,10 @@ function expectNext<T extends BaseToken>(
): [Token[], T] {
let tok;
[t, tok] = next(t);
const token = expectToken(kind, tok);
return [t, token];
if (tok.kind !== kind) {
throw new CompilerError(`expected ${kind}, found ${tok.kind}`, tok.span);
}
return [t, tok as unknown as T];
}
function next(t: Token[]): [Token[], Token] {
@ -270,13 +372,3 @@ function maybeNextT(t: Token[]): [Token[], Token | undefined] {
function unexpectedToken(token: Token): never {
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;
}

View file

@ -1,4 +1,4 @@
import { Expr, FunctionDef, Item } from "./ast";
import { Expr, FunctionDef, Item, Type } from "./ast";
export function printAst(ast: Item[]): string {
return ast.map(printItem).join("\n");
@ -13,8 +13,11 @@ function printItem(item: Item): string {
}
function printFunction(func: FunctionDef): string {
const args = func.args.map(({ name }) => name).join(", ");
return `function ${func.name}(${args}) = ${printExpr(func.body, 0)}`;
const args = func.args
.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 {
@ -23,9 +26,12 @@ function printExpr(expr: Expr, indent: number): string {
return "";
}
case "let": {
return `let ${expr.name} = ${printExpr(expr.rhs, 1)} in${linebreak(
indent
)}`;
const type = expr.type ? `: ${printType(expr.type)}` : "";
return `let ${expr.name}${type} = ${printExpr(
expr.rhs,
indent + 1
)} in${linebreak(indent)}${printExpr(expr.after, indent)}`;
}
case "block": {
const exprs = expr.exprs.map((expr) => printExpr(expr, indent + 1));
@ -35,8 +41,10 @@ function printExpr(expr: Expr, indent: number): string {
}
const shortExprs =
exprs.map((s) => s.length).reduce((a, b) => a + b, 0) < 40;
const alreadyHasTrailingSpace =
expr.exprs[exprs.length - 1]?.kind === "empty";
if (shortExprs) {
const alreadyHasTrailingSpace = expr.exprs[exprs.length - 1]?.kind === "empty";
const trailingSpace = alreadyHasTrailingSpace ? "" : " ";
return `( ${exprs.join("; ")}${trailingSpace})`;
} 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(", ")})`;
}
}