We need a way to know the definition of each symbol used in to program.
## 7.1 Symbols
We'll start by defining the symbol type we need.
```ts
type Sym = {
ident: string,
type: "let" | "fn" | "fn_param" | "builtin",
pos?: Pos,
stmt?: Stmt,
param?: Param,
}
```
The identifier and position are the same, as in chapter 4. But now, instead of knowing the value of a symbol, we instead need to know the definition of the symbol.
The 4 types are currently all the ways to introduce symbols. The defining statement and param is set according to the type.
We don't just need to check that every symbol is defined, we also need to be able to get the definition of each symbol, whenever we need it. We'll do this, by replacing all identifiers, *identifying a symbol*, by the symbol itself.
The estute reader will have recognized that the fields `stmt` and `param` may refer back to a related node in the structure, which implies that the structure is cyclic. We therefore no longer have a tree (directed aclyclic graph), but instead a graph. This will have no real consequences, only theoritical implications. I will still refer to the structure as the AST.
## 7.4 The resolver class
We'll make a resolver class, which, just like the evaluat in chapter 4, has a root symbol table.
When resolving an identifier, we essentially convert the identifier expression in-AST into a symbol expression. Therefore we need to take the expression, so we can mutate it. Because the `Expr` type is unspecific, we have to assert we've gotten an identifer. Then we try and find the symbol in the symbol table. And then we do the mutation, ie. converting the identifier expression into a symbol expression. We have to check that `sym.stmt` and `sym.param` are present, before we assign them.
throw new Error(`unknown expression ${expr.kind.type}`);
}
// ...
}
```
We traverse the tree, meaning we call `resolveExpr` on each childnode recursively. This way, we reach all identifiers in the AST, that need to be resolved.
Binary expressions are resolved by resolving the left and right operands.
Block expressions are resolved by making a child symbol table, and resolving each statement and the expression if present.
### Exercises
1. Implement the rest of the expressions.
## 7.7 Resolving let statements
```ts
class Resolver {
// ...
private resolveLetStmt(stmt: Stmt, syms: Syms) {
if (stmt.kind.type !== "let")
throw new Error("expected let statement");
this.resolveExpr(stmt.kind.value, syms);
const ident = stmt.kind.param.ident;
if (syms.locallyDefined(ident)) {
this.reportAlreadyDefined(ident, stmt.pos, syms);
return;
}
syms.define(ident, {
ident,
type: "let",
pos: stmt.param.pos,
stmt,
param: stmt.param,
});
}
// ...
}
```
To resolve a let statement, we resolve the value expression, then we check that the symbol has not been defined already, and then we define the symbol as a let-symbol.
To resolve a function definition we first check that the symbol is not already defined, then we define it. Then we make a child symbol table, define all the parameters and lastly resolve the function body.
Parameters must not have the same name, to that end we check that each parameters identififer is not already defined.
Contrary to resolving the let statement, we define the function symbol before resolving the body expression. This is so that the function body is able to call the function recursively.
## 7.9 Resolving statements
```ts
class Resolver {
// ...
private resolveStmt(stmt: Stmt, syms: Syms) {
if (stmt.kind.type === "error") {
return;
}
if (stmt.kind.type === "let") {
this.resolveLetStmt(stmt, syms);
return;
}
if (stmt.kind.type === "fn") {
this.resolveFnStmt(stmt, syms);
return;
}
if (stmt.kind.type === "return") {
if (stmt.kind.expr)
this.resolveExpr(stmt.kind.expr, syms);
return;
}
// ...
throw new Error(`unknown statement ${expr.kind.type}`);
}
// ...
}
```
Just like expressions, we traverse the AST and resolve every sub-statement and expression.
1. Implement the rest of the expressions. The reason for this, is that all identifiers that are sub-expressions of other expressions, also need to be resolved.