feat: functional single-threaded imports
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
# Output:
|
# Output:
|
||||||
# -33
|
# -33
|
||||||
|
#
|
||||||
|
|
||||||
# Exit:
|
# Exit:
|
||||||
# 0
|
# 0
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
# Output:
|
# Output:
|
||||||
# 15
|
# 15
|
||||||
# 0
|
|
||||||
# -33
|
# -33
|
||||||
# 0
|
# 0
|
||||||
# -33
|
# -33
|
||||||
|
|||||||
@@ -71,21 +71,23 @@ val outputOpt: Opts[Option[Path]] =
|
|||||||
def frontend(
|
def frontend(
|
||||||
contents: String,
|
contents: String,
|
||||||
file: File
|
file: File
|
||||||
): Either[NonEmptyList[Error], microWacc.Program] =
|
): IO[Either[NonEmptyList[Error], microWacc.Program]] =
|
||||||
parser.parse(contents) match {
|
parser.parse(contents) match {
|
||||||
case Failure(msg) => Left(NonEmptyList.one(Error.SyntaxError(file, msg)))
|
case Failure(msg) => IO.pure(Left(NonEmptyList.one(Error.SyntaxError(file, msg))))
|
||||||
case Success(fn) =>
|
case Success(fn) =>
|
||||||
val ast.PartialProgram(_, prog) = fn(file)
|
val partialProg = fn(file)
|
||||||
given errors: mutable.Builder[Error, List[Error]] = List.newBuilder
|
given errors: mutable.Builder[Error, List[Error]] = List.newBuilder
|
||||||
|
|
||||||
val (names, funcs) = renamer.rename(prog)
|
for {
|
||||||
given ctx: typeChecker.TypeCheckerCtx = typeChecker.TypeCheckerCtx(names, funcs, errors)
|
(prog, renameErrors) <- renamer.rename(partialProg)
|
||||||
val typedProg = typeChecker.check(prog)
|
_ = errors.addAll(renameErrors.toList)
|
||||||
|
typedProg = typeChecker.check(prog, errors)
|
||||||
|
|
||||||
NonEmptyList.fromList(errors.result) match {
|
res = NonEmptyList.fromList(errors.result) match {
|
||||||
case Some(errors) => Left(errors)
|
case Some(errors) => Left(errors)
|
||||||
case None => Right(typedProg)
|
case None => Right(typedProg)
|
||||||
}
|
}
|
||||||
|
} yield res
|
||||||
}
|
}
|
||||||
|
|
||||||
def backend(typedProg: microWacc.Program): Chain[asm.AsmLine] =
|
def backend(typedProg: microWacc.Program): Chain[asm.AsmLine] =
|
||||||
@@ -109,23 +111,25 @@ def compile(
|
|||||||
logger.info(s"Success: ${outputPath.toAbsolutePath}")
|
logger.info(s"Success: ${outputPath.toAbsolutePath}")
|
||||||
|
|
||||||
def processProgram(contents: String, file: File, outDir: Path): IO[Int] =
|
def processProgram(contents: String, file: File, outDir: Path): IO[Int] =
|
||||||
frontend(contents, file) match {
|
for {
|
||||||
case Left(errors) =>
|
frontendResult <- frontend(contents, file)
|
||||||
val code = errors.map(err => err.exitCode).toList.min
|
res <- frontendResult match {
|
||||||
given errorContent: String = contents
|
case Left(errors) =>
|
||||||
val errorMsg = errors.map(formatError).toIterable.mkString("\n")
|
val code = errors.map(err => err.exitCode).toList.min
|
||||||
for {
|
val errorMsg = errors.map(formatError).toIterable.mkString("\n")
|
||||||
_ <- logAction(s"Compilation failed for $filePath\nExit code: $code")
|
for {
|
||||||
_ <- IO.blocking(
|
_ <- logAction(s"Compilation failed for $filePath\nExit code: $code")
|
||||||
// Explicit println since we want this to always show without logger thread info e.t.c.
|
_ <- IO.blocking(
|
||||||
println(s"Compilation failed for ${file.getCanonicalPath}:\n$errorMsg")
|
// Explicit println since we want this to always show without logger thread info e.t.c.
|
||||||
)
|
println(s"Compilation failed for ${file.getCanonicalPath}:\n$errorMsg")
|
||||||
} yield code
|
)
|
||||||
|
} yield code
|
||||||
|
|
||||||
case Right(typedProg) =>
|
case Right(typedProg) =>
|
||||||
val outputFile = outDir.resolve(filePath.getFileName.toString.stripSuffix(".wacc") + ".s")
|
val outputFile = outDir.resolve(filePath.getFileName.toString.stripSuffix(".wacc") + ".s")
|
||||||
writeOutputFile(typedProg, outputFile).as(SUCCESS)
|
writeOutputFile(typedProg, outputFile).as(SUCCESS)
|
||||||
}
|
}
|
||||||
|
} yield res
|
||||||
|
|
||||||
for {
|
for {
|
||||||
contents <- readSourceFile
|
contents <- readSourceFile
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ private class LabelGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def getLabel(target: CallTarget | RuntimeError): String = target match {
|
private def getLabel(target: CallTarget | RuntimeError): String = target match {
|
||||||
case Ident(v, _) => s"wacc_$v"
|
case Ident(v, guid) => s"wacc_${v}_$guid"
|
||||||
case Builtin(name) => s"_$name"
|
case Builtin(name) => s"_$name"
|
||||||
case err: RuntimeError => s".L.${err.name}"
|
case err: RuntimeError => s".L.${err.name}"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ extension (e: Error) {
|
|||||||
* @param errorContent
|
* @param errorContent
|
||||||
* Contents of the file to generate code snippets
|
* Contents of the file to generate code snippets
|
||||||
*/
|
*/
|
||||||
def formatError(error: Error)(using errorContent: String): String = {
|
def formatError(error: Error): String = {
|
||||||
val sb = new StringBuilder()
|
val sb = new StringBuilder()
|
||||||
|
|
||||||
/** Format the file of an error
|
/** Format the file of an error
|
||||||
@@ -66,7 +66,7 @@ def formatError(error: Error)(using errorContent: String): String = {
|
|||||||
* Size(in chars) of section to highlight
|
* Size(in chars) of section to highlight
|
||||||
*/
|
*/
|
||||||
def formatHighlight(pos: Position, size: Int): Unit = {
|
def formatHighlight(pos: Position, size: Int): Unit = {
|
||||||
val lines = errorContent.split("\n")
|
val lines = os.read(os.Path(pos.file.getCanonicalPath)).split("\n")
|
||||||
val preLine = if (pos.line > 1) lines(pos.line - 2) else ""
|
val preLine = if (pos.line > 1) lines(pos.line - 2) else ""
|
||||||
val midLine = lines(pos.line - 1)
|
val midLine = lines(pos.line - 1)
|
||||||
val postLine = if (pos.line < lines.size) lines(pos.line) else ""
|
val postLine = if (pos.line < lines.size) lines(pos.line) else ""
|
||||||
@@ -87,38 +87,38 @@ def formatError(error: Error)(using errorContent: String): String = {
|
|||||||
error match {
|
error match {
|
||||||
case Error.DuplicateDeclaration(ident) =>
|
case Error.DuplicateDeclaration(ident) =>
|
||||||
formatPosition(ident.pos)
|
formatPosition(ident.pos)
|
||||||
sb.append(s"Duplicate declaration of identifier ${ident.v}")
|
sb.append(s"Duplicate declaration of identifier ${ident.v}\n")
|
||||||
formatHighlight(ident.pos, ident.v.length)
|
formatHighlight(ident.pos, ident.v.length)
|
||||||
case Error.UndeclaredVariable(ident) =>
|
case Error.UndeclaredVariable(ident) =>
|
||||||
formatPosition(ident.pos)
|
formatPosition(ident.pos)
|
||||||
sb.append(s"Undeclared variable ${ident.v}")
|
sb.append(s"Undeclared variable ${ident.v}\n")
|
||||||
formatHighlight(ident.pos, ident.v.length)
|
formatHighlight(ident.pos, ident.v.length)
|
||||||
case Error.UndefinedFunction(ident) =>
|
case Error.UndefinedFunction(ident) =>
|
||||||
formatPosition(ident.pos)
|
formatPosition(ident.pos)
|
||||||
sb.append(s"Undefined function ${ident.v}")
|
sb.append(s"Undefined function ${ident.v}\n")
|
||||||
formatHighlight(ident.pos, ident.v.length)
|
formatHighlight(ident.pos, ident.v.length)
|
||||||
case Error.FunctionParamsMismatch(id, expected, got, funcType) =>
|
case Error.FunctionParamsMismatch(id, expected, got, funcType) =>
|
||||||
formatPosition(id.pos)
|
formatPosition(id.pos)
|
||||||
sb.append(s"Function expects $expected parameters, got $got")
|
sb.append(s"Function expects $expected parameters, got $got\n")
|
||||||
sb.append(
|
sb.append(
|
||||||
s"(function ${id.v} has type (${funcType.params.mkString(", ")}) -> ${funcType.returnType})"
|
s"(function ${id.v} has type (${funcType.params.mkString(", ")}) -> ${funcType.returnType})\n"
|
||||||
)
|
)
|
||||||
formatHighlight(id.pos, 1)
|
formatHighlight(id.pos, 1)
|
||||||
case Error.TypeMismatch(pos, expected, got, msg) =>
|
case Error.TypeMismatch(pos, expected, got, msg) =>
|
||||||
formatPosition(pos)
|
formatPosition(pos)
|
||||||
sb.append(s"Type mismatch: $msg\nExpected: $expected\nGot: $got")
|
sb.append(s"Type mismatch: $msg\nExpected: $expected\nGot: $got\n")
|
||||||
formatHighlight(pos, 1)
|
formatHighlight(pos, 1)
|
||||||
case Error.SemanticError(pos, msg) =>
|
case Error.SemanticError(pos, msg) =>
|
||||||
formatPosition(pos)
|
formatPosition(pos)
|
||||||
sb.append(msg)
|
sb.append(msg + "\n")
|
||||||
formatHighlight(pos, 1)
|
formatHighlight(pos, 1)
|
||||||
case wacc.Error.InternalError(pos, msg) =>
|
case wacc.Error.InternalError(pos, msg) =>
|
||||||
formatPosition(pos)
|
formatPosition(pos)
|
||||||
sb.append(s"Internal error: $msg")
|
sb.append(s"Internal error: $msg\n")
|
||||||
formatHighlight(pos, 1)
|
formatHighlight(pos, 1)
|
||||||
case Error.SyntaxError(file, msg) =>
|
case Error.SyntaxError(file, msg) =>
|
||||||
formatFile(file)
|
formatFile(file)
|
||||||
sb.append(msg)
|
sb.append(msg + "\n")
|
||||||
sb.append("\n")
|
sb.append("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ object ast {
|
|||||||
object StrLiter extends ParserBridgePos1Atom[String, StrLiter]
|
object StrLiter extends ParserBridgePos1Atom[String, StrLiter]
|
||||||
case class PairLiter()(val pos: Position) extends Expr6
|
case class PairLiter()(val pos: Position) extends Expr6
|
||||||
object PairLiter extends ParserBridgePos0[PairLiter]
|
object PairLiter extends ParserBridgePos0[PairLiter]
|
||||||
case class Ident(v: String, var guid: Int = -1, var ty: types.RenamerType = types.?)(
|
case class Ident(var v: String, var guid: Int = -1, var ty: types.RenamerType = types.?)(
|
||||||
val pos: Position
|
val pos: Position
|
||||||
) extends Expr6
|
) extends Expr6
|
||||||
with LValue
|
with LValue
|
||||||
|
|||||||
@@ -6,12 +6,8 @@ import cats.effect.IO
|
|||||||
import cats.syntax.all._
|
import cats.syntax.all._
|
||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
import cats.data.Chain
|
import cats.data.Chain
|
||||||
import cats.Foldable
|
|
||||||
import cats.Functor
|
|
||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
||||||
import parsley.{Failure, Success}
|
import parsley.{Failure, Success}
|
||||||
import cats.data.NonEmptyChain
|
|
||||||
import cats.NonEmptyParallel
|
|
||||||
|
|
||||||
private val MAIN = "$main"
|
private val MAIN = "$main"
|
||||||
|
|
||||||
@@ -54,7 +50,8 @@ object renamer {
|
|||||||
* A new scope with an empty current scope, and this scope flattened into the parent scope.
|
* A new scope with an empty current scope, and this scope flattened into the parent scope.
|
||||||
*/
|
*/
|
||||||
def withSubscope[T](f: Scope => T): T = {
|
def withSubscope[T](f: Scope => T): T = {
|
||||||
val subscope = Scope(mutable.Map.empty, Map.empty.withDefault(current.withDefault(parent)), guid, guidInc)
|
val subscope =
|
||||||
|
Scope(mutable.Map.empty, Map.empty.withDefault(current.withDefault(parent)), guid, guidInc)
|
||||||
immutable = true
|
immutable = true
|
||||||
val result = f(subscope)
|
val result = f(subscope)
|
||||||
guid = subscope.guid // Sync GUID
|
guid = subscope.guid // Sync GUID
|
||||||
@@ -64,7 +61,7 @@ object renamer {
|
|||||||
|
|
||||||
/** Create new scopes with the current scope as its parent and GUID numbering adjusted
|
/** Create new scopes with the current scope as its parent and GUID numbering adjusted
|
||||||
* correctly.
|
* correctly.
|
||||||
*
|
*
|
||||||
* This will permanently mark the current scope as immutable, for thread safety.
|
* This will permanently mark the current scope as immutable, for thread safety.
|
||||||
*
|
*
|
||||||
* To be used for multi-threaded applications.
|
* To be used for multi-threaded applications.
|
||||||
@@ -115,10 +112,10 @@ object renamer {
|
|||||||
/** Attempt to add a new identifier as an alias to another to the existing scope.
|
/** Attempt to add a new identifier as an alias to another to the existing scope.
|
||||||
*
|
*
|
||||||
* @param alias
|
* @param alias
|
||||||
* The (new) alias identifier.
|
* The (new) alias identifier.
|
||||||
* @param orig
|
* @param orig
|
||||||
* The (existing) original identifier.
|
* The (existing) original identifier.
|
||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
* An error, if one occurred.
|
* An error, if one occurred.
|
||||||
*/
|
*/
|
||||||
@@ -150,15 +147,21 @@ object renamer {
|
|||||||
case _: NoSuchElementException => None
|
case _: NoSuchElementException => None
|
||||||
}
|
}
|
||||||
|
|
||||||
def getVar(name: Ident): Option[Ident] = get(name.pos.file.getCanonicalPath, name.v, IdentType.Var).map(_.id)
|
def getVar(name: Ident): Option[Ident] =
|
||||||
def getFunc(name: Ident): Option[Ident] = get(name.pos.file.getCanonicalPath, name.v, IdentType.Func).map(_.id)
|
get(name.pos.file.getCanonicalPath, name.v, IdentType.Var).map(_.id)
|
||||||
|
def getFunc(name: Ident): Option[Ident] =
|
||||||
|
get(name.pos.file.getCanonicalPath, name.v, IdentType.Func).map(_.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def prepareGlobalScope(partialProg: PartialProgram)(using scope: Scope): IO[(FuncDecl, Chain[FuncDecl], Chain[Error])] = {
|
private def prepareGlobalScope(
|
||||||
|
partialProg: PartialProgram
|
||||||
|
)(using scope: Scope): IO[(FuncDecl, Chain[FuncDecl], Chain[Error])] = {
|
||||||
def readImportFile(file: File): IO[String] =
|
def readImportFile(file: File): IO[String] =
|
||||||
IO.blocking(os.read(os.Path(file.getCanonicalPath)))
|
IO.blocking(os.read(os.Path(file.getCanonicalPath)))
|
||||||
|
|
||||||
def prepareImport(contents: String, file: File)(using scope: Scope): IO[(Chain[FuncDecl], Chain[Error])] = {
|
def prepareImport(contents: String, file: File)(using
|
||||||
|
scope: Scope
|
||||||
|
): IO[(Chain[FuncDecl], Chain[Error])] = {
|
||||||
parser.parse(contents) match {
|
parser.parse(contents) match {
|
||||||
case Failure(msg) =>
|
case Failure(msg) =>
|
||||||
IO.pure(Chain.empty, Chain.one(Error.SyntaxError(file, msg)))
|
IO.pure(Chain.empty, Chain.one(Error.SyntaxError(file, msg)))
|
||||||
@@ -168,15 +171,27 @@ object renamer {
|
|||||||
(main, chunks, errors) <- prepareGlobalScope(partialProg)
|
(main, chunks, errors) <- prepareGlobalScope(partialProg)
|
||||||
} yield (main +: chunks, errors)
|
} yield (main +: chunks, errors)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def addImportsToScope(importFile: File, funcs: NonEmptyList[ImportedFunc])(using
|
||||||
|
scope: Scope
|
||||||
|
): Chain[Error] =
|
||||||
|
funcs.foldMap { case ImportedFunc(srcName, aliasName) =>
|
||||||
|
scope.get(importFile.getCanonicalPath, srcName.v, IdentType.Func) match {
|
||||||
|
case Some(src) if src.public =>
|
||||||
|
aliasName.ty = src.id.ty
|
||||||
|
scope.addAlias(aliasName, src)
|
||||||
|
case _ =>
|
||||||
|
Chain.one(Error.UndefinedFunction(srcName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val PartialProgram(imports, prog) = partialProg
|
val PartialProgram(imports, prog) = partialProg
|
||||||
|
|
||||||
// First prepare this file's functions...
|
// First prepare this file's functions...
|
||||||
val Program(funcs, main) = prog
|
val Program(funcs, main) = prog
|
||||||
val (funcChunks, funcErrors) = funcs.foldLeft((Chain.empty[FuncDecl], Chain.empty[Error])) {
|
val (funcChunks, funcErrors) = funcs.foldLeft((Chain.empty[FuncDecl], Chain.empty[Error])) {
|
||||||
case ((chunks, errors), func@FuncDecl(retType, name, params, body)) =>
|
case ((chunks, errors), func @ FuncDecl(retType, name, params, body)) =>
|
||||||
val paramTypes = params.map { param =>
|
val paramTypes = params.map { param =>
|
||||||
val paramType = SemType(param.paramType)
|
val paramType = SemType(param.paramType)
|
||||||
param.name.ty = paramType
|
param.name.ty = paramType
|
||||||
@@ -192,20 +207,49 @@ object renamer {
|
|||||||
|
|
||||||
// Now handle imports
|
// Now handle imports
|
||||||
val file = prog.pos.file
|
val file = prog.pos.file
|
||||||
val preparedImports = imports.foldLeftM[IO, (Chain[FuncDecl], Chain[Error])]((Chain.empty[FuncDecl], Chain.empty[Error])) {
|
val preparedImports = imports.foldLeftM[IO, (Chain[FuncDecl], Chain[Error])](
|
||||||
case ((chunks, errors), Import(name, funcs)) =>
|
(Chain.empty[FuncDecl], Chain.empty[Error])
|
||||||
val importFile = File(file.getParent, name.v)
|
) { case ((chunks, errors), Import(name, funcs)) =>
|
||||||
|
val importFile = File(file.getParent, name.v)
|
||||||
|
if (!importFile.exists()) {
|
||||||
|
IO.pure(
|
||||||
|
(
|
||||||
|
chunks,
|
||||||
|
errors :+ Error.SemanticError(
|
||||||
|
name.pos,
|
||||||
|
s"File not found: ${importFile.getCanonicalPath}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (!importFile.canRead()) {
|
||||||
|
IO.pure(
|
||||||
|
(
|
||||||
|
chunks,
|
||||||
|
errors :+ Error.SemanticError(
|
||||||
|
name.pos,
|
||||||
|
s"File not readable: ${importFile.getCanonicalPath}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (importFile.getCanonicalPath == file.getCanonicalPath) {
|
||||||
|
IO.pure(
|
||||||
|
(
|
||||||
|
chunks,
|
||||||
|
errors :+ Error.SemanticError(
|
||||||
|
name.pos,
|
||||||
|
s"Cannot import self: ${importFile.getCanonicalPath}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else if (scope.get(importFile.getCanonicalPath, MAIN, IdentType.Func).isDefined) {
|
||||||
|
IO.pure(chunks, errors ++ addImportsToScope(importFile, funcs))
|
||||||
|
} else {
|
||||||
for {
|
for {
|
||||||
contents <- readImportFile(importFile)
|
contents <- readImportFile(importFile)
|
||||||
(importChunks, importErrors) <- prepareImport(contents, importFile)
|
(importChunks, importErrors) <- prepareImport(contents, importFile)
|
||||||
importAliasErrors = funcs.foldMap { case ImportedFunc(srcName, aliasName) =>
|
importAliasErrors = addImportsToScope(importFile, funcs)
|
||||||
scope.get(importFile.getCanonicalPath, srcName.v, IdentType.Func) match {
|
|
||||||
case Some(src) if src.public => scope.addAlias(aliasName, src)
|
|
||||||
case _ => Chain.one(Error.UndefinedFunction(srcName))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} yield (chunks ++ importChunks, errors ++ importErrors)
|
} yield (chunks ++ importChunks, errors ++ importErrors)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@@ -228,55 +272,15 @@ object renamer {
|
|||||||
for {
|
for {
|
||||||
(main, chunks, errors) <- prepareGlobalScope(partialProg)
|
(main, chunks, errors) <- prepareGlobalScope(partialProg)
|
||||||
toRename = (main +: chunks).toList
|
toRename = (main +: chunks).toList
|
||||||
res = (toRename zip scope.subscopes(toRename.size)).parTraverse { case (func@FuncDecl(retType, name, params, body), subscope) =>
|
res = (toRename zip scope.subscopes(toRename.size)).foldMap {
|
||||||
val paramErrors = params.foldMap { param => subscope.add(param.name) }
|
case (func @ FuncDecl(retType, name, params, body), subscope) =>
|
||||||
val bodyErrors = subscope.withSubscope { s => body.foldMap(rename(s)) }
|
val paramErrors = params.foldMap { param => subscope.add(param.name) }
|
||||||
paramErrors ++ bodyErrors
|
val bodyErrors = subscope.withSubscope { s => body.foldMap(rename(s)) }
|
||||||
|
paramErrors ++ bodyErrors
|
||||||
}
|
}
|
||||||
} yield (Program(chunks.toList, main.body)(main.pos), errors)
|
} yield (Program(chunks.toList, main.body)(main.pos), errors ++ res)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /** 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.
|
/** Check scoping of all identifies in a given AST node.
|
||||||
*
|
*
|
||||||
* @param scope
|
* @param scope
|
||||||
@@ -326,7 +330,8 @@ object renamer {
|
|||||||
}
|
}
|
||||||
case Call(name, args) => {
|
case Call(name, args) => {
|
||||||
val nameErrors = scope.getFunc(name) match {
|
val nameErrors = scope.getFunc(name) match {
|
||||||
case Some(Ident(_, guid, ty)) =>
|
case Some(Ident(realName, guid, ty)) =>
|
||||||
|
name.v = realName
|
||||||
name.ty = ty
|
name.ty = ty
|
||||||
name.guid = guid
|
name.guid = guid
|
||||||
Chain.empty
|
Chain.empty
|
||||||
@@ -365,6 +370,7 @@ object renamer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// These literals cannot contain identifies, exit immediately.
|
// These literals cannot contain identifies, exit immediately.
|
||||||
case IntLiter(_) | BoolLiter(_) | CharLiter(_) | StrLiter(_) | PairLiter() | Skip() => Chain.empty
|
case IntLiter(_) | BoolLiter(_) | CharLiter(_) | StrLiter(_) | PairLiter() | Skip() =>
|
||||||
|
Chain.empty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,8 @@ object typeChecker {
|
|||||||
import wacc.types._
|
import wacc.types._
|
||||||
|
|
||||||
case class TypeCheckerCtx(
|
case class TypeCheckerCtx(
|
||||||
globalNames: Map[ast.Ident, SemType],
|
|
||||||
globalFuncs: Map[ast.Ident, FuncType],
|
|
||||||
errors: mutable.Builder[Error, List[Error]]
|
errors: mutable.Builder[Error, List[Error]]
|
||||||
) {
|
) {
|
||||||
def typeOf(ident: ast.Ident): SemType = globalNames(ident)
|
|
||||||
def funcType(ident: ast.Ident): FuncType = globalFuncs(ident)
|
|
||||||
|
|
||||||
def error(err: Error): SemType =
|
def error(err: Error): SemType =
|
||||||
errors += err
|
errors += err
|
||||||
?
|
?
|
||||||
@@ -99,18 +94,17 @@ object typeChecker {
|
|||||||
* The type checker context which includes the global names and functions, and an errors
|
* The type checker context which includes the global names and functions, and an errors
|
||||||
* builder.
|
* builder.
|
||||||
*/
|
*/
|
||||||
def check(prog: ast.Program)(using
|
def check(prog: ast.Program, errors: mutable.Builder[Error, List[Error]]): microWacc.Program =
|
||||||
ctx: TypeCheckerCtx
|
given ctx: TypeCheckerCtx = TypeCheckerCtx(errors)
|
||||||
): microWacc.Program =
|
|
||||||
microWacc.Program(
|
microWacc.Program(
|
||||||
// Ignore function syntax types for return value and params, since those have been converted
|
// Ignore function syntax types for return value and params, since those have been converted
|
||||||
// to SemTypes by the renamer.
|
// to SemTypes by the renamer.
|
||||||
prog.funcs.map { case ast.FuncDecl(_, name, params, stmts) =>
|
prog.funcs.map { case ast.FuncDecl(_, name, params, stmts) =>
|
||||||
val FuncType(retType, paramTypes) = ctx.funcType(name)
|
val FuncType(retType, paramTypes) = name.ty.asInstanceOf[FuncType]
|
||||||
microWacc.FuncDecl(
|
microWacc.FuncDecl(
|
||||||
microWacc.Ident(name.v, name.uid)(retType),
|
microWacc.Ident(name.v, name.guid)(retType),
|
||||||
params.zip(paramTypes).map { case (ast.Param(_, ident), ty) =>
|
params.zip(paramTypes).map { case (ast.Param(_, ident), ty) =>
|
||||||
microWacc.Ident(ident.v, ident.uid)(ty)
|
microWacc.Ident(ident.v, ident.guid)(ty)
|
||||||
},
|
},
|
||||||
stmts.toList
|
stmts.toList
|
||||||
.flatMap(
|
.flatMap(
|
||||||
@@ -134,15 +128,20 @@ object typeChecker {
|
|||||||
): List[microWacc.Stmt] = stmt match {
|
): List[microWacc.Stmt] = stmt match {
|
||||||
// Ignore the type of the variable, since it has been converted to a SemType by the renamer.
|
// Ignore the type of the variable, since it has been converted to a SemType by the renamer.
|
||||||
case ast.VarDecl(_, name, value) =>
|
case ast.VarDecl(_, name, value) =>
|
||||||
val expectedTy = ctx.typeOf(name)
|
val expectedTy = name.ty
|
||||||
val typedValue = checkValue(
|
val typedValue = checkValue(
|
||||||
value,
|
value,
|
||||||
Constraint.Is(
|
Constraint.Is(
|
||||||
expectedTy,
|
expectedTy.asInstanceOf[SemType],
|
||||||
s"variable ${name.v} must be assigned a value of type $expectedTy"
|
s"variable ${name.v} must be assigned a value of type $expectedTy"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
List(microWacc.Assign(microWacc.Ident(name.v, name.uid)(expectedTy), typedValue))
|
List(
|
||||||
|
microWacc.Assign(
|
||||||
|
microWacc.Ident(name.v, name.guid)(expectedTy.asInstanceOf[SemType]),
|
||||||
|
typedValue
|
||||||
|
)
|
||||||
|
)
|
||||||
case ast.Assign(lhs, rhs) =>
|
case ast.Assign(lhs, rhs) =>
|
||||||
val lhsTyped = checkLValue(lhs, Constraint.Unconstrained)
|
val lhsTyped = checkLValue(lhs, Constraint.Unconstrained)
|
||||||
val rhsTyped =
|
val rhsTyped =
|
||||||
@@ -315,7 +314,7 @@ object typeChecker {
|
|||||||
KnownType.Pair(fstTyped.ty, sndTyped.ty).satisfies(constraint, l.pos)
|
KnownType.Pair(fstTyped.ty, sndTyped.ty).satisfies(constraint, l.pos)
|
||||||
)
|
)
|
||||||
case ast.Call(id, args) =>
|
case ast.Call(id, args) =>
|
||||||
val funcTy @ FuncType(retTy, paramTys) = ctx.funcType(id)
|
val funcTy @ FuncType(retTy, paramTys) = id.ty.asInstanceOf[FuncType]
|
||||||
if (args.length != paramTys.length) {
|
if (args.length != paramTys.length) {
|
||||||
ctx.error(Error.FunctionParamsMismatch(id, paramTys.length, args.length, funcTy))
|
ctx.error(Error.FunctionParamsMismatch(id, paramTys.length, args.length, funcTy))
|
||||||
}
|
}
|
||||||
@@ -324,7 +323,7 @@ object typeChecker {
|
|||||||
val argsTyped = args.zip(paramTys).map { case (arg, paramTy) =>
|
val argsTyped = args.zip(paramTys).map { case (arg, paramTy) =>
|
||||||
checkValue(arg, Constraint.Is(paramTy, s"argument type mismatch in function ${id.v}"))
|
checkValue(arg, Constraint.Is(paramTy, s"argument type mismatch in function ${id.v}"))
|
||||||
}
|
}
|
||||||
microWacc.Call(microWacc.Ident(id.v, id.uid)(retTy.satisfies(constraint, id.pos)), argsTyped)
|
microWacc.Call(microWacc.Ident(id.v, id.guid)(retTy.satisfies(constraint, id.pos)), argsTyped)
|
||||||
|
|
||||||
// Unary operators
|
// Unary operators
|
||||||
case ast.Negate(x) =>
|
case ast.Negate(x) =>
|
||||||
@@ -416,30 +415,32 @@ object typeChecker {
|
|||||||
private def checkLValue(value: ast.LValue, constraint: Constraint)(using
|
private def checkLValue(value: ast.LValue, constraint: Constraint)(using
|
||||||
ctx: TypeCheckerCtx
|
ctx: TypeCheckerCtx
|
||||||
): microWacc.LValue = value match {
|
): microWacc.LValue = value match {
|
||||||
case id @ ast.Ident(name, uid) =>
|
case id @ ast.Ident(name, guid, ty) =>
|
||||||
microWacc.Ident(name, uid)(ctx.typeOf(id).satisfies(constraint, id.pos))
|
microWacc.Ident(name, guid)(ty.asInstanceOf[SemType].satisfies(constraint, id.pos))
|
||||||
case ast.ArrayElem(id, indices) =>
|
case ast.ArrayElem(id, indices) =>
|
||||||
val arrayTy = ctx.typeOf(id)
|
val arrayTy = id.ty.asInstanceOf[SemType]
|
||||||
val (elemTy, indicesTyped) = indices.mapAccumulate(arrayTy) { (acc, elem) =>
|
val (elemTy, indicesTyped) = indices.mapAccumulate(arrayTy.asInstanceOf[SemType]) {
|
||||||
val idxTyped = checkValue(elem, Constraint.Is(KnownType.Int, "array index must be an int"))
|
(acc, elem) =>
|
||||||
val next = acc match {
|
val idxTyped =
|
||||||
case KnownType.Array(innerTy) => innerTy
|
checkValue(elem, Constraint.Is(KnownType.Int, "array index must be an int"))
|
||||||
case ? => ? // we can keep indexing an unknown type
|
val next = acc match {
|
||||||
case nonArrayTy =>
|
case KnownType.Array(innerTy) => innerTy
|
||||||
ctx.error(
|
case ? => ? // we can keep indexing an unknown type
|
||||||
Error.TypeMismatch(
|
case nonArrayTy =>
|
||||||
elem.pos,
|
ctx.error(
|
||||||
KnownType.Array(?),
|
Error.TypeMismatch(
|
||||||
acc,
|
elem.pos,
|
||||||
"cannot index into a non-array"
|
KnownType.Array(?),
|
||||||
|
acc,
|
||||||
|
"cannot index into a non-array"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
?
|
||||||
?
|
}
|
||||||
}
|
(next, idxTyped)
|
||||||
(next, idxTyped)
|
|
||||||
}
|
}
|
||||||
val firstArrayElem = microWacc.ArrayElem(
|
val firstArrayElem = microWacc.ArrayElem(
|
||||||
microWacc.Ident(id.v, id.uid)(arrayTy),
|
microWacc.Ident(id.v, id.guid)(arrayTy),
|
||||||
indicesTyped.head
|
indicesTyped.head
|
||||||
)(elemTy.satisfies(constraint, value.pos))
|
)(elemTy.satisfies(constraint, value.pos))
|
||||||
val arrayElem = indicesTyped.tail.foldLeft(firstArrayElem) { (acc, idx) =>
|
val arrayElem = indicesTyped.tail.foldLeft(firstArrayElem) { (acc, idx) =>
|
||||||
|
|||||||
@@ -101,16 +101,16 @@ class ParallelExamplesSpec extends AsyncFreeSpec with AsyncIOSpec with BeforeAnd
|
|||||||
|
|
||||||
private def fileIsPendingFrontend(filename: String): Boolean =
|
private def fileIsPendingFrontend(filename: String): Boolean =
|
||||||
List(
|
List(
|
||||||
"^.*extension/examples/invalid/syntax/imports/importBadSyntax.*$",
|
// "^.*extension/examples/invalid/syntax/imports/importBadSyntax.*$",
|
||||||
"^.*extension/examples/invalid/semantics/imports.*$",
|
// "^.*extension/examples/invalid/semantics/imports.*$",
|
||||||
"^.*extension/examples/valid/imports.*$"
|
// "^.*extension/examples/valid/imports.*$"
|
||||||
).exists(filename.matches)
|
).exists(filename.matches)
|
||||||
|
|
||||||
private def fileIsPendingBackend(filename: String): Boolean =
|
private def fileIsPendingBackend(filename: String): Boolean =
|
||||||
List(
|
List(
|
||||||
"^.*extension/examples/invalid/syntax/imports.*$",
|
// "^.*extension/examples/invalid/syntax/imports.*$",
|
||||||
"^.*extension/examples/invalid/semantics/imports.*$",
|
// "^.*extension/examples/invalid/semantics/imports.*$",
|
||||||
"^.*extension/examples/valid/imports.*$"
|
// "^.*extension/examples/valid/imports.*$"
|
||||||
).exists(filename.matches)
|
).exists(filename.matches)
|
||||||
|
|
||||||
private def extractInput(contents: List[String]): String =
|
private def extractInput(contents: List[String]): String =
|
||||||
|
|||||||
Reference in New Issue
Block a user