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";
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<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;
}
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 });
}

View file

@ -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);
});
}

View file

@ -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> = (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
);
}
}

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");
}
@ -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)}`;
}

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;
}