feat: functional single-threaded imports

This commit is contained in:
2025-03-13 22:24:41 +00:00
parent ee54a1201c
commit 6e592e7d9b
10 changed files with 166 additions and 155 deletions

View File

@@ -2,6 +2,7 @@
# Output: # Output:
# -33 # -33
#
# Exit: # Exit:
# 0 # 0

View File

@@ -2,7 +2,6 @@
# Output: # Output:
# 15 # 15
# 0
# -33 # -33
# 0 # 0
# -33 # -33

View File

@@ -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,10 +111,11 @@ 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 {
frontendResult <- frontend(contents, file)
res <- frontendResult match {
case Left(errors) => case Left(errors) =>
val code = errors.map(err => err.exitCode).toList.min val code = errors.map(err => err.exitCode).toList.min
given errorContent: String = contents
val errorMsg = errors.map(formatError).toIterable.mkString("\n") val errorMsg = errors.map(formatError).toIterable.mkString("\n")
for { for {
_ <- logAction(s"Compilation failed for $filePath\nExit code: $code") _ <- logAction(s"Compilation failed for $filePath\nExit code: $code")
@@ -126,6 +129,7 @@ def compile(
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

View File

@@ -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}"
} }

View File

@@ -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")
} }

View File

@@ -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

View File

@@ -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
@@ -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,7 +171,19 @@ 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
@@ -176,7 +191,7 @@ object renamer {
// 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,21 +207,50 @@ 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])
) { case ((chunks, errors), Import(name, funcs)) =>
val importFile = File(file.getParent, name.v) 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 {
(importChunks, importErrors) <- preparedImports (importChunks, importErrors) <- preparedImports
@@ -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 {
case (func @ FuncDecl(retType, name, params, body), subscope) =>
val paramErrors = params.foldMap { param => subscope.add(param.name) } val paramErrors = params.foldMap { param => subscope.add(param.name) }
val bodyErrors = subscope.withSubscope { s => body.foldMap(rename(s)) } val bodyErrors = subscope.withSubscope { s => body.foldMap(rename(s)) }
paramErrors ++ bodyErrors 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
} }
} }

View File

@@ -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,12 +415,14 @@ 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 idxTyped =
checkValue(elem, Constraint.Is(KnownType.Int, "array index must be an int"))
val next = acc match { val next = acc match {
case KnownType.Array(innerTy) => innerTy case KnownType.Array(innerTy) => innerTy
case ? => ? // we can keep indexing an unknown type case ? => ? // we can keep indexing an unknown type
@@ -439,7 +440,7 @@ object typeChecker {
(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) =>

View File

@@ -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 =