update kotlin version

cleaning up the way the root of the Ast and the global namespace work (introduced ProgramAst node)
This commit is contained in:
Irmen de Jong 2019-06-20 00:34:55 +02:00
parent 6a17f7a0ad
commit e96d3d4455
14 changed files with 242 additions and 199 deletions

View File

@ -1,5 +1,5 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.3.30"
id "org.jetbrains.kotlin.jvm" version "1.3.40"
id 'application'
}
@ -8,7 +8,7 @@ repositories {
jcenter()
}
def kotlinVersion = '1.3.30'
def kotlinVersion = '1.3.40'
dependencies {
implementation project(':parser')

View File

@ -1 +1 @@
1.7
1.8

View File

@ -8,6 +8,7 @@ import prog8.optimizing.constantFold
import prog8.optimizing.optimizeStatements
import prog8.optimizing.simplifyExpressions
import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule
import prog8.parser.importModule
import java.io.File
import java.io.PrintStream
@ -71,60 +72,65 @@ private fun compileMain(args: Array<String>) {
try {
val totalTime = measureTimeMillis {
// import main module and process additional imports
// import main module and everything it needs
println("Parsing...")
val moduleAst = importModule(filepath)
moduleAst.linkParents()
var namespace = moduleAst.definingScope()
// determine special compiler options
val compilerOptions = determineCompilationOptions(moduleAst)
val programAst = Program(filepath.fileName.toString(), mutableListOf())
importModule(programAst, filepath)
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${moduleAst.position} BASIC launcher requires output type PRG.")
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
if(compilerOptions.launcher==LauncherType.BASIC || compilerOptions.output==OutputType.PRG) {
importLibraryModule(programAst, "c64lib")
importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importLibraryModule(programAst, "math")
importLibraryModule(programAst, "prog8lib")
// perform initial syntax checks and constant folding
println("Syntax check...")
val heap = HeapValues()
val time1= measureTimeMillis {
moduleAst.checkIdentifiers(namespace)
programAst.checkIdentifiers()
}
//println(" time1: $time1")
val time2 = measureTimeMillis {
moduleAst.constantFold(namespace, heap)
programAst.constantFold()
}
//println(" time2: $time2")
val time3 = measureTimeMillis {
moduleAst.reorderStatements(namespace,heap) // reorder statements to please the compiler later
programAst.reorderStatements() // reorder statements to please the compiler later
}
//println(" time3: $time3")
val time4 = measureTimeMillis {
moduleAst.checkValid(namespace, compilerOptions, heap) // check if tree is valid
programAst.checkValid(compilerOptions) // check if tree is valid
}
//println(" time4: $time4")
moduleAst.checkIdentifiers(namespace)
programAst.checkIdentifiers()
if(optimize) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = moduleAst.simplifyExpressions(namespace, heap)
val optsDone2 = moduleAst.optimizeStatements(namespace, heap)
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements()
if (optsDone1 + optsDone2 == 0)
break
}
}
namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime
moduleAst.checkValid(namespace, compilerOptions, heap) // check if final tree is valid
moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
// namespace.debugPrint()
// compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(moduleAst, namespace, heap)
val compiler = Compiler(programAst)
val intermediate = compiler.compile(compilerOptions)
if(optimize)
intermediate.optimize()
@ -140,7 +146,7 @@ private fun compileMain(args: Array<String>) {
if(writeAssembly) {
val zeropage = C64Zeropage(compilerOptions)
intermediate.allocateZeropage(zeropage)
val assembly = AsmGen(compilerOptions, intermediate, heap, zeropage).compileToAssembly(optimize)
val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programname = assembly.name
}
@ -180,7 +186,9 @@ private fun compileMain(args: Array<String>) {
}
}
fun determineCompilationOptions(moduleAst: Module): CompilationOptions {
fun determineCompilationOptions(program: Program): CompilationOptions {
val moduleAst = program.modules.first()
val options = moduleAst.statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val outputType = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()

View File

@ -130,6 +130,10 @@ data class Position(val file: String, val line: Int, val startCol: Int, val endC
interface IAstProcessor {
fun process(program: Program) {
program.modules.forEach { process(it) }
}
fun process(module: Module) {
module.statements = module.statements.asSequence().map { it.process(this) }.toMutableList()
}
@ -301,6 +305,13 @@ interface Node {
val position: Position
var parent: Node // will be linked correctly later (late init)
fun linkParents(parent: Node)
fun definingModule(): Module {
if(this is Module)
return this
return findParentNode<Module>(this)!!
}
fun definingScope(): INameScope {
val scope = findParentNode<INameScope>(this)
if(scope!=null) {
@ -398,22 +409,27 @@ interface INameScope {
fun allLabelsAndVariables(): Set<String> =
statements.filterIsInstance<Label>().map { it.name }.toSet() + statements.filterIsInstance<VarDecl>().map { it.name }.toSet()
fun lookup(scopedName: List<String>, statement: Node) : IStatement? {
fun lookup(scopedName: List<String>, localContext: Node) : IStatement? {
if(scopedName.size>1) {
// it's a qualified name, look it up from the namespace root
var scope: INameScope? = this
scopedName.dropLast(1).forEach {
scope = scope?.subScopes()?.get(it)
if(scope==null)
return null
// it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
for(module in localContext.definingModule().program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScopes()?.get(name)
if(scope==null)
break
}
if(scope!=null) {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScopes()[scopedName.last()] as IStatement?
}
}
val foundScope : INameScope = scope!!
return foundScope.getLabelOrVariable(scopedName.last())
?:
foundScope.subScopes()[scopedName.last()] as IStatement?
return null
} else {
// unqualified name, find the scope the statement is in, look in that first
var statementScope = statement
// unqualified name, find the scope the localContext is in, look in that first
var statementScope = localContext
while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope()
val result = localScope.getLabelOrVariable(scopedName[0])
@ -460,12 +476,26 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
}
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String, val modules: MutableList<Module>) {
val namespace = GlobalNamespace(modules)
val heap = HeapValues()
val loadAddress: Int
get() = modules.first().loadAddress
}
class Module(override val name: String,
override var statements: MutableList<IStatement>,
override val position: Position,
val isLibraryModule: Boolean,
val importedFrom: Path) : Node, INameScope {
val source: Path) : Node, INameScope {
override lateinit var parent: Node
lateinit var program: Program
override fun linkParents(parent: Node) {
this.parent=parent
@ -478,28 +508,31 @@ class Module(override val name: String,
statements.forEach {it.linkParents(this)}
}
fun process(processor: IAstProcessor) {
processor.process(this)
}
override fun definingScope(): INameScope = program.namespace
override fun definingScope(): INameScope = GlobalNamespace("<<<global>>>", statements, position)
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
}
private class GlobalNamespace(override val name: String,
override var statements: MutableList<IStatement>,
override val position: Position) : INameScope {
override var parent = ParentSentinel
override fun linkParents(parent: Node) {}
class GlobalNamespace(val modules: List<Module>): INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<IStatement>()
override val parent: Node = ParentSentinel
override fun lookup(scopedName: List<String>, statement: Node): IStatement? {
if(scopedName.size==1 && scopedName[0] in BuiltinFunctions) {
// builtin functions always exist, return a dummy statement for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", statement.position)
override fun linkParents(parent: Node) {
modules.forEach { it.linkParents(ParentSentinel) }
}
override fun lookup(scopedName: List<String>, localContext: Node): IStatement? {
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
// builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder
}
val stmt = super.lookup(scopedName, statement)
val stmt = localContext.definingModule().lookup(scopedName, localContext)
return when (stmt) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null
@ -813,6 +846,7 @@ data class AssignTarget(val register: Register?,
interface IExpression: Node {
// TODO pass programAst instead of namespace + heap?
fun isIterable(namespace: INameScope, heap: HeapValues): Boolean
fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue?
fun process(processor: IAstProcessor): IExpression
@ -1816,9 +1850,9 @@ class RepeatLoop(var body: AnonymousScope,
/***************** Antlr Extension methods to create AST ****************/
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, importedFrom: Path) : Module {
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, importedFrom)
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source)
}

View File

@ -13,9 +13,9 @@ import java.io.File
* General checks on the Ast
*/
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions, heap: HeapValues) {
val checker = AstChecker(globalNamespace, compilerOptions, heap)
this.process(checker)
fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(namespace, compilerOptions, heap)
checker.process(this)
printErrors(checker.result(), name)
}
@ -66,6 +66,56 @@ private class AstChecker(private val namespace: INameScope,
return checkResult
}
override fun process(program: Program) {
// there must be a single 'main' block with a 'start' subroutine for the program entry point.
val mainBlocks = program.modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
if(mainBlocks.size>1)
checkResult.add(SyntaxError("more than one 'main' block", mainBlocks[0].position))
for(mainBlock in mainBlocks) {
val startSub = mainBlock.subScopes().get("start") as? Subroutine
if (startSub == null) {
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position))
} else {
if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", startSub.position))
}
// the main module cannot contain 'regular' statements (they will never be executed!)
for (statement in mainBlock.statements) {
val ok = when (statement) {
is Block -> true
is Directive -> true
is Label -> true
is VarDecl -> true
is InlineAssembly -> true
is INameScope -> true
is VariableInitializationAssignment -> true
else -> false
}
if (!ok) {
checkResult.add(SyntaxError("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position))
break
}
}
}
// there can be an optional single 'irq' block with a 'irq' subroutine in it,
// which will be used as the 60hz irq routine in the vm if it's present
val irqBlocks = program.modules.flatMap { it.statements }.filter { it is Block && it.name=="irq" }.map { it as Block }
if(irqBlocks.size>1)
checkResult.add(SyntaxError("more than one 'irq' block", irqBlocks[0].position))
for(irqBlock in irqBlocks) {
val irqSub = irqBlock?.subScopes()?.get("irq") as? Subroutine
if (irqSub != null) {
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position))
}
}
super.process(program)
}
override fun process(module: Module) {
super.process(module)
val directives = module.statements.filterIsInstance<Directive>().groupBy { it.directive }
@ -75,45 +125,6 @@ private class AstChecker(private val namespace: INameScope,
entry.value.mapTo(checkResult) { SyntaxError("directive can just occur once", it.position) }
}
}
// there must be a 'main' block with a 'start' subroutine for the program entry point.
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" } as? Block?
val startSub = mainBlock?.subScopes()?.get("start") as? Subroutine
if(startSub==null) {
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", module.position))
} else {
if(startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", startSub.position))
}
if(mainBlock!=null) {
// the main module cannot contain 'regular' statements (they will never be executed!)
for (statement in mainBlock.statements) {
val ok = when(statement) {
is Block->true
is Directive->true
is Label->true
is VarDecl->true
is InlineAssembly->true
is INameScope->true
is VariableInitializationAssignment->true
else->false
}
if(!ok) {
checkResult.add(SyntaxError("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position))
break
}
}
}
// there can be an optional 'irq' block with a 'irq' subroutine in it,
// which will be used as the 60hz irq routine in the vm if it's present
val irqBlock = module.statements.singleOrNull { it is Block && it.name=="irq" } as? Block?
val irqSub = irqBlock?.subScopes()?.get("irq") as? Subroutine
if(irqSub!=null) {
if(irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position))
}
}
override fun process(returnStmt: Return): IStatement {
@ -661,7 +672,7 @@ private class AstChecker(private val namespace: INameScope,
var definingModule = directive.parent
while (definingModule !is Module)
definingModule = definingModule.parent
if (!(filename.startsWith("library:") || definingModule.importedFrom.resolveSibling(filename).toFile().isFile || File(filename).isFile))
if (!(filename.startsWith("library:") || definingModule.source.resolveSibling(filename).toFile().isFile || File(filename).isFile))
checkResult.add(NameError("included file not found: $filename", directive.position))
}

View File

@ -8,9 +8,13 @@ import prog8.functions.BuiltinFunctions
* Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly.
*/
fun Module.checkIdentifiers(namespace: INameScope) {
fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(namespace)
this.process(checker)
checker.process(this)
if(modules.map {it.name}.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
}
// add any anonymous variables for heap values that are used,
// and replace an iterable literalvalue by identifierref to new local variable
@ -58,6 +62,11 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
}
override fun process(module: Module) {
blocks.clear() // blocks may be redefined within a different module
super.process(module)
}
override fun process(block: Block): IStatement {
val existing = blocks[block.name]
if(existing!=null)

View File

@ -4,9 +4,9 @@ package prog8.ast
* Checks for the occurrence of recursive subroutine calls
*/
fun Module.checkRecursion(namespace: INameScope) {
fun Program.checkRecursion() {
val checker = AstRecursionChecker(namespace)
this.process(checker)
checker.process(this)
printErrors(checker.result(), name)
}

View File

@ -6,9 +6,9 @@ package prog8.ast
*/
fun Module.checkImportedValid() {
val checker = ImportedAstChecker()
this.linkParents()
this.process(checker)
val checker = ImportedAstChecker()
checker.process(this)
printErrors(checker.result(), name)
}

View File

@ -2,12 +2,12 @@ package prog8.ast
import prog8.compiler.HeapValues
fun Module.reorderStatements(namespace: INameScope, heap: HeapValues) {
fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
this.process(initvalueCreator)
initvalueCreator.process(this)
val checker = StatementReorderer(namespace, heap)
this.process(checker)
checker.process(this)
}
const val initvarsSubName="prog8_init_vars" // the name of the subroutine that should be called for every block to initialize its variables
@ -42,8 +42,8 @@ private class StatementReorderer(private val namespace: INameScope, private val
module.statements.removeAt(nonLibBlock.first)
for(nonLibBlock in nonLibraryBlocks)
module.statements.add(0, nonLibBlock.second)
val mainBlock = module.statements.single { it is Block && it.name=="main" }
if((mainBlock as Block).address==null) {
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
if(mainBlock!=null && (mainBlock as Block).address==null) {
module.statements.remove(mainBlock)
module.statements.add(0, mainBlock)
}
@ -103,6 +103,7 @@ private class StatementReorderer(private val namespace: INameScope, private val
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
block.statements.removeAll(directives)
block.statements.addAll(0, directives)
block.linkParents(block.parent)
sortConstantAssignments(block.statements)

View File

@ -145,11 +145,11 @@ data class CompilationOptions(val output: OutputType,
val floats: Boolean)
internal class Compiler(private val rootModule: Module,
private val namespace: INameScope,
private val heap: HeapValues): IAstProcessor {
internal class Compiler(private val programAst2: Program): IAstProcessor {
val prog: IntermediateProgram = IntermediateProgram(rootModule.name, rootModule.loadAddress, heap, rootModule.importedFrom)
val prog: IntermediateProgram = IntermediateProgram(programAst2.name, programAst2.loadAddress, programAst2.heap, programAst2.modules.first().source)
val namespace = programAst2.namespace
val heap = programAst2.heap
private var generatedLabelSequenceNumber = 0
private val breakStmtLabelStack : Stack<String> = Stack()
@ -157,7 +157,9 @@ internal class Compiler(private val rootModule: Module,
fun compile(options: CompilationOptions) : IntermediateProgram {
println("Creating stackVM code...")
process(rootModule)
programAst2.modules.forEach {
process(it)
}
return prog
}
@ -218,7 +220,7 @@ internal class Compiler(private val rootModule: Module,
is Return -> translate(stmt)
is Directive -> {
when(stmt.directive) {
"%asminclude" -> translateAsmInclude(stmt.args, prog.importedFrom)
"%asminclude" -> translateAsmInclude(stmt.args, prog.source)
"%asmbinary" -> translateAsmBinary(stmt.args)
"%breakpoint" -> {
prog.line(stmt.position)
@ -2148,7 +2150,7 @@ internal class Compiler(private val rootModule: Module,
throw CompilerException("cannot take memory pointer $addrof")
}
private fun translateAsmInclude(args: List<DirectiveArg>, importedFrom: Path) {
private fun translateAsmInclude(args: List<DirectiveArg>, source: Path) {
val scopeprefix = if(args[1].str!!.isNotBlank()) "${args[1].str}\t.proc\n" else ""
val scopeprefixEnd = if(args[1].str!!.isNotBlank()) "\t.pend\n" else ""
val filename=args[0].str!!
@ -2158,7 +2160,7 @@ internal class Compiler(private val rootModule: Module,
resource.bufferedReader().use { it.readText() }
} else {
// first try in the same folder as where the containing file was imported from
val sib = importedFrom.resolveSibling(filename)
val sib = source.resolveSibling(filename)
if(sib.toFile().isFile)
sib.toFile().readText()
else

View File

@ -9,7 +9,7 @@ import java.io.PrintStream
import java.nio.file.Path
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val importedFrom: Path) {
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
class ProgramBlock(val name: String,
var address: Int?,

View File

@ -9,7 +9,7 @@ import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
import kotlin.math.floor
class ConstantFolding(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor {
class ConstantFolding(private val namespace: GlobalNamespace, private val heap: HeapValues) : IAstProcessor {
var optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf()

View File

@ -1,48 +1,46 @@
package prog8.optimizing
import prog8.ast.AstException
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.compiler.HeapValues
import prog8.ast.Program
import prog8.parser.ParsingFailedError
fun Module.constantFold(globalNamespace: INameScope, heap: HeapValues) {
val optimizer = ConstantFolding(globalNamespace, heap)
fun Program.constantFold() {
val optimizer = ConstantFolding(this.namespace, heap)
try {
this.process(optimizer)
optimizer.process(this)
} catch (ax: AstException) {
optimizer.addError(ax)
}
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
optimizer.optimizationsDone = 0
this.process(optimizer)
optimizer.process(this)
}
if(optimizer.errors.isNotEmpty()) {
optimizer.errors.forEach { System.err.println(it) }
throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
} else {
this.linkParents() // re-link in final configuration
modules.forEach { it.linkParents() } // re-link in final configuration
}
}
fun Module.optimizeStatements(globalNamespace: INameScope, heap: HeapValues): Int {
val optimizer = StatementOptimizer(globalNamespace, heap)
this.process(optimizer)
fun Program.optimizeStatements(): Int {
val optimizer = StatementOptimizer(namespace, heap)
optimizer.process(this)
for(stmt in optimizer.statementsToRemove) {
val scope=stmt.definingScope()
scope.remove(stmt)
}
this.linkParents() // re-link in final configuration
modules.forEach { it.linkParents() } // re-link in final configuration
return optimizer.optimizationsDone
}
fun Module.simplifyExpressions(namespace: INameScope, heap: HeapValues) : Int {
fun Program.simplifyExpressions() : Int {
val optimizer = SimplifyExpressions(namespace, heap)
this.process(optimizer)
optimizer.process(this)
return optimizer.optimizationsDone
}

View File

@ -2,22 +2,15 @@ package prog8.parser
import org.antlr.v4.runtime.*
import prog8.ast.*
import prog8.compiler.LauncherType
import prog8.compiler.OutputType
import prog8.determineCompilationOptions
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
class ParsingFailedError(override var message: String) : Exception(message)
private val importedModules : HashMap<String, Module> = hashMapOf()
private class LexerErrorListener: BaseErrorListener() {
var numberOfErrors: Int = 0
override fun syntaxError(p0: Recognizer<*, *>?, p1: Any?, p2: Int, p3: Int, p4: String?, p5: RecognitionException?) {
@ -29,7 +22,33 @@ private class LexerErrorListener: BaseErrorListener() {
internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
fun importModule(program: Program, filePath: Path): Module {
print("importing '${filePath.fileName}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString()
if(importloc.startsWith(curdir))
importloc = "." + importloc.substring(curdir.length)
println(" (from '$importloc')")
}
else
println("")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
val input = CharStreams.fromPath(filePath)
val module = importModule(program, input, filePath, filePath.parent==null)
return module
}
fun importLibraryModule(program: Program, name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0 ,0))
), Position("<<<implicit-import>>>", 0, 0 ,0))
return executeImportDirective(program, import, Paths.get(""))
}
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = modulePath.fileName
val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener()
@ -46,67 +65,24 @@ fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Modu
// convert to Ast
val moduleAst = parseTree.toAst(moduleName.toString(), isLibrary, modulePath)
importedModules[moduleAst.name] = moduleAst
moduleAst.program = program
moduleAst.linkParents()
program.modules.add(moduleAst)
// process imports
val lines = moduleAst.statements.toMutableList()
if(!moduleAst.position.file.startsWith("c64utils.") && !moduleAst.isLibraryModule) {
// if the output is a PRG or BASIC program, include the c64utils library
val compilerOptions = determineCompilationOptions(moduleAst)
if(compilerOptions.launcher==LauncherType.BASIC || compilerOptions.output==OutputType.PRG) {
lines.add(0, Directive("%import", listOf(DirectiveArg(null, "c64utils", null, moduleAst.position)), moduleAst.position))
}
}
// always import the prog8lib and math compiler libraries
if(!moduleAst.position.file.startsWith("math."))
lines.add(0, Directive("%import", listOf(DirectiveArg(null, "math", null, moduleAst.position)), moduleAst.position))
if(!moduleAst.position.file.startsWith("prog8lib."))
lines.add(0, Directive("%import", listOf(DirectiveArg(null, "prog8lib", null, moduleAst.position)), moduleAst.position))
val imports = lines
.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.map { Pair(it.first, executeImportDirective(it.second as Directive, modulePath)) }
.toList()
imports.reversed().forEach {
if(it.second==null) {
// this import was already satisfied. just remove this line.
lines.removeAt(it.first)
} else {
// merge imported lines at this spot
lines.addAll(it.first, it.second!!.statements)
}
}
lines.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
moduleAst.statements = lines
return moduleAst
}
fun importModule(filePath: Path) : Module {
print("importing '${filePath.fileName}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString()
if(importloc.startsWith(curdir))
importloc = "." + importloc.substring(curdir.length)
println(" (from '$importloc')")
}
else
println("")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
val input = CharStreams.fromPath(filePath)
return importModule(input, filePath, filePath.parent==null)
}
private fun discoverImportedModuleFile(name: String, importedFrom: Path, position: Position?): Path {
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8"
val locations = mutableListOf(Paths.get(importedFrom.parent.toString()))
val locations = mutableListOf(Paths.get(source.parent.toString()))
val propPath = System.getProperty("prog8.libdir")
if(propPath!=null)
@ -124,13 +100,15 @@ private fun discoverImportedModuleFile(name: String, importedFrom: Path, positio
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
}
private fun executeImportDirective(import: Directive, importedFrom: Path): Module? {
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
if(importedModules.containsKey(moduleName))
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null
val resource = tryGetEmbeddedResource(moduleName+".p8")
@ -138,12 +116,14 @@ private fun executeImportDirective(import: Directive, importedFrom: Path): Modul
if(resource!=null) {
// load the module from the embedded resource
resource.use {
if(import.args[0].int==42)
print("automatically ")
println("importing '$moduleName' (embedded library)")
importModule(CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
}
} else {
val modulePath = discoverImportedModuleFile(moduleName, importedFrom, import.position)
importModule(modulePath)
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
importModule(program, modulePath)
}
importedModule.checkImportedValid()