diff --git a/src/main/wacc/renamer.scala b/src/main/wacc/renamer.scala index 77e0bd6..6b78dc1 100644 --- a/src/main/wacc/renamer.scala +++ b/src/main/wacc/renamer.scala @@ -15,9 +15,33 @@ object renamer { current: mutable.Map[(String, IdentType), Ident], 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 semType + * The semantic type of the identifier. + * @param name + * The name of the identifier. + * @param identType + * The identifier type (function or variable). + * @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(semType: SemType, name: Ident, identType: IdentType)(using globalNames: mutable.Map[Ident, SemType], globalNumbering: mutable.Map[String, Int], @@ -36,6 +60,16 @@ object renamer { } } + /** 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] = @@ -44,6 +78,7 @@ object renamer { 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) @@ -52,6 +87,8 @@ object renamer { scope.add(KnownType.Func(SemType(retType), paramTypes), name, IdentType.Func) (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) => @@ -62,19 +99,52 @@ object renamer { main.toList.foreach(rename(scope)) globalNames.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 + node: Ident | Stmt | LValue | RValue | Expr )(using globalNames: mutable.Map[Ident, SemType], 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, IdentType.Var) } + 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) } @@ -83,16 +153,6 @@ object renamer { case Return(expr) => rename(scope)(expr) case Exit(expr) => rename(scope)(expr) case Print(expr, _) => rename(scope)(expr) - case If(cond, thenStmt, elseStmt) => { - rename(scope)(cond) - thenStmt.toList.foreach(rename(scope.subscope)) - elseStmt.toList.foreach(rename(scope.subscope)) - } - case While(cond, body) => { - rename(scope)(cond) - body.toList.foreach(rename(scope.subscope)) - } - case Block(body) => body.toList.foreach(rename(scope.subscope)) case NewPair(fst, snd) => { rename(scope)(fst) rename(scope)(snd) @@ -116,9 +176,26 @@ object renamer { } // Default to variables. Only `call` uses IdentType.Func. case id: Ident => renameIdent(scope, id, IdentType.Var) + // These literals cannot contain identifies, exit immediately. case IntLiter(_) | BoolLiter(_) | CharLiter(_) | StrLiter(_) | PairLiter() | Skip() => () } + /** Lookup an identifier in the current scope and rename it. If the identifier is not found, add + * an error to the error list and add it to the current scope with an unknown type. + * + * @param scope + * The current scope and flattened parent scope. + * @param ident + * The identifier to rename. + * @param identType + * The type of the identifier (function or variable). + * @param globalNames + * Used to add not-found identifiers to scope. + * @param globalNumbering + * Used to add not-found identifiers to scope. + * @param errors + * The list of errors to append to. + */ private def renameIdent(scope: Scope, ident: Ident, identType: IdentType)(using globalNames: mutable.Map[Ident, SemType], globalNumbering: mutable.Map[String, Int], diff --git a/src/main/wacc/types.scala b/src/main/wacc/types.scala index e62f0db..e2a7988 100644 --- a/src/main/wacc/types.scala +++ b/src/main/wacc/types.scala @@ -21,6 +21,7 @@ object types { case BoolType() => KnownType.Bool case CharType() => KnownType.Char case StringType() => KnownType.String + // For semantic types it is easier to work with recursion rather than a fixed size case ArrayType(elemType, dimension) => (0 until dimension).foldLeft(SemType(elemType))((acc, _) => KnownType.Array(acc)) case PairType(fst, snd) => KnownType.Pair(SemType(fst), SemType(snd))