From 06c8a069fbd761d2cbdb4226cbd2d9839147bc62 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Tue, 4 Feb 2025 22:26:38 +0000 Subject: [PATCH 1/5] feat: renamer maybe maybe maybe maybe --- src/main/wacc/Error.scala | 8 +++ src/main/wacc/Main.scala | 12 +++- src/main/wacc/ast.scala | 19 +++--- src/main/wacc/renamer.scala | 119 ++++++++++++++++++++++++++++++++++++ src/main/wacc/types.scala | 32 ++++++++++ 5 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 src/main/wacc/Error.scala create mode 100644 src/main/wacc/renamer.scala create mode 100644 src/main/wacc/types.scala diff --git a/src/main/wacc/Error.scala b/src/main/wacc/Error.scala new file mode 100644 index 0000000..a9f0490 --- /dev/null +++ b/src/main/wacc/Error.scala @@ -0,0 +1,8 @@ +package wacc + +enum Error { + case DuplicateDeclaration(ident: ast.Ident) + case UndefinedIdentifier(ident: ast.Ident) + case FunctionParamsMismatch(expected: Int, got: Int) + case TypeMismatch(expected: types.SemType, got: types.SemType) +} diff --git a/src/main/wacc/Main.scala b/src/main/wacc/Main.scala index d4b070a..dcc7e6d 100644 --- a/src/main/wacc/Main.scala +++ b/src/main/wacc/Main.scala @@ -1,5 +1,6 @@ package wacc +import scala.collection.mutable import parsley.{Failure, Success} import scopt.OParser import java.io.File @@ -32,8 +33,15 @@ val cliParser = { def compile(contents: String): Int = { parser.parse(contents) match { case Success(ast) => - // TODO: Do semantics things - 0 + given errors: mutable.Builder[Error, List[Error]] = List.newBuilder + val names = renamer.rename(ast) + // given ctx: types.TypeCheckerCtx[List[Error]] = + // types.TypeCheckerCtx(names, errors) + // types.check(ast) + if (errors.result.nonEmpty) { + errors.result.foreach(println) + 200 + } else 0 case Failure(msg) => println(msg) 100 diff --git a/src/main/wacc/ast.scala b/src/main/wacc/ast.scala index 0076006..2457c8f 100644 --- a/src/main/wacc/ast.scala +++ b/src/main/wacc/ast.scala @@ -28,8 +28,10 @@ object ast { object StrLiter extends ParserBridgePos1[String, StrLiter] case class PairLiter()(pos: Position) extends Expr6 object PairLiter extends Expr6 with ParserBridgePos0[PairLiter] - case class Ident(v: String)(pos: Position) extends Expr6 with LValue - object Ident extends ParserBridgePos1[String, Ident] + case class Ident(v: String, var uid: Int = -1) extends Expr6 with LValue + object Ident extends ParserBridgePos1[String, Ident] { + def apply(x1: String): Ident = new Ident(x1) + } case class ArrayElem(name: Ident, indices: NonEmptyList[Expr])(pos: Position) extends Expr6 with LValue @@ -44,15 +46,18 @@ object ast { sealed trait UnaryOp extends Expr { val x: Expr } - case class Negate(x: Expr6)(pos: Position) extends Expr6 with UnaryOp + sealed trait UnaryOp extends Expr { + val x: Expr + } + case class Negate(x: Expr6)(pos: Position) extends Expr6 with UnaryOp with UnaryOp object Negate extends ParserBridgePos1[Expr6, Negate] - case class Not(x: Expr6)(pos: Position) extends Expr6 with UnaryOp + case class Not(x: Expr6)(pos: Position) extends Expr6 with UnaryOp with UnaryOp object Not extends ParserBridgePos1[Expr6, Not] - case class Len(x: Expr6)(pos: Position) extends Expr6 with UnaryOp + case class Len(x: Expr6)(pos: Position) extends Expr6 with UnaryOp with UnaryOp object Len extends ParserBridgePos1[Expr6, Len] - case class Ord(x: Expr6)(pos: Position) extends Expr6 with UnaryOp + case class Ord(x: Expr6)(pos: Position) extends Expr6 with UnaryOp with UnaryOp object Ord extends ParserBridgePos1[Expr6, Ord] - case class Chr(x: Expr6)(pos: Position) extends Expr6 with UnaryOp + case class Chr(x: Expr6)(pos: Position) extends Expr6 with UnaryOp with UnaryOp object Chr extends ParserBridgePos1[Expr6, Chr] // Binary operators diff --git a/src/main/wacc/renamer.scala b/src/main/wacc/renamer.scala new file mode 100644 index 0000000..023b317 --- /dev/null +++ b/src/main/wacc/renamer.scala @@ -0,0 +1,119 @@ +package wacc + +import scala.collection.mutable + +object renamer { + import ast._ + import types._ + + private case class Scope( + current: mutable.Map[String, Ident], + parent: Map[String, Ident] + ) { + def subscope: Scope = + Scope(mutable.Map.empty, Map.empty.withDefault(current.withDefault(parent))) + + def add(semType: SemType, name: Ident)(using + globalNames: mutable.Map[Ident, SemType], + globalNumbering: mutable.Map[String, Int], + errors: mutable.Builder[Error, List[Error]] + ) = { + if (current.contains(name.v)) { + errors += Error.DuplicateDeclaration(name) + } else { + val uid = globalNumbering.getOrElse(name.v, 0) + name.uid = uid + current(name.v) = name + + globalNames(name) = semType + globalNumbering(name.v) = uid + 1 + } + } + } + + def rename(prog: Program)(using + errors: mutable.Builder[Error, List[Error]] + ): Map[Ident, SemType] = + given globalNames: mutable.Map[Ident, SemType] = mutable.Map.empty + given globalNumbering: mutable.Map[String, Int] = mutable.Map.empty + rename(Scope(mutable.Map.empty, Map.empty))(prog) + globalNames.toMap + + private def rename(scope: Scope)( + node: Program | FuncDecl | Ident | Stmt | LValue | RValue + )(using + globalNames: mutable.Map[Ident, SemType], + globalNumbering: mutable.Map[String, Int], + errors: mutable.Builder[Error, List[Error]] + ): Unit = node match { + case Program(funcs, main) => { + funcs.foreach(rename(scope)) + main.toList.foreach(rename(scope)) + } + case FuncDecl(retType, name, params, body) => { + val functionScope = scope.subscope + val paramTypes = params.map { param => + val paramType = SemType(param.paramType) + functionScope.add(paramType, param.name) + paramType + } + scope.add(KnownType.Func(SemType(retType), paramTypes), name) + body.toList.foreach(rename(functionScope)) + } + case VarDecl(synType, name, value) => { + // Order matters here. Variable isn't declared until after the value is evaluated. + rename(scope)(value) + scope.add(SemType(synType), name) + } + case Assign(lhs, value) => { + 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 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) + } + case Call(name, args) => { + rename(scope)(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) + } + case id: Ident => { + scope.current.withDefault(scope.parent).get(id.v) match { + case Some(Ident(_, uid)) => id.uid = uid + case None => { + errors += Error.UndefinedIdentifier(id) + scope.add(?, id) + } + } + } + case IntLiter(_) | BoolLiter(_) | CharLiter(_) | StrLiter(_) | PairLiter | Skip => () + } +} diff --git a/src/main/wacc/types.scala b/src/main/wacc/types.scala new file mode 100644 index 0000000..2ce5f27 --- /dev/null +++ b/src/main/wacc/types.scala @@ -0,0 +1,32 @@ +package wacc + +import scala.collection.mutable + +object types { + import ast._ + + sealed trait SemType + case object ? extends SemType + enum KnownType extends SemType { + case Int + case Bool + case Char + case String + case Array(elem: SemType) + case Pair(left: SemType, right: SemType) + case Func(ret: SemType, params: List[SemType]) + } + + object SemType { + def apply(synType: Type | PairElemType): KnownType = synType match { + case IntType => KnownType.Int + case BoolType => KnownType.Bool + case CharType => KnownType.Char + case StringType => KnownType.String + case ArrayType(elemType, dimension) => + (0 until dimension).foldLeft(SemType(elemType))((acc, _) => KnownType.Array(acc)) + case PairType(fst, snd) => KnownType.Pair(SemType(fst), SemType(snd)) + case UntypedPairType => KnownType.Pair(?, ?) + } + } +} From 8c5b85b8c22c60becbf0f9741972f1daa0ed282e Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Wed, 5 Feb 2025 05:12:32 +0000 Subject: [PATCH 2/5] fix: separate variable and function in scope --- src/main/wacc/Error.scala | 2 +- src/main/wacc/renamer.scala | 48 +++++++++++++++++++++++-------------- src/main/wacc/types.scala | 2 -- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/main/wacc/Error.scala b/src/main/wacc/Error.scala index a9f0490..6370925 100644 --- a/src/main/wacc/Error.scala +++ b/src/main/wacc/Error.scala @@ -2,7 +2,7 @@ package wacc enum Error { case DuplicateDeclaration(ident: ast.Ident) - case UndefinedIdentifier(ident: ast.Ident) + case UndefinedIdentifier(ident: ast.Ident, identType: renamer.IdentType) case FunctionParamsMismatch(expected: Int, got: Int) case TypeMismatch(expected: types.SemType, got: types.SemType) } diff --git a/src/main/wacc/renamer.scala b/src/main/wacc/renamer.scala index 023b317..cdfe19d 100644 --- a/src/main/wacc/renamer.scala +++ b/src/main/wacc/renamer.scala @@ -6,24 +6,29 @@ object renamer { import ast._ import types._ + enum IdentType { + case Func + case Var + } + private case class Scope( - current: mutable.Map[String, Ident], - parent: Map[String, Ident] + current: mutable.Map[(String, IdentType), Ident], + parent: Map[(String, IdentType), Ident] ) { def subscope: Scope = Scope(mutable.Map.empty, Map.empty.withDefault(current.withDefault(parent))) - def add(semType: SemType, name: Ident)(using + def add(semType: SemType, name: Ident, identType: IdentType)(using globalNames: mutable.Map[Ident, SemType], globalNumbering: mutable.Map[String, Int], errors: mutable.Builder[Error, List[Error]] ) = { - if (current.contains(name.v)) { + if (current.contains((name.v, identType))) { errors += Error.DuplicateDeclaration(name) } else { val uid = globalNumbering.getOrElse(name.v, 0) name.uid = uid - current(name.v) = name + current((name.v, identType)) = name globalNames(name) = semType globalNumbering(name.v) = uid + 1 @@ -54,16 +59,16 @@ object renamer { val functionScope = scope.subscope val paramTypes = params.map { param => val paramType = SemType(param.paramType) - functionScope.add(paramType, param.name) + functionScope.add(paramType, param.name, IdentType.Var) paramType } - scope.add(KnownType.Func(SemType(retType), paramTypes), name) + scope.add(KnownType.Func(SemType(retType), paramTypes), name, IdentType.Func) body.toList.foreach(rename(functionScope)) } case VarDecl(synType, name, value) => { // Order matters here. Variable isn't declared until after the value is evaluated. rename(scope)(value) - scope.add(SemType(synType), name) + scope.add(SemType(synType), name, IdentType.Var) } case Assign(lhs, value) => { rename(scope)(lhs) @@ -89,7 +94,7 @@ object renamer { rename(scope)(snd) } case Call(name, args) => { - rename(scope)(name) + renameIdent(scope, name, IdentType.Func) args.foreach(rename(scope)) } case Fst(elem) => rename(scope)(elem) @@ -105,15 +110,22 @@ object renamer { rename(scope)(op.x) rename(scope)(op.y) } - case id: Ident => { - scope.current.withDefault(scope.parent).get(id.v) match { - case Some(Ident(_, uid)) => id.uid = uid - case None => { - errors += Error.UndefinedIdentifier(id) - scope.add(?, id) - } - } - } + // Default to variables. Only `call` uses IdentType.Func. + case id: Ident => renameIdent(scope, id, IdentType.Var) case IntLiter(_) | BoolLiter(_) | CharLiter(_) | StrLiter(_) | PairLiter | Skip => () } + + private def renameIdent(scope: Scope, ident: Ident, identType: IdentType)(using + globalNames: mutable.Map[Ident, SemType], + globalNumbering: mutable.Map[String, Int], + errors: mutable.Builder[Error, List[Error]] + ): Unit = { + scope.current.withDefault(scope.parent).get((ident.v, identType)) match { + case Some(Ident(_, uid)) => ident.uid = uid + case None => { + errors += Error.UndefinedIdentifier(ident, identType) + scope.add(?, ident, identType) + } + } + } } diff --git a/src/main/wacc/types.scala b/src/main/wacc/types.scala index 2ce5f27..388416f 100644 --- a/src/main/wacc/types.scala +++ b/src/main/wacc/types.scala @@ -1,7 +1,5 @@ package wacc -import scala.collection.mutable - object types { import ast._ From 6027bea95e6ad180e7109c27582b3215584bbd97 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Wed, 5 Feb 2025 18:04:04 +0000 Subject: [PATCH 3/5] fix: use apply() instead of get() for Maps --- src/main/wacc/renamer.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/wacc/renamer.scala b/src/main/wacc/renamer.scala index cdfe19d..e46d9dd 100644 --- a/src/main/wacc/renamer.scala +++ b/src/main/wacc/renamer.scala @@ -120,12 +120,15 @@ object renamer { globalNumbering: mutable.Map[String, Int], errors: mutable.Builder[Error, List[Error]] ): Unit = { - scope.current.withDefault(scope.parent).get((ident.v, identType)) match { - case Some(Ident(_, uid)) => ident.uid = uid - case None => { + // 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 { + val Ident(_, uid) = scope.current.withDefault(scope.parent)((ident.v, identType)) + ident.uid = uid + } catch { + case _: NoSuchElementException => errors += Error.UndefinedIdentifier(ident, identType) scope.add(?, ident, identType) - } } } } From 0f18bca7fdf7807bface33002302d5a5041ed6ab Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Wed, 5 Feb 2025 20:41:49 +0000 Subject: [PATCH 4/5] fix: fix merge breaks, add function names to scope before renaming bodies --- src/main/wacc/Main.scala | 2 +- src/main/wacc/ast.scala | 19 ++++++++----------- src/main/wacc/renamer.scala | 38 ++++++++++++++++++++----------------- src/main/wacc/types.scala | 10 +++++----- 4 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/main/wacc/Main.scala b/src/main/wacc/Main.scala index dcc7e6d..a271c3c 100644 --- a/src/main/wacc/Main.scala +++ b/src/main/wacc/Main.scala @@ -34,7 +34,7 @@ def compile(contents: String): Int = { parser.parse(contents) match { case Success(ast) => given errors: mutable.Builder[Error, List[Error]] = List.newBuilder - val names = renamer.rename(ast) + renamer.rename(ast) // given ctx: types.TypeCheckerCtx[List[Error]] = // types.TypeCheckerCtx(names, errors) // types.check(ast) diff --git a/src/main/wacc/ast.scala b/src/main/wacc/ast.scala index 2457c8f..123adb5 100644 --- a/src/main/wacc/ast.scala +++ b/src/main/wacc/ast.scala @@ -27,10 +27,10 @@ object ast { case class StrLiter(v: String)(pos: Position) extends Expr6 object StrLiter extends ParserBridgePos1[String, StrLiter] case class PairLiter()(pos: Position) extends Expr6 - object PairLiter extends Expr6 with ParserBridgePos0[PairLiter] - case class Ident(v: String, var uid: Int = -1) extends Expr6 with LValue + object PairLiter extends ParserBridgePos0[PairLiter] + case class Ident(v: String, var uid: Int = -1)(pos: Position) extends Expr6 with LValue object Ident extends ParserBridgePos1[String, Ident] { - def apply(x1: String): Ident = new Ident(x1) + def apply(v: String)(pos: Position): Ident = new Ident(v)(pos) } case class ArrayElem(name: Ident, indices: NonEmptyList[Expr])(pos: Position) extends Expr6 @@ -46,18 +46,15 @@ object ast { sealed trait UnaryOp extends Expr { val x: Expr } - sealed trait UnaryOp extends Expr { - val x: Expr - } - case class Negate(x: Expr6)(pos: Position) extends Expr6 with UnaryOp with UnaryOp + case class Negate(x: Expr6)(pos: Position) extends Expr6 with UnaryOp object Negate extends ParserBridgePos1[Expr6, Negate] - case class Not(x: Expr6)(pos: Position) extends Expr6 with UnaryOp with UnaryOp + case class Not(x: Expr6)(pos: Position) extends Expr6 with UnaryOp object Not extends ParserBridgePos1[Expr6, Not] - case class Len(x: Expr6)(pos: Position) extends Expr6 with UnaryOp with UnaryOp + case class Len(x: Expr6)(pos: Position) extends Expr6 with UnaryOp object Len extends ParserBridgePos1[Expr6, Len] - case class Ord(x: Expr6)(pos: Position) extends Expr6 with UnaryOp with UnaryOp + case class Ord(x: Expr6)(pos: Position) extends Expr6 with UnaryOp object Ord extends ParserBridgePos1[Expr6, Ord] - case class Chr(x: Expr6)(pos: Position) extends Expr6 with UnaryOp with UnaryOp + case class Chr(x: Expr6)(pos: Position) extends Expr6 with UnaryOp object Chr extends ParserBridgePos1[Expr6, Chr] // Binary operators diff --git a/src/main/wacc/renamer.scala b/src/main/wacc/renamer.scala index e46d9dd..77e0bd6 100644 --- a/src/main/wacc/renamer.scala +++ b/src/main/wacc/renamer.scala @@ -41,30 +41,34 @@ object renamer { ): Map[Ident, SemType] = given globalNames: mutable.Map[Ident, SemType] = mutable.Map.empty given globalNumbering: mutable.Map[String, Int] = mutable.Map.empty - rename(Scope(mutable.Map.empty, Map.empty))(prog) + val scope = Scope(mutable.Map.empty, Map.empty) + val Program(funcs, main) = prog + funcs + .map { case FuncDecl(retType, name, params, body) => + val paramTypes = params.map { param => + val paramType = SemType(param.paramType) + paramType + } + scope.add(KnownType.Func(SemType(retType), paramTypes), name, IdentType.Func) + (params zip paramTypes, body) + } + .foreach { case (params, body) => + val functionScope = scope.subscope + params.foreach { case (param, paramType) => + functionScope.add(paramType, param.name, IdentType.Var) + } + body.toList.foreach(rename(functionScope.subscope)) // body can shadow function params + } + main.toList.foreach(rename(scope)) globalNames.toMap private def rename(scope: Scope)( - node: Program | FuncDecl | Ident | Stmt | LValue | RValue + node: Ident | Stmt | LValue | RValue )(using globalNames: mutable.Map[Ident, SemType], globalNumbering: mutable.Map[String, Int], errors: mutable.Builder[Error, List[Error]] ): Unit = node match { - case Program(funcs, main) => { - funcs.foreach(rename(scope)) - main.toList.foreach(rename(scope)) - } - case FuncDecl(retType, name, params, body) => { - val functionScope = scope.subscope - val paramTypes = params.map { param => - val paramType = SemType(param.paramType) - functionScope.add(paramType, param.name, IdentType.Var) - paramType - } - scope.add(KnownType.Func(SemType(retType), paramTypes), name, IdentType.Func) - body.toList.foreach(rename(functionScope)) - } case VarDecl(synType, name, value) => { // Order matters here. Variable isn't declared until after the value is evaluated. rename(scope)(value) @@ -112,7 +116,7 @@ object renamer { } // Default to variables. Only `call` uses IdentType.Func. case id: Ident => renameIdent(scope, id, IdentType.Var) - case IntLiter(_) | BoolLiter(_) | CharLiter(_) | StrLiter(_) | PairLiter | Skip => () + case IntLiter(_) | BoolLiter(_) | CharLiter(_) | StrLiter(_) | PairLiter() | Skip() => () } private def renameIdent(scope: Scope, ident: Ident, identType: IdentType)(using diff --git a/src/main/wacc/types.scala b/src/main/wacc/types.scala index 388416f..e62f0db 100644 --- a/src/main/wacc/types.scala +++ b/src/main/wacc/types.scala @@ -17,14 +17,14 @@ object types { object SemType { def apply(synType: Type | PairElemType): KnownType = synType match { - case IntType => KnownType.Int - case BoolType => KnownType.Bool - case CharType => KnownType.Char - case StringType => KnownType.String + case IntType() => KnownType.Int + case BoolType() => KnownType.Bool + case CharType() => KnownType.Char + case StringType() => KnownType.String case ArrayType(elemType, dimension) => (0 until dimension).foldLeft(SemType(elemType))((acc, _) => KnownType.Array(acc)) case PairType(fst, snd) => KnownType.Pair(SemType(fst), SemType(snd)) - case UntypedPairType => KnownType.Pair(?, ?) + case UntypedPairType() => KnownType.Pair(?, ?) } } } From 5210a55d9e630aa0ae7c617f3d9997fd7af79524 Mon Sep 17 00:00:00 2001 From: Gleb Koval Date: Wed, 5 Feb 2025 22:03:26 +0000 Subject: [PATCH 5/5] refactor: add comments to renamer --- src/main/wacc/renamer.scala | 99 ++++++++++++++++++++++++++++++++----- src/main/wacc/types.scala | 1 + 2 files changed, 89 insertions(+), 11 deletions(-) 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))