mirror of
https://github.com/irmen/prog8.git
synced 2025-01-13 10:29:52 +00:00
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:
parent
6a17f7a0ad
commit
e96d3d4455
@ -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')
|
||||
|
@ -1 +1 @@
|
||||
1.7
|
||||
1.8
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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?,
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
x
Reference in New Issue
Block a user