220 lines
7.8 KiB
Scala
220 lines
7.8 KiB
Scala
package wacc
|
|
|
|
import scala.collection.mutable
|
|
|
|
object renamer {
|
|
import ast._
|
|
import types._
|
|
|
|
private enum IdentType {
|
|
case Func
|
|
case Var
|
|
}
|
|
|
|
private class Scope(
|
|
val current: mutable.Map[(String, IdentType), Ident],
|
|
val parent: Map[(String, IdentType), Ident]
|
|
) {
|
|
|
|
/** Create a new scope with the current scope as its parent.
|
|
*
|
|
* @return
|
|
* A new scope with an empty current scope, and this scope flattened into the parent scope.
|
|
*/
|
|
def subscope: Scope =
|
|
Scope(mutable.Map.empty, Map.empty.withDefault(current.withDefault(parent)))
|
|
|
|
/** Attempt to add a new identifier to the current scope. If the identifier already exists in
|
|
* the current scope, add an error to the error list.
|
|
*
|
|
* @param ty
|
|
* The semantic type of the variable identifier, or function identifier type.
|
|
* @param name
|
|
* The name of the identifier.
|
|
* @param globalNames
|
|
* The global map of identifiers to semantic types - the identifier will be added to this
|
|
* map.
|
|
* @param globalNumbering
|
|
* The global map of identifier names to the number of times they have been declared - will
|
|
* used to rename this identifier, and will be incremented.
|
|
* @param errors
|
|
* The list of errors to append to.
|
|
*/
|
|
def add(ty: SemType | FuncType, name: Ident)(using
|
|
globalNames: mutable.Map[Ident, SemType],
|
|
globalFuncs: mutable.Map[Ident, FuncType],
|
|
globalNumbering: mutable.Map[String, Int],
|
|
errors: mutable.Builder[Error, List[Error]]
|
|
) = {
|
|
val identType = ty match {
|
|
case _: SemType => IdentType.Var
|
|
case _: FuncType => IdentType.Func
|
|
}
|
|
current.get((name.v, identType)) match {
|
|
case Some(Ident(_, uid)) =>
|
|
errors += Error.DuplicateDeclaration(name)
|
|
name.uid = uid
|
|
case None =>
|
|
val uid = globalNumbering.getOrElse(name.v, 0)
|
|
name.uid = uid
|
|
current((name.v, identType)) = name
|
|
|
|
ty match {
|
|
case semType: SemType =>
|
|
globalNames(name) = semType
|
|
case funcType: FuncType =>
|
|
globalFuncs(name) = funcType
|
|
}
|
|
globalNumbering(name.v) = uid + 1
|
|
}
|
|
}
|
|
|
|
private def get(name: String, identType: IdentType): Option[Ident] =
|
|
// Unfortunately map defaults only work with `.apply()`, which throws an error when the key is not found.
|
|
// Neither is there a way to check whether a default exists, so we have to use a try-catch.
|
|
try {
|
|
Some(current.withDefault(parent)((name, identType)))
|
|
} catch {
|
|
case _: NoSuchElementException => None
|
|
}
|
|
|
|
def getVar(name: String): Option[Ident] = get(name, IdentType.Var)
|
|
def getFunc(name: String): Option[Ident] = get(name, IdentType.Func)
|
|
}
|
|
|
|
/** Check scoping of all variables and functions in the program. Also generate semantic types for
|
|
* all identifiers.
|
|
*
|
|
* @param prog
|
|
* AST of the program
|
|
* @param errors
|
|
* List of errors to append to
|
|
* @return
|
|
* Map of all (renamed) identifies to their semantic types
|
|
*/
|
|
def rename(prog: Program)(using
|
|
errors: mutable.Builder[Error, List[Error]]
|
|
): (Map[Ident, SemType], Map[Ident, FuncType]) = {
|
|
given globalNames: mutable.Map[Ident, SemType] = mutable.Map.empty
|
|
given globalFuncs: mutable.Map[Ident, FuncType] = mutable.Map.empty
|
|
given globalNumbering: mutable.Map[String, Int] = mutable.Map.empty
|
|
val scope = Scope(mutable.Map.empty, Map.empty)
|
|
val Program(funcs, main) = prog
|
|
funcs
|
|
// First add all function declarations to the scope
|
|
.map { case FuncDecl(retType, name, params, body) =>
|
|
val paramTypes = params.map { param =>
|
|
val paramType = SemType(param.paramType)
|
|
paramType
|
|
}
|
|
scope.add(FuncType(SemType(retType), paramTypes), name)
|
|
(params zip paramTypes, body)
|
|
}
|
|
// Only then rename the function bodies
|
|
// (functions can call one-another regardless of order of declaration)
|
|
.foreach { case (params, body) =>
|
|
val functionScope = scope.subscope
|
|
params.foreach { case (param, paramType) =>
|
|
functionScope.add(paramType, param.name)
|
|
}
|
|
body.toList.foreach(rename(functionScope.subscope)) // body can shadow function params
|
|
}
|
|
main.toList.foreach(rename(scope))
|
|
(globalNames.toMap, globalFuncs.toMap)
|
|
}
|
|
|
|
/** Check scoping of all identifies in a given AST node.
|
|
*
|
|
* @param scope
|
|
* The current scope and flattened parent scope.
|
|
* @param node
|
|
* The AST node.
|
|
* @param globalNames
|
|
* The global map of identifiers to semantic types - renamed identifiers will be added to this
|
|
* map.
|
|
* @param globalNumbering
|
|
* The global map of identifier names to the number of times they have been declared - used and
|
|
* updated during identifier renaming.
|
|
* @param errors
|
|
*/
|
|
private def rename(scope: Scope)(
|
|
node: Ident | Stmt | LValue | RValue | Expr
|
|
)(using
|
|
globalNames: mutable.Map[Ident, SemType],
|
|
globalFuncs: mutable.Map[Ident, FuncType],
|
|
globalNumbering: mutable.Map[String, Int],
|
|
errors: mutable.Builder[Error, List[Error]]
|
|
): Unit = node match {
|
|
// These cases are more interesting because the involve making subscopes
|
|
// or modifying the current scope.
|
|
case VarDecl(synType, name, value) => {
|
|
// Order matters here. Variable isn't declared until after the value is evaluated.
|
|
rename(scope)(value)
|
|
// Attempt to add the new variable to the current scope.
|
|
scope.add(SemType(synType), name)
|
|
}
|
|
case If(cond, thenStmt, elseStmt) => {
|
|
rename(scope)(cond)
|
|
// then and else both have their own scopes
|
|
thenStmt.toList.foreach(rename(scope.subscope))
|
|
elseStmt.toList.foreach(rename(scope.subscope))
|
|
}
|
|
case While(cond, body) => {
|
|
rename(scope)(cond)
|
|
// while bodies have their own scopes
|
|
body.toList.foreach(rename(scope.subscope))
|
|
}
|
|
// begin-end blocks have their own scopes
|
|
case Block(body) => body.toList.foreach(rename(scope.subscope))
|
|
|
|
// These cases are simpler, mostly just recursive calls to rename()
|
|
case Assign(lhs, value) => {
|
|
// Variables may be reassigned with their value in the rhs, so order doesn't matter here.
|
|
rename(scope)(lhs)
|
|
rename(scope)(value)
|
|
}
|
|
case Read(lhs) => rename(scope)(lhs)
|
|
case Free(expr) => rename(scope)(expr)
|
|
case Return(expr) => rename(scope)(expr)
|
|
case Exit(expr) => rename(scope)(expr)
|
|
case Print(expr, _) => rename(scope)(expr)
|
|
case NewPair(fst, snd) => {
|
|
rename(scope)(fst)
|
|
rename(scope)(snd)
|
|
}
|
|
case Call(name, args) => {
|
|
scope.getFunc(name.v) match {
|
|
case Some(Ident(_, uid)) => name.uid = uid
|
|
case None =>
|
|
errors += Error.UndefinedFunction(name)
|
|
scope.add(FuncType(?, args.map(_ => ?)), name)
|
|
}
|
|
args.foreach(rename(scope))
|
|
}
|
|
case Fst(elem) => rename(scope)(elem)
|
|
case Snd(elem) => rename(scope)(elem)
|
|
case ArrayLiter(elems) => elems.foreach(rename(scope))
|
|
case ArrayElem(name, indices) => {
|
|
rename(scope)(name)
|
|
indices.toList.foreach(rename(scope))
|
|
}
|
|
case Parens(expr) => rename(scope)(expr)
|
|
case op: UnaryOp => rename(scope)(op.x)
|
|
case op: BinaryOp => {
|
|
rename(scope)(op.x)
|
|
rename(scope)(op.y)
|
|
}
|
|
// Default to variables. Only `call` uses IdentType.Func.
|
|
case id: Ident => {
|
|
scope.getVar(id.v) match {
|
|
case Some(Ident(_, uid)) => id.uid = uid
|
|
case None =>
|
|
errors += Error.UndeclaredVariable(id)
|
|
scope.add(?, id)
|
|
}
|
|
}
|
|
// These literals cannot contain identifies, exit immediately.
|
|
case IntLiter(_) | BoolLiter(_) | CharLiter(_) | StrLiter(_) | PairLiter() | Skip() => ()
|
|
}
|
|
}
|