diff --git a/src/ast.ts b/src/ast.ts index 5db6527..805c323 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1,5 +1,13 @@ import { Span } from "./error"; +export type Ast = Item[]; + +export type Identifier = { + name: string; + span: Span; + res?: Resolution; +}; + export type ItemKind = { kind: "function"; node: FunctionDef; @@ -22,40 +30,67 @@ export type FunctionArg = { span: Span; }; +export type ExprEmpty = { kind: "empty" }; + +export type ExprLet = { + kind: "let"; + name: string; + type?: Type; + rhs: Expr; + after: Expr; +}; + +export type ExprBlock = { + kind: "block"; + exprs: Expr[]; +}; + +export type ExprLiteral = { + kind: "literal"; + value: Literal; +}; + +export type ExprIdent = { + kind: "ident"; + value: Identifier; +}; + +export type ExprBinary = { + kind: "binary"; + binaryKind: BinaryKind; + lhs: Expr; + rhs: Expr; +}; + +export type ExprUnary = { + kind: "unary"; + unaryKind: UnaryKind; + rhs: Expr; +}; + +export type ExprCall = { + kind: "call"; + lhs: Expr; + args: Expr[]; +}; + +export type ExprIf = { + kind: "if"; + cond: Expr; + then: Expr; + else?: Expr; +}; + export type ExprKind = - | { kind: "empty" } - | { kind: "let"; name: string; type?: Type; rhs: Expr; after: Expr } - | { kind: "block"; exprs: Expr[] } - | { - kind: "literal"; - value: Literal; - } - | { - kind: "ident"; - value: string; - } - | { - kind: "binary"; - binaryKind: BinaryKind; - lhs: Expr; - rhs: Expr; - } - | { - kind: "unary"; - unaryKind: UnaryKind; - rhs: Expr; - } - | { - kind: "call"; - lhs: Expr; - args: Expr[]; - } - | { - kind: "if"; - cond: Expr; - then: Expr; - else?: Expr; - }; + | ExprEmpty + | ExprLet + | ExprBlock + | ExprLiteral + | ExprIdent + | ExprBinary + | ExprUnary + | ExprCall + | ExprIf; export type Expr = ExprKind & { span: Span; @@ -126,7 +161,7 @@ export const UNARY_KINDS: UnaryKind[] = ["!", "-"]; export type TypeKind = | { kind: "ident"; - value: string; + value: Identifier; } | { kind: "list"; @@ -140,3 +175,177 @@ export type TypeKind = export type Type = TypeKind & { span: Span; }; + +export type Resolution = + | { + kind: "local"; + /** + * The index of the local variable, from inside out. + * ``` + * let a in let b in (a, b); + * ^ ^ + * 1 0 + * ``` + * When traversing resolutions, a stack of locals has to be kept. + * It's similar to a De Bruijn index. + */ + index: number; + } + | { + kind: "item"; + /** + * Items are numbered in the order they appear in. + * Right now we only have one scope of items (global) + * so this is enough. + */ + index: number; + } + | { + kind: "builtin"; + }; + +// folders + +export type FoldFn = (value: T) => T; + +export type Folder = { + item: FoldFn; + expr: FoldFn; + ident: FoldFn; + type: FoldFn; +}; + +export const DEFAULT_FOLDER: Folder = { + item(item) { + return super_fold_item(item, this); + }, + expr(expr) { + return super_fold_expr(expr, this); + }, + ident(ident) { + return ident; + }, + type(type) { + return super_fold_type(type, this); + }, +}; + +export function fold_ast(ast: Ast, folder: Folder): Ast { + return ast.map((item) => folder.item(item)); +} + +export function super_fold_item(item: Item, folder: Folder): Item { + switch (item.kind) { + case "function": { + const args = item.node.args.map(({ name, type, span }) => ({ + name, + type: folder.type(type), + span, + })); + + return { + kind: "function", + span: item.span, + node: { + name: item.node.name, + args, + body: folder.expr(item.node.body), + returnType: item.node.returnType && folder.type(item.node.returnType), + }, + }; + } + } +} + +export function super_fold_expr(expr: Expr, folder: Folder): Expr { + const span = expr.span; + switch (expr.kind) { + case "empty": { + return { kind: "empty", span }; + } + case "let": { + return { + kind: "let", + name: expr.name, + type: expr.type && folder.type(expr.type), + rhs: folder.expr(expr.rhs), + after: folder.expr(expr.after), + span, + }; + } + case "block": { + return { + kind: "block", + exprs: expr.exprs.map((expr) => folder.expr(expr)), + span, + }; + } + case "literal": { + return { kind: "literal", value: expr.value, span }; + } + case "ident": { + return { kind: "ident", value: folder.ident(expr.value), span }; + } + case "binary": { + return { + kind: "binary", + binaryKind: expr.binaryKind, + lhs: folder.expr(expr.lhs), + rhs: folder.expr(expr.rhs), + span, + }; + } + case "unary": { + return { + kind: "unary", + unaryKind: expr.unaryKind, + rhs: folder.expr(expr.rhs), + span, + }; + } + case "call": { + return { + kind: "call", + lhs: folder.expr(expr.lhs), + args: expr.args.map((expr) => folder.expr(expr)), + span, + }; + } + case "if": { + return { + kind: "if", + cond: folder.expr(expr.cond), + then: folder.expr(expr.then), + else: expr.else && folder.expr(expr.else), + span, + }; + } + } +} + +export function super_fold_type(type: Type, folder: Folder): Type { + const span = type.span; + switch (type.kind) { + case "ident": { + return { + kind: "ident", + value: folder.ident(type.value), + span, + }; + } + case "list": { + return { + kind: "list", + elem: folder.type(type.elem), + span, + }; + } + case "tuple": { + return { + kind: "tuple", + elems: type.elems.map((type) => folder.type(type)), + span, + }; + } + } +} diff --git a/src/error.ts b/src/error.ts index 8754d3f..cba03b1 100644 --- a/src/error.ts +++ b/src/error.ts @@ -76,6 +76,6 @@ export function lines(input: string): Span[] { return lines; } -export function todo(msg: string, span: Span): never { - throw new CompilerError(`TODO: ${msg}`, span); +export function todo(msg: string): never { + throw new CompilerError(`TODO: ${msg}`, { start: 0, end: 0 }); } diff --git a/src/index.ts b/src/index.ts index fe9fe11..9698fe6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,15 +2,20 @@ import { withErrorHandler } from "./error"; import { tokenize } from "./lexer"; import { parse } from "./parser"; import { printAst } from "./printer"; +import { resolve } from "./resolve"; const input = ` -function main(argv: [String]): uwu = ( - print("Hello, world!"); - let a: [String] = 0 in - let b = 1 in - if 0 then 0 else ( - if 1 == 1 then 1 else "what" - ;"meow") +function main(argv: [String]): () = ( + print(argv); + if 1 then ( + print("AAAAAAAAAAAAAAAAAAAA"); + let a = 0 in + a; + ) else ( + print("AAAAAAAAAAAAAAAAAAAAAA"); + let b = 0 in + b; + ) ); `; @@ -22,13 +27,17 @@ function main() { const ast = parse(tokens); console.log("-----AST------"); - - console.dir(ast, { depth: 10 }); + + console.dir(ast, { depth: 50 }); const printed = printAst(ast); console.log("-----AST pretty------"); console.log(printed); - + + const resolved = resolve(ast); + console.log("-----AST resolved------"); + const resolvedPrinted = printAst(resolved); + console.log(resolvedPrinted); }); } diff --git a/src/parser.ts b/src/parser.ts index cf8d2e9..f5cfdad 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1,6 +1,7 @@ import { ARITH_FACTOR_KINDS, ARITH_TERM_KINDS, + Ast, BinaryKind, COMPARISON_KINDS, Expr, @@ -17,7 +18,7 @@ import { BaseToken, Token, TokenIdent } from "./lexer"; type Parser = (t: Token[]) => [Token[], T]; -export function parse(t: Token[]): Item[] { +export function parse(t: Token[]): Ast { const items: Item[] = []; while (t.length > 0) { @@ -242,6 +243,7 @@ function parseExprCall(t: Token[]): [Token[], Expr] { function parseExprAtom(startT: Token[]): [Token[], Expr] { let [t, tok] = next(startT); + const span = tok.span; if (tok.kind === "(") { let expr: Expr; @@ -255,7 +257,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] { } [t] = expectNext(t, ")"); - return [t, { kind: "block", span: tok.span, exprs }]; + return [t, { kind: "block", span, exprs }]; } if (tok.kind === "lit_string") { @@ -263,7 +265,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] { t, { kind: "literal", - span: tok.span, + span, value: { kind: "str", value: tok.value }, }, ]; @@ -274,33 +276,48 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] { t, { kind: "literal", - span: tok.span, + span, value: { kind: "int", value: tok.value }, }, ]; } if (tok.kind === "identifier") { - return [t, { kind: "ident", span: tok.span, value: tok.ident }]; + return [ + t, + { + kind: "ident", + span, + value: { name: tok.ident, span }, + }, + ]; } // Parse nothing at all. - return [startT, { kind: "empty", span: tok.span }]; + return [startT, { kind: "empty", span }]; } function parseType(t: Token[]): [Token[], Type] { let tok; [t, tok] = next(t); + const span = tok.span; switch (tok.kind) { case "identifier": { - return [t, { kind: "ident", value: tok.ident, span: tok.span }]; + return [ + t, + { + kind: "ident", + value: { name: tok.ident, span }, + span, + }, + ]; } case "[": { let elem; [t, elem] = parseType(t); [t] = expectNext(t, "]"); - return [t, { kind: "list", elem, span: tok.span }]; + return [t, { kind: "list", elem, span }]; } case "(": { let first = true; @@ -316,12 +333,12 @@ function parseType(t: Token[]): [Token[], Type] { } [t] = expectNext(t, ")"); - return [t, { kind: "tuple", elems, span: tok.span }]; + return [t, { kind: "tuple", elems, span }]; } default: { throw new CompilerError( `unexpected token: \`${tok.kind}\`, expected type`, - tok.span + span ); } } diff --git a/src/printer.ts b/src/printer.ts index bfd9c82..ec330dd 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -1,6 +1,14 @@ -import { Expr, FunctionDef, Item, Type } from "./ast"; +import { + Ast, + Expr, + FunctionDef, + Identifier, + Item, + Resolution, + Type, +} from "./ast"; -export function printAst(ast: Item[]): string { +export function printAst(ast: Ast): string { return ast.map(printItem).join("\n"); } @@ -67,7 +75,7 @@ function printExpr(expr: Expr, indent: number): string { } } case "ident": { - return expr.value; + return printIdent(expr.value); } case "binary": { return `${printExpr(expr.lhs, indent)} ${expr.binaryKind} ${printExpr( @@ -107,7 +115,7 @@ function printExpr(expr: Expr, indent: number): string { function printType(type: Type): string { switch (type.kind) { case "ident": - return type.value; + return printIdent(type.value); case "list": return `[${printType(type.elem)}]`; case "tuple": @@ -115,6 +123,22 @@ function printType(type: Type): string { } } +function printIdent(ident: Identifier): string { + const printRes = (res: Resolution): string => { + switch (res.kind) { + case "local": + return `#${res.index}`; + case "item": + return `#G${res.index}`; + case "builtin": { + return `#B`; + } + } + }; + const res = ident.res ? printRes(ident.res) : ""; + return `${ident.name}${res}`; +} + function linebreak(indent: number): string { return `\n${ind(indent)}`; } diff --git a/src/resolve.ts b/src/resolve.ts new file mode 100644 index 0000000..a351a62 --- /dev/null +++ b/src/resolve.ts @@ -0,0 +1,130 @@ +import { + Ast, + DEFAULT_FOLDER, + Folder, + Identifier, + Resolution, + fold_ast, + super_fold_expr, + super_fold_item, +} from "./ast"; +import { CompilerError } from "./error"; + +const BUILTINS = new Set(["print", "String"]); + +export function resolve(ast: Ast): Ast { + const items = new Map(); + + for (let i = 0; i < ast.length; i++) { + const item = ast[i]; + const existing = items.get(item.node.name); + if (existing !== undefined) { + throw new CompilerError( + `item \`${item.node.name}\` has already been declared`, + item.span + ); + } + items.set(item.node.name, i); + } + + const scopes: string[] = []; + + const popScope = (expected: string) => { + const popped = scopes.pop(); + if (popped !== expected) { + throw new Error( + `Scopes corrupted, wanted to pop ${name} but popped ${popped}` + ); + } + }; + + const resolveIdent = (ident: Identifier): Resolution => { + const lastIdx = scopes.length - 1; + for (let i = lastIdx; i >= 0; i--) { + const candidate = scopes[i]; + if (candidate === ident.name) { + const index = lastIdx - i; + return { + kind: "local", + index, + }; + } + } + + const item = items.get(ident.name); + if (item !== undefined) { + return { + kind: "item", + index: item, + }; + } + + if (BUILTINS.has(ident.name)) { + return { kind: "builtin" }; + } + + throw new CompilerError(`cannot find ${ident.name}`, ident.span); + }; + + const resolver: Folder = { + ...DEFAULT_FOLDER, + item(item) { + switch (item.kind) { + case "function": { + const args = item.node.args.map(({ name, span, type }) => ({ + name, + span, + type: this.type(type), + })); + const returnType = + item.node.returnType && this.type(item.node.returnType); + + item.node.args.forEach(({ name }) => scopes.push(name)); + const body = super_fold_expr(item.node.body, this); + item.node.args.forEach(({ name }) => popScope(name)); + + return { + kind: "function", + span: item.span, + node: { + name: item.node.name, + args, + returnType, + body, + }, + }; + } + } + + return super_fold_item(item, this); + }, + expr(expr) { + if (expr.kind === "let") { + const rhs = this.expr(expr.rhs); + const type = expr.type && this.type(expr.type); + scopes.push(expr.name); + const after = this.expr(expr.after); + popScope(expr.name); + + return { + kind: "let", + name: expr.name, + rhs, + after, + type, + span: expr.span, + }; + } + + return super_fold_expr(expr, this); + }, + ident(ident) { + const res = resolveIdent(ident); + return { name: ident.name, span: ident.span, res }; + }, + }; + + const resolved = fold_ast(ast, resolver); + + return resolved; +}