1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-02 00:41:40 +00:00
millfork/src/main/scala/millfork/parser/AbstractSourceLoadingQueue.scala
2020-07-31 16:07:10 +02:00

145 lines
6.5 KiB
Scala

package millfork.parser
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Paths}
import fastparse.core.Parsed.{Failure, Success}
import millfork.{CompilationFlag, CompilationOptions, Tarjan}
import millfork.node.{AliasDefinitionStatement, DeclarationStatement, ImportStatement, Position, Program}
import scala.collection.mutable
import scala.collection.convert.ImplicitConversionsToScala._
abstract class AbstractSourceLoadingQueue[T](val initialFilenames: List[String],
val includePath: List[String],
val options: CompilationOptions) {
protected val parsedModules: mutable.Map[String, Program] = mutable.Map[String, Program]()
protected val moduleDependecies: mutable.Set[(String, String)] = mutable.Set[(String, String)]()
protected val moduleQueue: mutable.Queue[() => Unit] = mutable.Queue[() => Unit]()
val extension: String = ".mfk"
def standardModules: IndexedSeq[String]
def enqueueStandardModules(): Unit
def pseudoModules: List[DeclarationStatement] = {
val encodingConversionAliases = (options.platform.defaultCodec.name, options.platform.screenCodec.name) match {
// TODO: don't rely on names!
case ("PETSCII", "CBM-Screen") |
("PETSCII-JP", "CBM-Screen-JP") =>
List(AliasDefinitionStatement("__from_screencode", "petscr_to_petscii", important = false),
AliasDefinitionStatement("__to_screencode", "petscii_to_petscr", important = false))
case ("ATASCII", "ATASCII-Screen") =>
List(AliasDefinitionStatement("__from_screencode", "atasciiscr_to_atascii", important = false),
AliasDefinitionStatement("__to_screencode", "atascii_to_atasciiscr", important = false))
case ("Color-Computer", "Color-Computer-Screen") =>
List(AliasDefinitionStatement("__from_screencode", "cocoscr_to_coco", important = false),
AliasDefinitionStatement("__to_screencode", "coco_to_cocoscr", important = false))
case _ => Nil
}
encodingConversionAliases
}
def run(): Program = {
for {
initialFilename <- initialFilenames
startingModule <- options.platform.startingModules
} {
val initialModule = extractName(initialFilename)
moduleDependecies += initialModule -> startingModule
for (standardModule <- standardModules) {
moduleDependecies += initialModule -> standardModule
moduleDependecies += startingModule -> standardModule
}
}
for {
earlier <- standardModules.indices
later <- (earlier + 1) until standardModules.length
} {
moduleDependecies += standardModules(later) -> standardModules(earlier)
}
initialFilenames.foreach { i =>
parseModule(extractName(i), includePath, Right(i), Nil)
}
options.platform.startingModules.foreach {m =>
moduleQueue.enqueue(() => parseModule(m, includePath, Left(None), Nil))
}
enqueueStandardModules()
while (moduleQueue.nonEmpty) {
if (options.flag(CompilationFlag.SingleThreaded)) {
moduleQueue.dequeueAll(_ => true).foreach(_())
} else {
moduleQueue.dequeueAll(_ => true).par.foreach(_())
}
}
options.log.assertNoErrors("Parse failed")
val compilationOrder = Tarjan.sort(parsedModules.keys, moduleDependecies)
options.log.debug("Compilation order: " + compilationOrder.mkString(", "))
compilationOrder.filter(parsedModules.contains).map(parsedModules).reduce(_ + _).applyImportantAliases
}
def lookupModuleFile(includePath: List[String], moduleName: String, position: Option[Position]): String = {
includePath.foreach { dir =>
val file = Paths.get(dir, moduleName + extension).toFile
options.log.debug("Checking " + file)
if (file.exists()) {
return file.getAbsolutePath
}
}
options.log.fatal(s"Module `$moduleName` not found", position)
}
def supportedPragmas: Set[String]
def createParser(filename: String, src: String, parentDir: String, featureConstants: Map[String, Long], pragmas: Set[String]) : MfParser[T]
def fullModuleName(moduleNameBase: String, templateParams: List[String]): String = {
if (templateParams.isEmpty) moduleNameBase else moduleNameBase + templateParams.mkString("<", ",", ">")
}
def parseModule(moduleName: String, includePath: List[String], why: Either[Option[Position], String], templateParams: List[String]): Unit = {
val filename: String = why.fold(p => lookupModuleFile(includePath, moduleName, p), s => s)
options.log.debug(s"Parsing $filename")
val path = Paths.get(filename)
val parentDir = path.toFile.getAbsoluteFile.getParent
val shortFileName = path.getFileName.toString
val PreprocessingResult(src, featureConstants, pragmas) = Preprocessor(options, shortFileName, Files.readAllLines(path, StandardCharsets.UTF_8).toIndexedSeq, templateParams)
for (pragma <- pragmas) {
if (!supportedPragmas(pragma._1) && options.flag(CompilationFlag.BuggyCodeWarning)) {
options.log.warn(s"Unsupported pragma: #pragma ${pragma._1}", Some(Position(moduleName, pragma._2, 1, 0)))
}
}
val parser = createParser(shortFileName, src, parentDir, featureConstants, pragmas.keySet)
options.log.addSource(shortFileName, src.linesIterator.toIndexedSeq)
parsedModules.put("pseudomodule\u0000", Program(pseudoModules))
parser.toAst match {
case Success(prog, _) =>
parsedModules.synchronized {
parsedModules.put(fullModuleName(moduleName, templateParams), prog)
prog.declarations.foreach {
case s@ImportStatement(m, ps) =>
moduleDependecies += moduleName -> m
if (!parsedModules.contains(fullModuleName(m, ps))) {
moduleQueue.enqueue(() => parseModule(m, parentDir :: includePath, Left(s.position), ps))
}
case _ => ()
}
}
case f@Failure(a, b, d) =>
options.log.error(s"Failed to parse the module `$moduleName` in $filename", Some(parser.indexToPosition(f.index, parser.lastLabel)))
if (parser.lastLabel != "") {
options.log.error(s"Syntax error: ${parser.lastLabel} expected", Some(parser.lastPosition))
} else {
options.log.error("Syntax error", Some(parser.lastPosition))
}
}
}
def extractName(i: String): String = {
val noExt = i.stripSuffix(extension)
val lastSlash = noExt.lastIndexOf('/') max noExt.lastIndexOf('\\')
if (lastSlash >= 0) noExt.substring(lastSlash + 1) else noExt
}
}