Skip to content

Commit

Permalink
refactor: merged evaluation and checking into a single pass
Browse files Browse the repository at this point in the history
  • Loading branch information
d01c2 committed Feb 10, 2025
1 parent 58a57c7 commit 58123e5
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 62 deletions.
13 changes: 1 addition & 12 deletions src/main/scala/esmeta/interpreter/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,9 @@ class Interpreter(
val detail: Boolean = false,
val logPW: Option[PrintWriter] = None,
val timeLimit: Option[Int] = None,
val tyCheck: Boolean = false,
) {
import Interpreter.*

/** base state */
val baseSt: State = st.copied

/** initial time */
lazy val startTime: Long = System.currentTimeMillis

Expand All @@ -48,12 +44,6 @@ class Interpreter(
/** final state */
lazy val result: State =
while (step) {}
if (tyCheck)
val dtc = new TypeChecker(baseSt)
while (dtc.step) {}
for (error <- dtc.errors)
println(error)
println(LINE_SEP)
if (log)
pw.println(st)
pw.close
Expand Down Expand Up @@ -477,8 +467,7 @@ object Interpreter {
detail: Boolean = false,
logPW: Option[PrintWriter] = None,
timeLimit: Option[Int] = None,
tyCheck: Boolean = false,
): State = new Interpreter(st, log, detail, logPW, timeLimit, tyCheck).result
): State = new Interpreter(st, log, detail, logPW, timeLimit).result

/** transition for lexical SDO */
def eval(lex: Lexical, sdoName: String): Value = {
Expand Down
11 changes: 10 additions & 1 deletion src/main/scala/esmeta/interpreter/TypeChecker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ class TypeChecker(st: State) extends Interpreter(st) {
import tyStringifier.given

// detected type errors while dynamic type checking
val errors: MSet[TypeError] = MSet()
private val errors: MSet[TypeError] = MSet()
protected def addError(error: TypeError): Unit = errors += error

// final state with collected type errors
lazy val collect: (State, Set[TypeError]) =
while (step) {}
(st, errors.toSet)

// transition for normal instructions (overrided for parameter type checking)
override def eval(inst: NormalInst): Unit = inst match {
case ret @ IReturn(expr) =>
Expand Down Expand Up @@ -71,3 +76,7 @@ class TypeChecker(st: State) extends Interpreter(st) {
map
}
}

object TypeChecker {
def apply(st: State): (State, Set[TypeError]) = new TypeChecker(st).collect
}
48 changes: 39 additions & 9 deletions src/main/scala/esmeta/phase/Eval.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,68 @@ package esmeta.phase
import esmeta.*
import esmeta.cfg.CFG
import esmeta.interpreter.*
import esmeta.ty.{*, given}
import esmeta.state.*
import esmeta.util.*
import esmeta.util.SystemUtils.*
import esmeta.es.*
import scala.collection.mutable.{Map => MMap}

/** `eval` phase */
case object Eval extends Phase[CFG, State] {
val name = "eval"
val help = "evaluates an ECMAScript file."

val totalErrors: MMap[TypeError, Set[String]] = MMap()

def apply(
cfg: CFG,
cmdConfig: CommandConfig,
config: Config,
): State =
if (config.multiple)
if (config.multiple) {
var st = State(cfg, Context(cfg.main))
for {
path <- cmdConfig.targets
file <- walkTree(path)
filename = file.toString
if jsFilter(filename)
} st = run(cfg, config, filename)
if (config.tyCheck)
val pw = getPrintWriter(EVAL_LOG_DIR)
val sorted: Vector[TypeError] =
totalErrors.iterator.toVector
.sortBy { case (_, tests) => -tests.size }
.map(_._1)
pw.println(s"${sorted.length} type errors detected." + LINE_SEP)
for { error <- sorted } do {
pw.println(error)
pw.println(s"- Found in ${totalErrors(error).size} file(s)")
val sample = totalErrors(error).head.drop(BASE_DIR.length + 1)
pw.println(s" - sample: ${sample}")
pw.println(LINE_SEP)
pw.flush
}
st
else run(cfg, config, getFirstFilename(cmdConfig, this.name))
} else run(cfg, config, getFirstFilename(cmdConfig, this.name))

def run(cfg: CFG, config: Config, filename: String): State = Interpreter(
cfg.init.fromFile(filename),
log = config.log,
detail = config.detail,
timeLimit = config.timeLimit,
tyCheck = config.tyCheck,
)
def run(cfg: CFG, config: Config, filename: String): State =
if (config.tyCheck) {
val (finalSt, errors) = TypeChecker(cfg.init.fromFile(filename))
if (config.multiple) {
for { error <- errors } do {
val updated = totalErrors.getOrElse(error, Set()) + filename
totalErrors += error -> updated
}
} else for (error <- errors) println(error.toString + LINE_SEP)
finalSt
} else
Interpreter(
cfg.init.fromFile(filename),
log = config.log,
detail = config.detail,
timeLimit = config.timeLimit,
)

def defaultConfig: Config = Config()
val options: List[PhaseOption[Config]] = List(
Expand Down
88 changes: 48 additions & 40 deletions src/main/scala/esmeta/test262/Test262.scala
Original file line number Diff line number Diff line change
Expand Up @@ -149,32 +149,6 @@ case class Test262(
val tyStringifier = TyElem.getStringifier(true, false)
import tyStringifier.given

// detected type errors while dynamic type checking
val totalErrors: MMap[TypeError, MSet[Test]] = MMap()
def addErrors(errors: MSet[TypeError], test: Test): Unit =
val taggedErrors = errors.map(error => error -> test)
for ((error, test) <- taggedErrors) totalErrors.get(error) match
case Some(tests) => tests += test
case None => totalErrors += (error -> MSet(test))

// helper dump function for type errors
def dumpTypeErrors(baseDir: String): Unit =
val dtcPW = getPrintWriter(s"$baseDir/errors")
val sorted: Vector[TypeError] =
totalErrors.iterator.toVector
.sortBy { case (_, tests) => -tests.size }
.map(_._1)
dtcPW.println(s"${sorted.length} type errors detected in Test262 tests")
dtcPW.println(LINE_SEP)
for { error <- sorted } do {
dtcPW.println(error)
dtcPW.println(s"- Found in ${totalErrors(error).size} test(s)")
dtcPW.println(s" - sample: ${totalErrors(error).head.relName}")
dtcPW.println(LINE_SEP)
dtcPW.flush
}
dtcPW.close()

// get progress bar for extracted tests
val progressBar = getProgressBar(
name = "eval",
Expand Down Expand Up @@ -204,14 +178,16 @@ case class Test262(
// check final execution status of each Test262 test
check = test =>
val filename = test.path
if (tyCheck)
val (ast, code) = loadTest(filename)
val dtc = new TypeChecker(cfg.init.from(code, ast))
while (dtc.step) {}
addErrors(dtc.errors, test)
val st =
if (!useCoverage)
evalFile(filename, log && !multiple, detail, Some(logPW), timeLimit)
evalFile(
filename,
log && !multiple,
detail,
Some(logPW),
timeLimit,
tyCheck,
)
else {
val (ast, code) = loadTest(filename)
cov.runAndCheck(Script(code, filename), ast)._1
Expand Down Expand Up @@ -297,9 +273,10 @@ case class Test262(
detail: Boolean = false,
logPW: Option[PrintWriter] = None,
timeLimit: Option[Int] = None,
tyCheck: Boolean,
): State =
val (ast, code) = loadTest(filename)
eval(code, ast, log, detail, logPW, timeLimit)
eval(code, ast, log, detail, logPW, timeLimit, filename, tyCheck)

// eval ECMAScript code
private def eval(
Expand All @@ -309,15 +286,25 @@ case class Test262(
detail: Boolean = false,
logPW: Option[PrintWriter] = None,
timeLimit: Option[Int] = None,
filename: String,
tyCheck: Boolean,
): State =
val st = cfg.init.from(code, ast)
Interpreter(
st = st,
log = log,
detail = detail,
logPW = logPW,
timeLimit = timeLimit,
)
if (tyCheck) {
val (finalSt, errors) = TypeChecker(st)
for { error <- errors } do {
val updated = totalErrors.getOrElse(error, Set()) + filename
totalErrors += error -> updated
}
finalSt
} else
Interpreter(
st = st,
log = log,
detail = detail,
logPW = logPW,
timeLimit = timeLimit,
)

// logging mode for tests
private def logForTests(
Expand Down Expand Up @@ -352,5 +339,26 @@ case class Test262(

// post job
postJob(logDir)

// detected type errors while dynamic type checking
private val totalErrors: MMap[TypeError, Set[String]] = MMap()

// helper dump function for type errors
private def dumpTypeErrors(baseDir: String): Unit =
val dtcPW = getPrintWriter(s"$baseDir/errors")
val sorted: Vector[TypeError] =
totalErrors.iterator.toVector
.sortBy { case (_, tests) => -tests.size }
.map(_._1)
dtcPW.println(s"${sorted.length} type errors detected." + LINE_SEP)
for { error <- sorted } do {
dtcPW.println(error)
dtcPW.println(s"- Found in ${totalErrors(error).size} test(s)")
val sample = totalErrors(error).head.drop(TEST262_TEST_DIR.length + 1)
dtcPW.println(s" - sample: ${sample}")
dtcPW.println(LINE_SEP)
dtcPW.flush
}
dtcPW.close()
}
object Test262 extends Git(TEST262_DIR)

0 comments on commit 58123e5

Please sign in to comment.