From cc2a9aeca86ffee386aa3f84726d8d96a4815fd6 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sun, 23 Jul 2023 14:39:09 +0200 Subject: [PATCH] parse more stuff --- src/ast.ts | 48 +++++++++++++----- src/index.ts | 8 ++- src/lexer.ts | 18 ++++++- src/parser.ts | 130 +++++++++++++++++++++++++++++++++++++++++-------- src/printer.ts | 42 +++++++++++++--- 5 files changed, 206 insertions(+), 40 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index 7fe0e36..5db6527 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -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[] = ['!', '-']; \ No newline at end of file +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; +}; diff --git a/src/index.ts b/src/index.ts index 4cd946d..fe9fe11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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") ); `; diff --git a/src/lexer.ts b/src/lexer.ts index 7731466..96a8821 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -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(["function", "let", "in"]); +const keywords = new Set([ + "function", + "let", + "in", + "if", + "then", + "else", +]); function isKeyword(kw: string): DatalessToken | undefined { return keywords.has(kw) ? (kw as DatalessToken) : undefined; } diff --git a/src/parser.ts b/src/parser.ts index df74686..cf8d2e9 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -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: Token[]) => [Token[], T]; @@ -35,7 +37,33 @@ function parseItem(t: Token[]): [Token[], Item] { [t, name] = expectNext(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(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(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( @@ -246,8 +346,10 @@ function expectNext( ): [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(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; -} diff --git a/src/printer.ts b/src/printer.ts index 1b23411..bfd9c82 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -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(", ")})`; } }