name resolution

This commit is contained in:
nora 2023-07-23 17:32:34 +02:00
parent cc2a9aeca8
commit 35f1c92e36
6 changed files with 449 additions and 60 deletions

View file

@ -1,5 +1,13 @@
import { Span } from "./error"; import { Span } from "./error";
export type Ast = Item[];
export type Identifier = {
name: string;
span: Span;
res?: Resolution;
};
export type ItemKind = { export type ItemKind = {
kind: "function"; kind: "function";
node: FunctionDef; node: FunctionDef;
@ -22,40 +30,67 @@ export type FunctionArg = {
span: Span; 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 = export type ExprKind =
| { kind: "empty" } | ExprEmpty
| { kind: "let"; name: string; type?: Type; rhs: Expr; after: Expr } | ExprLet
| { kind: "block"; exprs: Expr[] } | ExprBlock
| { | ExprLiteral
kind: "literal"; | ExprIdent
value: Literal; | ExprBinary
} | ExprUnary
| { | ExprCall
kind: "ident"; | ExprIf;
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;
};
export type Expr = ExprKind & { export type Expr = ExprKind & {
span: Span; span: Span;
@ -126,7 +161,7 @@ export const UNARY_KINDS: UnaryKind[] = ["!", "-"];
export type TypeKind = export type TypeKind =
| { | {
kind: "ident"; kind: "ident";
value: string; value: Identifier;
} }
| { | {
kind: "list"; kind: "list";
@ -140,3 +175,177 @@ export type TypeKind =
export type Type = TypeKind & { export type Type = TypeKind & {
span: Span; 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<T> = (value: T) => T;
export type Folder = {
item: FoldFn<Item>;
expr: FoldFn<Expr>;
ident: FoldFn<Identifier>;
type: FoldFn<Type>;
};
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,
};
}
}
}

View file

@ -76,6 +76,6 @@ export function lines(input: string): Span[] {
return lines; return lines;
} }
export function todo(msg: string, span: Span): never { export function todo(msg: string): never {
throw new CompilerError(`TODO: ${msg}`, span); throw new CompilerError(`TODO: ${msg}`, { start: 0, end: 0 });
} }

View file

@ -2,15 +2,20 @@ import { withErrorHandler } from "./error";
import { tokenize } from "./lexer"; import { tokenize } from "./lexer";
import { parse } from "./parser"; import { parse } from "./parser";
import { printAst } from "./printer"; import { printAst } from "./printer";
import { resolve } from "./resolve";
const input = ` const input = `
function main(argv: [String]): uwu = ( function main(argv: [String]): () = (
print("Hello, world!"); print(argv);
let a: [String] = 0 in if 1 then (
let b = 1 in print("AAAAAAAAAAAAAAAAAAAA");
if 0 then 0 else ( let a = 0 in
if 1 == 1 then 1 else "what" a;
;"meow") ) else (
print("AAAAAAAAAAAAAAAAAAAAAA");
let b = 0 in
b;
)
); );
`; `;
@ -22,13 +27,17 @@ function main() {
const ast = parse(tokens); const ast = parse(tokens);
console.log("-----AST------"); console.log("-----AST------");
console.dir(ast, { depth: 10 }); console.dir(ast, { depth: 50 });
const printed = printAst(ast); const printed = printAst(ast);
console.log("-----AST pretty------"); console.log("-----AST pretty------");
console.log(printed); console.log(printed);
const resolved = resolve(ast);
console.log("-----AST resolved------");
const resolvedPrinted = printAst(resolved);
console.log(resolvedPrinted);
}); });
} }

View file

@ -1,6 +1,7 @@
import { import {
ARITH_FACTOR_KINDS, ARITH_FACTOR_KINDS,
ARITH_TERM_KINDS, ARITH_TERM_KINDS,
Ast,
BinaryKind, BinaryKind,
COMPARISON_KINDS, COMPARISON_KINDS,
Expr, Expr,
@ -17,7 +18,7 @@ import { BaseToken, Token, TokenIdent } from "./lexer";
type Parser<T> = (t: Token[]) => [Token[], T]; type Parser<T> = (t: Token[]) => [Token[], T];
export function parse(t: Token[]): Item[] { export function parse(t: Token[]): Ast {
const items: Item[] = []; const items: Item[] = [];
while (t.length > 0) { while (t.length > 0) {
@ -242,6 +243,7 @@ function parseExprCall(t: Token[]): [Token[], Expr] {
function parseExprAtom(startT: Token[]): [Token[], Expr] { function parseExprAtom(startT: Token[]): [Token[], Expr] {
let [t, tok] = next(startT); let [t, tok] = next(startT);
const span = tok.span;
if (tok.kind === "(") { if (tok.kind === "(") {
let expr: Expr; let expr: Expr;
@ -255,7 +257,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
} }
[t] = expectNext(t, ")"); [t] = expectNext(t, ")");
return [t, { kind: "block", span: tok.span, exprs }]; return [t, { kind: "block", span, exprs }];
} }
if (tok.kind === "lit_string") { if (tok.kind === "lit_string") {
@ -263,7 +265,7 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
t, t,
{ {
kind: "literal", kind: "literal",
span: tok.span, span,
value: { kind: "str", value: tok.value }, value: { kind: "str", value: tok.value },
}, },
]; ];
@ -274,33 +276,48 @@ function parseExprAtom(startT: Token[]): [Token[], Expr] {
t, t,
{ {
kind: "literal", kind: "literal",
span: tok.span, span,
value: { kind: "int", value: tok.value }, value: { kind: "int", value: tok.value },
}, },
]; ];
} }
if (tok.kind === "identifier") { 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. // Parse nothing at all.
return [startT, { kind: "empty", span: tok.span }]; return [startT, { kind: "empty", span }];
} }
function parseType(t: Token[]): [Token[], Type] { function parseType(t: Token[]): [Token[], Type] {
let tok; let tok;
[t, tok] = next(t); [t, tok] = next(t);
const span = tok.span;
switch (tok.kind) { switch (tok.kind) {
case "identifier": { case "identifier": {
return [t, { kind: "ident", value: tok.ident, span: tok.span }]; return [
t,
{
kind: "ident",
value: { name: tok.ident, span },
span,
},
];
} }
case "[": { case "[": {
let elem; let elem;
[t, elem] = parseType(t); [t, elem] = parseType(t);
[t] = expectNext(t, "]"); [t] = expectNext(t, "]");
return [t, { kind: "list", elem, span: tok.span }]; return [t, { kind: "list", elem, span }];
} }
case "(": { case "(": {
let first = true; let first = true;
@ -316,12 +333,12 @@ function parseType(t: Token[]): [Token[], Type] {
} }
[t] = expectNext(t, ")"); [t] = expectNext(t, ")");
return [t, { kind: "tuple", elems, span: tok.span }]; return [t, { kind: "tuple", elems, span }];
} }
default: { default: {
throw new CompilerError( throw new CompilerError(
`unexpected token: \`${tok.kind}\`, expected type`, `unexpected token: \`${tok.kind}\`, expected type`,
tok.span span
); );
} }
} }

View file

@ -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"); return ast.map(printItem).join("\n");
} }
@ -67,7 +75,7 @@ function printExpr(expr: Expr, indent: number): string {
} }
} }
case "ident": { case "ident": {
return expr.value; return printIdent(expr.value);
} }
case "binary": { case "binary": {
return `${printExpr(expr.lhs, indent)} ${expr.binaryKind} ${printExpr( return `${printExpr(expr.lhs, indent)} ${expr.binaryKind} ${printExpr(
@ -107,7 +115,7 @@ function printExpr(expr: Expr, indent: number): string {
function printType(type: Type): string { function printType(type: Type): string {
switch (type.kind) { switch (type.kind) {
case "ident": case "ident":
return type.value; return printIdent(type.value);
case "list": case "list":
return `[${printType(type.elem)}]`; return `[${printType(type.elem)}]`;
case "tuple": 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 { function linebreak(indent: number): string {
return `\n${ind(indent)}`; return `\n${ind(indent)}`;
} }

130
src/resolve.ts Normal file
View file

@ -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<string>(["print", "String"]);
export function resolve(ast: Ast): Ast {
const items = new Map<string, number>();
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;
}