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 if we don't, we report and error and return a non-ok result.
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.