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 { plugins {
id "org.jetbrains.kotlin.jvm" version "1.3.30" id "org.jetbrains.kotlin.jvm" version "1.3.40"
id 'application' id 'application'
} }
@ -8,7 +8,7 @@ repositories {
jcenter() jcenter()
} }
def kotlinVersion = '1.3.30' def kotlinVersion = '1.3.40'
dependencies { dependencies {
implementation project(':parser') 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.optimizeStatements
import prog8.optimizing.simplifyExpressions import prog8.optimizing.simplifyExpressions
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule
import prog8.parser.importModule import prog8.parser.importModule
import java.io.File import java.io.File
import java.io.PrintStream import java.io.PrintStream
@ -71,60 +72,65 @@ private fun compileMain(args: Array<String>) {
try { try {
val totalTime = measureTimeMillis { val totalTime = measureTimeMillis {
// import main module and process additional imports // import main module and everything it needs
println("Parsing...") println("Parsing...")
val moduleAst = importModule(filepath) val programAst = Program(filepath.fileName.toString(), mutableListOf())
moduleAst.linkParents() importModule(programAst, filepath)
var namespace = moduleAst.definingScope()
// determine special compiler options
val compilerOptions = determineCompilationOptions(moduleAst)
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG) 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 // perform initial syntax checks and constant folding
println("Syntax check...") println("Syntax check...")
val heap = HeapValues()
val time1= measureTimeMillis { val time1= measureTimeMillis {
moduleAst.checkIdentifiers(namespace) programAst.checkIdentifiers()
} }
//println(" time1: $time1") //println(" time1: $time1")
val time2 = measureTimeMillis { val time2 = measureTimeMillis {
moduleAst.constantFold(namespace, heap) programAst.constantFold()
} }
//println(" time2: $time2") //println(" time2: $time2")
val time3 = measureTimeMillis { 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") //println(" time3: $time3")
val time4 = measureTimeMillis { val time4 = measureTimeMillis {
moduleAst.checkValid(namespace, compilerOptions, heap) // check if tree is valid programAst.checkValid(compilerOptions) // check if tree is valid
} }
//println(" time4: $time4") //println(" time4: $time4")
moduleAst.checkIdentifiers(namespace) programAst.checkIdentifiers()
if(optimize) { if(optimize) {
// optimize the parse tree // optimize the parse tree
println("Optimizing...") println("Optimizing...")
while (true) { while (true) {
// keep optimizing expressions and statements until no more steps remain // keep optimizing expressions and statements until no more steps remain
val optsDone1 = moduleAst.simplifyExpressions(namespace, heap) val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = moduleAst.optimizeStatements(namespace, heap) val optsDone2 = programAst.optimizeStatements()
if (optsDone1 + optsDone2 == 0) if (optsDone1 + optsDone2 == 0)
break break
} }
} }
namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime programAst.checkValid(compilerOptions) // check if final tree is valid
moduleAst.checkValid(namespace, compilerOptions, heap) // check if final tree is valid programAst.checkRecursion() // check if there are recursive subroutine calls
moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls
// namespace.debugPrint() // namespace.debugPrint()
// compile the syntax tree into stackvmProg form, and optimize that // 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) val intermediate = compiler.compile(compilerOptions)
if(optimize) if(optimize)
intermediate.optimize() intermediate.optimize()
@ -140,7 +146,7 @@ private fun compileMain(args: Array<String>) {
if(writeAssembly) { if(writeAssembly) {
val zeropage = C64Zeropage(compilerOptions) val zeropage = C64Zeropage(compilerOptions)
intermediate.allocateZeropage(zeropage) 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) assembly.assemble(compilerOptions)
programname = assembly.name 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 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" } val outputType = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase() 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 { interface IAstProcessor {
fun process(program: Program) {
program.modules.forEach { process(it) }
}
fun process(module: Module) { fun process(module: Module) {
module.statements = module.statements.asSequence().map { it.process(this) }.toMutableList() module.statements = module.statements.asSequence().map { it.process(this) }.toMutableList()
} }
@ -301,6 +305,13 @@ interface Node {
val position: Position val position: Position
var parent: Node // will be linked correctly later (late init) var parent: Node // will be linked correctly later (late init)
fun linkParents(parent: Node) fun linkParents(parent: Node)
fun definingModule(): Module {
if(this is Module)
return this
return findParentNode<Module>(this)!!
}
fun definingScope(): INameScope { fun definingScope(): INameScope {
val scope = findParentNode<INameScope>(this) val scope = findParentNode<INameScope>(this)
if(scope!=null) { if(scope!=null) {
@ -398,22 +409,27 @@ interface INameScope {
fun allLabelsAndVariables(): Set<String> = fun allLabelsAndVariables(): Set<String> =
statements.filterIsInstance<Label>().map { it.name }.toSet() + statements.filterIsInstance<VarDecl>().map { it.name }.toSet() 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) { if(scopedName.size>1) {
// it's a qualified name, look it up from the namespace root // it's a qualified name, look it up from the root of the module's namespace (consider all modules in the program)
var scope: INameScope? = this for(module in localContext.definingModule().program.modules) {
scopedName.dropLast(1).forEach { var scope: INameScope? = module
scope = scope?.subScopes()?.get(it) for(name in scopedName.dropLast(1)) {
if(scope==null) scope = scope?.subScopes()?.get(name)
return null 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 null
return foundScope.getLabelOrVariable(scopedName.last())
?:
foundScope.subScopes()[scopedName.last()] as IStatement?
} else { } else {
// unqualified name, find the scope the statement is in, look in that first // unqualified name, find the scope the localContext is in, look in that first
var statementScope = statement var statementScope = localContext
while(statementScope !is ParentSentinel) { while(statementScope !is ParentSentinel) {
val localScope = statementScope.definingScope() val localScope = statementScope.definingScope()
val result = localScope.getLabelOrVariable(scopedName[0]) val result = localScope.getLabelOrVariable(scopedName[0])
@ -460,12 +476,26 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder 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, class Module(override val name: String,
override var statements: MutableList<IStatement>, override var statements: MutableList<IStatement>,
override val position: Position, override val position: Position,
val isLibraryModule: Boolean, val isLibraryModule: Boolean,
val importedFrom: Path) : Node, INameScope { val source: Path) : Node, INameScope {
override lateinit var parent: Node override lateinit var parent: Node
lateinit var program: Program
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent=parent this.parent=parent
@ -478,28 +508,31 @@ class Module(override val name: String,
statements.forEach {it.linkParents(this)} statements.forEach {it.linkParents(this)}
} }
fun process(processor: IAstProcessor) { override fun definingScope(): INameScope = program.namespace
processor.process(this)
}
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, class GlobalNamespace(val modules: List<Module>): INameScope {
override var statements: MutableList<IStatement>, override val name = "<<<global>>>"
override val position: Position) : INameScope { override val position = Position("<<<global>>>", 0, 0, 0)
override var parent = ParentSentinel override val statements = mutableListOf<IStatement>()
override fun linkParents(parent: Node) {} override val parent: Node = ParentSentinel
override fun lookup(scopedName: List<String>, statement: Node): IStatement? { override fun linkParents(parent: Node) {
if(scopedName.size==1 && scopedName[0] in BuiltinFunctions) { modules.forEach { it.linkParents(ParentSentinel) }
// builtin functions always exist, return a dummy statement for them }
val builtinPlaceholder = Label("builtin::${scopedName.last()}", statement.position)
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 builtinPlaceholder.parent = ParentSentinel
return builtinPlaceholder return builtinPlaceholder
} }
val stmt = super.lookup(scopedName, statement)
val stmt = localContext.definingModule().lookup(scopedName, localContext)
return when (stmt) { return when (stmt) {
is Label, is VarDecl, is Block, is Subroutine -> stmt is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null null -> null
@ -813,6 +846,7 @@ data class AssignTarget(val register: Register?,
interface IExpression: Node { interface IExpression: Node {
// TODO pass programAst instead of namespace + heap?
fun isIterable(namespace: INameScope, heap: HeapValues): Boolean fun isIterable(namespace: INameScope, heap: HeapValues): Boolean
fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue?
fun process(processor: IAstProcessor): IExpression fun process(processor: IAstProcessor): IExpression
@ -1816,9 +1850,9 @@ class RepeatLoop(var body: AnonymousScope,
/***************** Antlr Extension methods to create AST ****************/ /***************** 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 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 * General checks on the Ast
*/ */
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions, heap: HeapValues) { fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(globalNamespace, compilerOptions, heap) val checker = AstChecker(namespace, compilerOptions, heap)
this.process(checker) checker.process(this)
printErrors(checker.result(), name) printErrors(checker.result(), name)
} }
@ -66,6 +66,56 @@ private class AstChecker(private val namespace: INameScope,
return checkResult 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) { override fun process(module: Module) {
super.process(module) super.process(module)
val directives = module.statements.filterIsInstance<Directive>().groupBy { it.directive } 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) } 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 { override fun process(returnStmt: Return): IStatement {
@ -661,7 +672,7 @@ private class AstChecker(private val namespace: INameScope,
var definingModule = directive.parent var definingModule = directive.parent
while (definingModule !is Module) while (definingModule !is Module)
definingModule = definingModule.parent 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)) 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. * 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) 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, // add any anonymous variables for heap values that are used,
// and replace an iterable literalvalue by identifierref to new local variable // 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)) 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 { override fun process(block: Block): IStatement {
val existing = blocks[block.name] val existing = blocks[block.name]
if(existing!=null) if(existing!=null)

View File

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

View File

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

View File

@ -2,12 +2,12 @@ package prog8.ast
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
fun Module.reorderStatements(namespace: INameScope, heap: HeapValues) { fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace) val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
this.process(initvalueCreator) initvalueCreator.process(this)
val checker = StatementReorderer(namespace, heap) 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 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) module.statements.removeAt(nonLibBlock.first)
for(nonLibBlock in nonLibraryBlocks) for(nonLibBlock in nonLibraryBlocks)
module.statements.add(0, nonLibBlock.second) module.statements.add(0, nonLibBlock.second)
val mainBlock = module.statements.single { it is Block && it.name=="main" } val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
if((mainBlock as Block).address==null) { if(mainBlock!=null && (mainBlock as Block).address==null) {
module.statements.remove(mainBlock) module.statements.remove(mainBlock)
module.statements.add(0, 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} val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
block.statements.removeAll(directives) block.statements.removeAll(directives)
block.statements.addAll(0, directives) block.statements.addAll(0, directives)
block.linkParents(block.parent)
sortConstantAssignments(block.statements) sortConstantAssignments(block.statements)

View File

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

View File

@ -9,7 +9,7 @@ import java.io.PrintStream
import java.nio.file.Path 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, class ProgramBlock(val name: String,
var address: Int?, var address: Int?,

View File

@ -9,7 +9,7 @@ import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
import kotlin.math.floor 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 optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf() var errors : MutableList<AstException> = mutableListOf()

View File

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

View File

@ -2,22 +2,15 @@ package prog8.parser
import org.antlr.v4.runtime.* import org.antlr.v4.runtime.*
import prog8.ast.* import prog8.ast.*
import prog8.compiler.LauncherType
import prog8.compiler.OutputType
import prog8.determineCompilationOptions
import java.io.InputStream import java.io.InputStream
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.util.*
class ParsingFailedError(override var message: String) : Exception(message) class ParsingFailedError(override var message: String) : Exception(message)
private val importedModules : HashMap<String, Module> = hashMapOf()
private class LexerErrorListener: BaseErrorListener() { private class LexerErrorListener: BaseErrorListener() {
var numberOfErrors: Int = 0 var numberOfErrors: Int = 0
override fun syntaxError(p0: Recognizer<*, *>?, p1: Any?, p2: Int, p3: Int, p4: String?, p5: RecognitionException?) { 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) 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 moduleName = modulePath.fileName
val lexer = CustomLexer(modulePath, stream) val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener() val lexerErrors = LexerErrorListener()
@ -46,67 +65,24 @@ fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Modu
// convert to Ast // convert to Ast
val moduleAst = parseTree.toAst(moduleName.toString(), isLibrary, modulePath) val moduleAst = parseTree.toAst(moduleName.toString(), isLibrary, modulePath)
importedModules[moduleAst.name] = moduleAst moduleAst.program = program
moduleAst.linkParents()
program.modules.add(moduleAst)
// process imports // process imports
val lines = moduleAst.statements.toMutableList() val lines = moduleAst.statements.toMutableList()
if(!moduleAst.position.file.startsWith("c64utils.") && !moduleAst.isLibraryModule) { lines.asSequence()
// if the output is a PRG or BASIC program, include the c64utils library .mapIndexed { i, it -> Pair(i, it) }
val compilerOptions = determineCompilationOptions(moduleAst) .filter { (it.second as? Directive)?.directive == "%import" }
if(compilerOptions.launcher==LauncherType.BASIC || compilerOptions.output==OutputType.PRG) { .forEach { executeImportDirective(program, it.second as Directive, modulePath) }
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)
}
}
moduleAst.statements = lines moduleAst.statements = lines
return moduleAst return moduleAst
} }
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
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 {
val fileName = "$name.p8" 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") val propPath = System.getProperty("prog8.libdir")
if(propPath!=null) 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)") 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) if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position) throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!! val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file) if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position) throw SyntaxError("cannot import self", import.position)
if(importedModules.containsKey(moduleName))
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null return null
val resource = tryGetEmbeddedResource(moduleName+".p8") val resource = tryGetEmbeddedResource(moduleName+".p8")
@ -138,12 +116,14 @@ private fun executeImportDirective(import: Directive, importedFrom: Path): Modul
if(resource!=null) { if(resource!=null) {
// load the module from the embedded resource // load the module from the embedded resource
resource.use { resource.use {
if(import.args[0].int==42)
print("automatically ")
println("importing '$moduleName' (embedded library)") 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 { } else {
val modulePath = discoverImportedModuleFile(moduleName, importedFrom, import.position) val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
importModule(modulePath) importModule(program, modulePath)
} }
importedModule.checkImportedValid() importedModule.checkImportedValid()