Untitled
unknown
plain_text
a year ago
6.1 kB
15
Indexable
package wacc
import ast.*
import TypeConverter._
import scala.collection.mutable
type FunctionTable = Map[String, FuncType]
object semanticAnalyser {
def analyse(
program: Program
): Either[List[SemanticError], (SymbolTable, FunctionTable)] = {
given ctx: SemanticContext = SemanticContext(
SymbolTable(),
mutable.Map(),
List.newBuilder
)
// Collect all function signatures first to allow for forward declarations
// We also error here if function name is reused
program.functions.foreach(func => {
if (ctx.functionTable.contains(func.name)) {
ctx.error(FunctionRedefinitionError(func))
Left(ctx.errorCollector.result())
} else {
ctx.functionTable += (func.name -> FuncType(
func.returnType.toSemType,
func.parameters.map(_._1.toSemType)
))
Right(ctx.functionTable(func.name))
}
})
// Analyse each function's paramters and body.
program.functions.foreach(analyseFunction)
// Analyse the main program body
analyseBody(program.body)
val errors = ctx.errorCollector.result()
if (errors.nonEmpty) {
Left(errors)
} else {
Right((ctx.symbolTable, ctx.functionTable.toMap))
}
}
def analyseFunction(
func: Func
)(using ctx: SemanticContext): Unit = {
// check for redeclared parameters
val paramNames = func.parameters.map(_._2)
val redeclaredParams = paramNames.diff(paramNames.distinct)
redeclaredParams.foreach { name =>
ctx.error(VariableRedeclarationError(name))
}
// Creates a new nested scope for the function body
val funcCtx = ctx.copy(symbolTable = ctx.symbolTable.createChild())
func.parameters.foreach {
case (typ, name) => {
if (ctx.symbolTable.lookupLocal(name).isDefined) {
ctx.error(VariableRedeclarationError(name))
} else {
ctx.symbolTable.add(name, typ.toSemType)
}
}
}
// Check that the returning block inn the function body returns a type
// that matches the syntactic type of the function
val actualRetType = getFunctionReturnType(func, func.body)
if (actualRetType != func.returnType.toSemType) {
ctx.error(
ReturnTypeMismatch(func.returnType.toSemType, actualRetType, func)
)
}
// Analyse the function body using the newly created context
analyseBody(func.body)(using funcCtx)
}
/** Pre: body is a list of statements within the function func Assumes that
* the function is syntactically correct and so the last statement of the
* function must be a returning block.
*
* @param func
* The function that the body belongs to
* @param body
* A list of statements within the function. This could be the entire
* function body or a body of an if statement within the returning block of
* the function.
* @return
*/
def getFunctionReturnType(
func: Func,
body: List[Stmt]
)(using ctx: SemanticContext): SemType = body.last match {
case Return(expr) => analyseExpr(expr)
case Exit(expr) => analyseExpr(expr)
case If(cond, thenBody, elseBody) => {
val thenRet = getFunctionReturnType(func, thenBody)
val elseRet = getFunctionReturnType(func, elseBody)
if (thenRet != elseRet) {
thenRet
} else {
ctx.error(IfReturnTypeMismatchError(thenRet, elseRet, func))
SemAny
}
}
case _ =>
throw new Exception(
"The last statement of a function must be a returning block. This is a syntax error."
)
}
def analyseBody(
stmts: List[Stmt]
)(using ctx: SemanticContext): Unit = {
stmts.foreach(analyseStmt)
}
def analyseStmt(
statement: Stmt
)(using ctx: SemanticContext): Unit = statement match {
case Skip => // No checking needed
case Scope(body) =>
val newCtx = ctx.copy(symbolTable = ctx.symbolTable.createChild())
analyseBody(body)(using newCtx)
case stmt @ Declaration(varType, name, value) =>
if (ctx.symbolTable.lookupLocal(name).isDefined) {
ctx.error(VariableRedeclarationError(name))
} else {
ctx.symbolTable.add(name, varType.toSemType)
}
val valueType = analyseRValue(value)
if (valueType != varType.toSemType) {
ctx.error(DeclarationTypeError(stmt, valueType))
}
case Exit(expr) =>
val typ = analyseExpr(expr)
if (typ != SemInt)
ctx.error(ExitTypeError(typ))
case Free(expr) =>
// val typ = analyseExpr(expr)
// if (!typ.isInstanceOf[FreeableType]) {
// ctx.error(FreeTypeError(typ))
// }
case If(condition, thenBody, elseBody) =>
val condTyp = analyseExpr(condition)
if (condTyp != SemBool) {
ctx.error(ConditionTypeError(statement, condTyp))
}
val thenCtx = ctx.copy(symbolTable = ctx.symbolTable.createChild())
analyseBody(thenBody)(using thenCtx)
val elseCtx = ctx.copy(symbolTable = ctx.symbolTable.createChild())
analyseBody(elseBody)(using elseCtx)
case Print(expr) => analyseExpr(expr)
case PrintLn(expr) => analyseExpr(expr)
case Read(lvalue) =>
// val typ = analyseLValue(lvalue)
// if (!typ.isInstanceOf[ReadableType])
// ctx.error(ReadTypeError(statement, typ))
case While(condition, body) =>
val condType = analyseExpr(condition)
if (condType != SemBool)
ctx.error(ConditionTypeError(statement, condType))
case Assignment(_, _) => ???
case Return(_) => // TODO
}
def analyseExpr(expression: Expr)(using ctx: SemanticContext): SemType =
SemInt
def analyseLValue(lvalue: LValue)(using ctx: SemanticContext): SemType =
SemInt
def analyseRValue(rvalue: RValue)(using ctx: SemanticContext): SemType =
SemInt
case class SemanticContext(
symbolTable: SymbolTable,
functionTable: mutable.Map[String, FuncType],
errorCollector: mutable.Builder[SemanticError, List[SemanticError]]
) {
def error(error: SemanticError): Unit = errorCollector += error
}
}
Editor is loading...
Leave a Comment