Compare commits

...

51 Commits
v1.9 ... v1.20

Author SHA1 Message Date
d9546f9dc7 version 2019-07-18 01:38:35 +02:00
2a6b0f5db7 remove some more dead code 2019-07-18 01:31:12 +02:00
b4e1b42cec remove some dead code 2019-07-17 22:35:38 +02:00
a8898a5993 using sealed class instead of interface 2019-07-17 02:35:26 +02:00
e03c68b632 optimize imports 2019-07-17 02:11:16 +02:00
a0074de12b updated the compiled examples 2019-07-17 00:39:03 +02:00
411bedcc46 fixed assignment type error with structs
added structs example
2019-07-16 23:56:00 +02:00
07d8caf884 string literal concatenation and repeating added again 2019-07-16 23:34:43 +02:00
c0e83ef8df wordings 2019-07-16 21:31:14 +02:00
4dbf4b2005 tweaks about initialization values 2019-07-16 20:32:23 +02:00
61af72b906 struct literals 2019-07-16 02:36:32 +02:00
17be722e2b arrays without init value are once again cleared with zeros 2019-07-15 23:05:04 +02:00
16d7927d2f fix arrays and some struct parsing issues 2019-07-15 22:28:05 +02:00
55a7a5d9d5 fix aggregate functions in astvm 2019-07-15 03:57:51 +02:00
78d7849197 fixes 2019-07-15 03:08:26 +02:00
d5b12fb01d made astchecker readonly 2019-07-15 01:47:59 +02:00
31f4e378aa split up Literalvalue into numeric and reference ones 2019-07-15 01:11:32 +02:00
8a26b7b248 - fixed lookup of members in structs defined in another scope
- preserve order of variable definitions in the Ast (and thus, the output)
2019-07-13 23:03:22 +02:00
87c28cfdbc restructure c64 machinedefinition 2019-07-13 03:16:48 +02:00
1f5420010d prevent struct member vars from shuffling around, can take address of struct now 2019-07-13 01:16:34 +02:00
a089c48378 finalize v 1.11 2019-07-12 20:31:18 +02:00
3e5deda46c struct finished 2019-07-12 20:07:41 +02:00
7500c6efd0 struct fixes 2019-07-12 17:57:56 +02:00
717b5f3b07 struct fixes 2019-07-12 16:40:18 +02:00
9f6fa60bf1 prepare 2019-07-12 14:38:37 +02:00
1e9586f635 Structs can be compiled and executed in the vm! structs are just syntactic sugar for a set of variables for now. 2019-07-12 12:41:08 +02:00
44f9d5e69e added struct syntax 2019-07-12 06:14:59 +02:00
7c9b8f7d43 cleaned up some buildprocess scripts 2019-07-11 17:27:57 +02:00
845a99d623 return statement only has one single possible value
astvm can now more or less run all examples
2019-07-10 19:27:44 +02:00
3d7a4bf81a astvm can now more or less run all examples 2019-07-10 18:44:54 +02:00
d4b3e35bd2 astvm almost complete 2019-07-10 16:50:41 +02:00
a59f7c75dc fixed some compile time and vm arithmetic errors 2019-07-10 13:33:52 +02:00
44fe2369d6 multitarget assignments removed 2019-07-10 10:11:37 +02:00
aaaab2cfcf fix asm gen for loops when dealing with registers as loopvar 2019-07-10 08:51:05 +02:00
9a3dab20dc extra warnings about register usage in loops 2019-07-10 08:30:17 +02:00
20379b5927 fixed astvm postincrdecr and rsave/rrestore 2019-07-10 08:13:42 +02:00
34dcce67e4 fixed petscii conversion when printing text 2019-07-10 07:10:34 +02:00
0c7f107d01 fix irq routine removal 2019-07-10 03:57:03 +02:00
1f89571aa5 proper NOP removal 2019-07-10 03:06:31 +02:00
7eed1ebbf8 optimized typecasting more 2019-07-10 02:54:39 +02:00
12cb7d7abe optimize redundant typecasts more 2019-07-10 01:52:04 +02:00
c9b16dcbd9 nicer printing of arrays, fix inc/dec overflow issue in runtimevalue 2019-07-10 01:16:32 +02:00
dcab6d00bb ver 2019-07-10 00:50:18 +02:00
a85743f241 docs about 'when' statement 2019-07-10 00:45:53 +02:00
14cabde5cf when statement extended with multiple choice values 2019-07-10 00:25:21 +02:00
cc078503e3 tehtriz example uses when statement 2019-07-09 23:39:03 +02:00
2a0c3377f9 fixed Nop statements without parent 2019-07-09 23:27:09 +02:00
16454f5560 optimized when asm 2019-07-09 21:59:50 +02:00
c1343a78f1 when working correctly in asm (corrected dup & cmp) 2019-07-09 21:41:47 +02:00
9d0c65c682 when working correctly in stackvm and astvm 2019-07-09 20:39:08 +02:00
9e6408244f fix scoping of variables in when statement 2019-07-09 19:44:59 +02:00
94 changed files with 4810 additions and 9489 deletions

2
.gitignore vendored
View File

@ -24,8 +24,6 @@ __pycache__/
parser.out parser.out
parsetab.py parsetab.py
.pytest_cache/ .pytest_cache/
compiler/src/prog8_kotlin.jar
compiler/src/compiled_java
.attach_pid* .attach_pid*
.gradle .gradle

View File

@ -21,6 +21,8 @@ which aims to provide many conveniences over raw assembly code (even when using
- automatic variable allocations, automatic string variables and string sharing - automatic variable allocations, automatic string variables and string sharing
- constant folding in expressions (compile-time evaluation) - constant folding in expressions (compile-time evaluation)
- conditional branches - conditional branches
- when statement to provide a 'jump table' alternative to if/elseif chains
- structs to group together sets of variables and manipulate them at once
- automatic type conversions - automatic type conversions
- floating point operations (uses the C64 Basic ROM routines for this) - floating point operations (uses the C64 Basic ROM routines for this)
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses - abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
@ -33,7 +35,9 @@ Rapid edit-compile-run-debug cycle:
- option to automatically run the program in the Vice emulator - option to automatically run the program in the Vice emulator
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them - breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly - source code labels automatically loaded in Vice emulator so it can show them in disassembly
- the compiler includes a virtual machine that can execute compiled code directy on the
host system without having to actually convert it to assembly to run on a real 6502.
This allows for very quick experimentation and debugging
It is mainly targeted at the Commodore-64 machine at this time. It is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome. Contributions to add support for other 8-bit (or other?!) machines are welcome.

View File

@ -76,17 +76,6 @@ artifacts {
archives shadowJar archives shadowJar
} }
task p8vmScript(type: CreateStartScripts) {
mainClassName = "prog8.vm.stackvm.MainKt"
applicationName = "p8vm"
outputDir = new File(project.buildDir, 'scripts')
classpath = jar.outputs.files + project.configurations.runtime
}
applicationDistribution.into("bin") {
from(p8vmScript)
fileMode = 0755
}
// To create a fat-jar use the 'create_compiler_jar' script for now // To create a fat-jar use the 'create_compiler_jar' script for now
// @todo investigate https://imperceptiblethoughts.com/shadow/introduction/ // @todo investigate https://imperceptiblethoughts.com/shadow/introduction/

View File

@ -1 +1 @@
1.9 1.20

View File

@ -1,8 +1,8 @@
package prog8 package prog8
import prog8.compiler.compileProgram
import prog8.vm.astvm.AstVm import prog8.vm.astvm.AstVm
import prog8.vm.stackvm.stackVmMain import prog8.vm.stackvm.stackVmMain
import prog8.compiler.*
import java.nio.file.Paths import java.nio.file.Paths
import kotlin.system.exitProcess import kotlin.system.exitProcess

View File

@ -1,18 +1,167 @@
package prog8.ast package prog8.ast
import prog8.ast.base.FatalAstException import prog8.ast.base.*
import prog8.ast.base.NameError import prog8.ast.expressions.Expression
import prog8.ast.base.ParentSentinel import prog8.ast.expressions.IdentifierReference
import prog8.ast.base.Position import prog8.ast.statements.*
import prog8.ast.statements.Block
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import java.nio.file.Path import java.nio.file.Path
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 definingSubroutine(): Subroutine? = findParentNode<Subroutine>(this)
fun definingScope(): INameScope {
val scope = findParentNode<INameScope>(this)
if(scope!=null) {
return scope
}
if(this is Label && this.name.startsWith("builtin::")) {
return BuiltinFunctionScopePlaceholder
}
if(this is GlobalNamespace)
return this
throw FatalAstException("scope missing from $this")
}
}
interface IFunctionCall {
var target: IdentifierReference
var arglist: MutableList<Expression>
}
interface INameScope {
val name: String
val position: Position
val statements: MutableList<Statement>
val parent: Node
fun linkParents(parent: Node)
fun subScopes(): Map<String, INameScope> {
val subscopes = mutableMapOf<String, INameScope>()
for(stmt in statements) {
when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> subscopes[stmt.body.name] = stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
is WhenStatement -> {
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
}
is INameScope -> subscopes[stmt.name] = stmt
else -> {}
}
}
return subscopes
}
fun getLabelOrVariable(name: String): Statement? {
// this is called A LOT and could perhaps be optimized a bit more,
// but adding a memoization cache didn't make much of a practical runtime difference
for (stmt in statements) {
if (stmt is VarDecl && stmt.name==name) return stmt
if (stmt is Label && stmt.name==name) return stmt
if (stmt is AnonymousScope) {
val sub = stmt.getLabelOrVariable(name)
if(sub!=null)
return sub
}
}
return null
}
fun allDefinedSymbols(): List<Pair<String, Statement>> {
return statements.mapNotNull {
when (it) {
is Label -> it.name to it
is VarDecl -> it.name to it
is Subroutine -> it.name to it
is Block -> it.name to it
else -> null
}
}
}
fun lookup(scopedName: List<String>, localContext: Node) : Statement? {
if(scopedName.size>1) {
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
// try the struct first.
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
val struct = thing?.struct
if (struct != null) {
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
// return ref to the mangled name variable
val mangled = mangledStructMemberName(thing.name, scopedName.last())
return thing.definingScope().getLabelOrVariable(mangled)
}
}
// 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 Statement?
}
}
return null
} else {
// 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])
if (result != null)
return result
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
return null
}
}
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoCodeNorVars() = !containsCodeOrVars()
fun remove(stmt: Statement) {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
}
}
/*********** Everything starts from here, the Program; zero or more modules *************/ /*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String, val modules: MutableList<Module>) { class Program(val name: String, val modules: MutableList<Module>) {
@ -35,7 +184,7 @@ class Program(val name: String, val modules: MutableList<Module>) {
} }
class Module(override val name: String, class Module(override val name: String,
override var statements: MutableList<IStatement>, override var statements: MutableList<Statement>,
override val position: Position, override val position: Position,
val isLibraryModule: Boolean, val isLibraryModule: Boolean,
val source: Path) : Node, INameScope { val source: Path) : Node, INameScope {
@ -59,14 +208,14 @@ class Module(override val name: String,
class GlobalNamespace(val modules: List<Module>): Node, INameScope { class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val name = "<<<global>>>" override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0) override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<IStatement>() override val statements = mutableListOf<Statement>()
override var parent: Node = ParentSentinel override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
modules.forEach { it.linkParents(this) } modules.forEach { it.linkParents(this) }
} }
override fun lookup(scopedName: List<String>, localContext: Node): IStatement? { override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) { if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
// builtin functions always exist, return a dummy localContext for them // builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position) val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
@ -74,6 +223,20 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
return builtinPlaceholder return builtinPlaceholder
} }
if(scopedName.size>1) {
// a scoped name can a) refer to a member of a struct, or b) refer to a name in another module.
// try the struct first.
val thing = lookup(scopedName.dropLast(1), localContext) as? VarDecl
val struct = thing?.struct
if (struct != null) {
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
// return ref to the mangled name variable
val mangled = mangledStructMemberName(thing.name, scopedName.last())
return thing.definingScope().getLabelOrVariable(mangled)
}
}
}
val stmt = localContext.definingModule().lookup(scopedName, localContext) 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
@ -86,7 +249,11 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
object BuiltinFunctionScopePlaceholder : INameScope { object BuiltinFunctionScopePlaceholder : INameScope {
override val name = "<<builtin-functions-scope-placeholder>>" override val name = "<<builtin-functions-scope-placeholder>>"
override val position = Position("<<placeholder>>", 0, 0, 0) override val position = Position("<<placeholder>>", 0, 0, 0)
override var statements = mutableListOf<IStatement>() override var statements = mutableListOf<Statement>()
override var parent: Node = ParentSentinel override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {} override fun linkParents(parent: Node) {}
} }
// prefix for struct member variables
internal fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName"

View File

@ -1,197 +0,0 @@
package prog8.ast
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
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 definingSubroutine(): Subroutine? = findParentNode<Subroutine>(this)
fun definingScope(): INameScope {
val scope = findParentNode<INameScope>(this)
if(scope!=null) {
return scope
}
if(this is Label && this.name.startsWith("builtin::")) {
return BuiltinFunctionScopePlaceholder
}
if(this is GlobalNamespace)
return this
throw FatalAstException("scope missing from $this")
}
}
interface IStatement : Node {
fun accept(visitor: IAstModifyingVisitor) : IStatement
fun accept(visitor: IAstVisitor)
fun makeScopedName(name: String): String {
// easy way out is to always return the full scoped name.
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
// and like this, we can cache the name even,
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
val scope = mutableListOf<String>()
var statementScope = this.parent
while(statementScope !is ParentSentinel && statementScope !is Module) {
if(statementScope is INameScope) {
scope.add(0, statementScope.name)
}
statementScope = statementScope.parent
}
if(name.isNotEmpty())
scope.add(name)
return scope.joinToString(".")
}
val expensiveToInline: Boolean
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
}
interface IFunctionCall {
var target: IdentifierReference
var arglist: MutableList<IExpression>
}
interface INameScope {
val name: String
val position: Position
val statements: MutableList<IStatement>
val parent: Node
fun linkParents(parent: Node)
fun subScopes(): Map<String, INameScope> {
val subscopes = mutableMapOf<String, INameScope>()
for(stmt in statements) {
when(stmt) {
is INameScope -> subscopes[stmt.name] = stmt
is ForLoop -> subscopes[stmt.body.name] = stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
}
}
}
return subscopes
}
fun getLabelOrVariable(name: String): IStatement? {
// TODO this is called A LOT and could perhaps be optimized a bit more, but adding a cache didn't make much of a practical runtime difference
for (stmt in statements) {
if (stmt is VarDecl && stmt.name==name) return stmt
if (stmt is Label && stmt.name==name) return stmt
}
return null
}
fun allDefinedSymbols(): List<Pair<String, IStatement>> {
return statements.mapNotNull {
when (it) {
is Label -> it.name to it
is VarDecl -> it.name to it
is Subroutine -> it.name to it
is Block -> it.name to it
else -> null
}
}
}
fun lookup(scopedName: List<String>, localContext: Node) : IStatement? {
if(scopedName.size>1) {
// 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?
}
}
return null
} else {
// 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])
if (result != null)
return result
val subscope = localScope.subScopes()[scopedName[0]] as IStatement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
statementScope = statementScope.parent
}
return null
}
}
fun containsCodeOrVars() = statements.any { it !is Directive || it.directive == "%asminclude" || it.directive == "%asm"}
fun containsNoCodeNorVars() = !containsCodeOrVars()
fun remove(stmt: IStatement) {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
}
}
interface IExpression: Node {
fun constValue(program: Program): LiteralValue?
fun accept(visitor: IAstModifyingVisitor): IExpression
fun accept(visitor: IAstVisitor)
fun referencesIdentifier(name: String): Boolean
fun inferType(program: Program): DataType?
infix fun isSameAs(other: IExpression): Boolean {
if(this===other)
return true
when(this) {
is RegisterExpr ->
return (other is RegisterExpr && other.register==register)
is IdentifierReference ->
return (other is IdentifierReference && other.nameInSource==nameInSource)
is PrefixExpression ->
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
is BinaryExpression ->
return (other is BinaryExpression && other.operator==operator
&& other.left isSameAs left
&& other.right isSameAs right)
is ArrayIndexedExpression -> {
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index)
}
is LiteralValue -> return (other is LiteralValue && other==this)
}
return false
}
}

View File

@ -3,16 +3,16 @@ package prog8.ast.antlr
import org.antlr.v4.runtime.IntStream import org.antlr.v4.runtime.IntStream
import org.antlr.v4.runtime.ParserRuleContext import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.tree.TerminalNode import org.antlr.v4.runtime.tree.TerminalNode
import prog8.ast.* import prog8.ast.Module
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import java.io.CharConversionException
import java.io.File
import java.nio.file.Path
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.parser.CustomLexer import prog8.parser.CustomLexer
import prog8.parser.prog8Parser import prog8.parser.prog8Parser
import java.io.CharConversionException
import java.io.File
import java.nio.file.Path
/***************** Antlr Extension methods to create AST ****************/ /***************** Antlr Extension methods to create AST ****************/
@ -39,7 +39,7 @@ private fun ParserRuleContext.toPosition() : Position {
} }
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : IStatement { private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : Statement {
val directive = directive()?.toAst() val directive = directive()?.toAst()
if(directive!=null) return directive if(directive!=null) return directive
@ -50,74 +50,104 @@ private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : ISt
} }
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : IStatement = private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement =
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition()) Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<IStatement> = private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList() statement().asSequence().map { it.toAst() }.toMutableList()
private fun prog8Parser.StatementContext.toAst() : IStatement { private fun prog8Parser.StatementContext.toAst() : Statement {
vardecl()?.let { vardecl()?.let { return it.toAst() }
return VarDecl(VarDeclType.VAR,
it.datatype().toAst(),
it.ZEROPAGE() != null,
it.arrayindex()?.toAst(),
it.identifier().text,
null,
it.ARRAYSIG() != null || it.arrayindex() != null,
false,
it.toPosition())
}
varinitializer()?.let { varinitializer()?.let {
val vd = it.vardecl() val vd = it.vardecl()
return VarDecl(VarDeclType.VAR, return VarDecl(
vd.datatype().toAst(), VarDeclType.VAR,
vd.ZEROPAGE() != null, vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(), vd.arrayindex()?.toAst(),
vd.identifier().text, vd.varname.text,
null,
it.expression().toAst(), it.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null, vd.ARRAYSIG() != null || vd.arrayindex() != null,
false, false,
it.toPosition()) it.toPosition()
)
}
structvarinitializer()?.let {
val vd = it.structvardecl()
return VarDecl(
VarDeclType.VAR,
DataType.STRUCT,
ZeropageWish.NOT_IN_ZEROPAGE,
null,
vd.varname.text,
vd.structname.text,
it.expression().toAst(),
isArray = false,
autogeneratedDontRemove = false,
position = it.toPosition()
)
}
structvardecl()?.let {
return VarDecl(
VarDeclType.VAR,
DataType.STRUCT,
ZeropageWish.NOT_IN_ZEROPAGE,
null,
it.varname.text,
it.structname.text,
null,
isArray = false,
autogeneratedDontRemove = false,
position = it.toPosition()
)
} }
constdecl()?.let { constdecl()?.let {
val cvarinit = it.varinitializer() val cvarinit = it.varinitializer()
val vd = cvarinit.vardecl() val vd = cvarinit.vardecl()
return VarDecl(VarDeclType.CONST, return VarDecl(
vd.datatype().toAst(), VarDeclType.CONST,
vd.ZEROPAGE() != null, vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(), vd.arrayindex()?.toAst(),
vd.identifier().text, vd.varname.text,
null,
cvarinit.expression().toAst(), cvarinit.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null, vd.ARRAYSIG() != null || vd.arrayindex() != null,
false, false,
cvarinit.toPosition()) cvarinit.toPosition()
)
} }
memoryvardecl()?.let { memoryvardecl()?.let {
val mvarinit = it.varinitializer() val mvarinit = it.varinitializer()
val vd = mvarinit.vardecl() val vd = mvarinit.vardecl()
return VarDecl(VarDeclType.MEMORY, return VarDecl(
vd.datatype().toAst(), VarDeclType.MEMORY,
vd.ZEROPAGE() != null, vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(), vd.arrayindex()?.toAst(),
vd.identifier().text, vd.varname.text,
null,
mvarinit.expression().toAst(), mvarinit.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null, vd.ARRAYSIG() != null || vd.arrayindex() != null,
false, false,
mvarinit.toPosition()) mvarinit.toPosition()
)
} }
assignment()?.let { assignment()?.let {
return Assignment(it.assign_targets().toAst(), null, it.expression().toAst(), it.toPosition()) return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
} }
augassignment()?.let { augassignment()?.let {
return Assignment(listOf(it.assign_target().toAst()), return Assignment(it.assign_target().toAst(),
it.operator.text, it.operator.text,
it.expression().toAst(), it.expression().toAst(),
it.toPosition()) it.toPosition())
@ -175,13 +205,16 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
val whenstmt = whenstmt()?.toAst() val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt if(whenstmt!=null) return whenstmt
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
toPosition())
}
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text") throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
} }
private fun prog8Parser.Assign_targetsContext.toAst(): List<AssignTarget> = assign_target().map { it.toAst() } private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement {
val name = identifier().text val name = identifier().text
val address = asmsub_address()?.address?.toAst()?.number?.toInt() val address = asmsub_address()?.address?.toAst()?.number?.toInt()
val params = asmsub_params()?.toAst() ?: emptyList() val params = asmsub_params()?.toAst() ?: emptyList()
@ -218,15 +251,19 @@ private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter> private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParameter>
= asmsub_param().map { = asmsub_param().map {
AsmSubroutineParameter(it.vardecl().identifier().text, it.vardecl().datatype().toAst(), val vardecl = it.vardecl()
it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) val datatype = vardecl.datatype()?.toAst() ?: DataType.STRUCT
AsmSubroutineParameter(vardecl.varname.text, datatype,
it.registerorpair()?.toAst(),
it.statusregister()?.toAst(),
!it.stack?.text.isNullOrEmpty(), toPosition())
} }
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text) private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
private fun prog8Parser.Functioncall_stmtContext.toAst(): IStatement { private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
val location = scoped_identifier().toAst() val location = scoped_identifier().toAst()
return if(expression_list() == null) return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), toPosition()) FunctionCallStatement(location, mutableListOf(), toPosition())
@ -249,8 +286,7 @@ private fun prog8Parser.InlineasmContext.toAst() =
private fun prog8Parser.ReturnstmtContext.toAst() : Return { private fun prog8Parser.ReturnstmtContext.toAst() : Return {
val values = expression_list() return Return(expression()?.toAst(), toPosition())
return Return(values?.toAst() ?: emptyList(), toPosition())
} }
private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump { private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
@ -260,7 +296,7 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
} }
private fun prog8Parser.LabeldefContext.toAst(): IStatement = private fun prog8Parser.LabeldefContext.toAst(): Statement =
Label(children[0].text, toPosition()) Label(children[0].text, toPosition())
@ -285,7 +321,8 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> = private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map { vardecl().map {
SubroutineParameter(it.identifier().text, it.datatype().toAst(), it.toPosition()) val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
SubroutineParameter(it.varname.text, datatype, it.toPosition())
} }
@ -372,30 +409,30 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
} }
private fun prog8Parser.ExpressionContext.toAst() : IExpression { private fun prog8Parser.ExpressionContext.toAst() : Expression {
val litval = literalvalue() val litval = literalvalue()
if(litval!=null) { if(litval!=null) {
val booleanlit = litval.booleanliteral()?.toAst() val booleanlit = litval.booleanliteral()?.toAst()
return if(booleanlit!=null) { return if(booleanlit!=null) {
LiteralValue.fromBoolean(booleanlit, litval.toPosition()) NumericLiteralValue.fromBoolean(booleanlit, litval.toPosition())
} }
else { else {
val intLit = litval.integerliteral()?.toAst() val intLit = litval.integerliteral()?.toAst()
when { when {
intLit!=null -> when(intLit.datatype) { intLit!=null -> when(intLit.datatype) {
DataType.UBYTE -> LiteralValue(DataType.UBYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition()) DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number.toShort(), litval.toPosition())
DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition()) DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number.toShort(), litval.toPosition())
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = intLit.number.toInt(), position = litval.toPosition()) DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number.toInt(), litval.toPosition())
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.number.toInt(), position = litval.toPosition()) DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number.toInt(), litval.toPosition())
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = intLit.number.toDouble(), position = litval.toPosition()) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number.toDouble(), litval.toPosition())
else -> throw FatalAstException("invalid datatype for numeric literal") else -> throw FatalAstException("invalid datatype for numeric literal")
} }
litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition()) litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition()) litval.stringliteral()!=null -> ReferenceLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
litval.charliteral()!=null -> { litval.charliteral()!=null -> {
try { try {
LiteralValue(DataType.UBYTE, bytevalue = Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], position = litval.toPosition()) NumericLiteralValue(DataType.UBYTE, Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], litval.toPosition())
} catch (ce: CharConversionException) { } catch (ce: CharConversionException) {
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition()) throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
} }
@ -404,7 +441,11 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
val array = litval.arrayliteral()?.toAst() val array = litval.arrayliteral()?.toAst()
// the actual type of the arraysize can not yet be determined here (missing namespace & heap) // the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFolder takes care of that and converts the type if needed. // the ConstantFolder takes care of that and converts the type if needed.
LiteralValue(DataType.ARRAY_UB, arrayvalue = array, position = litval.toPosition()) ReferenceLiteralValue(DataType.ARRAY_UB, array = array, position = litval.toPosition())
}
litval.structliteral()!=null -> {
val values = litval.structliteral().expression().map { it.toAst() }
StructLiteralValue(values, litval.toPosition())
} }
else -> throw FatalAstException("invalid parsed literal") else -> throw FatalAstException("invalid parsed literal")
} }
@ -427,7 +468,7 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
if(funcall!=null) return funcall if(funcall!=null) return funcall
if (rangefrom!=null && rangeto!=null) { if (rangefrom!=null && rangeto!=null) {
val step = rangestep?.toAst() ?: LiteralValue(DataType.UBYTE, 1, position = toPosition()) val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, 1, toPosition())
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition()) return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
} }
@ -478,10 +519,9 @@ private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
} }
private fun prog8Parser.ArrayliteralContext.toAst() : Array<IExpression> = private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
expression().map { it.toAst() }.toTypedArray() expression().map { it.toAst() }.toTypedArray()
private fun prog8Parser.If_stmtContext.toAst(): IfStatement { private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
val condition = expression().toAst() val condition = expression().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst()) val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@ -492,7 +532,7 @@ private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
return IfStatement(condition, trueScope, elseScope, toPosition()) return IfStatement(condition, trueScope, elseScope, toPosition())
} }
private fun prog8Parser.Else_partContext.toAst(): MutableList<IStatement> { private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst()) return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
} }
@ -513,7 +553,7 @@ private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf
private fun prog8Parser.ForloopContext.toAst(): ForLoop { private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst() val loopregister = register()?.toAst()
val datatype = datatype()?.toAst() val datatype = datatype()?.toAst()
val zeropage = ZEROPAGE()!=null val zeropage = if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE
val loopvar = identifier()?.toAst() val loopvar = identifier()?.toAst()
val iterable = expression()!!.toAst() val iterable = expression()!!.toAst()
val scope = val scope =
@ -554,13 +594,28 @@ private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
} }
private fun prog8Parser.When_choiceContext.toAst(): WhenChoice { private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
val value = expression()?.toAst() val values = expression_list()?.toAst()
val stmt = statement()?.toAst() val stmt = statement()?.toAst()
val stmt_block = statement_block()?.toAst()?.toMutableList() ?: mutableListOf() val stmt_block = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
if(stmt!=null) if(stmt!=null)
stmt_block.add(stmt) stmt_block.add(stmt)
val scope = AnonymousScope(stmt_block, toPosition()) val scope = AnonymousScope(stmt_block, toPosition())
return WhenChoice(value, scope, toPosition()) return WhenChoice(values, scope, toPosition())
}
private fun prog8Parser.VardeclContext.toAst(): VarDecl {
return VarDecl(
VarDeclType.VAR,
datatype()?.toAst() ?: DataType.STRUCT,
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
arrayindex()?.toAst(),
varname.text,
null,
null,
ARRAYSIG() != null || arrayindex() != null,
false,
toPosition()
)
} }
internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r") internal fun escape(str: String) = str.replace("\t", "\\t").replace("\n", "\\n").replace("\r", "\\r")
@ -588,3 +643,4 @@ internal fun unescape(str: String, position: Position): String {
} }
return result.joinToString("") return result.joinToString("")
} }

View File

@ -5,18 +5,19 @@ import prog8.ast.Node
/**************************** AST Data classes ****************************/ /**************************** AST Data classes ****************************/
enum class DataType { enum class DataType {
UBYTE, UBYTE, // pass by value
BYTE, BYTE, // pass by value
UWORD, UWORD, // pass by value
WORD, WORD, // pass by value
FLOAT, FLOAT, // pass by value
STR, STR, // pass by reference
STR_S, STR_S, // pass by reference
ARRAY_UB, ARRAY_UB, // pass by reference
ARRAY_B, ARRAY_B, // pass by reference
ARRAY_UW, ARRAY_UW, // pass by reference
ARRAY_W, ARRAY_W, // pass by reference
ARRAY_F; ARRAY_F, // pass by reference
STRUCT; // pass by reference
/** /**
* is the type assignable to the given other type? * is the type assignable to the given other type?
@ -24,26 +25,33 @@ enum class DataType {
infix fun isAssignableTo(targetType: DataType) = infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision? // what types are assignable to others without loss of precision?
when(this) { when(this) {
UBYTE -> targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT UBYTE -> targetType in setOf(UBYTE, UWORD, WORD, FLOAT)
BYTE -> targetType == BYTE || targetType == UBYTE || targetType == UWORD || targetType==WORD || targetType == FLOAT BYTE -> targetType in setOf(BYTE, UBYTE, UWORD, WORD, FLOAT)
UWORD -> targetType == UWORD || targetType == FLOAT UWORD -> targetType in setOf(UWORD, FLOAT)
WORD -> targetType == WORD || targetType==UWORD || targetType == FLOAT WORD -> targetType in setOf(WORD, UWORD, FLOAT)
FLOAT -> targetType == FLOAT FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType==STR_S STR -> targetType == STR || targetType==STR_S
STR_S -> targetType == STR || targetType==STR_S STR_S -> targetType == STR || targetType==STR_S
in ArrayDatatypes -> targetType === this in ArrayDatatypes -> targetType == this
else -> false else -> false
} }
infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it } infix fun isAssignableTo(targetTypes: Set<DataType>) = targetTypes.any { this isAssignableTo it }
infix fun biggerThan(other: DataType) = infix fun largerThan(other: DataType) =
when(this) { when(this) {
in ByteDatatypes -> false in ByteDatatypes -> false
in WordDatatypes -> other in ByteDatatypes in WordDatatypes -> other in ByteDatatypes
else -> true else -> true
} }
infix fun equalsSize(other: DataType) =
when(this) {
in ByteDatatypes -> other in ByteDatatypes
in WordDatatypes -> other in WordDatatypes
else -> false
}
} }
enum class Register { enum class Register {
@ -89,17 +97,19 @@ enum class VarDeclType {
MEMORY MEMORY
} }
val IterableDatatypes = setOf(
DataType.STR, DataType.STR_S,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE) val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD) val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD) val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT) val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val StringDatatypes = setOf(DataType.STR, DataType.STR_S) val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F) val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
val IterableDatatypes = setOf(
DataType.STR, DataType.STR_S,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
val ArrayElementTypes = mapOf( val ArrayElementTypes = mapOf(
DataType.ARRAY_B to DataType.BYTE, DataType.ARRAY_B to DataType.BYTE,
DataType.ARRAY_UB to DataType.UBYTE, DataType.ARRAY_UB to DataType.UBYTE,

View File

@ -1,11 +1,10 @@
package prog8.ast.base package prog8.ast.base
import prog8.ast.* import prog8.ast.Module
import prog8.ast.expressions.IdentifierReference import prog8.ast.Program
import prog8.ast.processing.* import prog8.ast.processing.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
// the name of the subroutine that should be called for every block to initialize its variables // the name of the subroutine that should be called for every block to initialize its variables
@ -16,6 +15,12 @@ internal const val initvarsSubName="prog8_init_vars"
internal const val autoHeapValuePrefix = "auto_heap_value_" internal const val autoHeapValuePrefix = "auto_heap_value_"
internal fun Program.removeNopsFlattenAnonScopes() {
val flattener = FlattenAnonymousScopesAndRemoveNops()
flattener.visit(this)
}
internal fun Program.checkValid(compilerOptions: CompilationOptions) { internal fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(this, compilerOptions) val checker = AstChecker(this, compilerOptions)
checker.visit(this) checker.visit(this)
@ -24,7 +29,7 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions) {
internal fun Program.reorderStatements() { internal fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace) val initvalueCreator = VarInitValueAndAddressOfCreator(namespace, heap)
initvalueCreator.visit(this) initvalueCreator.visit(this)
val checker = StatementReorderer(this) val checker = StatementReorderer(this)
@ -45,40 +50,12 @@ internal fun Program.checkRecursion() {
internal fun Program.checkIdentifiers() { internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(namespace) val checker = AstIdentifiersChecker(this)
checker.visit(this) checker.visit(this)
if(modules.map {it.name}.toSet().size != modules.size) { if(modules.map {it.name}.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique") 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
for (variable in checker.anonymousVariablesFromHeap.values) {
val scope = variable.first.definingScope()
scope.statements.add(variable.second)
val parent = variable.first.parent
when {
parent is Assignment && parent.value === variable.first -> {
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.value = idref
}
parent is IFunctionCall -> {
val parameterPos = parent.arglist.indexOf(variable.first)
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.arglist[parameterPos] = idref
}
parent is ForLoop -> {
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.iterable = idref
}
else -> TODO("replace literalvalue by identifierref: $variable (in $parent)")
}
variable.second.linkParents(scope as Node)
}
printErrors(checker.result(), name) printErrors(checker.result(), name)
} }

View File

@ -1,10 +1,17 @@
package prog8.ast.expressions package prog8.ast.expressions
import prog8.ast.* import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.antlr.escape
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
@ -12,13 +19,43 @@ import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType import prog8.functions.builtinFunctionReturnType
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.floor
val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=") val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=")
class PrefixExpression(val operator: String, var expression: IExpression, override val position: Position) : IExpression { sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstModifyingVisitor): Expression
abstract fun accept(visitor: IAstVisitor)
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this here and move it into CallGraph instead
abstract fun inferType(program: Program): DataType?
infix fun isSameAs(other: Expression): Boolean {
if(this===other)
return true
when(this) {
is RegisterExpr ->
return (other is RegisterExpr && other.register==register)
is IdentifierReference ->
return (other is IdentifierReference && other.nameInSource==nameInSource)
is PrefixExpression ->
return (other is PrefixExpression && other.operator==operator && other.expression isSameAs expression)
is BinaryExpression ->
return (other is BinaryExpression && other.operator==operator
&& other.left isSameAs left
&& other.right isSameAs right)
is ArrayIndexedExpression -> {
return (other is ArrayIndexedExpression && other.identifier.nameInSource == identifier.nameInSource
&& other.arrayspec.index isSameAs arrayspec.index)
}
else -> return other==this
}
}
}
class PrefixExpression(val operator: String, var expression: Expression, override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -26,10 +63,10 @@ class PrefixExpression(val operator: String, var expression: IExpression, overri
expression.linkParents(this) expression.linkParents(this)
} }
override fun constValue(program: Program): LiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name) override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? = expression.inferType(program) override fun inferType(program: Program): DataType? = expression.inferType(program)
override fun toString(): String { override fun toString(): String {
@ -37,7 +74,7 @@ class PrefixExpression(val operator: String, var expression: IExpression, overri
} }
} }
class BinaryExpression(var left: IExpression, var operator: String, var right: IExpression, override val position: Position) : IExpression { class BinaryExpression(var left: Expression, var operator: String, var right: Expression, override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -51,11 +88,11 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
} }
// binary expression should actually have been optimized away into a single value, before const value was requested... // binary expression should actually have been optimized away into a single value, before const value was requested...
override fun constValue(program: Program): LiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name) override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): DataType? {
val leftDt = left.inferType(program) val leftDt = left.inferType(program)
val rightDt = right.inferType(program) val rightDt = right.inferType(program)
@ -148,7 +185,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
} }
fun commonDatatype(leftDt: DataType, rightDt: DataType, fun commonDatatype(leftDt: DataType, rightDt: DataType,
left: IExpression, right: IExpression): Pair<DataType, IExpression?> { left: Expression, right: Expression): Pair<DataType, Expression?> {
// byte + byte -> byte // byte + byte -> byte
// byte + word -> word // byte + word -> word
// word + byte -> word // word + byte -> word
@ -169,7 +206,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
DataType.UWORD -> Pair(DataType.UWORD, left) DataType.UWORD -> Pair(DataType.UWORD, left)
DataType.WORD -> Pair(DataType.WORD, left) DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left) DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt") else -> Pair(leftDt, null) // non-numeric datatype
} }
} }
DataType.BYTE -> { DataType.BYTE -> {
@ -179,7 +216,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
DataType.UWORD -> Pair(DataType.WORD, left) DataType.UWORD -> Pair(DataType.WORD, left)
DataType.WORD -> Pair(DataType.WORD, left) DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left) DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt") else -> Pair(leftDt, null) // non-numeric datatype
} }
} }
DataType.UWORD -> { DataType.UWORD -> {
@ -189,7 +226,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
DataType.UWORD -> Pair(DataType.UWORD, null) DataType.UWORD -> Pair(DataType.UWORD, null)
DataType.WORD -> Pair(DataType.WORD, left) DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left) DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt") else -> Pair(leftDt, null) // non-numeric datatype
} }
} }
DataType.WORD -> { DataType.WORD -> {
@ -199,20 +236,20 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
DataType.UWORD -> Pair(DataType.WORD, right) DataType.UWORD -> Pair(DataType.WORD, right)
DataType.WORD -> Pair(DataType.WORD, null) DataType.WORD -> Pair(DataType.WORD, null)
DataType.FLOAT -> Pair(DataType.FLOAT, left) DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt") else -> Pair(leftDt, null) // non-numeric datatype
} }
} }
DataType.FLOAT -> { DataType.FLOAT -> {
Pair(DataType.FLOAT, right) Pair(DataType.FLOAT, right)
} }
else -> throw FatalAstException("non-numeric datatype $leftDt") else -> Pair(leftDt, null) // non-numeric datatype
} }
} }
} }
class ArrayIndexedExpression(val identifier: IdentifierReference, class ArrayIndexedExpression(val identifier: IdentifierReference,
var arrayspec: ArrayIndex, var arrayspec: ArrayIndex,
override val position: Position) : IExpression { override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -220,10 +257,10 @@ class ArrayIndexedExpression(val identifier: IdentifierReference,
arrayspec.linkParents(this) arrayspec.linkParents(this)
} }
override fun constValue(program: Program): LiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String) = identifier.referencesIdentifier(name) override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): DataType? {
val target = identifier.targetStatement(program.namespace) val target = identifier.targetStatement(program.namespace)
@ -231,11 +268,7 @@ class ArrayIndexedExpression(val identifier: IdentifierReference,
return when (target.datatype) { return when (target.datatype) {
in NumericDatatypes -> null in NumericDatatypes -> null
in StringDatatypes -> DataType.UBYTE in StringDatatypes -> DataType.UBYTE
DataType.ARRAY_UB -> DataType.UBYTE in ArrayDatatypes -> ArrayElementTypes[target.datatype]
DataType.ARRAY_B -> DataType.BYTE
DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
else -> throw FatalAstException("invalid dt") else -> throw FatalAstException("invalid dt")
} }
} }
@ -247,7 +280,7 @@ class ArrayIndexedExpression(val identifier: IdentifierReference,
} }
} }
class TypecastExpression(var expression: IExpression, var type: DataType, val implicit: Boolean, override val position: Position) : IExpression { class TypecastExpression(var expression: Expression, var type: DataType, val implicit: Boolean, override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -257,9 +290,9 @@ class TypecastExpression(var expression: IExpression, var type: DataType, val im
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name) override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? = type override fun inferType(program: Program): DataType? = type
override fun constValue(program: Program): LiteralValue? { override fun constValue(program: Program): NumericLiteralValue? {
val cv = expression.constValue(program) ?: return null val cv = expression.constValue(program) ?: return null
return cv.cast(type) return cv.cast(type)
// val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type) // val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
@ -271,7 +304,7 @@ class TypecastExpression(var expression: IExpression, var type: DataType, val im
} }
} }
data class AddressOf(val identifier: IdentifierReference, override val position: Position) : IExpression { data class AddressOf(val identifier: IdentifierReference, override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -280,14 +313,14 @@ data class AddressOf(val identifier: IdentifierReference, override val position:
} }
var scopedname: String? = null // will be set in a later state by the compiler var scopedname: String? = null // will be set in a later state by the compiler
override fun constValue(program: Program): LiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifier(name: String) = false override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program) = DataType.UWORD override fun inferType(program: Program) = DataType.UWORD
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
} }
class DirectMemoryRead(var addressExpression: IExpression, override val position: Position) : IExpression { class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -297,165 +330,218 @@ class DirectMemoryRead(var addressExpression: IExpression, override val position
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String) = false override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program): DataType? = DataType.UBYTE override fun inferType(program: Program): DataType? = DataType.UBYTE
override fun constValue(program: Program): LiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun toString(): String { override fun toString(): String {
return "DirectMemoryRead($addressExpression)" return "DirectMemoryRead($addressExpression)"
} }
} }
open class LiteralValue(val type: DataType, class NumericLiteralValue(val type: DataType, // only numerical types allowed
val bytevalue: Short? = null, val number: Number, // can be byte, word or float depending on the type
val wordvalue: Int? = null, override val position: Position) : Expression() {
val floatvalue: Double? = null,
val strvalue: String? = null,
val arrayvalue: Array<IExpression>? = null,
initHeapId: Int? =null,
override val position: Position) : IExpression {
override lateinit var parent: Node override lateinit var parent: Node
override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false
val isString = type in StringDatatypes
val isNumeric = type in NumericDatatypes
val isArray = type in ArrayDatatypes
var heapId = initHeapId
private set
companion object { companion object {
fun fromBoolean(bool: Boolean, position: Position) = fun fromBoolean(bool: Boolean, position: Position) =
LiteralValue(DataType.UBYTE, bytevalue = if (bool) 1 else 0, position = position) NumericLiteralValue(DataType.UBYTE, if (bool) 1 else 0, position)
fun fromNumber(value: Number, type: DataType, position: Position) : LiteralValue { fun optimalNumeric(value: Number, position: Position): NumericLiteralValue {
return when(type) {
in ByteDatatypes -> LiteralValue(type, bytevalue = value.toShort(), position = position)
in WordDatatypes -> LiteralValue(type, wordvalue = value.toInt(), position = position)
DataType.FLOAT -> LiteralValue(type, floatvalue = value.toDouble(), position = position)
else -> throw FatalAstException("non numeric datatype")
}
}
fun optimalNumeric(value: Number, position: Position): LiteralValue {
return if(value is Double) { return if(value is Double) {
LiteralValue(DataType.FLOAT, floatvalue = value, position = position) NumericLiteralValue(DataType.FLOAT, value, position)
} else { } else {
when (val intval = value.toInt()) { when (val intval = value.toInt()) {
in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = intval.toShort(), position = position) in 0..255 -> NumericLiteralValue(DataType.UBYTE, intval, position)
in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = intval.toShort(), position = position) in -128..127 -> NumericLiteralValue(DataType.BYTE, intval, position)
in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = intval, position = position) in 0..65535 -> NumericLiteralValue(DataType.UWORD, intval, position)
in -32768..32767 -> LiteralValue(DataType.WORD, wordvalue = intval, position = position) in -32768..32767 -> NumericLiteralValue(DataType.WORD, intval, position)
else -> LiteralValue(DataType.FLOAT, floatvalue = intval.toDouble(), position = position) else -> NumericLiteralValue(DataType.FLOAT, intval.toDouble(), position)
} }
} }
} }
fun optimalInteger(value: Number, position: Position): LiteralValue { fun optimalInteger(value: Int, position: Position): NumericLiteralValue {
val intval = value.toInt() return when (value) {
if(intval.toDouble() != value.toDouble()) in 0..255 -> NumericLiteralValue(DataType.UBYTE, value, position)
throw FatalAstException("value is not an integer: $value") in -128..127 -> NumericLiteralValue(DataType.BYTE, value, position)
return when (intval) { in 0..65535 -> NumericLiteralValue(DataType.UWORD, value, position)
in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = value.toShort(), position = position)
in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = value.toShort(), position = position)
in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = value.toInt(), position = position)
else -> throw FatalAstException("integer overflow: $value") else -> throw FatalAstException("integer overflow: $value")
} }
} }
} }
init { val asBooleanValue: Boolean = number!=0
when(type){
in ByteDatatypes -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue")
in WordDatatypes -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue")
DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue")
in StringDatatypes ->
if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
in ArrayDatatypes ->
if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId")
else -> throw FatalAstException("invalid type $type")
}
if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==null)
throw FatalAstException("literal value without actual value")
}
val asNumericValue: Number? = when {
bytevalue!=null -> bytevalue
wordvalue!=null -> wordvalue
floatvalue!=null -> floatvalue
else -> null
}
val asIntegerValue: Int? = when {
bytevalue!=null -> bytevalue.toInt()
wordvalue!=null -> wordvalue
// don't round a float value, otherwise code will not detect that it's not an integer
else -> null
}
val asBooleanValue: Boolean =
(floatvalue!=null && floatvalue != 0.0) ||
(bytevalue!=null && bytevalue != 0.toShort()) ||
(wordvalue!=null && wordvalue != 0) ||
(strvalue!=null && strvalue.isNotEmpty()) ||
(arrayvalue != null && arrayvalue.isNotEmpty())
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
arrayvalue?.forEach {it.linkParents(this)}
} }
override fun constValue(program: Program): LiteralValue? { override fun referencesIdentifiers(vararg name: String) = false
if(arrayvalue!=null) { override fun constValue(program: Program) = this
for(v in arrayvalue) {
if(v.constValue(program)==null) return null override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
} override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
override fun inferType(program: Program) = type
override fun hashCode(): Int = type.hashCode() * 31 xor number.hashCode()
override fun equals(other: Any?): Boolean {
if(other==null || other !is NumericLiteralValue)
return false
return number.toDouble()==other.number.toDouble()
} }
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
fun cast(targettype: DataType): NumericLiteralValue? {
if(type==targettype)
return this return this
val numval = number.toDouble()
when(type) {
DataType.UBYTE -> {
if(targettype== DataType.BYTE && numval <= 127)
return NumericLiteralValue(targettype, number.toShort(), position)
if(targettype== DataType.WORD || targettype== DataType.UWORD)
return NumericLiteralValue(targettype, number.toInt(), position)
if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position)
}
DataType.BYTE -> {
if(targettype== DataType.UBYTE && numval >= 0)
return NumericLiteralValue(targettype, number.toShort(), position)
if(targettype== DataType.UWORD && numval >= 0)
return NumericLiteralValue(targettype, number.toInt(), position)
if(targettype== DataType.WORD)
return NumericLiteralValue(targettype, number.toInt(), position)
if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position)
}
DataType.UWORD -> {
if(targettype== DataType.BYTE && numval <= 127)
return NumericLiteralValue(targettype, number.toShort(), position)
if(targettype== DataType.UBYTE && numval <= 255)
return NumericLiteralValue(targettype, number.toShort(), position)
if(targettype== DataType.WORD && numval <= 32767)
return NumericLiteralValue(targettype, number.toInt(), position)
if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position)
}
DataType.WORD -> {
if(targettype== DataType.BYTE && numval >= -128 && numval <=127)
return NumericLiteralValue(targettype, number.toShort(), position)
if(targettype== DataType.UBYTE && numval >= 0 && numval <= 255)
return NumericLiteralValue(targettype, number.toShort(), position)
if(targettype== DataType.UWORD && numval >=0)
return NumericLiteralValue(targettype, number.toInt(), position)
if(targettype== DataType.FLOAT)
return NumericLiteralValue(targettype, number.toDouble(), position)
}
DataType.FLOAT -> {
if (targettype == DataType.BYTE && numval >= -128 && numval <=127)
return NumericLiteralValue(targettype, number.toShort(), position)
if (targettype == DataType.UBYTE && numval >=0 && numval <= 255)
return NumericLiteralValue(targettype, number.toShort(), position)
if (targettype == DataType.WORD && numval >= -32768 && numval <= 32767)
return NumericLiteralValue(targettype, number.toInt(), position)
if (targettype == DataType.UWORD && numval >=0 && numval <= 65535)
return NumericLiteralValue(targettype, number.toInt(), position)
}
else -> {}
}
return null // invalid type conversion from $this to $targettype
}
}
class StructLiteralValue(var values: List<Expression>,
override val position: Position): Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent=parent
values.forEach { it.linkParents(this) }
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
override fun inferType(program: Program) = DataType.STRUCT
override fun toString(): String {
return "struct{ ${values.joinToString(", ")} }"
}
}
class ReferenceLiteralValue(val type: DataType, // only reference types allowed here
val str: String? = null,
val array: Array<Expression>? = null,
// actually, at the moment, we don't have struct literals in the language
initHeapId: Int? =null,
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun referencesIdentifiers(vararg name: String) = array?.any { it.referencesIdentifiers(*name) } ?: false
val isString = type in StringDatatypes
val isArray = type in ArrayDatatypes
var heapId = initHeapId
private set
init {
when(type){
in StringDatatypes ->
if(str==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId")
in ArrayDatatypes ->
if(array==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId")
else -> throw FatalAstException("invalid type $type")
}
if(array==null && str==null && heapId==null)
throw FatalAstException("literal ref value without actual value")
}
override fun linkParents(parent: Node) {
this.parent = parent
array?.forEach {it.linkParents(this)}
}
override fun constValue(program: Program): NumericLiteralValue? {
// note that we can't handle arrays that only contain constant numbers here anymore
// so they're not treated as constants anymore
return null
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String { override fun toString(): String {
val vstr = when(type) { val valueStr = when(type) {
DataType.UBYTE -> "ubyte:$bytevalue" in StringDatatypes -> "'${escape(str!!)}'"
DataType.BYTE -> "byte:$bytevalue" in ArrayDatatypes -> "$array"
DataType.UWORD -> "uword:$wordvalue" else -> throw FatalAstException("weird ref type")
DataType.WORD -> "word:$wordvalue"
DataType.FLOAT -> "float:$floatvalue"
in StringDatatypes -> "str:$strvalue"
in ArrayDatatypes -> "array:$arrayvalue"
else -> throw FatalAstException("weird datatype")
} }
return "LiteralValue($vstr)" return "ReferenceValueLiteral($type, $valueStr)"
} }
override fun inferType(program: Program) = type override fun inferType(program: Program) = type
override fun hashCode(): Int { override fun hashCode(): Int {
val bh = bytevalue?.hashCode() ?: 0x10001234 val sh = str?.hashCode() ?: 0x00014567
val wh = wordvalue?.hashCode() ?: 0x01002345 val ah = array?.hashCode() ?: 0x11119876
val fh = floatvalue?.hashCode() ?: 0x00103456 return sh * 31 xor ah xor type.hashCode()
val sh = strvalue?.hashCode() ?: 0x00014567
val ah = arrayvalue?.hashCode() ?: 0x11119876
var hash = bh * 31 xor wh
hash = hash*31 xor fh
hash = hash*31 xor sh
hash = hash*31 xor ah
hash = hash*31 xor type.hashCode()
return hash
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if(other==null || other !is LiteralValue) if(other==null || other !is ReferenceLiteralValue)
return false return false
if(isNumeric && other.isNumeric)
return asNumericValue?.toDouble()==other.asNumericValue?.toDouble()
if(isArray && other.isArray) if(isArray && other.isArray)
return arrayvalue!!.contentEquals(other.arrayvalue!!) && heapId==other.heapId return array!!.contentEquals(other.array!!) && heapId==other.heapId
if(isString && other.isString) if(isString && other.isString)
return strvalue==other.strvalue && heapId==other.heapId return str==other.str && heapId==other.heapId
if(type!=other.type) if(type!=other.type)
return false return false
@ -463,76 +549,17 @@ open class LiteralValue(val type: DataType,
return compareTo(other) == 0 return compareTo(other) == 0
} }
operator fun compareTo(other: LiteralValue): Int { operator fun compareTo(other: ReferenceLiteralValue): Int {
val numLeft = asNumericValue?.toDouble()
val numRight = other.asNumericValue?.toDouble()
if(numLeft!=null && numRight!=null)
return numLeft.compareTo(numRight)
if(strvalue!=null && other.strvalue!=null)
return strvalue.compareTo(other.strvalue)
throw ExpressionError("cannot order compare type $type with ${other.type}", other.position) throw ExpressionError("cannot order compare type $type with ${other.type}", other.position)
} }
fun cast(targettype: DataType): LiteralValue? { fun cast(targettype: DataType): ReferenceLiteralValue? {
if(type==targettype) if(type==targettype)
return this return this
when(type) { when(type) {
DataType.UBYTE -> {
if(targettype== DataType.BYTE && bytevalue!! <= 127)
return LiteralValue(targettype, bytevalue = bytevalue, position = position)
if(targettype== DataType.WORD || targettype== DataType.UWORD)
return LiteralValue(targettype, wordvalue = bytevalue!!.toInt(), position = position)
if(targettype== DataType.FLOAT)
return LiteralValue(targettype, floatvalue = bytevalue!!.toDouble(), position = position)
}
DataType.BYTE -> {
if(targettype== DataType.UBYTE && bytevalue!! >= 0)
return LiteralValue(targettype, bytevalue = bytevalue, position = position)
if(targettype== DataType.UWORD && bytevalue!! >= 0)
return LiteralValue(targettype, wordvalue = bytevalue.toInt(), position = position)
if(targettype== DataType.WORD)
return LiteralValue(targettype, wordvalue = bytevalue!!.toInt(), position = position)
if(targettype== DataType.FLOAT)
return LiteralValue(targettype, floatvalue = bytevalue!!.toDouble(), position = position)
}
DataType.UWORD -> {
if(targettype== DataType.BYTE && wordvalue!! <= 127)
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
if(targettype== DataType.UBYTE && wordvalue!! <= 255)
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
if(targettype== DataType.WORD && wordvalue!! <= 32767)
return LiteralValue(targettype, wordvalue = wordvalue, position = position)
if(targettype== DataType.FLOAT)
return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position)
}
DataType.WORD -> {
if(targettype== DataType.BYTE && wordvalue!! in -128..127)
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
if(targettype== DataType.UBYTE && wordvalue!! in 0..255)
return LiteralValue(targettype, bytevalue = wordvalue.toShort(), position = position)
if(targettype== DataType.UWORD && wordvalue!! >=0)
return LiteralValue(targettype, wordvalue = wordvalue, position = position)
if(targettype== DataType.FLOAT)
return LiteralValue(targettype, floatvalue = wordvalue!!.toDouble(), position = position)
}
DataType.FLOAT -> {
if(floor(floatvalue!!) ==floatvalue) {
val value = floatvalue.toInt()
if (targettype == DataType.BYTE && value in -128..127)
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
if (targettype == DataType.UBYTE && value in 0..255)
return LiteralValue(targettype, bytevalue = value.toShort(), position = position)
if (targettype == DataType.WORD && value in -32768..32767)
return LiteralValue(targettype, wordvalue = value, position = position)
if (targettype == DataType.UWORD && value in 0..65535)
return LiteralValue(targettype, wordvalue = value, position = position)
}
}
in StringDatatypes -> { in StringDatatypes -> {
if(targettype in StringDatatypes) if(targettype in StringDatatypes)
return this return ReferenceLiteralValue(targettype, str, initHeapId = heapId, position = position)
} }
else -> {} else -> {}
} }
@ -540,22 +567,22 @@ open class LiteralValue(val type: DataType,
} }
fun addToHeap(heap: HeapValues) { fun addToHeap(heap: HeapValues) {
if(heapId==null) { if (heapId != null) return
if (strvalue != null) { if (str != null) {
heapId = heap.addString(type, strvalue) heapId = heap.addString(type, str)
} }
else if (arrayvalue!=null) { else if (array!=null) {
if(arrayvalue.any {it is AddressOf }) { if(array.any {it is AddressOf }) {
val intArrayWithAddressOfs = arrayvalue.map { val intArrayWithAddressOfs = array.map {
when (it) { when (it) {
is AddressOf -> IntegerOrAddressOf(null, it) is AddressOf -> IntegerOrAddressOf(null, it)
is LiteralValue -> IntegerOrAddressOf(it.asIntegerValue, null) is NumericLiteralValue -> IntegerOrAddressOf(it.number.toInt(), null)
else -> throw FatalAstException("invalid datatype in array") else -> throw FatalAstException("invalid datatype in array")
} }
} }
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray()) heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
} else { } else {
val valuesInArray = arrayvalue.map { (it as LiteralValue).asNumericValue!! } val valuesInArray = array.map { (it as NumericLiteralValue).number }
heapId = if(type== DataType.ARRAY_F) { heapId = if(type== DataType.ARRAY_F) {
val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray() val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray()
heap.addDoublesArray(doubleArray) heap.addDoublesArray(doubleArray)
@ -567,12 +594,11 @@ open class LiteralValue(val type: DataType,
} }
} }
} }
}
class RangeExpr(var from: IExpression, class RangeExpr(var from: Expression,
var to: IExpression, var to: Expression,
var step: IExpression, var step: Expression,
override val position: Position) : IExpression { override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -582,10 +608,10 @@ class RangeExpr(var from: IExpression,
step.linkParents(this) step.linkParents(this)
} }
override fun constValue(program: Program): LiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name) override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): DataType? {
val fromDt=from.inferType(program) val fromDt=from.inferType(program)
val toDt=to.inferType(program) val toDt=to.inferType(program)
@ -605,30 +631,32 @@ class RangeExpr(var from: IExpression,
} }
fun size(): Int? { fun size(): Int? {
val fromLv = (from as? LiteralValue) val fromLv = (from as? NumericLiteralValue)
val toLv = (to as? LiteralValue) val toLv = (to as? NumericLiteralValue)
if(fromLv==null || toLv==null) if(fromLv==null || toLv==null)
return null return null
return toConstantIntegerRange()?.count() return toConstantIntegerRange()?.count()
} }
fun toConstantIntegerRange(): IntProgression? { fun toConstantIntegerRange(): IntProgression? {
val fromLv = from as? LiteralValue
val toLv = to as? LiteralValue
if(fromLv==null || toLv==null)
return null // non-constant range
val fromVal: Int val fromVal: Int
val toVal: Int val toVal: Int
if(fromLv.isString && toLv.isString) { val fromRlv = from as? ReferenceLiteralValue
val toRlv = to as? ReferenceLiteralValue
if(fromRlv!=null && fromRlv.isString && toRlv!=null && toRlv.isString) {
// string range -> int range over petscii values // string range -> int range over petscii values
fromVal = Petscii.encodePetscii(fromLv.strvalue!!, true)[0].toInt() fromVal = Petscii.encodePetscii(fromRlv.str!!, true)[0].toInt()
toVal = Petscii.encodePetscii(toLv.strvalue!!, true)[0].toInt() toVal = Petscii.encodePetscii(toRlv.str!!, true)[0].toInt()
} else { } else {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
if(fromLv==null || toLv==null)
return null // non-constant range
// integer range // integer range
fromVal = (from as LiteralValue).asIntegerValue!! fromVal = fromLv.number.toInt()
toVal = (to as LiteralValue).asIntegerValue!! toVal = toLv.number.toInt()
} }
val stepVal = (step as? LiteralValue)?.asIntegerValue ?: 1 val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
return when { return when {
fromVal <= toVal -> when { fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY stepVal <= 0 -> IntRange.EMPTY
@ -644,17 +672,17 @@ class RangeExpr(var from: IExpression,
} }
} }
class RegisterExpr(val register: Register, override val position: Position) : IExpression { class RegisterExpr(val register: Register, override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
} }
override fun constValue(program: Program): LiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String): Boolean = false override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
override fun toString(): String { override fun toString(): String {
return "RegisterExpr(register=$register, pos=$position)" return "RegisterExpr(register=$register, pos=$position)"
} }
@ -662,7 +690,7 @@ class RegisterExpr(val register: Register, override val position: Position) : IE
override fun inferType(program: Program) = DataType.UBYTE override fun inferType(program: Program) = DataType.UBYTE
} }
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : IExpression { data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
fun targetStatement(namespace: INameScope) = fun targetStatement(namespace: INameScope) =
@ -678,7 +706,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
this.parent = parent this.parent = parent
} }
override fun constValue(program: Program): LiteralValue? { override fun constValue(program: Program): NumericLiteralValue? {
val node = program.namespace.lookup(nameInSource, this) val node = program.namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolError(this) ?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl val vardecl = node as? VarDecl
@ -696,7 +724,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time? override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name // @todo is this correct all the time?
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): DataType? {
val targetStmt = targetStatement(program.namespace) val targetStmt = targetStatement(program.namespace)
@ -709,13 +737,18 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun heapId(namespace: INameScope): Int { fun heapId(namespace: INameScope): Int {
val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this) val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this)
return ((node as? VarDecl)?.value as? LiteralValue)?.heapId ?: throw FatalAstException("identifier is not on the heap: $this") val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
return when (value) {
is IdentifierReference -> value.heapId(namespace)
is ReferenceLiteralValue -> value.heapId ?: throw FatalAstException("refLv is not on the heap: $value")
else -> throw FatalAstException("requires a reference value")
}
} }
} }
class FunctionCall(override var target: IdentifierReference, class FunctionCall(override var target: IdentifierReference,
override var arglist: MutableList<IExpression>, override var arglist: MutableList<Expression>,
override val position: Position) : IExpression, IFunctionCall { override val position: Position) : Expression(), IFunctionCall {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -726,12 +759,12 @@ class FunctionCall(override var target: IdentifierReference,
override fun constValue(program: Program) = constValue(program, true) override fun constValue(program: Program) = constValue(program, true)
private fun constValue(program: Program, withDatatypeCheck: Boolean): LiteralValue? { private fun constValue(program: Program, withDatatypeCheck: Boolean): NumericLiteralValue? {
// if the function is a built-in function and the args are consts, should try to const-evaluate! // if the function is a built-in function and the args are consts, should try to const-evaluate!
// lenghts of arrays and strings are constants that are determined at compile time! // lenghts of arrays and strings are constants that are determined at compile time!
if(target.nameInSource.size>1) return null if(target.nameInSource.size>1) return null
try { try {
var resultValue: LiteralValue? = null var resultValue: NumericLiteralValue? = null
val func = BuiltinFunctions[target.nameInSource[0]] val func = BuiltinFunctions[target.nameInSource[0]]
if(func!=null) { if(func!=null) {
val exprfunc = func.constExpressionFunc val exprfunc = func.constExpressionFunc
@ -762,7 +795,7 @@ class FunctionCall(override var target: IdentifierReference,
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)} override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || arglist.any{it.referencesIdentifiers(*name)}
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): DataType? {
val constVal = constValue(program ,false) val constVal = constValue(program ,false)
@ -784,8 +817,7 @@ class FunctionCall(override var target: IdentifierReference,
return stmt.returntypes[0] return stmt.returntypes[0]
return null // has multiple return types... so not a single resulting datatype possible return null // has multiple return types... so not a single resulting datatype possible
} }
is Label -> return null else -> return null
} }
return null // calling something we don't recognise...
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,41 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.* import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.base.autoHeapValuePrefix
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstModifyingVisitor { internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf() private val checkResult: MutableList<AstException> = mutableListOf()
private var blocks: MutableMap<String, Block> = mutableMapOf() private var blocks = mutableMapOf<String, Block>()
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
internal fun result(): List<AstException> { internal fun result(): List<AstException> {
return checkResult return checkResult
} }
private fun nameError(name: String, position: Position, existing: IStatement) { private fun nameError(name: String, position: Position, existing: Statement) {
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 visit(module: Module) { override fun visit(module: Module) {
vardeclsToAdd.clear()
blocks.clear() // blocks may be redefined within a different module blocks.clear() // blocks may be redefined within a different module
super.visit(module) super.visit(module)
// add any new vardecls to the various scopes
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
} }
override fun visit(block: Block): IStatement { override fun visit(block: Block): Statement {
val existing = blocks[block.name] val existing = blocks[block.name]
if(existing!=null) if(existing!=null)
nameError(block.name, block.position, existing) nameError(block.name, block.position, existing)
@ -36,7 +45,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
return super.visit(block) return super.visit(block)
} }
override fun visit(functionCall: FunctionCall): IExpression { override fun visit(functionCall: FunctionCall): Expression {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") { if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte" // lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position) val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position)
@ -46,7 +55,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
return super.visit(functionCall) return super.visit(functionCall)
} }
override fun visit(decl: VarDecl): IStatement { override fun visit(decl: VarDecl): Statement {
// first, check if there are datatype errors on the vardecl // first, check if there are datatype errors on the vardecl
decl.datatypeErrors.forEach { checkResult.add(it) } decl.datatypeErrors.forEach { checkResult.add(it) }
@ -55,14 +64,40 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
// the builtin functions can't be redefined // the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position)) checkResult.add(NameError("builtin function cannot be redefined", decl.position))
val existing = namespace.lookup(listOf(decl.name), decl) // is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT) {
if(decl.structHasBeenFlattened)
return super.visit(decl) // don't do this multiple times
if(decl.struct==null) {
checkResult.add(NameError("undefined struct type", decl.position))
return super.visit(decl)
}
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
if(decl.value is NumericLiteralValue) {
checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position))
return super.visit(decl)
}
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
result.linkParents(decl.parent)
return result
}
val existing = program.namespace.lookup(listOf(decl.name), decl)
if (existing != null && existing !== decl) if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing) nameError(decl.name, decl.position, existing)
return super.visit(decl) return super.visit(decl)
} }
override fun visit(subroutine: Subroutine): IStatement { override fun visit(subroutine: Subroutine): Statement {
if(subroutine.name in BuiltinFunctions) { if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined // the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position)) checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
@ -70,7 +105,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
if (subroutine.parameters.any { it.name in BuiltinFunctions }) if (subroutine.parameters.any { it.name in BuiltinFunctions })
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position)) checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
val existing = namespace.lookup(listOf(subroutine.name), subroutine) val existing = program.namespace.lookup(listOf(subroutine.name), subroutine)
if (existing != null && existing !== subroutine) if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing) nameError(subroutine.name, subroutine.position, existing)
@ -98,8 +133,8 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
subroutine.parameters subroutine.parameters
.filter { it.name !in namesInSub } .filter { it.name !in namesInSub }
.forEach { .forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null, val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.DONTCARE, null, it.name, null, null,
isArray = false, autoGenerated = true, position = subroutine.position) isArray = false, autogeneratedDontRemove = true, position = subroutine.position)
vardecl.linkParents(subroutine) vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl) subroutine.statements.add(0, vardecl)
} }
@ -109,19 +144,19 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
return super.visit(subroutine) return super.visit(subroutine)
} }
override fun visit(label: Label): IStatement { override fun visit(label: Label): Statement {
if(label.name in BuiltinFunctions) { if(label.name in BuiltinFunctions) {
// the builtin functions can't be redefined // the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position)) checkResult.add(NameError("builtin function cannot be redefined", label.position))
} else { } else {
val existing = namespace.lookup(listOf(label.name), label) val existing = program.namespace.lookup(listOf(label.name), label)
if (existing != null && existing !== label) if (existing != null && existing !== label)
nameError(label.name, label.position, existing) nameError(label.name, label.position, existing)
} }
return super.visit(label) return super.visit(label)
} }
override fun visit(forLoop: ForLoop): IStatement { override fun visit(forLoop: ForLoop): Statement {
// If the for loop has a decltype, it means to declare the loopvar inside the loop body // If the for loop has a decltype, it means to declare the loopvar inside the loop body
// rather than reusing an already declared loopvar from an outer scope. // rather than reusing an already declared loopvar from an outer scope.
// For loops that loop over an interable variable (instead of a range of numbers) get an // For loops that loop over an interable variable (instead of a range of numbers) get an
@ -137,8 +172,8 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first()) val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
if(existing==null) { if(existing==null) {
// create the local scoped for loop variable itself // create the local scoped for loop variable itself
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, null,
isArray = false, autoGenerated = true, position = forLoop.loopVar.position) isArray = false, autogeneratedDontRemove = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body) vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl) forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body' forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@ -150,8 +185,8 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first()) val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first())
if(existing==null) { if(existing==null) {
// create loop iteration counter variable (without value, to avoid an assignment) // create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null, val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, ForLoop.iteratorLoopcounterVarname, null, null,
isArray = false, autoGenerated = true, position = forLoop.loopVar.position) isArray = false, autogeneratedDontRemove = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body) vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl) forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body' forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@ -167,50 +202,100 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
return super.visit(assignTarget) return super.visit(assignTarget)
} }
override fun visit(returnStmt: Return): IStatement { override fun visit(returnStmt: Return): Statement {
if(returnStmt.values.isNotEmpty()) { if(returnStmt.value!=null) {
// possibly adjust any literal values returned, into the desired returning data type // possibly adjust any literal values returned, into the desired returning data type
val subroutine = returnStmt.definingSubroutine()!! val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size!=returnStmt.values.size) if(subroutine.returntypes.size!=1)
return returnStmt // mismatch in number of return values, error will be printed later. return returnStmt // mismatch in number of return values, error will be printed later.
val newValues = mutableListOf<IExpression>() val newValue: Expression
for(returnvalue in returnStmt.values.zip(subroutine.returntypes)) { val lval = returnStmt.value as? NumericLiteralValue
val lval = returnvalue.first as? LiteralValue
if(lval!=null) { if(lval!=null) {
val adjusted = lval.cast(returnvalue.second) val adjusted = lval.cast(subroutine.returntypes.single())
if(adjusted!=null && adjusted !== lval) newValue = if(adjusted!=null && adjusted !== lval) adjusted else lval
newValues.add(adjusted) } else {
else newValue = returnStmt.value!!
newValues.add(lval)
} }
else
newValues.add(returnvalue.first) returnStmt.value = newValue
}
returnStmt.values = newValues
} }
return super.visit(returnStmt) return super.visit(returnStmt)
} }
override fun visit(refLiteral: ReferenceLiteralValue): Expression {
internal val anonymousVariablesFromHeap = mutableMapOf<String, Pair<LiteralValue, VarDecl>>() if(refLiteral.parent !is VarDecl) {
return makeIdentifierFromRefLv(refLiteral)
override fun visit(literalValue: LiteralValue): LiteralValue {
if(literalValue.heapId!=null && literalValue.parent !is VarDecl) {
// a literal value that's not declared as a variable, which refers to something on the heap.
// we need to introduce an auto-generated variable for this to be able to refer to the value!
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue,
isArray = false, autoGenerated = false, position = literalValue.position)
anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable)
} }
return super.visit(literalValue) return super.visit(refLiteral)
} }
override fun visit(addressOf: AddressOf): IExpression { private fun makeIdentifierFromRefLv(refLiteral: ReferenceLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable
// we need to introduce an auto-generated variable for this to be able to refer to the value
refLiteral.addToHeap(program.heap)
val variable = VarDecl.createAuto(refLiteral, program.heap)
addVarDecl(refLiteral.definingScope(), variable)
// replace the reference literal by a identifier reference
val identifier = IdentifierReference(listOf(variable.name), variable.position)
identifier.parent = refLiteral.parent
return identifier
}
override fun visit(addressOf: AddressOf): Expression {
// register the scoped name of the referenced identifier // register the scoped name of the referenced identifier
val variable= addressOf.identifier.targetVarDecl(namespace) ?: return addressOf val variable= addressOf.identifier.targetVarDecl(program.namespace) ?: return addressOf
addressOf.scopedname = variable.scopedname addressOf.scopedname = variable.scopedname
return super.visit(addressOf) return super.visit(addressOf)
} }
override fun visit(structDecl: StructDecl): Statement {
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl!=null && decl.datatype !in NumericDatatypes)
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
}
return super.visit(structDecl)
}
override fun visit(expr: BinaryExpression): Expression {
return when {
expr.left is ReferenceLiteralValue ->
processBinaryExprWithReferenceVal(expr.left as ReferenceLiteralValue, expr.right, expr)
expr.right is ReferenceLiteralValue ->
processBinaryExprWithReferenceVal(expr.right as ReferenceLiteralValue, expr.left, expr)
else -> super.visit(expr)
}
}
private fun processBinaryExprWithReferenceVal(refLv: ReferenceLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
// expressions on strings or arrays
if(refLv.isString) {
val constvalue = operand.constValue(program)
if(constvalue!=null) {
if (expr.operator == "*") {
// repeat a string a number of times
return ReferenceLiteralValue(refLv.inferType(program),
refLv.str!!.repeat(constvalue.number.toInt()), null, null, expr.position)
}
}
if(expr.operator == "+" && operand is ReferenceLiteralValue) {
if (operand.isString) {
// concatenate two strings
return ReferenceLiteralValue(refLv.inferType(program),
"${refLv.str}${operand.str}", null, null, expr.position)
}
}
}
return expr
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
if(declList.all{it.name!=variable.name})
declList.add(variable)
}
} }

View File

@ -1,6 +1,6 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.* import prog8.ast.INameScope
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.ast.expressions.FunctionCall import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.FunctionCallStatement

View File

@ -1,6 +1,7 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.* import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
@ -13,38 +14,38 @@ interface IAstModifyingVisitor {
module.statements = module.statements.asSequence().map { it.accept(this) }.toMutableList() module.statements = module.statements.asSequence().map { it.accept(this) }.toMutableList()
} }
fun visit(expr: PrefixExpression): IExpression { fun visit(expr: PrefixExpression): Expression {
expr.expression = expr.expression.accept(this) expr.expression = expr.expression.accept(this)
return expr return expr
} }
fun visit(expr: BinaryExpression): IExpression { fun visit(expr: BinaryExpression): Expression {
expr.left = expr.left.accept(this) expr.left = expr.left.accept(this)
expr.right = expr.right.accept(this) expr.right = expr.right.accept(this)
return expr return expr
} }
fun visit(directive: Directive): IStatement { fun visit(directive: Directive): Statement {
return directive return directive
} }
fun visit(block: Block): IStatement { fun visit(block: Block): Statement {
block.statements = block.statements.asSequence().map { it.accept(this) }.toMutableList() block.statements = block.statements.asSequence().map { it.accept(this) }.toMutableList()
return block return block
} }
fun visit(decl: VarDecl): IStatement { fun visit(decl: VarDecl): Statement {
decl.value = decl.value?.accept(this) decl.value = decl.value?.accept(this)
decl.arraysize?.accept(this) decl.arraysize?.accept(this)
return decl return decl
} }
fun visit(subroutine: Subroutine): IStatement { fun visit(subroutine: Subroutine): Statement {
subroutine.statements = subroutine.statements.asSequence().map { it.accept(this) }.toMutableList() subroutine.statements = subroutine.statements.asSequence().map { it.accept(this) }.toMutableList()
return subroutine return subroutine
} }
fun visit(functionCall: FunctionCall): IExpression { fun visit(functionCall: FunctionCall): Expression {
val newtarget = functionCall.target.accept(this) val newtarget = functionCall.target.accept(this)
if(newtarget is IdentifierReference) if(newtarget is IdentifierReference)
functionCall.target = newtarget functionCall.target = newtarget
@ -52,7 +53,7 @@ interface IAstModifyingVisitor {
return functionCall return functionCall
} }
fun visit(functionCallStatement: FunctionCallStatement): IStatement { fun visit(functionCallStatement: FunctionCallStatement): Statement {
val newtarget = functionCallStatement.target.accept(this) val newtarget = functionCallStatement.target.accept(this)
if(newtarget is IdentifierReference) if(newtarget is IdentifierReference)
functionCallStatement.target = newtarget functionCallStatement.target = newtarget
@ -60,13 +61,13 @@ interface IAstModifyingVisitor {
return functionCallStatement return functionCallStatement
} }
fun visit(identifier: IdentifierReference): IExpression { fun visit(identifier: IdentifierReference): Expression {
// note: this is an identifier that is used in an expression. // note: this is an identifier that is used in an expression.
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc) // other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
return identifier return identifier
} }
fun visit(jump: Jump): IStatement { fun visit(jump: Jump): Statement {
if(jump.identifier!=null) { if(jump.identifier!=null) {
val ident = jump.identifier.accept(this) val ident = jump.identifier.accept(this)
if(ident is IdentifierReference && ident!==jump.identifier) { if(ident is IdentifierReference && ident!==jump.identifier) {
@ -76,84 +77,88 @@ interface IAstModifyingVisitor {
return jump return jump
} }
fun visit(ifStatement: IfStatement): IStatement { fun visit(ifStatement: IfStatement): Statement {
ifStatement.condition = ifStatement.condition.accept(this) ifStatement.condition = ifStatement.condition.accept(this)
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
return ifStatement return ifStatement
} }
fun visit(branchStatement: BranchStatement): IStatement { fun visit(branchStatement: BranchStatement): Statement {
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
return branchStatement return branchStatement
} }
fun visit(range: RangeExpr): IExpression { fun visit(range: RangeExpr): Expression {
range.from = range.from.accept(this) range.from = range.from.accept(this)
range.to = range.to.accept(this) range.to = range.to.accept(this)
range.step = range.step.accept(this) range.step = range.step.accept(this)
return range return range
} }
fun visit(label: Label): IStatement { fun visit(label: Label): Statement {
return label return label
} }
fun visit(literalValue: LiteralValue): LiteralValue { fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
if(literalValue.arrayvalue!=null) {
for(av in literalValue.arrayvalue.withIndex()) {
val newvalue = av.value.accept(this)
literalValue.arrayvalue[av.index] = newvalue
}
}
return literalValue return literalValue
} }
fun visit(assignment: Assignment): IStatement { fun visit(refLiteral: ReferenceLiteralValue): Expression {
assignment.targets = assignment.targets.map { it.accept(this) } if(refLiteral.array!=null) {
for(av in refLiteral.array.withIndex()) {
val newvalue = av.value.accept(this)
refLiteral.array[av.index] = newvalue
}
}
return refLiteral
}
fun visit(assignment: Assignment): Statement {
assignment.target = assignment.target.accept(this)
assignment.value = assignment.value.accept(this) assignment.value = assignment.value.accept(this)
return assignment return assignment
} }
fun visit(postIncrDecr: PostIncrDecr): IStatement { fun visit(postIncrDecr: PostIncrDecr): Statement {
postIncrDecr.target = postIncrDecr.target.accept(this) postIncrDecr.target = postIncrDecr.target.accept(this)
return postIncrDecr return postIncrDecr
} }
fun visit(contStmt: Continue): IStatement { fun visit(contStmt: Continue): Statement {
return contStmt return contStmt
} }
fun visit(breakStmt: Break): IStatement { fun visit(breakStmt: Break): Statement {
return breakStmt return breakStmt
} }
fun visit(forLoop: ForLoop): IStatement { fun visit(forLoop: ForLoop): Statement {
forLoop.loopVar?.accept(this) forLoop.loopVar?.accept(this)
forLoop.iterable = forLoop.iterable.accept(this) forLoop.iterable = forLoop.iterable.accept(this)
forLoop.body = forLoop.body.accept(this) as AnonymousScope forLoop.body = forLoop.body.accept(this) as AnonymousScope
return forLoop return forLoop
} }
fun visit(whileLoop: WhileLoop): IStatement { fun visit(whileLoop: WhileLoop): Statement {
whileLoop.condition = whileLoop.condition.accept(this) whileLoop.condition = whileLoop.condition.accept(this)
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
return whileLoop return whileLoop
} }
fun visit(repeatLoop: RepeatLoop): IStatement { fun visit(repeatLoop: RepeatLoop): Statement {
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this) repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
return repeatLoop return repeatLoop
} }
fun visit(returnStmt: Return): IStatement { fun visit(returnStmt: Return): Statement {
returnStmt.values = returnStmt.values.map { it.accept(this) } returnStmt.value = returnStmt.value?.accept(this)
return returnStmt return returnStmt
} }
fun visit(arrayIndexedExpression: ArrayIndexedExpression): IExpression { fun visit(arrayIndexedExpression: ArrayIndexedExpression): Expression {
arrayIndexedExpression.identifier.accept(this) arrayIndexedExpression.identifier.accept(this)
arrayIndexedExpression.arrayspec.accept(this) arrayIndexedExpression.arrayspec.accept(this)
return arrayIndexedExpression return arrayIndexedExpression
@ -166,17 +171,17 @@ interface IAstModifyingVisitor {
return assignTarget return assignTarget
} }
fun visit(scope: AnonymousScope): IStatement { fun visit(scope: AnonymousScope): Statement {
scope.statements = scope.statements.asSequence().map { it.accept(this) }.toMutableList() scope.statements = scope.statements.asSequence().map { it.accept(this) }.toMutableList()
return scope return scope
} }
fun visit(typecast: TypecastExpression): IExpression { fun visit(typecast: TypecastExpression): Expression {
typecast.expression = typecast.expression.accept(this) typecast.expression = typecast.expression.accept(this)
return typecast return typecast
} }
fun visit(memread: DirectMemoryRead): IExpression { fun visit(memread: DirectMemoryRead): Expression {
memread.addressExpression = memread.addressExpression.accept(this) memread.addressExpression = memread.addressExpression.accept(this)
return memread return memread
} }
@ -185,35 +190,45 @@ interface IAstModifyingVisitor {
memwrite.addressExpression = memwrite.addressExpression.accept(this) memwrite.addressExpression = memwrite.addressExpression.accept(this)
} }
fun visit(addressOf: AddressOf): IExpression { fun visit(addressOf: AddressOf): Expression {
addressOf.identifier.accept(this) addressOf.identifier.accept(this)
return addressOf return addressOf
} }
fun visit(inlineAssembly: InlineAssembly): IStatement { fun visit(inlineAssembly: InlineAssembly): Statement {
return inlineAssembly return inlineAssembly
} }
fun visit(registerExpr: RegisterExpr): IExpression { fun visit(registerExpr: RegisterExpr): Expression {
return registerExpr return registerExpr
} }
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): IStatement { fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
return builtinFunctionStatementPlaceholder return builtinFunctionStatementPlaceholder
} }
fun visit(nopStatement: NopStatement): IStatement { fun visit(nopStatement: NopStatement): Statement {
return nopStatement return nopStatement
} }
fun visit(whenStatement: WhenStatement): IStatement { fun visit(whenStatement: WhenStatement): Statement {
whenStatement.condition.accept(this) whenStatement.condition.accept(this)
whenStatement.choices.forEach { it.accept(this) } whenStatement.choices.forEach { it.accept(this) }
return whenStatement return whenStatement
} }
fun visit(whenChoice: WhenChoice) { fun visit(whenChoice: WhenChoice) {
whenChoice.value?.accept(this) whenChoice.values?.forEach { it.accept(this) }
whenChoice.statements.accept(this) whenChoice.statements.accept(this)
} }
fun visit(structDecl: StructDecl): Statement {
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
return structDecl
}
fun visit(structLv: StructLiteralValue): Expression {
structLv.values = structLv.values.map { it.accept(this) }
return structLv
}
} }

View File

@ -1,6 +1,7 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.* import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
@ -75,12 +76,15 @@ interface IAstVisitor {
fun visit(label: Label) { fun visit(label: Label) {
} }
fun visit(literalValue: LiteralValue) { fun visit(numLiteral: NumericLiteralValue) {
literalValue.arrayvalue?.let { it.forEach { v->v.accept(this) }} }
fun visit(refLiteral: ReferenceLiteralValue) {
refLiteral.array?.let { it.forEach { v->v.accept(this) }}
} }
fun visit(assignment: Assignment) { fun visit(assignment: Assignment) {
assignment.targets.forEach { it.accept(this) } assignment.target.accept(this)
assignment.value.accept(this) assignment.value.accept(this)
} }
@ -111,7 +115,7 @@ interface IAstVisitor {
} }
fun visit(returnStmt: Return) { fun visit(returnStmt: Return) {
returnStmt.values.forEach { it.accept(this) } returnStmt.value?.accept(this)
} }
fun visit(arrayIndexedExpression: ArrayIndexedExpression) { fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
@ -163,7 +167,15 @@ interface IAstVisitor {
} }
fun visit(whenChoice: WhenChoice) { fun visit(whenChoice: WhenChoice) {
whenChoice.value?.accept(this) whenChoice.values?.forEach { it.accept(this) }
whenChoice.statements.accept(this) whenChoice.statements.accept(this)
} }
fun visit(structDecl: StructDecl) {
structDecl.statements.forEach { it.accept(this) }
}
fun visit(structLv: StructLiteralValue) {
structLv.values.forEach { it.accept(this) }
}
} }

View File

@ -1,9 +1,10 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.* import prog8.ast.Module
import prog8.ast.base.SyntaxError import prog8.ast.base.SyntaxError
import prog8.ast.base.printWarning import prog8.ast.base.printWarning
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.ast.statements.Statement
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor { internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
private val checkResult: MutableList<SyntaxError> = mutableListOf() private val checkResult: MutableList<SyntaxError> = mutableListOf()
@ -17,7 +18,7 @@ internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
*/ */
override fun visit(module: Module) { override fun visit(module: Module) {
super.visit(module) super.visit(module)
val newStatements : MutableList<IStatement> = mutableListOf() val newStatements : MutableList<Statement> = mutableListOf()
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address") val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
for (sourceStmt in module.statements) { for (sourceStmt in module.statements) {

View File

@ -1,17 +1,54 @@
package prog8.ast.processing package prog8.ast.processing
import kotlin.comparisons.nullsLast
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName import prog8.ast.base.initvarsSubName
import prog8.ast.base.printWarning import prog8.ast.base.printWarning
import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.*
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
when {
structAssignment.value is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null)
throw FatalAstException("can only assign arrays or structs to structs")
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
null, sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
structAssignment.value is StructLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}
}
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor { internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
// Reorders the statements in a way the compiler needs. // Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set. // - 'main' block must be the very first statement UNLESS it has an address set.
@ -60,7 +97,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
module.statements.addAll(0, directives) module.statements.addAll(0, directives)
} }
override fun visit(block: Block): IStatement { override fun visit(block: Block): Statement {
val subroutines = block.statements.filterIsInstance<Subroutine>() val subroutines = block.statements.filterIsInstance<Subroutine>()
var numSubroutinesAtEnd = 0 var numSubroutinesAtEnd = 0
@ -91,7 +128,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
&& stmtBeforeFirstSub !is Jump && stmtBeforeFirstSub !is Jump
&& stmtBeforeFirstSub !is Subroutine && stmtBeforeFirstSub !is Subroutine
&& stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) { && stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) {
val ret = Return(emptyList(), stmtBeforeFirstSub.position) val ret = Return(null, stmtBeforeFirstSub.position)
ret.linkParents(block) ret.linkParents(block)
block.statements.add(block.statements.size - numSubroutinesAtEnd, ret) block.statements.add(block.statements.size - numSubroutinesAtEnd, ret)
} }
@ -124,7 +161,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return super.visit(block) return super.visit(block)
} }
override fun visit(subroutine: Subroutine): IStatement { override fun visit(subroutine: Subroutine): Statement {
super.visit(subroutine) super.visit(subroutine)
val varDecls = subroutine.statements.filterIsInstance<VarDecl>() val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
@ -139,7 +176,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
// and if an assembly block doesn't contain a rts/rti // and if an assembly block doesn't contain a rts/rti
if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) { if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) {
if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) { if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) {
val returnStmt = Return(emptyList(), subroutine.position) val returnStmt = Return(null, subroutine.position)
returnStmt.linkParents(subroutine) returnStmt.linkParents(subroutine)
subroutine.statements.add(returnStmt) subroutine.statements.add(returnStmt)
} }
@ -149,7 +186,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return subroutine return subroutine
} }
override fun visit(expr: BinaryExpression): IExpression { override fun visit(expr: BinaryExpression): Expression {
val leftDt = expr.left.inferType(program) val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program) val rightDt = expr.right.inferType(program)
if(leftDt!=null && rightDt!=null && leftDt!=rightDt) { if(leftDt!=null && rightDt!=null && leftDt!=rightDt) {
@ -172,30 +209,68 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return super.visit(expr) return super.visit(expr)
} }
override fun visit(assignment: Assignment): IStatement { override fun visit(assignment: Assignment): Statement {
val target=assignment.singleTarget val assg = super.visit(assignment)
if(target!=null) { if(assg !is Assignment)
return assg
// see if a typecast is needed to convert the value's type into the proper target type // see if a typecast is needed to convert the value's type into the proper target type
val valuetype = assignment.value.inferType(program) val valuetype = assg.value.inferType(program)
val targettype = target.inferType(program, assignment) val targettype = assg.target.inferType(program, assg)
if(targettype!=null && valuetype!=null && valuetype!=targettype) { if(targettype!=null && valuetype!=null) {
if(valuetype!=targettype) {
if (valuetype isAssignableTo targettype) { if (valuetype isAssignableTo targettype) {
assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position) assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
assignment.value.linkParents(assignment) assg.value.linkParents(assg)
} }
// if they're not assignable, we'll get a proper error later from the AstChecker // if they're not assignable, we'll get a proper error later from the AstChecker
} }
} else TODO("multi-target assign")
return super.visit(assignment)
} }
override fun visit(functionCallStatement: FunctionCallStatement): IStatement { // struct assignments will be flattened (if it's not a struct literal)
if(valuetype==DataType.STRUCT && targettype==DataType.STRUCT) {
if(assg.value is StructLiteralValue)
return assg // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed)
val assignments = flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
return if(assignments.isEmpty()) {
// something went wrong (probably incompatible struct types)
// we'll get an error later from the AstChecker
assg
} else {
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
scope.linkParents(assg.parent)
scope
}
}
if(assg.aug_op!=null) {
// transform augmented assg into normal assg so we have one case less to deal with later
val newTarget: Expression =
when {
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
assg.target.identifier != null -> assg.target.identifier!!
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
else -> throw FatalAstException("strange assg")
}
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
expression.linkParents(assg.parent)
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
convertedAssignment.linkParents(assg.parent)
return super.visit(convertedAssignment)
}
return assg
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope()) checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
return super.visit(functionCallStatement) return super.visit(functionCallStatement)
} }
override fun visit(functionCall: FunctionCall): IExpression { override fun visit(functionCall: FunctionCall): Expression {
checkFunctionCallArguments(functionCall, functionCall.definingScope()) checkFunctionCallArguments(functionCall, functionCall.definingScope())
return super.visit(functionCall) return super.visit(functionCall)
} }
@ -246,29 +321,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
} }
} }
private fun sortConstantAssignmentSequence(first: Assignment, stmtIter: MutableIterator<IStatement>): Pair<List<Assignment>, IStatement?> { override fun visit(typecast: TypecastExpression): Expression {
val sequence= mutableListOf(first)
var trailing: IStatement? = null
while(stmtIter.hasNext()) {
val next = stmtIter.next()
if(next is Assignment) {
val constValue = next.value.constValue(program)
if(constValue==null) {
trailing = next
break
}
sequence.add(next)
}
else {
trailing=next
break
}
}
val sorted = sequence.sortedWith(compareBy({it.value.inferType(program)}, {it.singleTarget?.shortString(true)}))
return Pair(sorted, trailing)
}
override fun visit(typecast: TypecastExpression): IExpression {
// warn about any implicit type casts to Float, because that may not be intended // warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) { if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position) printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
@ -276,10 +329,90 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return super.visit(typecast) return super.visit(typecast)
} }
override fun visit(whenStatement: WhenStatement): IStatement { override fun visit(whenStatement: WhenStatement): Statement {
// sort the choices in low-to-high value order // make sure all choices are just for one single value
val choices = whenStatement.choices.toList()
for(choice in choices) {
if(choice.values==null || choice.values.size==1)
continue
for(v in choice.values) {
val newchoice=WhenChoice(listOf(v), choice.statements, choice.position)
newchoice.parent = choice.parent
whenStatement.choices.add(newchoice)
}
whenStatement.choices.remove(choice)
}
// sort the choices in low-to-high value order (nulls last)
whenStatement.choices whenStatement.choices
.sortWith(compareBy<WhenChoice, Int?>(nullsLast(), {it.value?.constValue(program)?.asIntegerValue})) .sortWith(compareBy<WhenChoice, Int?>(nullsLast(), {it.values?.single()?.constValue(program)?.number?.toInt()}))
return super.visit(whenStatement) return super.visit(whenStatement)
} }
override fun visit(memread: DirectMemoryRead): Expression {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt!=DataType.UWORD) {
val literaladdr = memread.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)!!
} else {
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
memread.addressExpression.parent = memread
}
}
return super.visit(memread)
}
override fun visit(memwrite: DirectMemoryWrite) {
val dt = memwrite.addressExpression.inferType(program)
if(dt!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)!!
} else {
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
memwrite.addressExpression.parent = memwrite
}
}
super.visit(memwrite)
}
override fun visit(structLv: StructLiteralValue): Expression {
val litval = super.visit(structLv)
if(litval !is StructLiteralValue)
return litval
val decl = litval.parent as? VarDecl
if(decl != null) {
val struct = decl.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
} else {
val assign = litval.parent as? Assignment
if (assign != null) {
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
if(decl2 != null) {
val struct = decl2.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
}
}
}
return litval
}
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
structLv.values = struct.statements.zip(structLv.values).map {
val memberDt = (it.first as VarDecl).datatype
val valueDt = it.second.inferType(program)
if (valueDt != memberDt)
TypecastExpression(it.second, memberDt, true, it.second.position)
else
it.second
}
}
} }

View File

@ -1,15 +1,15 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.* import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.base.autoHeapValuePrefix import prog8.ast.expressions.*
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.HeapValues
internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstModifyingVisitor {
internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope, private val heap: HeapValues): IAstModifyingVisitor {
// For VarDecls that declare an initialization value: // For VarDecls that declare an initialization value:
// Replace the vardecl with an assignment (to set the initial value), // Replace the vardecl with an assignment (to set the initial value),
// and add a new vardecl with the default constant value of that type (usually zero) to the scope. // and add a new vardecl with the default constant value of that type (usually zero) to the scope.
@ -21,22 +21,31 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc). // Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val vardeclsToAdd = mutableMapOf<INameScope, MutableMap<String, VarDecl>>() private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
override fun visit(module: Module) { override fun visit(module: Module) {
vardeclsToAdd.clear() vardeclsToAdd.clear()
super.visit(module) super.visit(module)
// add any new vardecls to the various scopes // add any new vardecls to the various scopes
for(decl in vardeclsToAdd) for((where, decls) in vardeclsToAdd) {
for(d in decl.value) { where.statements.addAll(0, decls)
d.value.linkParents(decl.key as Node) decls.forEach { it.linkParents(where as Node) }
decl.key.statements.add(0, d.value)
} }
} }
override fun visit(decl: VarDecl): IStatement { override fun visit(decl: VarDecl): Statement {
super.visit(decl) super.visit(decl)
if(decl.isArray && decl.value==null) {
// array datatype without initialization value, add list of zeros
val arraysize = decl.arraysize!!.size()!!
val array = ReferenceLiteralValue(decl.datatype, null,
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
null, decl.position)
array.addToHeap(heap)
decl.value = array
}
if(decl.type!= VarDeclType.VAR || decl.value==null) if(decl.type!= VarDeclType.VAR || decl.value==null)
return decl return decl
@ -45,13 +54,13 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
addVarDecl(scope, decl.asDefaultValueDecl(null)) addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!! val declvalue = decl.value!!
val value = val value =
if(declvalue is LiteralValue) { if(declvalue is NumericLiteralValue) {
val converted = declvalue.cast(decl.datatype) val converted = declvalue.cast(decl.datatype)
converted ?: declvalue converted ?: declvalue
} }
else else
declvalue declvalue
val identifierName = listOf(decl.name) // // TODO this was: (scoped name) decl.scopedname.split(".") val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".")
return VariableInitializationAssignment( return VariableInitializationAssignment(
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position), AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
null, null,
@ -59,35 +68,36 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
decl.position decl.position
) )
} }
return decl return decl
} }
override fun visit(functionCall: FunctionCall): IExpression { override fun visit(functionCall: FunctionCall): Expression {
val targetStatement = functionCall.target.targetSubroutine(namespace) val targetStatement = functionCall.target.targetSubroutine(namespace)
if(targetStatement!=null) { if(targetStatement!=null) {
var node: Node = functionCall var node: Node = functionCall
while(node !is IStatement) while(node !is Statement)
node=node.parent node=node.parent
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node) addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node)
} }
return functionCall return functionCall
} }
override fun visit(functionCallStatement: FunctionCallStatement): IStatement { override fun visit(functionCallStatement: FunctionCallStatement): Statement {
val targetStatement = functionCallStatement.target.targetSubroutine(namespace) val targetStatement = functionCallStatement.target.targetSubroutine(namespace)
if(targetStatement!=null) if(targetStatement!=null)
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement) addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
return functionCallStatement return functionCallStatement
} }
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<IExpression>, parent: IStatement) { private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<Expression>, parent: Statement) {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead. // functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
for(argparam in subroutine.parameters.withIndex().zip(arglist)) { for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) { if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) {
if(argparam.second is AddressOf) if(argparam.second is AddressOf)
continue continue
val idref = argparam.second as? IdentifierReference val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? LiteralValue val strvalue = argparam.second as? ReferenceLiteralValue
if(idref!=null) { if(idref!=null) {
val variable = idref.targetVarDecl(namespace) val variable = idref.targetVarDecl(namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) { if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
@ -99,18 +109,15 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
} }
else if(strvalue!=null) { else if(strvalue!=null) {
if(strvalue.isString) { if(strvalue.isString) {
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl.createAuto(strvalue, heap)
addVarDecl(strvalue.definingScope(), variable)
// replace the argument with &autovar // replace the argument with &autovar
val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}" val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position) val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
pointerExpr.scopedname = parent.makeScopedName(autoVarName) pointerExpr.scopedname = parent.makeScopedName(variable.name)
pointerExpr.linkParents(arglist[argparam.first.index].parent) pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr arglist[argparam.first.index] = pointerExpr
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue,
isArray = false, autoGenerated = false, position = strvalue.position)
addVarDecl(strvalue.definingScope(), variable)
// println("MADE ANONVAR $variable") // XXX
} }
} }
} }
@ -119,8 +126,10 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
private fun addVarDecl(scope: INameScope, variable: VarDecl) { private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd) if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableMapOf() vardeclsToAdd[scope] = mutableListOf()
vardeclsToAdd.getValue(scope)[variable.name]=variable val declList = vardeclsToAdd.getValue(scope)
if(declList.all{it.name!=variable.name})
declList.add(variable)
} }
} }

View File

@ -6,11 +6,40 @@ import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import kotlin.math.max
import kotlin.math.min
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : IStatement { sealed class Statement : Node {
abstract fun accept(visitor: IAstModifyingVisitor) : Statement
abstract fun accept(visitor: IAstVisitor)
fun makeScopedName(name: String): String {
// easy way out is to always return the full scoped name.
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
// and like this, we can cache the name even,
// like in a lazy property on the statement object itself (label, subroutine, vardecl)
val scope = mutableListOf<String>()
var statementScope = this.parent
while(statementScope !is ParentSentinel && statementScope !is Module) {
if(statementScope is INameScope) {
scope.add(0, statementScope.name)
}
statementScope = statementScope.parent
}
if(name.isNotEmpty())
scope.add(name)
return scope.joinToString(".")
}
abstract val expensiveToInline: Boolean
fun definingBlock(): Block {
if(this is Block)
return this
return findParentNode<Block>(this)!!
}
}
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
override var parent: Node = ParentSentinel override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {} override fun linkParents(parent: Node) {}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@ -25,9 +54,9 @@ data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusf
class Block(override val name: String, class Block(override val name: String,
val address: Int?, val address: Int?,
override var statements: MutableList<IStatement>, override var statements: MutableList<Statement>,
val isInLibrary: Boolean, val isInLibrary: Boolean,
override val position: Position) : IStatement, INameScope { override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline override val expensiveToInline
get() = statements.any { it.expensiveToInline } get() = statements.any { it.expensiveToInline }
@ -47,7 +76,7 @@ class Block(override val name: String,
fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet() fun options() = statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.map {it.name!!}.toSet()
} }
data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : IStatement { data class Directive(val directive: String, val args: List<DirectiveArg>, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false override val expensiveToInline = false
@ -68,7 +97,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
} }
} }
data class Label(val name: String, override val position: Position) : IStatement { data class Label(val name: String, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false override val expensiveToInline = false
@ -86,24 +115,24 @@ data class Label(val name: String, override val position: Position) : IStatement
val scopedname: String by lazy { makeScopedName(name) } val scopedname: String by lazy { makeScopedName(name) }
} }
open class Return(var values: List<IExpression>, override val position: Position) : IStatement { open class Return(var value: Expression?, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = values.any { it !is LiteralValue } override val expensiveToInline = value!=null && value !is NumericLiteralValue
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
values.forEach {it.linkParents(this)} value?.linkParents(this)
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String { override fun toString(): String {
return "Return(values: $values, pos=$position)" return "Return($value, pos=$position)"
} }
} }
class ReturnFromIrq(override val position: Position) : Return(emptyList(), position) { class ReturnFromIrq(override val position: Position) : Return(null, position) {
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
@ -112,7 +141,7 @@ class ReturnFromIrq(override val position: Position) : Return(emptyList(), posit
} }
} }
class Continue(override val position: Position) : IStatement { class Continue(override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false override val expensiveToInline = false
@ -124,7 +153,7 @@ class Continue(override val position: Position) : IStatement {
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
} }
class Break(override val position: Position) : IStatement { class Break(override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false override val expensiveToInline = false
@ -136,18 +165,51 @@ class Break(override val position: Position) : IStatement {
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
} }
enum class ZeropageWish {
REQUIRE_ZEROPAGE,
PREFER_ZEROPAGE,
DONTCARE,
NOT_IN_ZEROPAGE
}
class VarDecl(val type: VarDeclType, class VarDecl(val type: VarDeclType,
private val declaredDatatype: DataType, private val declaredDatatype: DataType,
val zeropage: Boolean, val zeropage: ZeropageWish,
var arraysize: ArrayIndex?, var arraysize: ArrayIndex?,
val name: String, val name: String,
var value: IExpression?, private val structName: String?,
var value: Expression?,
val isArray: Boolean, val isArray: Boolean,
val autoGenerated: Boolean, val autogeneratedDontRemove: Boolean,
override val position: Position) : IStatement { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
var struct: StructDecl? = null // set later (because at parse time, we only know the name)
private set
var structHasBeenFlattened = false // set later
private set
override val expensiveToInline override val expensiveToInline
get() = value!=null && value !is LiteralValue get() = value!=null && value !is NumericLiteralValue
companion object {
fun createAuto(refLv: ReferenceLiteralValue, heap: HeapValues): VarDecl {
if(refLv.heapId==null)
throw FatalAstException("can only create autovar for a ref lv that has a heapid $refLv")
val autoVarName = "$autoHeapValuePrefix${refLv.heapId}"
return if(refLv.isArray) {
val declaredType = ArrayElementTypes.getValue(refLv.type)
val arraysize = ArrayIndex.forArray(refLv, heap)
VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, refLv,
isArray = true, autogeneratedDontRemove = true, position = refLv.position)
} else {
VarDecl(VarDeclType.VAR, refLv.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, refLv,
isArray = false, autogeneratedDontRemove = true, position = refLv.position)
}
}
}
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
val datatype = val datatype =
@ -160,7 +222,7 @@ class VarDecl(val type: VarDeclType,
DataType.FLOAT -> DataType.ARRAY_F DataType.FLOAT -> DataType.ARRAY_F
else -> { else -> {
datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position)) datatypeErrors.add(SyntaxError("array can only contain bytes/words/floats", position))
DataType.UBYTE DataType.ARRAY_UB
} }
} }
@ -168,6 +230,11 @@ class VarDecl(val type: VarDeclType,
this.parent = parent this.parent = parent
arraysize?.linkParents(this) arraysize?.linkParents(this)
value?.linkParents(this) value?.linkParents(this)
if(structName!=null) {
val structStmt = definingScope().lookup(listOf(structName), this)
if(structStmt!=null)
struct = definingScope().lookup(listOf(structName), this) as StructDecl
}
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@ -176,26 +243,47 @@ class VarDecl(val type: VarDeclType,
val scopedname: String by lazy { makeScopedName(name) } val scopedname: String by lazy { makeScopedName(name) }
override fun toString(): String { override fun toString(): String {
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, array=$isArray, value=$value, pos=$position)" return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
} }
fun asDefaultValueDecl(parent: Node?): VarDecl { fun asDefaultValueDecl(parent: Node?): VarDecl {
val constValue = when(declaredDatatype) { val constValue = when(declaredDatatype) {
DataType.UBYTE -> LiteralValue(DataType.UBYTE, 0, position = position) DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
DataType.BYTE -> LiteralValue(DataType.BYTE, 0, position = position) DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = 0, position = position) DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = 0, position = position) DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = 0.0, position = position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only set a default value for a numeric type") else -> throw FatalAstException("can only set a default value for a numeric type")
} }
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, constValue, isArray, true, position) val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, false, position)
if(parent!=null) if(parent!=null)
decl.linkParents(parent) decl.linkParents(parent)
return decl return decl
} }
fun flattenStructMembers(): MutableList<Statement> {
val result = struct!!.statements.withIndex().map {
val member = it.value as VarDecl
val initvalue = if(value!=null) (value as StructLiteralValue).values[it.index] else null
VarDecl(
VarDeclType.VAR,
member.datatype,
ZeropageWish.NOT_IN_ZEROPAGE,
member.arraysize,
mangledStructMemberName(name, member.name),
struct!!.name,
initvalue,
member.isArray,
true,
member.position
) as Statement
}.toMutableList()
structHasBeenFlattened = true
return result
}
} }
class ArrayIndex(var index: IExpression, override val position: Position) : Node { class ArrayIndex(var index: Expression, override val position: Position) : Node {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -204,9 +292,9 @@ class ArrayIndex(var index: IExpression, override val position: Position) : Node
} }
companion object { companion object {
fun forArray(v: LiteralValue, heap: HeapValues): ArrayIndex { fun forArray(v: ReferenceLiteralValue, heap: HeapValues): ArrayIndex {
val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize val arraySize = v.array?.size ?: heap.get(v.heapId!!).arraysize
return ArrayIndex(LiteralValue.optimalNumeric(arraySize, v.position), v.position) return ArrayIndex(NumericLiteralValue.optimalNumeric(arraySize, v.position), v.position)
} }
} }
@ -221,17 +309,17 @@ class ArrayIndex(var index: IExpression, override val position: Position) : Node
return("ArrayIndex($index, pos=$position)") return("ArrayIndex($index, pos=$position)")
} }
fun size() = (index as? LiteralValue)?.asIntegerValue fun size() = (index as? NumericLiteralValue)?.number?.toInt()
} }
open class Assignment(var targets: List<AssignTarget>, val aug_op : String?, var value: IExpression, override val position: Position) : IStatement { open class Assignment(var target: AssignTarget, val aug_op : String?, var value: Expression, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline override val expensiveToInline
get() = value !is LiteralValue get() = value !is NumericLiteralValue
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
targets.forEach { it.linkParents(this) } this.target.linkParents(this)
value.linkParents(this) value.linkParents(this)
} }
@ -239,19 +327,14 @@ open class Assignment(var targets: List<AssignTarget>, val aug_op : String?, var
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String { override fun toString(): String {
return("Assignment(augop: $aug_op, targets: $targets, value: $value, pos=$position)") return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
}
val singleTarget: AssignTarget?
get() {
return targets.singleOrNull() // common case
} }
} }
// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope, // This is a special class so the compiler can see if the assignments are for initializing the vars in the scope,
// or just a regular assignment. It may optimize the initialization step from this. // or just a regular assignment. It may optimize the initialization step from this.
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: IExpression, position: Position) class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: Expression, position: Position)
: Assignment(listOf(target), aug_op, value, position) : Assignment(target, aug_op, value, position)
data class AssignTarget(val register: Register?, data class AssignTarget(val register: Register?,
val identifier: IdentifierReference?, val identifier: IdentifierReference?,
@ -271,7 +354,7 @@ data class AssignTarget(val register: Register?,
fun accept(visitor: IAstVisitor) = visitor.visit(this) fun accept(visitor: IAstVisitor) = visitor.visit(this)
companion object { companion object {
fun fromExpr(expr: IExpression): AssignTarget { fun fromExpr(expr: Expression): AssignTarget {
return when (expr) { return when (expr) {
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position) is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position) is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
@ -282,7 +365,7 @@ data class AssignTarget(val register: Register?,
} }
} }
fun inferType(program: Program, stmt: IStatement): DataType? { fun inferType(program: Program, stmt: Statement): DataType? {
if(register!=null) if(register!=null)
return DataType.UBYTE return DataType.UBYTE
@ -303,23 +386,7 @@ data class AssignTarget(val register: Register?,
return null return null
} }
fun shortString(withTypePrefix: Boolean=false): String { infix fun isSameAs(value: Expression): Boolean {
if(register!=null)
return (if(withTypePrefix) "0register::" else "") + register.name
if(identifier!=null)
return (if(withTypePrefix) "3identifier::" else "") + identifier.nameInSource.last()
if(arrayindexed!=null)
return (if(withTypePrefix) "2arrayidx::" else "") + arrayindexed.identifier.nameInSource.last()
val address = memoryAddress?.addressExpression
if(address is LiteralValue)
return (if(withTypePrefix) "1address::" else "") +address.asIntegerValue.toString()
return if(withTypePrefix) "???::???" else "???"
}
fun isMemoryMapped(namespace: INameScope): Boolean =
memoryAddress!=null || (identifier?.targetVarDecl(namespace)?.type== VarDeclType.MEMORY)
infix fun isSameAs(value: IExpression): Boolean {
return when { return when {
this.memoryAddress!=null -> false this.memoryAddress!=null -> false
this.register!=null -> value is RegisterExpr && value.register==register this.register!=null -> value is RegisterExpr && value.register==register
@ -374,7 +441,7 @@ data class AssignTarget(val register: Register?,
} }
} }
class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : IStatement { class PostIncrDecr(var target: AssignTarget, val operator: String, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false override val expensiveToInline = false
@ -394,7 +461,7 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
class Jump(val address: Int?, class Jump(val address: Int?,
val identifier: IdentifierReference?, val identifier: IdentifierReference?,
val generatedLabel: String?, // used in code generation scenarios val generatedLabel: String?, // used in code generation scenarios
override val position: Position) : IStatement { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false override val expensiveToInline = false
@ -412,11 +479,11 @@ class Jump(val address: Int?,
} }
class FunctionCallStatement(override var target: IdentifierReference, class FunctionCallStatement(override var target: IdentifierReference,
override var arglist: MutableList<IExpression>, override var arglist: MutableList<Expression>,
override val position: Position) : IStatement, IFunctionCall { override val position: Position) : Statement(), IFunctionCall {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline override val expensiveToInline
get() = arglist.any { it !is LiteralValue } get() = arglist.any { it !is NumericLiteralValue }
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -432,7 +499,7 @@ class FunctionCallStatement(override var target: IdentifierReference,
} }
} }
class InlineAssembly(val assembly: String, override val position: Position) : IStatement { class InlineAssembly(val assembly: String, override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = true override val expensiveToInline = true
@ -444,8 +511,8 @@ class InlineAssembly(val assembly: String, override val position: Position) : IS
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
} }
class AnonymousScope(override var statements: MutableList<IStatement>, class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, IStatement { override val position: Position) : INameScope, Statement() {
override val name: String override val name: String
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline override val expensiveToInline
@ -469,7 +536,7 @@ class AnonymousScope(override var statements: MutableList<IStatement>,
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
} }
class NopStatement(override val position: Position): IStatement { class NopStatement(override val position: Position): Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = false override val expensiveToInline = false
@ -479,6 +546,14 @@ class NopStatement(override val position: Position): IStatement {
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
companion object {
fun insteadOf(stmt: Statement): NopStatement {
val nop = NopStatement(stmt.position)
nop.parent = stmt.parent
return nop
}
}
} }
// the subroutine class covers both the normal user-defined subroutines, // the subroutine class covers both the normal user-defined subroutines,
@ -492,8 +567,8 @@ class Subroutine(override val name: String,
val asmClobbers: Set<Register>, val asmClobbers: Set<Register>,
val asmAddress: Int?, val asmAddress: Int?,
val isAsmSubroutine: Boolean, val isAsmSubroutine: Boolean,
override var statements: MutableList<IStatement>, override var statements: MutableList<Statement>,
override val position: Position) : IStatement, INameScope { override val position: Position) : Statement(), INameScope {
var keepAlways: Boolean = false var keepAlways: Boolean = false
override val expensiveToInline override val expensiveToInline
@ -569,10 +644,10 @@ open class SubroutineParameter(val name: String,
} }
} }
class IfStatement(var condition: IExpression, class IfStatement(var condition: Expression,
var truepart: AnonymousScope, var truepart: AnonymousScope,
var elsepart: AnonymousScope, var elsepart: AnonymousScope,
override val position: Position) : IStatement { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline: Boolean override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline get() = truepart.expensiveToInline || elsepart.expensiveToInline
@ -591,7 +666,7 @@ class IfStatement(var condition: IExpression,
class BranchStatement(var condition: BranchCondition, class BranchStatement(var condition: BranchCondition,
var truepart: AnonymousScope, var truepart: AnonymousScope,
var elsepart: AnonymousScope, var elsepart: AnonymousScope,
override val position: Position) : IStatement { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline: Boolean override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline get() = truepart.expensiveToInline || elsepart.expensiveToInline
@ -608,11 +683,11 @@ class BranchStatement(var condition: BranchCondition,
class ForLoop(val loopRegister: Register?, class ForLoop(val loopRegister: Register?,
val decltype: DataType?, val decltype: DataType?,
val zeropage: Boolean, val zeropage: ZeropageWish,
val loopVar: IdentifierReference?, val loopVar: IdentifierReference?,
var iterable: IExpression, var iterable: Expression,
var body: AnonymousScope, var body: AnonymousScope,
override val position: Position) : IStatement { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = true override val expensiveToInline = true
@ -635,9 +710,9 @@ class ForLoop(val loopRegister: Register?,
} }
} }
class WhileLoop(var condition: IExpression, class WhileLoop(var condition: Expression,
var body: AnonymousScope, var body: AnonymousScope,
override val position: Position) : IStatement { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = true override val expensiveToInline = true
@ -652,8 +727,8 @@ class WhileLoop(var condition: IExpression,
} }
class RepeatLoop(var body: AnonymousScope, class RepeatLoop(var body: AnonymousScope,
var untilCondition: IExpression, var untilCondition: Expression,
override val position: Position) : IStatement { override val position: Position) : Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline = true override val expensiveToInline = true
@ -667,9 +742,9 @@ class RepeatLoop(var body: AnonymousScope,
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
} }
class WhenStatement(val condition: IExpression, class WhenStatement(val condition: Expression,
val choices: MutableList<WhenChoice>, val choices: MutableList<WhenChoice>,
override val position: Position): IStatement { override val position: Position): Statement() {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline: Boolean = true override val expensiveToInline: Boolean = true
@ -679,35 +754,40 @@ class WhenStatement(val condition: IExpression,
choices.forEach { it.linkParents(this) } choices.forEach { it.linkParents(this) }
} }
fun choiceValues(program: Program): List<Pair<Int?, WhenChoice>> { fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
// only gives sensible results when the choices are all valid (constant integers) // only gives sensible results when the choices are all valid (constant integers)
return choices val result = mutableListOf<Pair<List<Int>?, WhenChoice>>()
.map { for(choice in choices) {
val cv = it.value?.constValue(program) if(choice.values==null)
if(cv==null) result.add(null to choice)
null to it else {
val values = choice.values.map { it.constValue(program)?.number?.toInt() }
if(values.contains(null))
result.add(null to choice)
else else
cv.asNumericValue!!.toInt() to it result.add(values.filterNotNull() to choice)
} }
} }
return result
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
} }
class WhenChoice(val value: IExpression?, // if null, this is the 'else' part class WhenChoice(val values: List<Expression>?, // if null, this is the 'else' part
val statements: AnonymousScope, val statements: AnonymousScope,
override val position: Position) : Node { override val position: Position) : Node {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
value?.linkParents(this) values?.forEach { it.linkParents(this) }
statements.linkParents(this) statements.linkParents(this)
this.parent = parent this.parent = parent
} }
override fun toString(): String { override fun toString(): String {
return "Choice($value at $position)" return "Choice($values at $position)"
} }
fun accept(visitor: IAstVisitor) = visitor.visit(this) fun accept(visitor: IAstVisitor) = visitor.visit(this)
@ -715,7 +795,26 @@ class WhenChoice(val value: IExpression?, // if null, this is the 'el
} }
class DirectMemoryWrite(var addressExpression: IExpression, override val position: Position) : Node { class StructDecl(override val name: String,
override var statements: MutableList<Statement>, // actually, only vardecls here
override val position: Position): Statement(), INameScope {
override lateinit var parent: Node
override val expensiveToInline: Boolean = true
override fun linkParents(parent: Node) {
this.parent = parent
this.statements.forEach { it.linkParents(this) }
}
val numberOfElements: Int
get() = this.statements.size
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
}
class DirectMemoryWrite(var addressExpression: Expression, override val position: Position) : Node {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {

View File

@ -1,22 +1,24 @@
package prog8.compiler package prog8.compiler
import prog8.ast.antlr.escape
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.IStatement
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.StringDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor { class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
var scopelevel = 0 private var scopelevel = 0
fun indent(s: String) = " ".repeat(scopelevel) + s private fun indent(s: String) = " ".repeat(scopelevel) + s
fun outputln(text: String) = output(text + "\n") private fun outputln(text: String) = output(text + "\n")
fun outputlni(s: Any) = outputln(indent(s.toString())) private fun outputlni(s: Any) = outputln(indent(s.toString()))
fun outputi(s: Any) = output(indent(s.toString())) private fun outputi(s: Any) = output(indent(s.toString()))
override fun visit(program: Program) { override fun visit(program: Program) {
outputln("============= PROGRAM ${program.name} (FROM AST) ===============") outputln("============= PROGRAM ${program.name} (FROM AST) ===============")
@ -76,7 +78,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
output("\n") output("\n")
} }
fun datatypeString(dt: DataType): String { private fun datatypeString(dt: DataType): String {
return when(dt) { return when(dt) {
in NumericDatatypes -> dt.toString().toLowerCase() in NumericDatatypes -> dt.toString().toLowerCase()
in StringDatatypes -> dt.toString().toLowerCase() in StringDatatypes -> dt.toString().toLowerCase()
@ -85,21 +87,30 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
DataType.ARRAY_UW -> "uword[" DataType.ARRAY_UW -> "uword["
DataType.ARRAY_W -> "word[" DataType.ARRAY_W -> "word["
DataType.ARRAY_F -> "float[" DataType.ARRAY_F -> "float["
else -> "?????" DataType.STRUCT -> "" // the name of the struct is enough
else -> "?????2"
} }
} }
override fun visit(structDecl: StructDecl) {
outputln("struct ${structDecl.name} {")
scopelevel++
for(decl in structDecl.statements) {
outputi("")
decl.accept(this)
output("\n")
}
scopelevel--
outputlni("}")
}
override fun visit(decl: VarDecl) { override fun visit(decl: VarDecl) {
if(decl.autoGenerated) {
// skip autogenerated vardecl
return
}
when(decl.type) { when(decl.type) {
VarDeclType.VAR -> {} VarDeclType.VAR -> {}
VarDeclType.CONST -> output("const ") VarDeclType.CONST -> output("const ")
VarDeclType.MEMORY -> output("&") VarDeclType.MEMORY -> output("&")
} }
output(decl.struct?.name ?: "")
output(datatypeString(decl.datatype)) output(datatypeString(decl.datatype))
if(decl.arraysize!=null) { if(decl.arraysize!=null) {
decl.arraysize!!.index.accept(this) decl.arraysize!!.index.accept(this)
@ -107,7 +118,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
if(decl.isArray) if(decl.isArray)
output("]") output("]")
if(decl.zeropage) if(decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
output(" @zp") output(" @zp")
output(" ${decl.name} ") output(" ${decl.name} ")
if(decl.value!=null) { if(decl.value!=null) {
@ -126,7 +137,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
true==param.second.stack -> "stack" true==param.second.stack -> "stack"
param.second.registerOrPair!=null -> param.second.registerOrPair.toString() param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
param.second.statusflag!=null -> param.second.statusflag.toString() param.second.statusflag!=null -> param.second.statusflag.toString()
else -> "?????" else -> "?????1"
} }
output("${datatypeString(param.first.type)} ${param.first.name} @$reg") output("${datatypeString(param.first.type)} ${param.first.name} @$reg")
if(param.first!==subroutine.parameters.last()) if(param.first!==subroutine.parameters.last())
@ -167,10 +178,10 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
} }
} }
private fun outputStatements(statements: List<IStatement>) { private fun outputStatements(statements: List<Statement>) {
for(stmt in statements) { for(stmt in statements) {
if(stmt is VarDecl && stmt.autoGenerated) if(stmt is VarDecl && stmt.autogeneratedDontRemove)
continue // skip autogenerated decls continue // skip autogenerated decls (to avoid generating a newline)
outputi("") outputi("")
stmt.accept(this) stmt.accept(this)
output("\n") output("\n")
@ -243,26 +254,50 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
output("${label.name}:") output("${label.name}:")
} }
override fun visit(literalValue: LiteralValue) { override fun visit(numLiteral: NumericLiteralValue) {
when { output(numLiteral.number.toString())
literalValue.isNumeric -> output(literalValue.asNumericValue.toString())
literalValue.isString -> output("\"${escape(literalValue.strvalue!!)}\"")
literalValue.isArray -> {
if(literalValue.arrayvalue!=null) {
output("[")
for (v in literalValue.arrayvalue) {
v.accept(this)
if (v !== literalValue.arrayvalue.last())
output(", ")
} }
output("]")
override fun visit(refLiteral: ReferenceLiteralValue) {
when {
refLiteral.isString -> output("\"${escape(refLiteral.str!!)}\"")
refLiteral.isArray -> {
if(refLiteral.array!=null) {
outputListMembers(refLiteral.array.asSequence(), '[', ']')
} }
} }
} }
} }
private fun outputListMembers(array: Sequence<Expression>, openchar: Char, closechar: Char) {
var counter = 0
output(openchar.toString())
scopelevel++
for (v in array) {
v.accept(this)
if (v !== array.last())
output(", ")
counter++
if (counter > 16) {
outputln("")
outputi("")
counter = 0
}
}
scopelevel--
output(closechar.toString())
}
override fun visit(assignment: Assignment) { override fun visit(assignment: Assignment) {
assignment.singleTarget!!.accept(this) if(assignment is VariableInitializationAssignment) {
val targetVar = assignment.target.identifier?.targetVarDecl(program.namespace)
if(targetVar?.struct != null) {
// skip STRUCT init assignments
return
}
}
assignment.target.accept(this)
if (assignment.aug_op != null) if (assignment.aug_op != null)
output(" ${assignment.aug_op} ") output(" ${assignment.aug_op} ")
else else
@ -287,7 +322,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
output("for ") output("for ")
if(forLoop.decltype!=null) { if(forLoop.decltype!=null) {
output(datatypeString(forLoop.decltype)) output(datatypeString(forLoop.decltype))
if (forLoop.zeropage) if (forLoop.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || forLoop.zeropage==ZeropageWish.PREFER_ZEROPAGE)
output(" @zp ") output(" @zp ")
else else
output(" ") output(" ")
@ -318,11 +353,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
override fun visit(returnStmt: Return) { override fun visit(returnStmt: Return) {
output("return ") output("return ")
for(v in returnStmt.values) { returnStmt.value?.accept(this)
v.accept(this)
if(v!==returnStmt.values.last())
output(", ")
}
} }
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) { override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
@ -351,8 +382,9 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
} }
override fun visit(typecast: TypecastExpression) { override fun visit(typecast: TypecastExpression) {
output("(")
typecast.expression.accept(this) typecast.expression.accept(this)
output(" as ${datatypeString(typecast.type)} ") output(" as ${datatypeString(typecast.type)}) ")
} }
override fun visit(memread: DirectMemoryRead) { override fun visit(memread: DirectMemoryRead) {
@ -397,11 +429,15 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
} }
override fun visit(whenChoice: WhenChoice) { override fun visit(whenChoice: WhenChoice) {
if(whenChoice.value==null) if(whenChoice.values==null)
outputi("else -> ") outputi("else -> ")
else { else {
outputi("") outputi("")
whenChoice.value.accept(this) for(value in whenChoice.values) {
value.accept(this)
if(value !== whenChoice.values.last())
output(",")
}
output(" -> ") output(" -> ")
} }
if(whenChoice.statements.statements.size==1) if(whenChoice.statements.statements.size==1)
@ -410,7 +446,12 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
whenChoice.statements.accept(this) whenChoice.statements.accept(this)
outputln("") outputln("")
} }
override fun visit(structLv: StructLiteralValue) {
outputListMembers(structLv.values.asSequence(), '{', '}')
}
override fun visit(nopStatement: NopStatement) { override fun visit(nopStatement: NopStatement) {
TODO("NOP???") output("; NOP @ ${nopStatement.position} $nopStatement")
} }
} }

View File

@ -1,9 +1,11 @@
package prog8.compiler package prog8.compiler
import prog8.ast.* import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.base.RegisterOrPair.* import prog8.ast.base.RegisterOrPair.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.mangledStructMemberName
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.intermediate.IntermediateProgram import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.Opcode import prog8.compiler.intermediate.Opcode
@ -115,7 +117,7 @@ class HeapValues {
fun get(heapId: Int): HeapValue { fun get(heapId: Int): HeapValue {
return heap[heapId] ?: return heap[heapId] ?:
throw IllegalArgumentException("heapId not found in heap") throw IllegalArgumentException("heapId $heapId not found in heap")
} }
fun allEntries() = heap.entries fun allEntries() = heap.entries
@ -179,8 +181,8 @@ internal class Compiler(private val program: Program) {
processVariables(subscope.value) processVariables(subscope.value)
} }
private fun translate(statements: List<IStatement>) { private fun translate(statements: List<Statement>) {
for (stmt: IStatement in statements) { for (stmt: Statement in statements) {
generatedLabelSequenceNumber++ generatedLabelSequenceNumber++
when (stmt) { when (stmt) {
is Label -> translate(stmt) is Label -> translate(stmt)
@ -213,6 +215,7 @@ internal class Compiler(private val program: Program) {
is NopStatement -> {} is NopStatement -> {}
is InlineAssembly -> translate(stmt) is InlineAssembly -> translate(stmt)
is WhenStatement -> translate(stmt) is WhenStatement -> translate(stmt)
is StructDecl -> {}
else -> TODO("translate statement $stmt to stackvm") else -> TODO("translate statement $stmt to stackvm")
} }
} }
@ -485,7 +488,7 @@ internal class Compiler(private val program: Program) {
} }
} }
private fun makeLabel(scopeStmt: IStatement, postfix: String): String { private fun makeLabel(scopeStmt: Statement, postfix: String): String {
generatedLabelSequenceNumber++ generatedLabelSequenceNumber++
return "${scopeStmt.makeScopedName("")}.<s-$generatedLabelSequenceNumber-$postfix>" return "${scopeStmt.makeScopedName("")}.<s-$generatedLabelSequenceNumber-$postfix>"
} }
@ -550,7 +553,7 @@ internal class Compiler(private val program: Program) {
prog.instr(Opcode.NOP) prog.instr(Opcode.NOP)
} }
private fun translate(expr: IExpression) { private fun translate(expr: Expression) {
when(expr) { when(expr) {
is RegisterExpr -> { is RegisterExpr -> {
prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = expr.register.name) prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = expr.register.name)
@ -591,22 +594,24 @@ internal class Compiler(private val program: Program) {
is TypecastExpression -> translate(expr) is TypecastExpression -> translate(expr)
is DirectMemoryRead -> translate(expr) is DirectMemoryRead -> translate(expr)
is AddressOf -> translate(expr) is AddressOf -> translate(expr)
is StructLiteralValue -> throw CompilerException("a struct Lv should have been flattened as assignments")
else -> { else -> {
val lv = expr.constValue(program) ?: throw CompilerException("constant expression required, not $expr") val lv = expr.constValue(program) ?: throw CompilerException("constant expression required, not $expr")
when(lv.type) { when(lv.type) {
in ByteDatatypes -> prog.instr(Opcode.PUSH_BYTE, RuntimeValue(lv.type, lv.bytevalue!!)) in ByteDatatypes -> prog.instr(Opcode.PUSH_BYTE, RuntimeValue(lv.type, lv.number.toShort()))
in WordDatatypes -> prog.instr(Opcode.PUSH_WORD, RuntimeValue(lv.type, lv.wordvalue!!)) in WordDatatypes -> prog.instr(Opcode.PUSH_WORD, RuntimeValue(lv.type, lv.number.toInt()))
DataType.FLOAT -> prog.instr(Opcode.PUSH_FLOAT, RuntimeValue(lv.type, lv.floatvalue!!)) DataType.FLOAT -> prog.instr(Opcode.PUSH_FLOAT, RuntimeValue(lv.type, lv.number.toDouble()))
in StringDatatypes -> { // TODO what about these ref types:
if(lv.heapId==null) // in StringDatatypes -> {
throw CompilerException("string should have been moved into heap ${lv.position}") // if(lv.heapId==null)
TODO("push address of string with PUSH_ADDR_HEAPVAR") // throw CompilerException("string should have been moved into heap ${lv.position}")
} // TODO("push address of string with PUSH_ADDR_HEAPVAR")
in ArrayDatatypes -> { // }
if(lv.heapId==null) // in ArrayDatatypes -> {
throw CompilerException("array should have been moved into heap ${lv.position}") // if(lv.heapId==null)
TODO("push address of array with PUSH_ADDR_HEAPVAR") // throw CompilerException("array should have been moved into heap ${lv.position}")
} // TODO("push address of array with PUSH_ADDR_HEAPVAR")
// }
else -> throw CompilerException("weird datatype") else -> throw CompilerException("weird datatype")
} }
} }
@ -673,11 +678,11 @@ internal class Compiler(private val program: Program) {
throw CompilerException("const ref should have been const-folded away") throw CompilerException("const ref should have been const-folded away")
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
when (target.datatype) { when (target.datatype) {
DataType.UBYTE -> prog.instr(Opcode.PUSH_MEM_UB, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!)) DataType.UBYTE -> prog.instr(Opcode.PUSH_MEM_UB, RuntimeValue(DataType.UWORD, (target.value as NumericLiteralValue).number))
DataType.BYTE-> prog.instr(Opcode.PUSH_MEM_B, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!)) DataType.BYTE-> prog.instr(Opcode.PUSH_MEM_B, RuntimeValue(DataType.UWORD, (target.value as NumericLiteralValue).number))
DataType.UWORD -> prog.instr(Opcode.PUSH_MEM_UW, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!)) DataType.UWORD -> prog.instr(Opcode.PUSH_MEM_UW, RuntimeValue(DataType.UWORD, (target.value as NumericLiteralValue).number))
DataType.WORD -> prog.instr(Opcode.PUSH_MEM_W, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!)) DataType.WORD -> prog.instr(Opcode.PUSH_MEM_W, RuntimeValue(DataType.UWORD, (target.value as NumericLiteralValue).number))
DataType.FLOAT -> prog.instr(Opcode.PUSH_MEM_FLOAT, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!)) DataType.FLOAT -> prog.instr(Opcode.PUSH_MEM_FLOAT, RuntimeValue(DataType.UWORD, (target.value as NumericLiteralValue).number))
else -> throw CompilerException("invalid datatype for memory variable expression: $target") else -> throw CompilerException("invalid datatype for memory variable expression: $target")
} }
} }
@ -713,7 +718,7 @@ internal class Compiler(private val program: Program) {
} }
} }
private fun translateBuiltinFunctionCall(funcname: String, args: List<IExpression>) { private fun translateBuiltinFunctionCall(funcname: String, args: List<Expression>) {
// some builtin functions are implemented directly as vm opcodes // some builtin functions are implemented directly as vm opcodes
if(funcname == "swap") { if(funcname == "swap") {
@ -743,7 +748,8 @@ internal class Compiler(private val program: Program) {
val arg=args.single() val arg=args.single()
when (arg.inferType(program)) { when (arg.inferType(program)) {
DataType.STR, DataType.STR_S -> createSyscall("${funcname}_str") DataType.STR, DataType.STR_S -> createSyscall("${funcname}_str")
else -> throw CompilerException("wrong datatype for len()") in ArrayDatatypes -> throw CompilerException("len() of an array type should have been const-folded")
else -> throw CompilerException("wrong datatype for len() $arg")
} }
} }
"any", "all" -> { "any", "all" -> {
@ -896,7 +902,7 @@ internal class Compiler(private val program: Program) {
} }
} }
private fun translateSwap(args: List<IExpression>) { private fun translateSwap(args: List<Expression>) {
// swap(x,y) is treated differently, it's not a normal function call // swap(x,y) is treated differently, it's not a normal function call
if (args.size != 2) if (args.size != 2)
throw AstException("swap requires 2 arguments") throw AstException("swap requires 2 arguments")
@ -919,7 +925,7 @@ internal class Compiler(private val program: Program) {
return return
} }
private fun translateSubroutineCall(subroutine: Subroutine, arguments: List<IExpression>, callPosition: Position) { private fun translateSubroutineCall(subroutine: Subroutine, arguments: List<Expression>, callPosition: Position) {
// evaluate the arguments and assign them into the subroutine's argument variables. // evaluate the arguments and assign them into the subroutine's argument variables.
var restoreX = Register.X in subroutine.asmClobbers var restoreX = Register.X in subroutine.asmClobbers
if(restoreX) if(restoreX)
@ -966,7 +972,7 @@ internal class Compiler(private val program: Program) {
} }
} }
private fun translateAsmSubCallArguments(subroutine: Subroutine, arguments: List<IExpression>, callPosition: Position, restoreXinitial: Boolean): Boolean { private fun translateAsmSubCallArguments(subroutine: Subroutine, arguments: List<Expression>, callPosition: Position, restoreXinitial: Boolean): Boolean {
var restoreX = restoreXinitial var restoreX = restoreXinitial
if (subroutine.parameters.size != subroutine.asmParameterRegisters.size) if (subroutine.parameters.size != subroutine.asmParameterRegisters.size)
TODO("no support yet for mix of register and non-register subroutine arguments") TODO("no support yet for mix of register and non-register subroutine arguments")
@ -982,7 +988,7 @@ internal class Compiler(private val program: Program) {
} else { } else {
when (arg.second.registerOrPair!!) { when (arg.second.registerOrPair!!) {
A -> { A -> {
val assign = Assignment(listOf(AssignTarget(Register.A, null, null, null, callPosition)), null, arg.first, callPosition) val assign = Assignment(AssignTarget(Register.A, null, null, null, callPosition), null, arg.first, callPosition)
assign.linkParents(arguments[0].parent) assign.linkParents(arguments[0].parent)
translate(assign) translate(assign)
} }
@ -991,12 +997,12 @@ internal class Compiler(private val program: Program) {
prog.instr(Opcode.RSAVEX) prog.instr(Opcode.RSAVEX)
restoreX = true restoreX = true
} }
val assign = Assignment(listOf(AssignTarget(Register.X, null, null, null, callPosition)), null, arg.first, callPosition) val assign = Assignment(AssignTarget(Register.X, null, null, null, callPosition), null, arg.first, callPosition)
assign.linkParents(arguments[0].parent) assign.linkParents(arguments[0].parent)
translate(assign) translate(assign)
} }
Y -> { Y -> {
val assign = Assignment(listOf(AssignTarget(Register.Y, null, null, null, callPosition)), null, arg.first, callPosition) val assign = Assignment(AssignTarget(Register.Y, null, null, null, callPosition), null, arg.first, callPosition)
assign.linkParents(arguments[0].parent) assign.linkParents(arguments[0].parent)
translate(assign) translate(assign)
} }
@ -1005,15 +1011,15 @@ internal class Compiler(private val program: Program) {
prog.instr(Opcode.RSAVEX) prog.instr(Opcode.RSAVEX)
restoreX = true restoreX = true
} }
val valueA: IExpression val valueA: Expression
val valueX: IExpression val valueX: Expression
val paramDt = arg.first.inferType(program) val paramDt = arg.first.inferType(program)
when (paramDt) { when (paramDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
valueA = arg.first valueA = arg.first
valueX = LiteralValue.optimalInteger(0, callPosition) valueX = NumericLiteralValue.optimalInteger(0, callPosition)
val assignA = Assignment(listOf(AssignTarget(Register.A, null, null, null, callPosition)), null, valueA, callPosition) val assignA = Assignment(AssignTarget(Register.A, null, null, null, callPosition), null, valueA, callPosition)
val assignX = Assignment(listOf(AssignTarget(Register.X, null, null, null, callPosition)), null, valueX, callPosition) val assignX = Assignment(AssignTarget(Register.X, null, null, null, callPosition), null, valueX, callPosition)
assignA.linkParents(arguments[0].parent) assignA.linkParents(arguments[0].parent)
assignX.linkParents(arguments[0].parent) assignX.linkParents(arguments[0].parent)
translate(assignA) translate(assignA)
@ -1028,15 +1034,15 @@ internal class Compiler(private val program: Program) {
} }
} }
AY -> { AY -> {
val valueA: IExpression val valueA: Expression
val valueY: IExpression val valueY: Expression
val paramDt = arg.first.inferType(program) val paramDt = arg.first.inferType(program)
when (paramDt) { when (paramDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
valueA = arg.first valueA = arg.first
valueY = LiteralValue.optimalInteger(0, callPosition) valueY = NumericLiteralValue.optimalInteger(0, callPosition)
val assignA = Assignment(listOf(AssignTarget(Register.A, null, null, null, callPosition)), null, valueA, callPosition) val assignA = Assignment(AssignTarget(Register.A, null, null, null, callPosition), null, valueA, callPosition)
val assignY = Assignment(listOf(AssignTarget(Register.Y, null, null, null, callPosition)), null, valueY, callPosition) val assignY = Assignment(AssignTarget(Register.Y, null, null, null, callPosition), null, valueY, callPosition)
assignA.linkParents(arguments[0].parent) assignA.linkParents(arguments[0].parent)
assignY.linkParents(arguments[0].parent) assignY.linkParents(arguments[0].parent)
translate(assignA) translate(assignA)
@ -1055,15 +1061,15 @@ internal class Compiler(private val program: Program) {
prog.instr(Opcode.RSAVEX) prog.instr(Opcode.RSAVEX)
restoreX = true restoreX = true
} }
val valueX: IExpression val valueX: Expression
val valueY: IExpression val valueY: Expression
val paramDt = arg.first.inferType(program) val paramDt = arg.first.inferType(program)
when (paramDt) { when (paramDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
valueX = arg.first valueX = arg.first
valueY = LiteralValue.optimalInteger(0, callPosition) valueY = NumericLiteralValue.optimalInteger(0, callPosition)
val assignX = Assignment(listOf(AssignTarget(Register.X, null, null, null, callPosition)), null, valueX, callPosition) val assignX = Assignment(AssignTarget(Register.X, null, null, null, callPosition), null, valueX, callPosition)
val assignY = Assignment(listOf(AssignTarget(Register.Y, null, null, null, callPosition)), null, valueY, callPosition) val assignY = Assignment(AssignTarget(Register.Y, null, null, null, callPosition), null, valueY, callPosition)
assignX.linkParents(arguments[0].parent) assignX.linkParents(arguments[0].parent)
assignY.linkParents(arguments[0].parent) assignY.linkParents(arguments[0].parent)
translate(assignX) translate(assignX)
@ -1251,10 +1257,10 @@ internal class Compiler(private val program: Program) {
prog.instr(opcode) prog.instr(opcode)
} }
private fun translateBitshiftedOperator(operator: String, leftDt: DataType, amount: LiteralValue?) { private fun translateBitshiftedOperator(operator: String, leftDt: DataType, amount: NumericLiteralValue?) {
if(amount?.asIntegerValue == null) if(amount?.number?.toInt() == null)
throw FatalAstException("bitshift operators should only have constant integer value as right operand") throw FatalAstException("bitshift operators should only have constant integer value as right operand")
var shifts=amount.asIntegerValue var shifts=amount.number.toInt()
if(shifts<0) if(shifts<0)
throw FatalAstException("bitshift value should be >= 0") throw FatalAstException("bitshift value should be >= 0")
@ -1380,7 +1386,7 @@ internal class Compiler(private val program: Program) {
} }
} }
stmt.target.memoryAddress != null -> { stmt.target.memoryAddress != null -> {
val address = stmt.target.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue val address = stmt.target.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt()
if(address!=null) { if(address!=null) {
when(stmt.operator) { when(stmt.operator) {
"++" -> prog.instr(Opcode.INC_MEMORY, RuntimeValue(DataType.UWORD, address)) "++" -> prog.instr(Opcode.INC_MEMORY, RuntimeValue(DataType.UWORD, address))
@ -1400,17 +1406,30 @@ internal class Compiler(private val program: Program) {
private fun translate(stmt: Assignment) { private fun translate(stmt: Assignment) {
prog.line(stmt.position) prog.line(stmt.position)
translate(stmt.value) if(stmt.value is StructLiteralValue) {
// flatten into individual struct member assignments
val assignTarget= stmt.singleTarget val identifier = stmt.target.identifier!!
if(assignTarget==null) { val identifierName = identifier.nameInSource.single()
// we're dealing with multiple return values val targetVar = identifier.targetVarDecl(program.namespace)!!
translateMultiReturnAssignment(stmt) val struct = targetVar.struct!!
val sourcevalues = (stmt.value as StructLiteralValue).values
val assignments = struct.statements.zip(sourcevalues).map { member ->
val decl = member.first as VarDecl
val mangled = mangledStructMemberName(identifierName, decl.name)
val idref = IdentifierReference(listOf(mangled), stmt.position)
val assign = Assignment(AssignTarget(null, idref, null, null, stmt.position),
null, member.second, member.second.position)
assign.linkParents(stmt)
assign
}
assignments.forEach { translate(it) }
return return
} }
translate(stmt.value)
val valueDt = stmt.value.inferType(program) val valueDt = stmt.value.inferType(program)
val targetDt = assignTarget.inferType(program, stmt) val targetDt = stmt.target.inferType(program, stmt)
if(valueDt!=targetDt) { if(valueDt!=targetDt) {
// convert value to target datatype if possible // convert value to target datatype if possible
// @todo use convertType()???? // @todo use convertType()????
@ -1461,45 +1480,38 @@ internal class Compiler(private val program: Program) {
throw CompilerException("augmented assignment should have been converted to regular assignment already") throw CompilerException("augmented assignment should have been converted to regular assignment already")
// pop the result value back into the assignment target // pop the result value back into the assignment target
val datatype = assignTarget.inferType(program, stmt)!! val datatype = stmt.target.inferType(program, stmt)!!
popValueIntoTarget(assignTarget, datatype) popValueIntoTarget(stmt.target, datatype)
} }
private fun pushHeapVarAddress(value: IExpression, removeLastOpcode: Boolean) { private fun pushHeapVarAddress(value: Expression, removeLastOpcode: Boolean) {
when (value) { if (value is IdentifierReference) {
is LiteralValue -> throw CompilerException("can only push address of string or array (value on the heap)")
is IdentifierReference -> {
val vardecl = value.targetVarDecl(program.namespace)!! val vardecl = value.targetVarDecl(program.namespace)!!
if(removeLastOpcode) prog.removeLastInstruction() if(removeLastOpcode) prog.removeLastInstruction()
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname) prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
} }
else -> throw CompilerException("can only take address of a literal string value or a string/array variable") else throw CompilerException("can only take address of a literal string value or a string/array variable")
}
} }
private fun pushFloatAddress(value: IExpression) { private fun pushFloatAddress(value: Expression) {
when (value) { if (value is IdentifierReference) {
is LiteralValue -> throw CompilerException("can only push address of float that is a variable on the heap")
is IdentifierReference -> {
val vardecl = value.targetVarDecl(program.namespace)!! val vardecl = value.targetVarDecl(program.namespace)!!
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname) prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
} }
else -> throw CompilerException("can only take address of a the float as constant literal or variable") else throw CompilerException("can only take address of a the float as constant literal or variable")
}
} }
private fun translateMultiReturnAssignment(stmt: Assignment) { private fun pushStructAddress(value: Expression) {
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(program.namespace) if (value is IdentifierReference) {
if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) { // notice that the mangled name of the first struct member is the start address of this struct var
// this is the only case where multiple assignment targets are allowed: a call to an asmsub with multiple return values val vardecl = value.targetVarDecl(program.namespace)!!
// the return values are already on the stack (the subroutine call puts them there) val firstStructMember = (vardecl.struct!!.statements.first() as VarDecl).name
if(stmt.targets.size!=targetStmt.asmReturnvaluesRegisters.size) val firstVarName = listOf(vardecl.name, firstStructMember)
throw CompilerException("asmsub number of return values doesn't match number of assignment targets ${stmt.position}") // find the flattened var that belongs to this first struct member
for(target in stmt.targets) { val firstVar = value.definingScope().lookup(firstVarName, value) as VarDecl
val dt = target.inferType(program, stmt) prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = firstVar.scopedname) // TODO
popValueIntoTarget(target, dt!!)
} }
} else throw CompilerException("can only use multiple assignment targets on an asmsub call") else throw CompilerException("can only take address of a the float as constant literal or variable")
} }
private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) { private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) {
@ -1514,7 +1526,7 @@ internal class Compiler(private val program: Program) {
} }
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
val opcode = opcodePopmem(datatype) val opcode = opcodePopmem(datatype)
val address = target.value?.constValue(program)!!.asIntegerValue!! val address = target.value?.constValue(program)!!.number.toInt()
prog.instr(opcode, RuntimeValue(DataType.UWORD, address)) prog.instr(opcode, RuntimeValue(DataType.UWORD, address))
} }
VarDeclType.CONST -> throw CompilerException("cannot assign to const") VarDeclType.CONST -> throw CompilerException("cannot assign to const")
@ -1524,7 +1536,7 @@ internal class Compiler(private val program: Program) {
assignTarget.register != null -> prog.instr(Opcode.POP_VAR_BYTE, callLabel = assignTarget.register.name) assignTarget.register != null -> prog.instr(Opcode.POP_VAR_BYTE, callLabel = assignTarget.register.name)
assignTarget.arrayindexed != null -> translate(assignTarget.arrayindexed, true) // write value to it assignTarget.arrayindexed != null -> translate(assignTarget.arrayindexed, true) // write value to it
assignTarget.memoryAddress != null -> { assignTarget.memoryAddress != null -> {
val address = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue val address = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt()
if(address!=null) { if(address!=null) {
// const integer address given // const integer address given
prog.instr(Opcode.POP_MEM_BYTE, arg= RuntimeValue(DataType.UWORD, address)) prog.instr(Opcode.POP_MEM_BYTE, arg= RuntimeValue(DataType.UWORD, address))
@ -1538,9 +1550,8 @@ internal class Compiler(private val program: Program) {
private fun translate(stmt: Return) { private fun translate(stmt: Return) {
// put the return values on the stack, in reversed order. The caller will accept them. // put the return values on the stack, in reversed order. The caller will accept them.
for(value in stmt.values.reversed()) { if(stmt.value!=null)
translate(value) translate(stmt.value!!)
}
prog.line(stmt.position) prog.line(stmt.position)
prog.instr(Opcode.RETURN) prog.instr(Opcode.RETURN)
} }
@ -1552,17 +1563,19 @@ internal class Compiler(private val program: Program) {
private fun translate(loop: ForLoop) { private fun translate(loop: ForLoop) {
if(loop.body.containsNoCodeNorVars()) return if(loop.body.containsNoCodeNorVars()) return
prog.line(loop.position) prog.line(loop.position)
val loopVarName: String val loopVarName: String?
val loopVarDt: DataType val loopRegister: Register?
val loopvalueDt: DataType
if(loop.loopRegister!=null) { if(loop.loopRegister!=null) {
val reg = loop.loopRegister loopVarName = null
loopVarName = reg.name loopRegister = loop.loopRegister
loopVarDt = DataType.UBYTE loopvalueDt = DataType.UBYTE
} else { } else {
val loopvar = loop.loopVar!!.targetVarDecl(program.namespace)!! val loopvar = loop.loopVar!!.targetVarDecl(program.namespace)!!
loopVarName = loopvar.scopedname loopVarName = loopvar.scopedname
loopVarDt = loopvar.datatype loopvalueDt = loopvar.datatype
loopRegister = null
} }
if(loop.iterable is RangeExpr) { if(loop.iterable is RangeExpr) {
@ -1575,7 +1588,7 @@ internal class Compiler(private val program: Program) {
throw CompilerException("loop over just 1 value should have been optimized away") throw CompilerException("loop over just 1 value should have been optimized away")
if((range.last-range.first) % range.step != 0) if((range.last-range.first) % range.step != 0)
throw CompilerException("range first and last must be exactly inclusive") throw CompilerException("range first and last must be exactly inclusive")
when (loopVarDt) { when (loopvalueDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
if (range.first < 0 || range.first > 255 || range.last < 0 || range.last > 255) if (range.first < 0 || range.first > 255 || range.last < 0 || range.last > 255)
throw CompilerException("range out of bounds for ubyte") throw CompilerException("range out of bounds for ubyte")
@ -1594,7 +1607,7 @@ internal class Compiler(private val program: Program) {
} }
else -> throw CompilerException("range must be byte or word") else -> throw CompilerException("range must be byte or word")
} }
translateForOverConstantRange(loopVarName, loopVarDt, range, loop.body) translateForOverConstantRange(loopVarName, loopRegister, loopvalueDt, range, loop.body)
} else { } else {
// loop over a range where one or more of the start, last or step values is not a constant // loop over a range where one or more of the start, last or step values is not a constant
if(loop.loopRegister!=null) { if(loop.loopRegister!=null) {
@ -1610,18 +1623,19 @@ internal class Compiler(private val program: Program) {
loop.iterable is IdentifierReference -> { loop.iterable is IdentifierReference -> {
val idRef = loop.iterable as IdentifierReference val idRef = loop.iterable as IdentifierReference
val vardecl = idRef.targetVarDecl(program.namespace)!! val vardecl = idRef.targetVarDecl(program.namespace)!!
val iterableValue = vardecl.value as LiteralValue // TODO check loop over iterable or not
if(iterableValue.type !in IterableDatatypes) // val iterableValue = vardecl.value as LiteralValue
throw CompilerException("loop over something that isn't iterable ${loop.iterable}") // if(iterableValue.type !in IterableDatatypes)
translateForOverIterableVar(loop, loopVarDt, iterableValue) // throw CompilerException("loop over something that isn't iterable ${loop.iterable}")
translateForOverIterableVar(loop, loopvalueDt, vardecl.value as ReferenceLiteralValue)
} }
loop.iterable is LiteralValue -> throw CompilerException("literal value in loop must have been moved to heap already $loop") // TODO what's this: loop.iterable is LiteralValue -> throw CompilerException("literal value in loop must have been moved to heap already $loop")
else -> throw CompilerException("loopvar is something strange ${loop.iterable}") else -> throw CompilerException("loopvar is something strange ${loop.iterable}")
} }
} }
} }
private fun translateForOverIterableVar(loop: ForLoop, loopvarDt: DataType, iterableValue: LiteralValue) { private fun translateForOverIterableVar(loop: ForLoop, loopvarDt: DataType, iterableValue: ReferenceLiteralValue) {
if(loopvarDt== DataType.UBYTE && iterableValue.type !in setOf(DataType.STR, DataType.STR_S, DataType.ARRAY_UB)) if(loopvarDt== DataType.UBYTE && iterableValue.type !in setOf(DataType.STR, DataType.STR_S, DataType.ARRAY_UB))
throw CompilerException("loop variable type doesn't match iterableValue type") throw CompilerException("loop variable type doesn't match iterableValue type")
else if(loopvarDt== DataType.UWORD && iterableValue.type != DataType.ARRAY_UW) else if(loopvarDt== DataType.UWORD && iterableValue.type != DataType.ARRAY_UW)
@ -1633,16 +1647,16 @@ internal class Compiler(private val program: Program) {
when(iterableValue.type) { when(iterableValue.type) {
!in IterableDatatypes -> throw CompilerException("non-iterableValue type") !in IterableDatatypes -> throw CompilerException("non-iterableValue type")
DataType.STR, DataType.STR_S -> { DataType.STR, DataType.STR_S -> {
numElements = iterableValue.strvalue!!.length numElements = iterableValue.str!!.length
if(numElements>255) throw CompilerException("string length > 255") if(numElements>255) throw CompilerException("string length > 255")
} }
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UW, DataType.ARRAY_W -> {
numElements = iterableValue.arrayvalue?.size ?: program.heap.get(iterableValue.heapId!!).arraysize numElements = iterableValue.array?.size ?: program.heap.get(iterableValue.heapId!!).arraysize
if(numElements>255) throw CompilerException("string length > 255") if(numElements>255) throw CompilerException("string length > 255")
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
numElements = iterableValue.arrayvalue?.size ?: program.heap.get(iterableValue.heapId!!).arraysize numElements = iterableValue.array?.size ?: program.heap.get(iterableValue.heapId!!).arraysize
if(numElements>255) throw CompilerException("string length > 255") if(numElements>255) throw CompilerException("string length > 255")
} }
else -> throw CompilerException("weird datatype") else -> throw CompilerException("weird datatype")
@ -1686,7 +1700,7 @@ internal class Compiler(private val program: Program) {
AssignTarget(null, loop.loopVar!!.copy(), null, null, loop.position) AssignTarget(null, loop.loopVar!!.copy(), null, null, loop.position)
val arrayspec = ArrayIndex(IdentifierReference(listOf(ForLoop.iteratorLoopcounterVarname), loop.position), loop.position) val arrayspec = ArrayIndex(IdentifierReference(listOf(ForLoop.iteratorLoopcounterVarname), loop.position), loop.position)
val assignLv = Assignment( val assignLv = Assignment(
listOf(assignTarget), null, assignTarget, null,
ArrayIndexedExpression((loop.iterable as IdentifierReference).copy(), arrayspec, loop.position), ArrayIndexedExpression((loop.iterable as IdentifierReference).copy(), arrayspec, loop.position),
loop.position) loop.position)
assignLv.linkParents(loop.body) assignLv.linkParents(loop.body)
@ -1706,7 +1720,7 @@ internal class Compiler(private val program: Program) {
continueStmtLabelStack.pop() continueStmtLabelStack.pop()
} }
private fun translateForOverConstantRange(varname: String, varDt: DataType, range: IntProgression, body: AnonymousScope) { private fun translateForOverConstantRange(varname: String?, loopregister: Register?, varDt: DataType, range: IntProgression, body: AnonymousScope) {
/** /**
* for LV in start..last { body } * for LV in start..last { body }
* (and we already know that the range is not empty, and first and last are exactly inclusive.) * (and we already know that the range is not empty, and first and last are exactly inclusive.)
@ -1734,7 +1748,9 @@ internal class Compiler(private val program: Program) {
breakStmtLabelStack.push(breakLabel) breakStmtLabelStack.push(breakLabel)
prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.first)) prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.first))
prog.instr(opcodePopvar(varDt), callLabel = varname) val variableLabel = varname ?: loopregister?.name
prog.instr(opcodePopvar(varDt), callLabel = variableLabel)
prog.label(loopLabel) prog.label(loopLabel)
translate(body) translate(body)
prog.label(continueLabel) prog.label(continueLabel)
@ -1742,25 +1758,25 @@ internal class Compiler(private val program: Program) {
when { when {
range.step in 1..numberOfIncDecsForOptimize -> { range.step in 1..numberOfIncDecsForOptimize -> {
repeat(range.step) { repeat(range.step) {
prog.instr(opcodeIncvar(varDt), callLabel = varname) prog.instr(opcodeIncvar(varDt), callLabel = variableLabel)
} }
} }
range.step in -1 downTo -numberOfIncDecsForOptimize -> { range.step in -1 downTo -numberOfIncDecsForOptimize -> {
repeat(abs(range.step)) { repeat(abs(range.step)) {
prog.instr(opcodeDecvar(varDt), callLabel = varname) prog.instr(opcodeDecvar(varDt), callLabel = variableLabel)
} }
} }
range.step>numberOfIncDecsForOptimize -> { range.step>numberOfIncDecsForOptimize -> {
prog.instr(opcodePushvar(varDt), callLabel = varname) prog.instr(opcodePushvar(varDt), callLabel = variableLabel)
prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.step)) prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.step))
prog.instr(opcodeAdd(varDt)) prog.instr(opcodeAdd(varDt))
prog.instr(opcodePopvar(varDt), callLabel = varname) prog.instr(opcodePopvar(varDt), callLabel = variableLabel)
} }
range.step<numberOfIncDecsForOptimize -> { range.step<numberOfIncDecsForOptimize -> {
prog.instr(opcodePushvar(varDt), callLabel = varname) prog.instr(opcodePushvar(varDt), callLabel = variableLabel)
prog.instr(opcodePush(varDt), RuntimeValue(varDt, abs(range.step))) prog.instr(opcodePush(varDt), RuntimeValue(varDt, abs(range.step)))
prog.instr(opcodeSub(varDt)) prog.instr(opcodeSub(varDt))
prog.instr(opcodePopvar(varDt), callLabel = varname) prog.instr(opcodePopvar(varDt), callLabel = variableLabel)
} }
} }
@ -1768,7 +1784,7 @@ internal class Compiler(private val program: Program) {
// optimize for the for loop that counts to 0 // optimize for the for loop that counts to 0
prog.instr(if(range.first>0) Opcode.BPOS else Opcode.BNEG, callLabel = loopLabel) prog.instr(if(range.first>0) Opcode.BPOS else Opcode.BNEG, callLabel = loopLabel)
} else { } else {
prog.instr(opcodePushvar(varDt), callLabel = varname) prog.instr(opcodePushvar(varDt), callLabel = variableLabel)
val checkValue = val checkValue =
when (varDt) { when (varDt) {
DataType.UBYTE -> (range.last + range.step) and 255 DataType.UBYTE -> (range.last + range.step) and 255
@ -1827,14 +1843,14 @@ internal class Compiler(private val program: Program) {
AssignTarget(register, null, null, null, range.position) AssignTarget(register, null, null, null, range.position)
} }
val startAssignment = Assignment(listOf(makeAssignmentTarget()), null, range.from, range.position) val startAssignment = Assignment(makeAssignmentTarget(), null, range.from, range.position)
startAssignment.linkParents(body) startAssignment.linkParents(body)
translate(startAssignment) translate(startAssignment)
val loopLabel = makeLabel(body, "loop") val loopLabel = makeLabel(body, "loop")
val continueLabel = makeLabel(body, "continue") val continueLabel = makeLabel(body, "continue")
val breakLabel = makeLabel(body, "break") val breakLabel = makeLabel(body, "break")
val literalStepValue = (range.step as? LiteralValue)?.asNumericValue?.toInt() val literalStepValue = (range.step as? NumericLiteralValue)?.number?.toInt()
continueStmtLabelStack.push(continueLabel) continueStmtLabelStack.push(continueLabel)
breakStmtLabelStack.push(breakLabel) breakStmtLabelStack.push(breakLabel)
@ -2048,7 +2064,7 @@ internal class Compiler(private val program: Program) {
private fun translate(memread: DirectMemoryRead) { private fun translate(memread: DirectMemoryRead) {
// for now, only a single memory location (ubyte) is read at a time. // for now, only a single memory location (ubyte) is read at a time.
val address = memread.addressExpression.constValue(program)?.asIntegerValue val address = memread.addressExpression.constValue(program)?.number?.toInt()
if(address!=null) { if(address!=null) {
prog.instr(Opcode.PUSH_MEM_UB, arg = RuntimeValue(DataType.UWORD, address)) prog.instr(Opcode.PUSH_MEM_UB, arg = RuntimeValue(DataType.UWORD, address))
} else { } else {
@ -2060,7 +2076,7 @@ internal class Compiler(private val program: Program) {
private fun translate(memwrite: DirectMemoryWrite) { private fun translate(memwrite: DirectMemoryWrite) {
// for now, only a single memory location (ubyte) is written at a time. // for now, only a single memory location (ubyte) is written at a time.
// TODO inline this function (it's only used once) // TODO inline this function (it's only used once)
val address = memwrite.addressExpression.constValue(program)?.asIntegerValue val address = memwrite.addressExpression.constValue(program)?.number?.toInt()
if(address!=null) { if(address!=null) {
prog.instr(Opcode.POP_MEM_BYTE, arg = RuntimeValue(DataType.UWORD, address)) prog.instr(Opcode.POP_MEM_BYTE, arg = RuntimeValue(DataType.UWORD, address))
} else { } else {
@ -2077,6 +2093,9 @@ internal class Compiler(private val program: Program) {
else if(target.datatype== DataType.FLOAT) { else if(target.datatype== DataType.FLOAT) {
pushFloatAddress(addrof.identifier) pushFloatAddress(addrof.identifier)
} }
else if(target.datatype == DataType.STRUCT) {
pushStructAddress(addrof.identifier)
}
else else
throw CompilerException("cannot take memory pointer $addrof") throw CompilerException("cannot take memory pointer $addrof")
} }
@ -2095,24 +2114,20 @@ internal class Compiler(private val program: Program) {
val endOfWhenLabel = makeLabel(whenstmt, "when_end") val endOfWhenLabel = makeLabel(whenstmt, "when_end")
val choiceLabels = mutableListOf<String>() val choiceLabels = mutableListOf<String>()
var previousValue = 0
for(choice in whenstmt.choiceValues(program)) { for(choice in whenstmt.choiceValues(program)) {
val choiceVal = choice.first if(choice.first==null) {
if(choiceVal==null) {
// the else clause // the else clause
translate(choice.second.statements) translate(choice.second.statements)
} else { } else {
val subtract = choiceVal-previousValue val choiceVal = choice.first!!.single()
previousValue = choiceVal val rval = RuntimeValue(conditionDt!!, choiceVal)
if (conditionDt in ByteDatatypes) { if (conditionDt in ByteDatatypes) {
prog.instr(Opcode.DUP_B) prog.instr(Opcode.DUP_B)
prog.instr(Opcode.PUSH_BYTE, RuntimeValue(conditionDt!!, subtract)) prog.instr(opcodeCompare(conditionDt), rval)
prog.instr(opcodeCompare(conditionDt))
} }
else { else {
prog.instr(Opcode.DUP_W) prog.instr(Opcode.DUP_W)
prog.instr(Opcode.PUSH_WORD, RuntimeValue(conditionDt!!, subtract)) prog.instr(opcodeCompare(conditionDt), rval)
prog.instr(opcodeCompare(conditionDt))
} }
val choiceLabel = makeLabel(whenstmt, "choice_$choiceVal") val choiceLabel = makeLabel(whenstmt, "choice_$choiceVal")
choiceLabels.add(choiceLabel) choiceLabels.add(choiceLabel)
@ -2122,12 +2137,12 @@ internal class Compiler(private val program: Program) {
prog.instr(Opcode.JUMP, callLabel = endOfWhenLabel) prog.instr(Opcode.JUMP, callLabel = endOfWhenLabel)
for(choice in whenstmt.choices.zip(choiceLabels)) { for(choice in whenstmt.choices.zip(choiceLabels)) {
// TODO the various code blocks here, don't forget to jump to the end label at their eind
prog.label(choice.second) prog.label(choice.second)
prog.instr(Opcode.NOP) translate(choice.first.statements)
prog.instr(Opcode.JUMP, callLabel = endOfWhenLabel) prog.instr(Opcode.JUMP, callLabel = endOfWhenLabel)
} }
prog.removeLastInstruction() // remove the last jump, that can fall through to here
prog.label(endOfWhenLabel) prog.label(endOfWhenLabel)
if (conditionDt in ByteDatatypes) prog.instr(Opcode.DISCARD_BYTE) if (conditionDt in ByteDatatypes) prog.instr(Opcode.DISCARD_BYTE)

View File

@ -2,12 +2,9 @@ package prog8.compiler
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.base.checkIdentifiers
import prog8.ast.base.checkValid
import prog8.ast.base.reorderStatements
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.compiler.target.c64.AsmGen import prog8.compiler.target.c64.AsmGen
import prog8.compiler.target.c64.C64Zeropage import prog8.compiler.target.c64.MachineDefinition
import prog8.optimizer.constantFold import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions import prog8.optimizer.simplifyExpressions
@ -17,7 +14,6 @@ import prog8.parser.importModule
import prog8.parser.moduleName import prog8.parser.moduleName
import java.io.File import java.io.File
import java.io.PrintStream import java.io.PrintStream
import java.lang.Exception
import java.nio.file.Path import java.nio.file.Path
import kotlin.system.exitProcess import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -63,6 +59,12 @@ fun compileProgram(filepath: Path,
} }
//println(" time2: $time2") //println(" time2: $time2")
val time3 = measureTimeMillis { val time3 = measureTimeMillis {
programAst.removeNopsFlattenAnonScopes()
// if you want to print the AST, do it before shuffling the statements around below
//printAst(programAst)
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later
} }
//println(" time3: $time3") //println(" time3: $time3")
@ -84,12 +86,10 @@ fun compileProgram(filepath: Path,
} }
} }
programAst.removeNopsFlattenAnonScopes()
programAst.checkValid(compilerOptions) // check if final tree is valid programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls programAst.checkRecursion() // check if there are recursive subroutine calls
// printAst(programAst)
// namespace.debugPrint()
if(generateVmCode) { if(generateVmCode) {
// compile the syntax tree into stackvmProg form, and optimize that // compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(programAst) val compiler = Compiler(programAst)
@ -106,7 +106,7 @@ fun compileProgram(filepath: Path,
} }
if (writeAssembly) { if (writeAssembly) {
val zeropage = C64Zeropage(compilerOptions) val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
intermediate.allocateZeropage(zeropage) intermediate.allocateZeropage(zeropage)
val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize) val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions) assembly.assemble(compilerOptions)
@ -144,7 +144,7 @@ fun compileProgram(filepath: Path,
fun printAst(programAst: Program) { fun printAst(programAst: Program) {
println() println()
val printer = AstToSourceCode(::print) val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst) printer.visit(programAst)
println() println()
} }

View File

@ -1,7 +1,6 @@
package prog8.compiler package prog8.compiler
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.base.printWarning
class ZeropageDepletedError(message: String) : Exception(message) class ZeropageDepletedError(message: String) : Exception(message)

View File

@ -2,57 +2,52 @@ package prog8.compiler.intermediate
import prog8.ast.antlr.escape import prog8.ast.antlr.escape
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.base.printWarning import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.LiteralValue import prog8.ast.expressions.ReferenceLiteralValue
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.vm.RuntimeValue import prog8.ast.statements.ZeropageWish
import prog8.compiler.CompilerException import prog8.compiler.CompilerException
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.Zeropage import prog8.compiler.Zeropage
import prog8.compiler.ZeropageDepletedError import prog8.compiler.ZeropageDepletedError
import prog8.vm.RuntimeValue
import java.io.PrintStream 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 source: Path) { class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?)
class Variable(val scopedname: String, val value: RuntimeValue, val params: VariableParameters)
class ProgramBlock(val name: String, class ProgramBlock(val name: String,
var address: Int?, var address: Int?,
val instructions: MutableList<Instruction> = mutableListOf(), val instructions: MutableList<Instruction> = mutableListOf(),
val variables: MutableMap<String, RuntimeValue> = mutableMapOf(), // names are fully scoped val variables: MutableList<Variable> = mutableListOf(),
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(), val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
val force_output: Boolean) val force_output: Boolean)
{
val numVariables: Int
get() { return variables.size }
val numInstructions: Int
get() { return instructions.filter { it.opcode!= Opcode.LINE }.size }
val variablesMarkedForZeropage: MutableSet<String> = mutableSetOf()
}
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>() val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
val blocks = mutableListOf<ProgramBlock>() val blocks = mutableListOf<ProgramBlock>()
val memory = mutableMapOf<Int, List<RuntimeValue>>() val memory = mutableMapOf<Int, List<RuntimeValue>>()
private lateinit var currentBlock: ProgramBlock private lateinit var currentBlock: ProgramBlock
val numVariables: Int
get() = blocks.sumBy { it.numVariables }
val numInstructions: Int
get() = blocks.sumBy { it.numInstructions }
fun allocateZeropage(zeropage: Zeropage) { fun allocateZeropage(zeropage: Zeropage) {
// allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP) // allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP)
var notAllocated = 0 var notAllocated = 0
for(block in blocks) { for(block in blocks) {
val zpVariables = block.variables.filter { it.key in block.variablesMarkedForZeropage } val zpVariables = block.variables.filter { it.params.zp==ZeropageWish.REQUIRE_ZEROPAGE || it.params.zp==ZeropageWish.PREFER_ZEROPAGE }
if (zpVariables.isNotEmpty()) { if (zpVariables.isNotEmpty()) {
for (variable in zpVariables) { for (variable in zpVariables) {
if(variable.params.zp==ZeropageWish.NOT_IN_ZEROPAGE || variable.params.memberOfStruct!=null)
throw CompilerException("zp conflict")
try { try {
val address = zeropage.allocate(variable.key, variable.value.type, null) val address = zeropage.allocate(variable.scopedname, variable.value.type, null)
allocatedZeropageVariables[variable.key] = Pair(address, variable.value.type) allocatedZeropageVariables[variable.scopedname] = Pair(address, variable.value.type)
} catch (x: ZeropageDepletedError) { } catch (x: ZeropageDepletedError) {
printWarning(x.toString() + " variable ${variable.key} type ${variable.value.type}") printWarning(x.toString() + " variable ${variable.scopedname} type ${variable.value.type}")
notAllocated++ notAllocated++
} }
} }
@ -393,39 +388,53 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
fun variable(scopedname: String, decl: VarDecl) { fun variable(scopedname: String, decl: VarDecl) {
when(decl.type) { when(decl.type) {
VarDeclType.VAR -> { VarDeclType.VAR -> {
// var decls that are defined inside of a StructDecl are skipped in the output
// because every occurrence of the members will have a separate mangled vardecl for that occurrence
if(decl.parent is StructDecl)
return
val valueparams = VariableParameters(decl.zeropage, decl.struct)
val value = when(decl.datatype) { val value = when(decl.datatype) {
in NumericDatatypes -> RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!) in NumericDatatypes -> {
RuntimeValue(decl.datatype, (decl.value as NumericLiteralValue).number)
}
in StringDatatypes -> { in StringDatatypes -> {
val litval = (decl.value as LiteralValue) val litval = (decl.value as ReferenceLiteralValue)
if(litval.heapId==null) if(litval.heapId==null)
throw CompilerException("string should already be in the heap") throw CompilerException("string should already be in the heap")
RuntimeValue(decl.datatype, heapId = litval.heapId) RuntimeValue(decl.datatype, heapId = litval.heapId)
} }
in ArrayDatatypes -> { in ArrayDatatypes -> {
val litval = (decl.value as LiteralValue) val litval = (decl.value as? ReferenceLiteralValue)
if(litval.heapId==null) if(litval!=null && litval.heapId==null)
throw CompilerException("array should already be in the heap") throw CompilerException("array should already be in the heap")
if(litval!=null){
RuntimeValue(decl.datatype, heapId = litval.heapId) RuntimeValue(decl.datatype, heapId = litval.heapId)
} else {
throw CompilerException("initialization value expected")
}
}
DataType.STRUCT -> {
// struct variables have been flattened already
return
} }
else -> throw CompilerException("weird datatype") else -> throw CompilerException("weird datatype")
} }
currentBlock.variables[scopedname] = value currentBlock.variables.add(Variable(scopedname, value, valueparams))
if(decl.zeropage)
currentBlock.variablesMarkedForZeropage.add(scopedname)
} }
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
// note that constants are all folded away, but assembly code may still refer to them // note that constants are all folded away, but assembly code may still refer to them
val lv = decl.value as LiteralValue val lv = decl.value as NumericLiteralValue
if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE) if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE)
throw CompilerException("expected integer memory address $lv") throw CompilerException("expected integer memory address $lv")
currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype) currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), decl.datatype)
} }
VarDeclType.CONST -> { VarDeclType.CONST -> {
// note that constants are all folded away, but assembly code may still refer to them (if their integers) // note that constants are all folded away, but assembly code may still refer to them (if their integers)
// floating point constants are not generated at all!! // floating point constants are not generated at all!!
val lv = decl.value as LiteralValue val lv = decl.value as NumericLiteralValue
if(lv.type in IntegerDatatypes) if(lv.type in IntegerDatatypes)
currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype) currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), decl.datatype)
} }
} }
} }
@ -459,10 +468,14 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
fun writeCode(out: PrintStream, embeddedLabels: Boolean=true) { fun writeCode(out: PrintStream, embeddedLabels: Boolean=true) {
out.println("; stackVM program code for '$name'") out.println("; stackVM program code for '$name'")
out.println("%memory") writeMemory(out)
if(memory.isNotEmpty()) writeHeap(out)
TODO("add support for writing/reading initial memory values") for(blk in blocks) {
out.println("%end_memory") writeBlock(out, blk, embeddedLabels)
}
}
private fun writeHeap(out: PrintStream) {
out.println("%heap") out.println("%heap")
heap.allEntries().forEach { heap.allEntries().forEach {
out.print("${it.key} ${it.value.type.name.toLowerCase()} ") out.print("${it.key} ${it.value.type.name.toLowerCase()} ")
@ -491,13 +504,18 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
} }
} }
out.println("%end_heap") out.println("%end_heap")
for(blk in blocks) { }
private fun writeBlock(out: PrintStream, blk: ProgramBlock, embeddedLabels: Boolean) {
out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}") out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
out.println("%variables") out.println("%variables")
for (variable in blk.variables) { for (variable in blk.variables) {
if(variable.params.zp==ZeropageWish.REQUIRE_ZEROPAGE)
throw CompilerException("zp conflict")
val valuestr = variable.value.toString() val valuestr = variable.value.toString()
out.println("${variable.key} ${variable.value.type.name.toLowerCase()} $valuestr") val struct = if(variable.params.memberOfStruct==null) "" else "struct=${variable.params.memberOfStruct.name}"
out.println("${variable.scopedname} ${variable.value.type.name.toLowerCase()} $valuestr zp=${variable.params.zp} s=$struct")
} }
out.println("%end_variables") out.println("%end_variables")
out.println("%memorypointers") out.println("%memorypointers")
@ -520,5 +538,11 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
out.println("%end_block") out.println("%end_block")
} }
private fun writeMemory(out: PrintStream) {
out.println("%memory")
if (memory.isNotEmpty())
TODO("add support for writing/reading initial memory values")
out.println("%end_memory")
} }
} }

View File

@ -6,9 +6,13 @@ package prog8.compiler.target.c64
import prog8.ast.antlr.escape import prog8.ast.antlr.escape
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.initvarsSubName import prog8.ast.base.initvarsSubName
import prog8.vm.RuntimeValue import prog8.ast.statements.ZeropageWish
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.intermediate.* import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.vm.RuntimeValue
import java.io.File import java.io.File
import java.util.* import java.util.*
import kotlin.math.abs import kotlin.math.abs
@ -44,8 +48,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
// Convert invalid label names (such as "<anon-1>") to something that's allowed. // Convert invalid label names (such as "<anon-1>") to something that's allowed.
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>() val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
for(block in program.blocks) { for(block in program.blocks) {
val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap() val newvars = block.variables.map { IntermediateProgram.Variable(symname(it.scopedname, block), it.value, it.params) }.toMutableList()
val newvarsZeropaged = block.variablesMarkedForZeropage.map{symname(it, block)}.toMutableSet()
val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap() val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap()
val newinstructions = block.instructions.asSequence().map { val newinstructions = block.instructions.asSequence().map {
when { when {
@ -66,8 +69,6 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
newMempointers, newMempointers,
newlabels, newlabels,
force_output = block.force_output) force_output = block.force_output)
newblock.variablesMarkedForZeropage.clear()
newblock.variablesMarkedForZeropage.addAll(newvarsZeropaged)
newblocks.add(newblock) newblocks.add(newblock)
} }
program.blocks.clear() program.blocks.clear()
@ -146,7 +147,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
return name.replace("-", "") return name.replace("-", "")
} }
private fun makeFloatFill(flt: Mflpt5): String { private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
val b0 = "$"+flt.b0.toString(16).padStart(2, '0') val b0 = "$"+flt.b0.toString(16).padStart(2, '0')
val b1 = "$"+flt.b1.toString(16).padStart(2, '0') val b1 = "$"+flt.b1.toString(16).padStart(2, '0')
val b2 = "$"+flt.b2.toString(16).padStart(2, '0') val b2 = "$"+flt.b2.toString(16).padStart(2, '0')
@ -164,7 +165,8 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
out("\n.cpu '6502'\n.enc 'none'\n") out("\n.cpu '6502'\n.enc 'none'\n")
if(program.loadAddress==0) // fix load address if(program.loadAddress==0) // fix load address
program.loadAddress = if(options.launcher==LauncherType.BASIC) BASIC_LOAD_ADDRESS else RAW_LOAD_ADDRESS program.loadAddress = if(options.launcher==LauncherType.BASIC)
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
when { when {
options.launcher == LauncherType.BASIC -> { options.launcher == LauncherType.BASIC -> {
@ -220,7 +222,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
// the global list of all floating point constants for the whole program // the global list of all floating point constants for the whole program
for(flt in globalFloatConsts) { for(flt in globalFloatConsts) {
val floatFill = makeFloatFill(Mflpt5.fromNumber(flt.key)) val floatFill = makeFloatFill(MachineDefinition.Mflpt5.fromNumber(flt.key))
out("${flt.value}\t.byte $floatFill ; float ${flt.key}") out("${flt.value}\t.byte $floatFill ; float ${flt.key}")
} }
} }
@ -237,16 +239,17 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
// deal with zeropage variables // deal with zeropage variables
for(variable in blk.variables) { for(variable in blk.variables) {
val sym = symname(blk.name+"."+variable.key, null) val sym = symname(blk.name+"."+variable.scopedname, null)
val zpVar = program.allocatedZeropageVariables[sym] val zpVar = program.allocatedZeropageVariables[sym]
if(zpVar==null) { if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space) // This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) { if(variable.params.zp != ZeropageWish.NOT_IN_ZEROPAGE &&
variable.value.type in zeropage.allowedDatatypes
&& variable.value.type != DataType.FLOAT) {
try { try {
val address = zeropage.allocate(sym, variable.value.type, null) val address = zeropage.allocate(sym, variable.value.type, null)
out("${variable.key} = $address\t; auto zp ${variable.value.type}") out("${variable.scopedname} = $address\t; auto zp ${variable.value.type}")
// make sure we add the var to the set of zpvars for this block // make sure we add the var to the set of zpvars for this block
blk.variablesMarkedForZeropage.add(variable.key)
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type) program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
} catch (x: ZeropageDepletedError) { } catch (x: ZeropageDepletedError) {
// leave it as it is. // leave it as it is.
@ -255,7 +258,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
} }
else { else {
// it was already allocated on the zp // it was already allocated on the zp
out("${variable.key} = ${zpVar.first}\t; zp ${zpVar.second}") out("${variable.scopedname} = ${zpVar.first}\t; zp ${zpVar.second}")
} }
} }
@ -289,77 +292,97 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
} }
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) { private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
// these are the non-zeropage variables val uniqueNames = block.variables.map { it.scopedname }.toSet()
val sortedVars = block.variables.filter{it.key !in block.variablesMarkedForZeropage}.toList().sortedBy { it.second.type } if (uniqueNames.size != block.variables.size)
for (v in sortedVars) { throw AssemblyError("not all variables have unique names")
when (v.second.type) {
DataType.UBYTE -> out("${v.first}\t.byte 0") // these are the non-zeropage variables.
DataType.BYTE -> out("${v.first}\t.char 0") // first get all the flattened struct members, they MUST remain in order
DataType.UWORD -> out("${v.first}\t.word 0") out("; flattened struct members")
DataType.WORD -> out("${v.first}\t.sint 0") val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null }
DataType.FLOAT -> out("${v.first}\t.byte 0,0,0,0,0 ; float") structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) }
// leave outsort the other variables by type
out("; other variables sorted by type")
val sortedVars = normalVars.sortedBy { it.value.type }
for (variable in sortedVars) {
val sym = symname(block.name + "." + variable.scopedname, null)
if(sym in program.allocatedZeropageVariables)
continue // skip the ones that already belong in the zero page
vardecl2asm(variable.scopedname, variable.value, variable.params)
}
}
private fun vardecl2asm(varname: String, value: RuntimeValue, parameters: IntermediateProgram.VariableParameters) {
when (value.type) {
DataType.UBYTE -> out("$varname\t.byte 0")
DataType.BYTE -> out("$varname\t.char 0")
DataType.UWORD -> out("$varname\t.word 0")
DataType.WORD -> out("$varname\t.sint 0")
DataType.FLOAT -> out("$varname\t.byte 0,0,0,0,0 ; float")
DataType.STR, DataType.STR_S -> { DataType.STR, DataType.STR_S -> {
val rawStr = heap.get(v.second.heapId!!).str!! val rawStr = heap.get(value.heapId!!).str!!
val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') } val bytes = encodeStr(rawStr, value.type).map { "$" + it.toString(16).padStart(2, '0') }
out("${v.first}\t; ${v.second.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"") out("$varname\t; ${value.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
for (chunk in bytes.chunked(16)) for (chunk in bytes.chunked(16))
out(" .byte " + chunk.joinToString()) out(" .byte " + chunk.joinToString())
} }
DataType.ARRAY_UB -> { DataType.ARRAY_UB -> {
// unsigned integer byte arraysize // unsigned integer byte arraysize
val data = makeArrayFillDataUnsigned(v.second) val data = makeArrayFillDataUnsigned(value)
if (data.size <= 16) if (data.size <= 16)
out("${v.first}\t.byte ${data.joinToString()}") out("$varname\t.byte ${data.joinToString()}")
else { else {
out(v.first) out(varname)
for (chunk in data.chunked(16)) for (chunk in data.chunked(16))
out(" .byte " + chunk.joinToString()) out(" .byte " + chunk.joinToString())
} }
} }
DataType.ARRAY_B -> { DataType.ARRAY_B -> {
// signed integer byte arraysize // signed integer byte arraysize
val data = makeArrayFillDataSigned(v.second) val data = makeArrayFillDataSigned(value)
if (data.size <= 16) if (data.size <= 16)
out("${v.first}\t.char ${data.joinToString()}") out("$varname\t.char ${data.joinToString()}")
else { else {
out(v.first) out(varname)
for (chunk in data.chunked(16)) for (chunk in data.chunked(16))
out(" .char " + chunk.joinToString()) out(" .char " + chunk.joinToString())
} }
} }
DataType.ARRAY_UW -> { DataType.ARRAY_UW -> {
// unsigned word arraysize // unsigned word arraysize
val data = makeArrayFillDataUnsigned(v.second) val data = makeArrayFillDataUnsigned(value)
if (data.size <= 16) if (data.size <= 16)
out("${v.first}\t.word ${data.joinToString()}") out("$varname\t.word ${data.joinToString()}")
else { else {
out(v.first) out(varname)
for (chunk in data.chunked(16)) for (chunk in data.chunked(16))
out(" .word " + chunk.joinToString()) out(" .word " + chunk.joinToString())
} }
} }
DataType.ARRAY_W -> { DataType.ARRAY_W -> {
// signed word arraysize // signed word arraysize
val data = makeArrayFillDataSigned(v.second) val data = makeArrayFillDataSigned(value)
if (data.size <= 16) if (data.size <= 16)
out("${v.first}\t.sint ${data.joinToString()}") out("$varname\t.sint ${data.joinToString()}")
else { else {
out(v.first) out(varname)
for (chunk in data.chunked(16)) for (chunk in data.chunked(16))
out(" .sint " + chunk.joinToString()) out(" .sint " + chunk.joinToString())
} }
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
// float arraysize // float arraysize
val array = heap.get(v.second.heapId!!).doubleArray!! val array = heap.get(value.heapId!!).doubleArray!!
val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) } val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) }
out(v.first) out(varname)
for (f in array.zip(floatFills)) for (f in array.zip(floatFills))
out(" .byte ${f.second} ; float ${f.first}") out(" .byte ${f.second} ; float ${f.first}")
} }
DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened")
} }
} }
}
private fun encodeStr(str: String, dt: DataType): List<Short> { private fun encodeStr(str: String, dt: DataType): List<Short> {
return when(dt) { return when(dt) {
@ -554,14 +577,14 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index*2} | bne + | dec $variable+${index*2+1} |+ | dec $variable+${index*2}") Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index*2} | bne + | dec $variable+${index*2+1} |+ | dec $variable+${index*2}")
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment( Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(
""" """
lda #<($variable+${index*Mflpt5.MemorySize}) lda #<($variable+${index* MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index*Mflpt5.MemorySize}) ldy #>($variable+${index* MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.inc_var_f jsr c64flt.inc_var_f
""") """)
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment( Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(
""" """
lda #<($variable+${index*Mflpt5.MemorySize}) lda #<($variable+${index* MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index*Mflpt5.MemorySize}) ldy #>($variable+${index* MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.dec_var_f jsr c64flt.dec_var_f
""") """)
@ -571,8 +594,8 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
private fun sameIndexedVarOperation(variable: String, indexVar: String, ins: Instruction): AsmFragment? { private fun sameIndexedVarOperation(variable: String, indexVar: String, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-value / op / push-index-var / pop-into-indexed-var // an in place operation that consists of a push-value / op / push-index-var / pop-into-indexed-var
val saveX = " stx ${C64Zeropage.SCRATCH_B1} |" val saveX = " stx ${MachineDefinition.C64Zeropage.SCRATCH_B1} |"
val restoreX = " | ldx ${C64Zeropage.SCRATCH_B1}" val restoreX = " | ldx ${MachineDefinition.C64Zeropage.SCRATCH_B1}"
val loadXWord: String val loadXWord: String
val loadX: String val loadX: String

View File

@ -1,6 +1,11 @@
package prog8.compiler.target.c64 package prog8.compiler.target.c64
import prog8.compiler.toHex import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
fun optimizeAssembly(lines: MutableList<String>): Int { fun optimizeAssembly(lines: MutableList<String>): Int {
@ -24,10 +29,19 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
numberOfOptimizations++ numberOfOptimizations++
} }
removeLines = optimizeCmpSequence(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeStoreLoadSame(linesByFour) removeLines = optimizeStoreLoadSame(linesByFour)
if(removeLines.isNotEmpty()) { if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed()) for (i in removeLines.reversed())
lines.removeAt(i) lines.removeAt(i)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++ numberOfOptimizations++
} }
@ -36,6 +50,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
if(removeLines.isNotEmpty()) { if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed()) for (i in removeLines.reversed())
lines.removeAt(i) lines.removeAt(i)
linesByFourteen = getLinesBy(lines, 14)
numberOfOptimizations++ numberOfOptimizations++
} }
@ -44,15 +59,36 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
return numberOfOptimizations return numberOfOptimizations
} }
fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
// the when statement (on bytes) generates a sequence of:
// lda $ce01,x
// cmp #$20
// beq check_prog8_s72choice_32
// lda $ce01,x
// cmp #$21
// beq check_prog8_s73choice_33
// the repeated lda can be removed
val removeLines = mutableListOf<Int>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
lines[1].value.trim().startsWith("cmp ") &&
lines[2].value.trim().startsWith("beq ") &&
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
removeLines.add(lines[3].index) // remove the second lda
}
}
return removeLines
}
fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Int> { fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write // sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
// this is a lot harder for word values because the instruction sequence varies. // this is a lot harder for word values because the instruction sequence varies.
val removeLines = mutableListOf<Int>() val removeLines = mutableListOf<Int>()
for(lines in linesByFour) { for(lines in linesByFour) {
if(lines[0].value.trim()=="sta ${ESTACK_LO.toHex()},x" && if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
lines[1].value.trim()=="dex" && lines[1].value.trim()=="dex" &&
lines[2].value.trim()=="inx" && lines[2].value.trim()=="inx" &&
lines[3].value.trim()=="lda ${ESTACK_LO.toHex()},x") { lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
removeLines.add(lines[0].index) removeLines.add(lines[0].index)
removeLines.add(lines[1].index) removeLines.add(lines[1].index)
removeLines.add(lines[2].index) removeLines.add(lines[2].index)
@ -128,7 +164,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
} }
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) = private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
// all lines (that aren't empty or comments) in sliding pairs of 2 // all lines (that aren't empty or comments) in sliding windows of certain size
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false) lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Int> { private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Int> {

View File

@ -3,8 +3,16 @@ package prog8.compiler.target.c64
import prog8.ast.base.printWarning import prog8.ast.base.printWarning
import prog8.compiler.intermediate.Instruction import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.Opcode import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.toHex import prog8.compiler.toHex
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
internal class AsmPattern(val sequence: List<Opcode>, val altSequence: List<Opcode>?=null, val asm: (List<Instruction>)->String?) internal class AsmPattern(val sequence: List<Opcode>, val altSequence: List<Opcode>?=null, val asm: (List<Instruction>)->String?)
internal fun loadAFromIndexedByVar(idxVarInstr: Instruction, readArrayInstr: Instruction): String { internal fun loadAFromIndexedByVar(idxVarInstr: Instruction, readArrayInstr: Instruction): String {
@ -1368,7 +1376,7 @@ internal val patterns = listOf<AsmPattern>(
}, },
// floatvar = floatarray[index] // floatvar = floatarray[index]
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.READ_INDEXED_VAR_FLOAT, Opcode.POP_VAR_FLOAT)) { segment -> AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.READ_INDEXED_VAR_FLOAT, Opcode.POP_VAR_FLOAT)) { segment ->
val index = intVal(segment[0]) * Mflpt5.MemorySize val index = intVal(segment[0]) * MachineDefinition.Mflpt5.MemorySize
""" """
lda #<${segment[1].callLabel}+$index lda #<${segment[1].callLabel}+$index
ldy #>${segment[1].callLabel}+$index ldy #>${segment[1].callLabel}+$index
@ -1567,7 +1575,7 @@ internal val patterns = listOf<AsmPattern>(
}, },
// memfloat = floatarray[index] // memfloat = floatarray[index]
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.READ_INDEXED_VAR_FLOAT, Opcode.POP_MEM_FLOAT)) { segment -> AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.READ_INDEXED_VAR_FLOAT, Opcode.POP_MEM_FLOAT)) { segment ->
val index = intVal(segment[0]) * Mflpt5.MemorySize val index = intVal(segment[0]) * MachineDefinition.Mflpt5.MemorySize
""" """
lda #<${segment[1].callLabel}+$index lda #<${segment[1].callLabel}+$index
ldy #>${segment[1].callLabel}+$index ldy #>${segment[1].callLabel}+$index
@ -1581,7 +1589,7 @@ internal val patterns = listOf<AsmPattern>(
// floatarray[idxbyte] = float // floatarray[idxbyte] = float
AsmPattern(listOf(Opcode.PUSH_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment -> AsmPattern(listOf(Opcode.PUSH_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
val floatConst = getFloatConst(segment[0].arg!!) val floatConst = getFloatConst(segment[0].arg!!)
val index = intVal(segment[1]) * Mflpt5.MemorySize val index = intVal(segment[1]) * MachineDefinition.Mflpt5.MemorySize
""" """
lda #<$floatConst lda #<$floatConst
ldy #>$floatConst ldy #>$floatConst
@ -1594,7 +1602,7 @@ internal val patterns = listOf<AsmPattern>(
}, },
// floatarray[idxbyte] = floatvar // floatarray[idxbyte] = floatvar
AsmPattern(listOf(Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment -> AsmPattern(listOf(Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
val index = intVal(segment[1]) * Mflpt5.MemorySize val index = intVal(segment[1]) * MachineDefinition.Mflpt5.MemorySize
""" """
lda #<${segment[0].callLabel} lda #<${segment[0].callLabel}
ldy #>${segment[0].callLabel} ldy #>${segment[0].callLabel}
@ -1607,7 +1615,7 @@ internal val patterns = listOf<AsmPattern>(
}, },
// floatarray[idxbyte] = memfloat // floatarray[idxbyte] = memfloat
AsmPattern(listOf(Opcode.PUSH_MEM_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment -> AsmPattern(listOf(Opcode.PUSH_MEM_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
val index = intVal(segment[1]) * Mflpt5.MemorySize val index = intVal(segment[1]) * MachineDefinition.Mflpt5.MemorySize
""" """
lda #<${hexVal(segment[0])} lda #<${hexVal(segment[0])}
ldy #>${hexVal(segment[0])} ldy #>${hexVal(segment[0])}
@ -1620,8 +1628,8 @@ internal val patterns = listOf<AsmPattern>(
}, },
// floatarray[idx2] = floatarray[idx1] // floatarray[idx2] = floatarray[idx1]
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.READ_INDEXED_VAR_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment -> AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.READ_INDEXED_VAR_FLOAT, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_FLOAT)) { segment ->
val index1 = intVal(segment[0]) * Mflpt5.MemorySize val index1 = intVal(segment[0]) * MachineDefinition.Mflpt5.MemorySize
val index2 = intVal(segment[2]) * Mflpt5.MemorySize val index2 = intVal(segment[2]) * MachineDefinition.Mflpt5.MemorySize
""" """
lda #<(${segment[1].callLabel}+$index1) lda #<(${segment[1].callLabel}+$index1)
ldy #>(${segment[1].callLabel}+$index1) ldy #>(${segment[1].callLabel}+$index1)
@ -1756,16 +1764,16 @@ internal val patterns = listOf<AsmPattern>(
// push word var as (u)byte // push word var as (u)byte
AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_W_TO_UB), AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_W_TO_UB),
listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_W_TO_B)) { segment -> listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_W_TO_B)) { segment ->
" lda ${segment[0].callLabel} | sta ${ESTACK_LO.toHex()},x | dex " " lda ${segment[0].callLabel} | sta $ESTACK_LO_HEX,x | dex "
}, },
// push uword var as (u)byte // push uword var as (u)byte
AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_UW_TO_UB), AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_UW_TO_UB),
listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_UW_TO_B)) { segment -> listOf(Opcode.PUSH_VAR_WORD, Opcode.CAST_UW_TO_B)) { segment ->
" lda ${segment[0].callLabel} | sta ${ESTACK_LO.toHex()},x | dex " " lda ${segment[0].callLabel} | sta $ESTACK_LO_HEX,x | dex "
}, },
// push msb(word var) // push msb(word var)
AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.MSB)) { segment -> AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.MSB)) { segment ->
" lda ${segment[0].callLabel}+1 | sta ${ESTACK_LO.toHex()},x | dex " " lda ${segment[0].callLabel}+1 | sta $ESTACK_LO_HEX,x | dex "
}, },
// set a register pair to a certain memory address (of a variable) // set a register pair to a certain memory address (of a variable)
@ -1782,29 +1790,29 @@ internal val patterns = listOf<AsmPattern>(
// push memory byte | bytevalue // push memory byte | bytevalue
AsmPattern(listOf(Opcode.PUSH_MEM_B, Opcode.PUSH_BYTE, Opcode.BITOR_BYTE), AsmPattern(listOf(Opcode.PUSH_MEM_B, Opcode.PUSH_BYTE, Opcode.BITOR_BYTE),
listOf(Opcode.PUSH_MEM_UB, Opcode.PUSH_BYTE, Opcode.BITOR_BYTE)) { segment -> listOf(Opcode.PUSH_MEM_UB, Opcode.PUSH_BYTE, Opcode.BITOR_BYTE)) { segment ->
" lda ${hexVal(segment[0])} | ora #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex " " lda ${hexVal(segment[0])} | ora #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
}, },
// push memory byte & bytevalue // push memory byte & bytevalue
AsmPattern(listOf(Opcode.PUSH_MEM_B, Opcode.PUSH_BYTE, Opcode.BITAND_BYTE), AsmPattern(listOf(Opcode.PUSH_MEM_B, Opcode.PUSH_BYTE, Opcode.BITAND_BYTE),
listOf(Opcode.PUSH_MEM_UB, Opcode.PUSH_BYTE, Opcode.BITAND_BYTE)) { segment -> listOf(Opcode.PUSH_MEM_UB, Opcode.PUSH_BYTE, Opcode.BITAND_BYTE)) { segment ->
" lda ${hexVal(segment[0])} | and #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex " " lda ${hexVal(segment[0])} | and #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
}, },
// push memory byte ^ bytevalue // push memory byte ^ bytevalue
AsmPattern(listOf(Opcode.PUSH_MEM_B, Opcode.PUSH_BYTE, Opcode.BITXOR_BYTE), AsmPattern(listOf(Opcode.PUSH_MEM_B, Opcode.PUSH_BYTE, Opcode.BITXOR_BYTE),
listOf(Opcode.PUSH_MEM_UB, Opcode.PUSH_BYTE, Opcode.BITXOR_BYTE)) { segment -> listOf(Opcode.PUSH_MEM_UB, Opcode.PUSH_BYTE, Opcode.BITXOR_BYTE)) { segment ->
" lda ${hexVal(segment[0])} | eor #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex " " lda ${hexVal(segment[0])} | eor #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
}, },
// push var byte | bytevalue // push var byte | bytevalue
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.BITOR_BYTE)) { segment -> AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.BITOR_BYTE)) { segment ->
" lda ${segment[0].callLabel} | ora #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex " " lda ${segment[0].callLabel} | ora #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
}, },
// push var byte & bytevalue // push var byte & bytevalue
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.BITAND_BYTE)) { segment -> AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.BITAND_BYTE)) { segment ->
" lda ${segment[0].callLabel} | and #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex " " lda ${segment[0].callLabel} | and #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
}, },
// push var byte ^ bytevalue // push var byte ^ bytevalue
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.BITXOR_BYTE)) { segment -> AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.BITXOR_BYTE)) { segment ->
" lda ${segment[0].callLabel} | eor #${hexVal(segment[1])} | sta ${ESTACK_LO.toHex()},x | dex " " lda ${segment[0].callLabel} | eor #${hexVal(segment[1])} | sta $ESTACK_LO_HEX,x | dex "
}, },
// push memory word | wordvalue // push memory word | wordvalue
@ -1813,10 +1821,10 @@ internal val patterns = listOf<AsmPattern>(
""" """
lda ${hexVal(segment[0])} lda ${hexVal(segment[0])}
ora #<${hexVal(segment[1])} ora #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(segment[0])} lda ${hexValPlusOne(segment[0])}
ora #>${hexVal(segment[1])} ora #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x sta $ESTACK_HI_HEX,x
dex dex
""" """
}, },
@ -1826,10 +1834,10 @@ internal val patterns = listOf<AsmPattern>(
""" """
lda ${hexVal(segment[0])} lda ${hexVal(segment[0])}
and #<${hexVal(segment[1])} and #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(segment[0])} lda ${hexValPlusOne(segment[0])}
and #>${hexVal(segment[1])} and #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x sta $ESTACK_HI_HEX,x
dex dex
""" """
}, },
@ -1839,10 +1847,10 @@ internal val patterns = listOf<AsmPattern>(
""" """
lda ${hexVal(segment[0])} lda ${hexVal(segment[0])}
eor #<${hexVal(segment[1])} eor #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(segment[0])} lda ${hexValPlusOne(segment[0])}
eor #>${hexVal(segment[1])} eor #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x sta $ESTACK_HI_HEX,x
dex dex
""" """
}, },
@ -1851,10 +1859,10 @@ internal val patterns = listOf<AsmPattern>(
""" """
lda ${segment[0].callLabel} lda ${segment[0].callLabel}
ora #<${hexVal(segment[1])} ora #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
lda ${segment[0].callLabel}+1 lda ${segment[0].callLabel}+1
ora #>${hexVal(segment[1])} ora #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x sta $ESTACK_HI_HEX,x
dex dex
""" """
}, },
@ -1863,10 +1871,10 @@ internal val patterns = listOf<AsmPattern>(
""" """
lda ${segment[0].callLabel} lda ${segment[0].callLabel}
and #<${hexVal(segment[1])} and #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
lda ${segment[0].callLabel}+1 lda ${segment[0].callLabel}+1
and #>${hexVal(segment[1])} and #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x sta $ESTACK_HI_HEX,x
dex dex
""" """
}, },
@ -1875,10 +1883,10 @@ internal val patterns = listOf<AsmPattern>(
""" """
lda ${segment[0].callLabel} lda ${segment[0].callLabel}
eor #<${hexVal(segment[1])} eor #<${hexVal(segment[1])}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
lda ${segment[0].callLabel}+1 lda ${segment[0].callLabel}+1
eor #>${hexVal(segment[1])} eor #>${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x sta $ESTACK_HI_HEX,x
dex dex
""" """
}, },
@ -1971,27 +1979,27 @@ internal val patterns = listOf<AsmPattern>(
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_VAR_BYTE, Opcode.MKWORD)) { segment -> AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_VAR_BYTE, Opcode.MKWORD)) { segment ->
""" """
lda ${segment[0].callLabel} lda ${segment[0].callLabel}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
lda ${segment[1].callLabel} lda ${segment[1].callLabel}
sta ${ESTACK_HI.toHex()},x sta $ESTACK_HI_HEX,x
dex dex
""" """
}, },
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.PUSH_VAR_BYTE, Opcode.MKWORD)) { segment -> AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.PUSH_VAR_BYTE, Opcode.MKWORD)) { segment ->
""" """
lda #${hexVal(segment[0])} lda #${hexVal(segment[0])}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
lda ${segment[1].callLabel} lda ${segment[1].callLabel}
sta ${ESTACK_HI.toHex()},x sta $ESTACK_HI_HEX,x
dex dex
""" """
}, },
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.MKWORD)) { segment -> AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.PUSH_BYTE, Opcode.MKWORD)) { segment ->
""" """
lda ${segment[0].callLabel} lda ${segment[0].callLabel}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
lda #${hexVal(segment[1])} lda #${hexVal(segment[1])}
sta ${ESTACK_HI.toHex()},x sta $ESTACK_HI_HEX,x
dex dex
""" """
}, },
@ -2032,28 +2040,28 @@ internal val patterns = listOf<AsmPattern>(
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.ADD_B), listOf(Opcode.PUSH_BYTE, Opcode.ADD_UB)) { segment -> AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.ADD_B), listOf(Opcode.PUSH_BYTE, Opcode.ADD_UB)) { segment ->
val amount = segment[0].arg!!.integerValue() val amount = segment[0].arg!!.integerValue()
if (amount in 1..2) { if (amount in 1..2) {
" inc ${(ESTACK_LO + 1).toHex()},x | ".repeat(amount) " inc $ESTACK_LO_PLUS1_HEX,x | ".repeat(amount)
} else } else
null null
}, },
AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.ADD_UW), listOf(Opcode.PUSH_WORD, Opcode.ADD_W)) { segment -> AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.ADD_UW), listOf(Opcode.PUSH_WORD, Opcode.ADD_W)) { segment ->
val amount = segment[0].arg!!.integerValue() val amount = segment[0].arg!!.integerValue()
if (amount in 1..2) { if (amount in 1..2) {
" inc ${(ESTACK_LO + 1).toHex()},x | bne + | inc ${(ESTACK_HI + 1).toHex()},x |+ | ".repeat(amount) " inc $ESTACK_LO_PLUS1_HEX,x | bne + | inc $ESTACK_HI_PLUS1_HEX,x |+ | ".repeat(amount)
} else } else
null null
}, },
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.SUB_B), listOf(Opcode.PUSH_BYTE, Opcode.SUB_UB)) { segment -> AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.SUB_B), listOf(Opcode.PUSH_BYTE, Opcode.SUB_UB)) { segment ->
val amount = segment[0].arg!!.integerValue() val amount = segment[0].arg!!.integerValue()
if (amount in 1..2) { if (amount in 1..2) {
" dec ${(ESTACK_LO + 1).toHex()},x | ".repeat(amount) " dec $ESTACK_LO_PLUS1_HEX,x | ".repeat(amount)
} else } else
null null
}, },
AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.SUB_UW), listOf(Opcode.PUSH_WORD, Opcode.SUB_W)) { segment -> AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.SUB_UW), listOf(Opcode.PUSH_WORD, Opcode.SUB_W)) { segment ->
val amount = segment[0].arg!!.integerValue() val amount = segment[0].arg!!.integerValue()
if (amount in 1..2) { if (amount in 1..2) {
" lda ${(ESTACK_LO + 1).toHex()},x | bne + | dec ${(ESTACK_HI + 1).toHex()},x |+ | dec ${(ESTACK_LO + 1).toHex()},x | ".repeat(amount) " lda $ESTACK_LO_PLUS1_HEX,x | bne + | dec $ESTACK_HI_PLUS1_HEX,x |+ | dec $ESTACK_LO_PLUS1_HEX,x | ".repeat(amount)
} else } else
null null
}, },
@ -2063,7 +2071,20 @@ internal val patterns = listOf<AsmPattern>(
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_B), listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_UB)) { segment -> AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_B), listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_UB)) { segment ->
// this pattern is encountered as part of the loop bound condition in for loops (var + cmp + jz/jnz) // this pattern is encountered as part of the loop bound condition in for loops (var + cmp + jz/jnz)
val cmpval = segment[1].arg!!.integerValue() val cmpval = segment[1].arg!!.integerValue()
when(segment[0].callLabel) {
"A" -> {
" cmp #$cmpval "
}
"X" -> {
" cpx #$cmpval "
}
"Y" -> {
" cpy #$cmpval "
}
else -> {
" lda ${segment[0].callLabel} | cmp #$cmpval " " lda ${segment[0].callLabel} | cmp #$cmpval "
}
}
}, },
AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.CMP_W), listOf(Opcode.PUSH_VAR_WORD, Opcode.CMP_UW)) { segment -> AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.CMP_W), listOf(Opcode.PUSH_VAR_WORD, Opcode.CMP_UW)) { segment ->
// this pattern is encountered as part of the loop bound condition in for loops (var + cmp + jz/jnz) // this pattern is encountered as part of the loop bound condition in for loops (var + cmp + jz/jnz)
@ -2093,14 +2114,14 @@ internal val patterns = listOf<AsmPattern>(
AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.MUL_B), listOf(Opcode.PUSH_BYTE, Opcode.MUL_UB)) { segment -> AsmPattern(listOf(Opcode.PUSH_BYTE, Opcode.MUL_B), listOf(Opcode.PUSH_BYTE, Opcode.MUL_UB)) { segment ->
val amount = segment[0].arg!!.integerValue() val amount = segment[0].arg!!.integerValue()
val result = optimizedIntMultiplicationsOnStack(segment[1], amount) val result = optimizedIntMultiplicationsOnStack(segment[1], amount)
result ?: " lda #${hexVal(segment[0])} | sta ${ESTACK_LO.toHex()},x | dex | jsr prog8_lib.mul_byte" result ?: " lda #${hexVal(segment[0])} | sta $ESTACK_LO_HEX,x | dex | jsr prog8_lib.mul_byte"
}, },
AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.MUL_W), listOf(Opcode.PUSH_WORD, Opcode.MUL_UW)) { segment -> AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.MUL_W), listOf(Opcode.PUSH_WORD, Opcode.MUL_UW)) { segment ->
val amount = segment[0].arg!!.integerValue() val amount = segment[0].arg!!.integerValue()
val result = optimizedIntMultiplicationsOnStack(segment[1], amount) val result = optimizedIntMultiplicationsOnStack(segment[1], amount)
if (result != null) result else { if (result != null) result else {
val value = hexVal(segment[0]) val value = hexVal(segment[0])
" lda #<$value | sta ${ESTACK_LO.toHex()},x | lda #>$value | sta ${ESTACK_HI.toHex()},x | dex | jsr prog8_lib.mul_word" " lda #<$value | sta $ESTACK_LO_HEX,x | lda #>$value | sta $ESTACK_HI_HEX,x | dex | jsr prog8_lib.mul_word"
} }
}, },
@ -2289,6 +2310,24 @@ internal val patterns = listOf<AsmPattern>(
ldx ${C64Zeropage.SCRATCH_REG_X} ldx ${C64Zeropage.SCRATCH_REG_X}
""" """
} else null } else null
},
AsmPattern(listOf(Opcode.DUP_W, Opcode.CMP_UW),
listOf(Opcode.DUP_W, Opcode.CMP_W)) { segment ->
"""
lda $ESTACK_HI_PLUS1_HEX,x
cmp #>${segment[1].arg!!.integerValue().toHex()}
bne +
lda $ESTACK_LO_PLUS1_HEX,x
cmp #<${segment[1].arg!!.integerValue().toHex()}
; bne + not necessary?
; lda #0 not necessary?
+
"""
},
AsmPattern(listOf(Opcode.DUP_B, Opcode.CMP_UB),
listOf(Opcode.DUP_B, Opcode.CMP_B)) { segment ->
""" lda $ESTACK_LO_PLUS1_HEX,x | cmp #${segment[1].arg!!.integerValue().toHex()} """
} }
) )

View File

@ -1,242 +0,0 @@
package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.CompilerException
import prog8.compiler.Zeropage
import prog8.compiler.ZeropageType
import java.awt.Color
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import kotlin.math.absoluteValue
import kotlin.math.pow
// 5-byte cbm MFLPT format limitations:
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
const val BASIC_LOAD_ADDRESS = 0x0801
const val RAW_LOAD_ADDRESS = 0xc000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
const val ESTACK_LO = 0xce00 // $ce00-$ceff inclusive
const val ESTACK_HI = 0xcf00 // $cf00-$cfff inclusive
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
companion object {
const val SCRATCH_B1 = 0x02
const val SCRATCH_REG = 0x03 // temp storage for a register
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
const val SCRATCH_W1 = 0xfb // $fb+$fc
const val SCRATCH_W2 = 0xfd // $fd+$fe
}
override val exitProgramStrategy: ExitProgramStrategy = when(options.zeropage) {
ZeropageType.BASICSAFE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if(options.floats && options.zeropage!=ZeropageType.FLOATSAFE && options.zeropage!=ZeropageType.BASICSAFE)
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe'")
if(options.zeropage == ZeropageType.FULL) {
free.addAll(0x04 .. 0xf9)
free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1+1, SCRATCH_W2, SCRATCH_W2+1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if(options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area'
))
}
if(options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list
free.removeAll(listOf(
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
))
}
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0d, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
}
assert(SCRATCH_B1 !in free)
assert(SCRATCH_REG !in free)
assert(SCRATCH_REG_X !in free)
assert(SCRATCH_W1 !in free)
assert(SCRATCH_W2 !in free)
for(reserved in options.zpReserved)
reserve(reserved)
}
}
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
companion object {
const val MemorySize = 5
val zero = Mflpt5(0, 0,0,0,0)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
// and https://en.wikipedia.org/wiki/IEEE_754-1985
val flt = num.toDouble()
if(flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
if(flt==0.0)
return zero
val sign = if(flt<0.0) 0x80L else 0x00L
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
var mantissa = flt.absoluteValue
// if mantissa is too large, shift right and adjust exponent
while(mantissa >= 0x100000000) {
mantissa /= 2.0
exponent ++
}
// if mantissa is too small, shift left and adjust exponent
while(mantissa < 0x80000000) {
mantissa *= 2.0
exponent --
}
return when {
exponent<0 -> zero // underflow, use zero instead
exponent>255 -> throw CompilerException("floating point overflow: $this")
exponent==0 -> zero
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toShort(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
(mantLong.and(0x000000ffL)).toShort())
}
}
}
}
fun toDouble(): Double {
if(this == zero) return 0.0
val exp = b0 - 128
val sign = (b1.toInt() and 0x80) > 0
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
return if(sign) -result else result
}
}
object Charset {
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
transparent.createGraphics().drawImage(img, 0, 0, null)
val black = Color(0,0,0).rgb
val nopixel = Color(0,0,0,0).rgb
for(y in 0 until transparent.height) {
for(x in 0 until transparent.width) {
val col = transparent.getRGB(x, y)
if(col==black)
transparent.setRGB(x, y, nopixel)
}
}
val numColumns = transparent.width / 8
val charImages = (0..255).map {
val charX = it % numColumns
val charY = it/ numColumns
transparent.getSubimage(charX*8, charY*8, 8, 8)
}
return charImages.toTypedArray()
}
val normalChars = scanChars(normalImg)
val shiftedChars = scanChars(shiftedImg)
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
val colorIdx = (color % Colors.palette.size).toShort()
val chars = coloredNormalChars[colorIdx]
if(chars!=null)
return chars[screenCode.toInt()]
val coloredChars = mutableListOf<BufferedImage>()
val transparent = Color(0,0,0,0).rgb
val rgb = Colors.palette[colorIdx.toInt()].rgb
for(c in normalChars) {
val colored = c.copy()
for(y in 0 until colored.height)
for(x in 0 until colored.width) {
if(colored.getRGB(x, y)!=transparent) {
colored.setRGB(x, y, rgb)
}
}
coloredChars.add(colored)
}
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
}
}
private fun BufferedImage.copy(): BufferedImage {
val bcopy = BufferedImage(this.width, this.height, this.type)
val g = bcopy.graphics
g.drawImage(this, 0, 0, null)
g.dispose()
return bcopy
}
object Colors {
val palette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
)
}

View File

@ -0,0 +1,250 @@
package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.CompilerException
import prog8.compiler.Zeropage
import prog8.compiler.ZeropageType
import java.awt.Color
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import kotlin.math.absoluteValue
import kotlin.math.pow
object MachineDefinition {
// 5-byte cbm MFLPT format limitations:
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
const val BASIC_LOAD_ADDRESS = 0x0801
const val RAW_LOAD_ADDRESS = 0xc000
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
// and some heavily used string constants derived from the two values above
const val ESTACK_LO_VALUE = 0xce00 // $ce00-$ceff inclusive
const val ESTACK_HI_VALUE = 0xcf00 // $cf00-$cfff inclusive
const val ESTACK_LO_HEX = "\$ce00"
const val ESTACK_LO_PLUS1_HEX = "\$ce01"
const val ESTACK_LO_PLUS2_HEX = "\$ce02"
const val ESTACK_HI_HEX = "\$cf00"
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
const val ESTACK_HI_PLUS2_HEX = "\$cf02"
class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
companion object {
const val SCRATCH_B1 = 0x02
const val SCRATCH_REG = 0x03 // temp storage for a register
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
const val SCRATCH_W1 = 0xfb // $fb+$fc
const val SCRATCH_W2 = 0xfd // $fd+$fe
}
override val exitProgramStrategy: ExitProgramStrategy = when (options.zeropage) {
ZeropageType.BASICSAFE -> ExitProgramStrategy.CLEAN_EXIT
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
}
init {
if (options.floats && options.zeropage != ZeropageType.FLOATSAFE && options.zeropage != ZeropageType.BASICSAFE)
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe'")
if (options.zeropage == ZeropageType.FULL) {
free.addAll(0x04..0xf9)
free.add(0xff)
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1 + 1, SCRATCH_W2, SCRATCH_W2 + 1))
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if (options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x22, 0x23, 0x24, 0x25,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area'
))
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list
free.removeAll(listOf(
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
))
}
// add the other free Zp addresses,
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0d, 0x0e,
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
}
assert(SCRATCH_B1 !in free)
assert(SCRATCH_REG !in free)
assert(SCRATCH_REG_X !in free)
assert(SCRATCH_W1 !in free)
assert(SCRATCH_W2 !in free)
for (reserved in options.zpReserved)
reserve(reserved)
}
}
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
companion object {
const val MemorySize = 5
val zero = Mflpt5(0, 0, 0, 0, 0)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
// and https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
// and https://en.wikipedia.org/wiki/IEEE_754-1985
val flt = num.toDouble()
if (flt < FLOAT_MAX_NEGATIVE || flt > FLOAT_MAX_POSITIVE)
throw CompilerException("floating point number out of 5-byte mflpt range: $this")
if (flt == 0.0)
return zero
val sign = if (flt < 0.0) 0x80L else 0x00L
var exponent = 128 + 32 // 128 is cbm's bias, 32 is this algo's bias
var mantissa = flt.absoluteValue
// if mantissa is too large, shift right and adjust exponent
while (mantissa >= 0x100000000) {
mantissa /= 2.0
exponent++
}
// if mantissa is too small, shift left and adjust exponent
while (mantissa < 0x80000000) {
mantissa *= 2.0
exponent--
}
return when {
exponent < 0 -> zero // underflow, use zero instead
exponent > 255 -> throw CompilerException("floating point overflow: $this")
exponent == 0 -> zero
else -> {
val mantLong = mantissa.toLong()
Mflpt5(
exponent.toShort(),
(mantLong.and(0x7f000000L) ushr 24).or(sign).toShort(),
(mantLong.and(0x00ff0000L) ushr 16).toShort(),
(mantLong.and(0x0000ff00L) ushr 8).toShort(),
(mantLong.and(0x000000ffL)).toShort())
}
}
}
}
fun toDouble(): Double {
if (this == zero) return 0.0
val exp = b0 - 128
val sign = (b1.toInt() and 0x80) > 0
val number = 0x80000000L.or(b1.toLong() shl 24).or(b2.toLong() shl 16).or(b3.toLong() shl 8).or(b4.toLong())
val result = number.toDouble() * (2.0).pow(exp) / 0x100000000
return if (sign) -result else result
}
}
object Charset {
private val normalImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-normal.png"))
private val shiftedImg = ImageIO.read(javaClass.getResource("/charset/c64/charset-shifted.png"))
private fun scanChars(img: BufferedImage): Array<BufferedImage> {
val transparent = BufferedImage(img.width, img.height, BufferedImage.TYPE_INT_ARGB)
transparent.createGraphics().drawImage(img, 0, 0, null)
val black = Color(0, 0, 0).rgb
val nopixel = Color(0, 0, 0, 0).rgb
for (y in 0 until transparent.height) {
for (x in 0 until transparent.width) {
val col = transparent.getRGB(x, y)
if (col == black)
transparent.setRGB(x, y, nopixel)
}
}
val numColumns = transparent.width / 8
val charImages = (0..255).map {
val charX = it % numColumns
val charY = it / numColumns
transparent.getSubimage(charX * 8, charY * 8, 8, 8)
}
return charImages.toTypedArray()
}
val normalChars = scanChars(normalImg)
val shiftedChars = scanChars(shiftedImg)
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
val colorIdx = (color % colorPalette.size).toShort()
val chars = coloredNormalChars[colorIdx]
if (chars != null)
return chars[screenCode.toInt()]
val coloredChars = mutableListOf<BufferedImage>()
val transparent = Color(0, 0, 0, 0).rgb
val rgb = colorPalette[colorIdx.toInt()].rgb
for (c in normalChars) {
val colored = c.copy()
for (y in 0 until colored.height)
for (x in 0 until colored.width) {
if (colored.getRGB(x, y) != transparent) {
colored.setRGB(x, y, rgb)
}
}
coloredChars.add(colored)
}
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
}
}
private fun BufferedImage.copy(): BufferedImage {
val bcopy = BufferedImage(this.width, this.height, this.type)
val g = bcopy.graphics
g.drawImage(this, 0, 0, null)
g.dispose()
return bcopy
}
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
)
}

View File

@ -1086,5 +1086,38 @@ class Petscii {
val decodeTable = if(lowercase) decodingScreencodeLowercase else decodingScreencodeUppercase val decodeTable = if(lowercase) decodingScreencodeLowercase else decodingScreencodeUppercase
return screencode.map { decodeTable[it.toInt()] }.joinToString("") return screencode.map { decodeTable[it.toInt()] }.joinToString("")
} }
fun petscii2scr(petscii_code: Short, inverseVideo: Boolean): Short {
val code = when {
petscii_code <= 0x1f -> petscii_code + 128
petscii_code <= 0x3f -> petscii_code.toInt()
petscii_code <= 0x5f -> petscii_code - 64
petscii_code <= 0x7f -> petscii_code - 32
petscii_code <= 0x9f -> petscii_code + 64
petscii_code <= 0xbf -> petscii_code - 64
petscii_code <= 0xfe -> petscii_code - 128
petscii_code == 255.toShort() -> 95
else -> throw CharConversionException("petscii code out of range")
}
if(inverseVideo)
return (code or 0x80).toShort()
return code.toShort()
}
fun scr2petscii(screencode: Short): Short {
val petscii = when {
screencode <= 0x1f -> screencode + 64
screencode <= 0x3f -> screencode.toInt()
screencode <= 0x5d -> screencode +123
screencode == 0x5e.toShort() -> 255
screencode == 0x5f.toShort() -> 223
screencode <= 0x7f -> screencode + 64
screencode <= 0xbf -> screencode - 128
screencode <= 0xfe -> screencode - 64
screencode == 255.toShort() -> 191
else -> throw CharConversionException("screencode out of range")
}
return petscii.toShort()
}
} }
} }

View File

@ -5,11 +5,20 @@ import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.IntermediateProgram import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.LabelInstr import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS2_HEX
import prog8.compiler.toHex import prog8.compiler.toHex
import prog8.vm.stackvm.Syscall import prog8.vm.stackvm.Syscall
import prog8.vm.stackvm.syscallsForStackVm import prog8.vm.stackvm.syscallsForStackVm
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
private var breakpointCounter = 0 private var breakpointCounter = 0
internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.ProgramBlock): String? { internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.ProgramBlock): String? {
@ -61,13 +70,32 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.RRESTOREX -> " sta ${C64Zeropage.SCRATCH_REG} | pla | tax | lda ${C64Zeropage.SCRATCH_REG}" Opcode.RRESTOREX -> " sta ${C64Zeropage.SCRATCH_REG} | pla | tax | lda ${C64Zeropage.SCRATCH_REG}"
Opcode.DISCARD_BYTE -> " inx" Opcode.DISCARD_BYTE -> " inx"
Opcode.DISCARD_WORD -> " inx" Opcode.DISCARD_WORD -> " inx"
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.DUP_B -> { Opcode.DUP_B -> {
" dex | lda ${(ESTACK_LO+1).toHex()},x | sta ${ESTACK_LO.toHex()},x" " lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | dex | ;DUP_B "
} }
Opcode.DUP_W -> { Opcode.DUP_W -> {
" dex | lda ${(ESTACK_LO+1).toHex()},x | sta ${ESTACK_LO.toHex()},x | lda ${(ESTACK_HI+1).toHex()},x | sta ${ESTACK_HI.toHex()},x " " lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_HI_HEX,x | dex "
} }
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.CMP_B, Opcode.CMP_UB -> {
" inx | lda $ESTACK_LO_HEX,x | cmp #${ins.arg!!.integerValue().toHex()} | ;CMP_B "
}
Opcode.CMP_W, Opcode.CMP_UW -> {
"""
inx
lda $ESTACK_HI_HEX,x
cmp #>${ins.arg!!.integerValue().toHex()}
bne +
lda $ESTACK_LO_HEX,x
cmp #<${ins.arg.integerValue().toHex()}
; bne + not necessary?
; lda #0 not necessary?
+
"""
}
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to accept it. Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to accept it.
Opcode.INCLUDE_FILE -> { Opcode.INCLUDE_FILE -> {
val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}" val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}"
@ -108,11 +136,11 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
} }
Opcode.PUSH_BYTE -> { Opcode.PUSH_BYTE -> {
" lda #${hexVal(ins)} | sta ${ESTACK_LO.toHex()},x | dex" " lda #${hexVal(ins)} | sta $ESTACK_LO_HEX,x | dex"
} }
Opcode.PUSH_WORD -> { Opcode.PUSH_WORD -> {
val value = hexVal(ins) val value = hexVal(ins)
" lda #<$value | sta ${ESTACK_LO.toHex()},x | lda #>$value | sta ${ESTACK_HI.toHex()},x | dex" " lda #<$value | sta $ESTACK_LO_HEX,x | lda #>$value | sta $ESTACK_HI_HEX,x | dex"
} }
Opcode.PUSH_FLOAT -> { Opcode.PUSH_FLOAT -> {
val floatConst = getFloatConst(ins.arg!!) val floatConst = getFloatConst(ins.arg!!)
@ -121,28 +149,28 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.PUSH_VAR_BYTE -> { Opcode.PUSH_VAR_BYTE -> {
when(ins.callLabel) { when(ins.callLabel) {
"X" -> throw CompilerException("makes no sense to push X, it's used as a stack pointer itself. You should probably not use the X register (or only in trivial assignments)") "X" -> throw CompilerException("makes no sense to push X, it's used as a stack pointer itself. You should probably not use the X register (or only in trivial assignments)")
"A" -> " sta ${ESTACK_LO.toHex()},x | dex" "A" -> " sta $ESTACK_LO_HEX,x | dex"
"Y" -> " tya | sta ${ESTACK_LO.toHex()},x | dex" "Y" -> " tya | sta $ESTACK_LO_HEX,x | dex"
else -> " lda ${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | dex" else -> " lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | dex"
} }
} }
Opcode.PUSH_VAR_WORD -> { Opcode.PUSH_VAR_WORD -> {
" lda ${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | lda ${ins.callLabel}+1 | sta ${ESTACK_HI.toHex()},x | dex" " lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda ${ins.callLabel}+1 | sta $ESTACK_HI_HEX,x | dex"
} }
Opcode.PUSH_VAR_FLOAT -> " lda #<${ins.callLabel} | ldy #>${ins.callLabel}| jsr c64flt.push_float" Opcode.PUSH_VAR_FLOAT -> " lda #<${ins.callLabel} | ldy #>${ins.callLabel}| jsr c64flt.push_float"
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> { Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> {
""" """
lda ${hexVal(ins)} lda ${hexVal(ins)}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
dex dex
""" """
} }
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> { Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> {
""" """
lda ${hexVal(ins)} lda ${hexVal(ins)}
sta ${ESTACK_LO.toHex()},x sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(ins)} lda ${hexValPlusOne(ins)}
sta ${ESTACK_HI.toHex()},x sta $ESTACK_HI_HEX,x
dex dex
""" """
} }
@ -151,43 +179,43 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
} }
Opcode.PUSH_MEMREAD -> { Opcode.PUSH_MEMREAD -> {
""" """
lda ${(ESTACK_LO+1).toHex()},x lda $ESTACK_LO_PLUS1_HEX,x
sta (+) +1 sta (+) +1
lda ${(ESTACK_HI+1).toHex()},x lda $ESTACK_HI_PLUS1_HEX,x
sta (+) +2 sta (+) +2
+ lda 65535 ; modified + lda 65535 ; modified
sta ${(ESTACK_LO+1).toHex()},x sta $ESTACK_LO_PLUS1_HEX,x
""" """
} }
Opcode.PUSH_REGAY_WORD -> { Opcode.PUSH_REGAY_WORD -> {
" sta ${ESTACK_LO.toHex()},x | tya | sta ${ESTACK_HI.toHex()},x | dex " " sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex "
} }
Opcode.PUSH_ADDR_HEAPVAR -> { Opcode.PUSH_ADDR_HEAPVAR -> {
" lda #<${ins.callLabel} | sta ${ESTACK_LO.toHex()},x | lda #>${ins.callLabel} | sta ${ESTACK_HI.toHex()},x | dex" " lda #<${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda #>${ins.callLabel} | sta $ESTACK_HI_HEX,x | dex"
} }
Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself") Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself") Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGAY_WORD -> { Opcode.POP_REGAY_WORD -> {
" inx | lda ${ESTACK_LO.toHex()},x | ldy ${ESTACK_HI.toHex()},x " " inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x "
} }
Opcode.READ_INDEXED_VAR_BYTE -> { Opcode.READ_INDEXED_VAR_BYTE -> {
""" """
ldy ${(ESTACK_LO+1).toHex()},x ldy $ESTACK_LO_PLUS1_HEX,x
lda ${ins.callLabel},y lda ${ins.callLabel},y
sta ${(ESTACK_LO+1).toHex()},x sta $ESTACK_LO_PLUS1_HEX,x
""" """
} }
Opcode.READ_INDEXED_VAR_WORD -> { Opcode.READ_INDEXED_VAR_WORD -> {
""" """
lda ${(ESTACK_LO+1).toHex()},x lda $ESTACK_LO_PLUS1_HEX,x
asl a asl a
tay tay
lda ${ins.callLabel},y lda ${ins.callLabel},y
sta ${(ESTACK_LO+1).toHex()},x sta $ESTACK_LO_PLUS1_HEX,x
lda ${ins.callLabel}+1,y lda ${ins.callLabel}+1,y
sta ${(ESTACK_HI+1).toHex()},x sta $ESTACK_HI_PLUS1_HEX,x
""" """
} }
Opcode.READ_INDEXED_VAR_FLOAT -> { Opcode.READ_INDEXED_VAR_FLOAT -> {
@ -200,22 +228,22 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.WRITE_INDEXED_VAR_BYTE -> { Opcode.WRITE_INDEXED_VAR_BYTE -> {
""" """
inx inx
ldy ${ESTACK_LO.toHex()},x ldy $ESTACK_LO_HEX,x
inx inx
lda ${ESTACK_LO.toHex()},x lda $ESTACK_LO_HEX,x
sta ${ins.callLabel},y sta ${ins.callLabel},y
""" """
} }
Opcode.WRITE_INDEXED_VAR_WORD -> { Opcode.WRITE_INDEXED_VAR_WORD -> {
""" """
inx inx
lda ${ESTACK_LO.toHex()},x lda $ESTACK_LO_HEX,x
asl a asl a
tay tay
inx inx
lda ${ESTACK_LO.toHex()},x lda $ESTACK_LO_HEX,x
sta ${ins.callLabel},y sta ${ins.callLabel},y
lda ${ESTACK_HI.toHex()},x lda $ESTACK_HI_HEX,x
sta ${ins.callLabel}+1,y sta ${ins.callLabel}+1,y
""" """
} }
@ -229,16 +257,16 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POP_MEM_BYTE -> { Opcode.POP_MEM_BYTE -> {
""" """
inx inx
lda ${ESTACK_LO.toHex()},x lda $ESTACK_LO_HEX,x
sta ${hexVal(ins)} sta ${hexVal(ins)}
""" """
} }
Opcode.POP_MEM_WORD -> { Opcode.POP_MEM_WORD -> {
""" """
inx inx
lda ${ESTACK_LO.toHex()},x lda $ESTACK_LO_HEX,x
sta ${hexVal(ins)} sta ${hexVal(ins)}
lda ${ESTACK_HI.toHex()},x lda $ESTACK_HI_HEX,x
sta ${hexValPlusOne(ins)} sta ${hexValPlusOne(ins)}
""" """
} }
@ -248,12 +276,12 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POP_MEMWRITE -> { Opcode.POP_MEMWRITE -> {
""" """
inx inx
lda ${ESTACK_LO.toHex()},x lda $ESTACK_LO_HEX,x
sta (+) +1 sta (+) +1
lda ${ESTACK_HI.toHex()},x lda $ESTACK_HI_HEX,x
sta (+) +2 sta (+) +2
inx inx
lda ${ESTACK_LO.toHex()},x lda $ESTACK_LO_HEX,x
+ sta 65535 ; modified + sta 65535 ; modified
""" """
} }
@ -261,13 +289,13 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POP_VAR_BYTE -> { Opcode.POP_VAR_BYTE -> {
when (ins.callLabel) { when (ins.callLabel) {
"X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself") "X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself")
"A" -> " inx | lda ${ESTACK_LO.toHex()},x" "A" -> " inx | lda $ESTACK_LO_HEX,x"
"Y" -> " inx | ldy ${ESTACK_LO.toHex()},x" "Y" -> " inx | ldy $ESTACK_LO_HEX,x"
else -> " inx | lda ${ESTACK_LO.toHex()},x | sta ${ins.callLabel}" else -> " inx | lda $ESTACK_LO_HEX,x | sta ${ins.callLabel}"
} }
} }
Opcode.POP_VAR_WORD -> { Opcode.POP_VAR_WORD -> {
" inx | lda ${ESTACK_LO.toHex()},x | ldy ${ESTACK_HI.toHex()},x | sta ${ins.callLabel} | sty ${ins.callLabel}+1" " inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x | sta ${ins.callLabel} | sty ${ins.callLabel}+1"
} }
Opcode.POP_VAR_FLOAT -> { Opcode.POP_VAR_FLOAT -> {
" lda #<${ins.callLabel} | ldy #>${ins.callLabel} | jsr c64flt.pop_float" " lda #<${ins.callLabel} | ldy #>${ins.callLabel} | jsr c64flt.pop_float"
@ -294,9 +322,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POP_INC_MEMORY -> { Opcode.POP_INC_MEMORY -> {
""" """
inx inx
lda ${ESTACK_LO.toHex()},x lda $ESTACK_LO_HEX,x
sta (+) +1 sta (+) +1
lda ${ESTACK_HI.toHex()},x lda $ESTACK_HI_HEX,x
sta (+) +2 sta (+) +2
+ inc 65535 ; modified + inc 65535 ; modified
""" """
@ -304,9 +332,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POP_DEC_MEMORY -> { Opcode.POP_DEC_MEMORY -> {
""" """
inx inx
lda ${ESTACK_LO.toHex()},x lda $ESTACK_LO_HEX,x
sta (+) +1 sta (+) +1
lda ${ESTACK_HI.toHex()},x lda $ESTACK_HI_HEX,x
sta (+) +2 sta (+) +2
+ dec 65535 ; modified + dec 65535 ; modified
""" """
@ -331,8 +359,8 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
} }
Opcode.INC_MEMORY -> " inc ${hexVal(ins)}" Opcode.INC_MEMORY -> " inc ${hexVal(ins)}"
Opcode.DEC_MEMORY -> " dec ${hexVal(ins)}" Opcode.DEC_MEMORY -> " dec ${hexVal(ins)}"
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx | txa | pha | lda ${ESTACK_LO.toHex()},x | tax | inc ${ins.callLabel},x | pla | tax" Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | inc ${ins.callLabel},x | pla | tax"
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx | txa | pha | lda ${ESTACK_LO.toHex()},x | tax | dec ${ins.callLabel},x | pla | tax" Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | dec ${ins.callLabel},x | pla | tax"
Opcode.NEG_B -> " jsr prog8_lib.neg_b" Opcode.NEG_B -> " jsr prog8_lib.neg_b"
Opcode.NEG_W -> " jsr prog8_lib.neg_w" Opcode.NEG_W -> " jsr prog8_lib.neg_w"
@ -343,9 +371,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.POW_F -> " jsr c64flt.pow_f" Opcode.POW_F -> " jsr c64flt.pow_f"
Opcode.INV_BYTE -> { Opcode.INV_BYTE -> {
""" """
lda ${(ESTACK_LO + 1).toHex()},x lda $ESTACK_LO_PLUS1_HEX,x
eor #255 eor #255
sta ${(ESTACK_LO + 1).toHex()},x sta $ESTACK_LO_PLUS1_HEX,x
""" """
} }
Opcode.INV_WORD -> " jsr prog8_lib.inv_word" Opcode.INV_WORD -> " jsr prog8_lib.inv_word"
@ -387,7 +415,7 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
val label = ins.callLabel ?: hexVal(ins) val label = ins.callLabel ?: hexVal(ins)
""" """
inx inx
lda ${(ESTACK_LO).toHex()},x lda $ESTACK_LO_HEX,x
beq $label beq $label
""" """
} }
@ -395,9 +423,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
val label = ins.callLabel ?: hexVal(ins) val label = ins.callLabel ?: hexVal(ins)
""" """
inx inx
lda ${(ESTACK_LO).toHex()},x lda $ESTACK_LO_HEX,x
beq $label beq $label
lda ${(ESTACK_HI).toHex()},x lda $ESTACK_HI_HEX,x
beq $label beq $label
""" """
} }
@ -405,7 +433,7 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
val label = ins.callLabel ?: hexVal(ins) val label = ins.callLabel ?: hexVal(ins)
""" """
inx inx
lda ${(ESTACK_LO).toHex()},x lda $ESTACK_LO_HEX,x
bne $label bne $label
""" """
} }
@ -413,9 +441,9 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
val label = ins.callLabel ?: hexVal(ins) val label = ins.callLabel ?: hexVal(ins)
""" """
inx inx
lda ${(ESTACK_LO).toHex()},x lda $ESTACK_LO_HEX,x
bne $label bne $label
lda ${(ESTACK_HI).toHex()},x lda $ESTACK_HI_HEX,x
bne $label bne $label
""" """
} }
@ -435,27 +463,27 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2w" Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2w"
Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw" Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw"
Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w" Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w"
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta ${(ESTACK_HI+1).toHex()},x" // clear the msb Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta $ESTACK_HI_PLUS1_HEX,x" // clear the msb
Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda ${(ESTACK_LO+1).toHex()},x | ${signExtendA("${(ESTACK_HI+1).toHex()},x")}" // sign extend the lsb Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda $ESTACK_LO_PLUS1_HEX,x | ${signExtendA("$ESTACK_HI_PLUS1_HEX,x")}" // sign extend the lsb
Opcode.MSB -> " lda ${(ESTACK_HI+1).toHex()},x | sta ${(ESTACK_LO+1).toHex()},x" Opcode.MSB -> " lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x"
Opcode.MKWORD -> " inx | lda ${ESTACK_LO.toHex()},x | sta ${(ESTACK_HI+1).toHex()},x " Opcode.MKWORD -> " inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x "
Opcode.ADD_UB, Opcode.ADD_B -> { // TODO inline better (pattern with more opcodes) Opcode.ADD_UB, Opcode.ADD_B -> { // TODO inline better (pattern with more opcodes)
""" """
lda ${(ESTACK_LO + 2).toHex()},x lda $ESTACK_LO_PLUS2_HEX,x
clc clc
adc ${(ESTACK_LO + 1).toHex()},x adc $ESTACK_LO_PLUS1_HEX,x
inx inx
sta ${(ESTACK_LO + 1).toHex()},x sta $ESTACK_LO_PLUS1_HEX,x
""" """
} }
Opcode.SUB_UB, Opcode.SUB_B -> { // TODO inline better (pattern with more opcodes) Opcode.SUB_UB, Opcode.SUB_B -> { // TODO inline better (pattern with more opcodes)
""" """
lda ${(ESTACK_LO + 2).toHex()},x lda $ESTACK_LO_PLUS2_HEX,x
sec sec
sbc ${(ESTACK_LO + 1).toHex()},x sbc $ESTACK_LO_PLUS1_HEX,x
inx inx
sta ${(ESTACK_LO + 1).toHex()},x sta $ESTACK_LO_PLUS1_HEX,x
""" """
} }
Opcode.ADD_W, Opcode.ADD_UW -> " jsr prog8_lib.add_w" Opcode.ADD_W, Opcode.ADD_UW -> " jsr prog8_lib.add_w"
@ -519,12 +547,12 @@ internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.Progra
Opcode.LESSEQ_W -> " jsr prog8_lib.lesseq_w" Opcode.LESSEQ_W -> " jsr prog8_lib.lesseq_w"
Opcode.LESSEQ_F -> " jsr c64flt.lesseq_f" Opcode.LESSEQ_F -> " jsr c64flt.lesseq_f"
Opcode.SHIFTEDL_BYTE -> " asl ${(ESTACK_LO+1).toHex()},x" Opcode.SHIFTEDL_BYTE -> " asl $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDL_WORD -> " asl ${(ESTACK_LO+1).toHex()},x | rol ${(ESTACK_HI+1).toHex()},x" Opcode.SHIFTEDL_WORD -> " asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x"
Opcode.SHIFTEDR_SBYTE -> " lda ${(ESTACK_LO+1).toHex()},x | asl a | ror ${(ESTACK_LO+1).toHex()},x" Opcode.SHIFTEDR_SBYTE -> " lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_UBYTE -> " lsr ${(ESTACK_LO+1).toHex()},x" Opcode.SHIFTEDR_UBYTE -> " lsr $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_SWORD -> " lda ${(ESTACK_HI+1).toHex()},x | asl a | ror ${(ESTACK_HI+1).toHex()},x | ror ${(ESTACK_LO+1).toHex()},x" Opcode.SHIFTEDR_SWORD -> " lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_UWORD -> " lsr ${(ESTACK_HI+1).toHex()},x | ror ${(ESTACK_LO+1).toHex()},x" Opcode.SHIFTEDR_UWORD -> " lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
else -> null else -> null
} }

View File

@ -1,21 +1,22 @@
package prog8.functions package prog8.functions
import prog8.ast.* import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.DirectMemoryRead import prog8.ast.expressions.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.VarDecl
import prog8.compiler.CompilerException import prog8.compiler.CompilerException
import kotlin.math.* import kotlin.math.*
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>) class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
class FunctionSignature(val pure: Boolean, // does it have side effects? class FunctionSignature(val pure: Boolean, // does it have side effects?
val parameters: List<BuiltinFunctionParam>, val parameters: List<BuiltinFunctionParam>,
val returntype: DataType?, val returntype: DataType?,
val constExpressionFunc: ((args: List<IExpression>, position: Position, program: Program) -> LiteralValue)? = null) val constExpressionFunc: ConstExpressionCaller? = null)
val BuiltinFunctions = mapOf( val BuiltinFunctions = mapOf(
@ -27,9 +28,9 @@ val BuiltinFunctions = mapOf(
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null), "lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null), "lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
// these few have a return value depending on the argument(s): // these few have a return value depending on the argument(s):
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.max()!! }}, // type depends on args "max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, _ -> collectionArgNeverConst(a, p) }, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.min()!! }}, // type depends on args "min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, _ -> collectionArgNeverConst(a, p) }, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.sum() }}, // type depends on args "sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, _ -> collectionArgNeverConst(a, p) }, // type depends on args
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument "abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length "len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
// normal functions follow: // normal functions follow:
@ -51,12 +52,12 @@ val BuiltinFunctions = mapOf(
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) }, "sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) }, "rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) }, "deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT, ::builtinAvg), "avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT) { a, p, _ -> collectionArgNeverConst(a, p) },
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) }, "round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) }, "floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) }, "ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.any { v -> v != 0.0} }}, "any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, _ -> collectionArgNeverConst(a, p) },
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.all { v -> v != 0.0} }}, "all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, _ -> collectionArgNeverConst(a, p) },
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }}, "lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}}, "msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FunctionSignature(true, listOf( "mkword" to FunctionSignature(true, listOf(
@ -112,12 +113,12 @@ val BuiltinFunctions = mapOf(
) )
fun builtinFunctionReturnType(function: String, args: List<IExpression>, program: Program): DataType? { fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): DataType? {
fun datatypeFromIterableArg(arglist: IExpression): DataType { fun datatypeFromIterableArg(arglist: Expression): DataType {
if(arglist is LiteralValue) { if(arglist is ReferenceLiteralValue) {
if(arglist.type== DataType.ARRAY_UB || arglist.type== DataType.ARRAY_UW || arglist.type== DataType.ARRAY_F) { if(arglist.type== DataType.ARRAY_UB || arglist.type== DataType.ARRAY_UW || arglist.type== DataType.ARRAY_F) {
val dt = arglist.arrayvalue!!.map {it.inferType(program)} val dt = arglist.array!!.map {it.inferType(program)}
if(dt.any { it!= DataType.UBYTE && it!= DataType.UWORD && it!= DataType.FLOAT}) { if(dt.any { it!= DataType.UBYTE && it!= DataType.UWORD && it!= DataType.FLOAT}) {
throw FatalAstException("fuction $function only accepts arraysize of numeric values") throw FatalAstException("fuction $function only accepts arraysize of numeric values")
} }
@ -127,8 +128,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
} }
} }
if(arglist is IdentifierReference) { if(arglist is IdentifierReference) {
val dt = arglist.inferType(program) return when(val dt = arglist.inferType(program)) {
return when(dt) {
in NumericDatatypes -> dt!! in NumericDatatypes -> dt!!
in StringDatatypes -> dt!! in StringDatatypes -> dt!!
in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!) in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!)
@ -145,8 +145,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
return when (function) { return when (function) {
"abs" -> { "abs" -> {
val dt = args.single().inferType(program) when(val dt = args.single().inferType(program)) {
when(dt) {
in ByteDatatypes -> DataType.UBYTE in ByteDatatypes -> DataType.UBYTE
in WordDatatypes -> DataType.UWORD in WordDatatypes -> DataType.UWORD
DataType.FLOAT -> DataType.FLOAT DataType.FLOAT -> DataType.FLOAT
@ -154,8 +153,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
} }
} }
"max", "min" -> { "max", "min" -> {
val dt = datatypeFromIterableArg(args.single()) when(val dt = datatypeFromIterableArg(args.single())) {
when(dt) {
in NumericDatatypes -> dt in NumericDatatypes -> dt
in StringDatatypes -> DataType.UBYTE in StringDatatypes -> DataType.UBYTE
in ArrayDatatypes -> ArrayElementTypes.getValue(dt) in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
@ -187,182 +185,98 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
class NotConstArgumentException: AstException("not a const argument to a built-in function") class NotConstArgumentException: AstException("not a const argument to a built-in function")
private fun oneDoubleArg(args: List<IExpression>, position: Position, program: Program, function: (arg: Double)->Number): LiteralValue { private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position) throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val float = getFloatArg(constval, args[0].position) val float = constval.number.toDouble()
return numericLiteral(function(float), args[0].position) return numericLiteral(function(float), args[0].position)
} }
private fun getFloatArg(v: LiteralValue, position: Position): Double { private fun oneDoubleArgOutputWord(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
val nv = v.asNumericValue ?: throw SyntaxError("numerical argument required", position)
return nv.toDouble()
}
private fun oneDoubleArgOutputWord(args: List<IExpression>, position: Position, program: Program, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position) throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val float = getFloatArg(constval, args[0].position) val float = constval.number.toDouble()
return LiteralValue(DataType.WORD, wordvalue = function(float).toInt(), position = args[0].position) return NumericLiteralValue(DataType.WORD, function(float).toInt(), args[0].position)
} }
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, program: Program, function: (arg: Int)->Number): LiteralValue { private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Number): NumericLiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position) throw SyntaxError("built-in function requires one integer argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD) if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD)
throw SyntaxError("built-in function requires one integer argument", position) throw SyntaxError("built-in function requires one integer argument", position)
val integer = constval.asNumericValue?.toInt()!! val integer = constval.number.toInt()
return numericLiteral(function(integer).toInt(), args[0].position) return numericLiteral(function(integer).toInt(), args[0].position)
} }
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position, private fun collectionArgNeverConst(args: List<Expression>, position: Position): NumericLiteralValue {
program: Program,
function: (arg: Collection<Double>)->Number): LiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position) throw SyntaxError("builtin function requires one non-scalar argument", position)
val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
val result = if(iterable.arrayvalue != null) { // max/min/sum etc only work on arrays and these are never considered to be const for these functions
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
if(null in constants)
throw NotConstArgumentException() throw NotConstArgumentException()
function(constants.map { it!!.toDouble() }).toDouble()
} else {
when(iterable.type) {
DataType.UBYTE, DataType.UWORD, DataType.FLOAT -> throw SyntaxError("function expects an iterable type", position)
else -> {
val heapId = iterable.heapId ?: throw FatalAstException("iterable value should be on the heap")
val array = program.heap.get(heapId).array ?: throw SyntaxError("function expects an iterable type", position)
function(array.map {
if(it.integer!=null)
it.integer.toDouble()
else
throw FatalAstException("cannot perform function over array that contains other values besides constant integers")
})
}
}
}
return numericLiteral(result, args[0].position)
} }
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position, private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
program: Program,
function: (arg: Collection<Double>)->Boolean): LiteralValue {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
val result = if(iterable.arrayvalue != null) {
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
if(null in constants)
throw NotConstArgumentException()
function(constants.map { it!!.toDouble() })
} else {
val array = program.heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array argument", position)
function(array.map {
if(it.integer!=null)
it.integer.toDouble()
else
throw FatalAstException("cannot perform function over array that contains other values besides constant integers")
})
}
return LiteralValue.fromBoolean(result, position)
}
private fun builtinAbs(args: List<IExpression>, position: Position, program: Program): LiteralValue {
// 1 arg, type = float or int, result type= isSameAs as argument type // 1 arg, type = float or int, result type= isSameAs as argument type
if(args.size!=1) if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position) throw SyntaxError("abs requires one numeric argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val number = constval.asNumericValue return when (constval.type) {
return when (number) { in IntegerDatatypes -> numericLiteral(abs(constval.number.toInt()), args[0].position)
is Int, is Byte, is Short -> numericLiteral(abs(number.toInt()), args[0].position) DataType.FLOAT -> numericLiteral(abs(constval.number.toDouble()), args[0].position)
is Double -> numericLiteral(abs(number.toDouble()), args[0].position)
else -> throw SyntaxError("abs requires one numeric argument", position) else -> throw SyntaxError("abs requires one numeric argument", position)
} }
} }
private fun builtinAvg(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if(args.size!=1)
throw SyntaxError("avg requires array argument", position)
val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
val result = if(iterable.arrayvalue!=null) {
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
if (null in constants)
throw NotConstArgumentException()
(constants.map { it!!.toDouble() }).average()
}
else {
val heapId = iterable.heapId!!
val integerarray = program.heap.get(heapId).array
if(integerarray!=null) {
if (integerarray.all { it.integer != null }) {
integerarray.map { it.integer!! }.average()
} else {
throw ExpressionError("cannot avg() over array that does not only contain constant numerical values", position)
}
} else {
val doublearray = program.heap.get(heapId).doubleArray
doublearray?.average() ?: throw SyntaxError("avg requires array argument", position)
}
}
return numericLiteral(result, args[0].position)
}
private fun builtinStrlen(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("strlen requires one argument", position) throw SyntaxError("strlen requires one argument", position)
val argument = args[0].constValue(program) ?: throw NotConstArgumentException() val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
if(argument.type !in StringDatatypes) if(argument.type !in StringDatatypes)
throw SyntaxError("strlen must have string argument", position) throw SyntaxError("strlen must have string argument", position)
val string = argument.strvalue!!
val zeroIdx = string.indexOf('\u0000') throw NotConstArgumentException() // this function is not considering the string argument a constant
return if(zeroIdx>=0)
LiteralValue.optimalInteger(zeroIdx, position=position)
else
LiteralValue.optimalInteger(string.length, position=position)
} }
private fun builtinLen(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE. // note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1) if(args.size!=1)
throw SyntaxError("len requires one argument", position) throw SyntaxError("len requires one argument", position)
var argument = args[0].constValue(program) val constArg = args[0].constValue(program)
if(argument==null) { if(constArg!=null)
throw SyntaxError("len of weird argument ${args[0]}", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace) val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
val arraySize = directMemVar?.arraysize?.size() var arraySize = directMemVar?.arraysize?.size()
if(arraySize != null) if(arraySize != null)
return LiteralValue.optimalInteger(arraySize, position) return NumericLiteralValue.optimalInteger(arraySize, position)
if(args[0] !is IdentifierReference) if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position) throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetStatement(program.namespace) val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
val argValue = (target as? VarDecl)?.value
argument = argValue?.constValue(program) return when(target.datatype) {
?: throw NotConstArgumentException()
}
return when(argument.type) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize arraySize = target.arraysize!!.size()!!
if(arraySize>256) if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${argument.position}") throw CompilerException("array length exceeds byte limit ${target.position}")
LiteralValue.optimalInteger(arraySize, args[0].position) NumericLiteralValue.optimalInteger(arraySize, args[0].position)
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize arraySize = target.arraysize!!.size()!!
if(arraySize>256) if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${argument.position}") throw CompilerException("array length exceeds byte limit ${target.position}")
LiteralValue.optimalInteger(arraySize, args[0].position) NumericLiteralValue.optimalInteger(arraySize, args[0].position)
} }
in StringDatatypes -> { in StringDatatypes -> {
val str = argument.strvalue!! val refLv = target.value as ReferenceLiteralValue
if(str.length>255) if(refLv.str!!.length>255)
throw CompilerException("string length exceeds byte limit ${argument.position}") throw CompilerException("string length exceeds byte limit ${refLv.position}")
LiteralValue.optimalInteger(str.length, args[0].position) NumericLiteralValue.optimalInteger(refLv.str.length, args[0].position)
} }
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position) in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
else -> throw CompilerException("weird datatype") else -> throw CompilerException("weird datatype")
@ -370,80 +284,80 @@ private fun builtinLen(args: List<IExpression>, position: Position, program: Pro
} }
private fun builtinMkword(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 2) if (args.size != 2)
throw SyntaxError("mkword requires lsb and msb arguments", position) throw SyntaxError("mkword requires lsb and msb arguments", position)
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException() val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException() val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsb.asIntegerValue!! shl 8) or constLsb.asIntegerValue!! val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
return LiteralValue(DataType.UWORD, wordvalue = result, position = position) return NumericLiteralValue(DataType.UWORD, result, position)
} }
private fun builtinSin8(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin8 requires one argument", position) throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.BYTE, bytevalue = (127.0 * sin(rad)).toShort(), position = position) return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toShort(), position)
} }
private fun builtinSin8u(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin8u requires one argument", position) throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * sin(rad)).toShort(), position = position) return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort(), position)
} }
private fun builtinCos8(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos8 requires one argument", position) throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.BYTE, bytevalue = (127.0 * cos(rad)).toShort(), position = position) return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toShort(), position)
} }
private fun builtinCos8u(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos8u requires one argument", position) throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * cos(rad)).toShort(), position = position) return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort(), position)
} }
private fun builtinSin16(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin16 requires one argument", position) throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.WORD, wordvalue = (32767.0 * sin(rad)).toInt(), position = position) return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
} }
private fun builtinSin16u(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin16u requires one argument", position) throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * sin(rad)).toInt(), position = position) return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
} }
private fun builtinCos16(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos16 requires one argument", position) throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.WORD, wordvalue = (32767.0 * cos(rad)).toInt(), position = position) return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
} }
private fun builtinCos16u(args: List<IExpression>, position: Position, program: Program): LiteralValue { private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos16u requires one argument", position) throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.number.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * cos(rad)).toInt(), position = position) return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
} }
private fun numericLiteral(value: Number, position: Position): LiteralValue { private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
val floatNum=value.toDouble() val floatNum=value.toDouble()
val tweakedValue: Number = val tweakedValue: Number =
if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535)) if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
@ -452,11 +366,11 @@ private fun numericLiteral(value: Number, position: Position): LiteralValue {
floatNum floatNum
return when(tweakedValue) { return when(tweakedValue) {
is Int -> LiteralValue.optimalNumeric(value.toInt(), position) is Int -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
is Short -> LiteralValue.optimalNumeric(value.toInt(), position) is Short -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
is Byte -> LiteralValue(DataType.UBYTE, bytevalue = value.toShort(), position = position) is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
is Double -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position) is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
is Float -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position) is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
else -> throw FatalAstException("invalid number type ${value::class}") else -> throw FatalAstException("invalid number type ${value::class}")
} }
} }

View File

@ -1,6 +1,10 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.* import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel import prog8.ast.base.ParentSentinel
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.base.initvarsSubName import prog8.ast.base.initvarsSubName
@ -17,7 +21,8 @@ class CallGraph(private val program: Program): IAstVisitor {
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() } val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() } val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() } val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
val usedSymbols = mutableSetOf<IStatement>() // TODO add dataflow graph: what statements use what variables
val usedSymbols = mutableSetOf<Statement>()
init { init {
visit(program) visit(program)
@ -92,11 +97,11 @@ class CallGraph(private val program: Program): IAstVisitor {
super.visit(identifier) super.visit(identifier)
} }
private fun addNodeAndParentScopes(stmt: IStatement) { private fun addNodeAndParentScopes(stmt: Statement) {
usedSymbols.add(stmt) usedSymbols.add(stmt)
var node: Node=stmt var node: Node=stmt
do { do {
if(node is INameScope && node is IStatement) { if(node is INameScope && node is Statement) {
usedSymbols.add(node) usedSymbols.add(node)
} }
node=node.parent node=node.parent
@ -104,7 +109,12 @@ class CallGraph(private val program: Program): IAstVisitor {
} }
override fun visit(subroutine: Subroutine) { override fun visit(subroutine: Subroutine) {
if((subroutine.name=="start" && subroutine.definingScope().name=="main") val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq")
)
if(Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|| subroutine.name== initvarsSubName || subroutine.definingModule().isLibraryModule) { || subroutine.name== initvarsSubName || subroutine.definingModule().isLibraryModule) {
// make sure the entrypoint is mentioned in the used symbols // make sure the entrypoint is mentioned in the used symbols
addNodeAndParentScopes(subroutine) addNodeAndParentScopes(subroutine)
@ -113,10 +123,14 @@ class CallGraph(private val program: Program): IAstVisitor {
} }
override fun visit(decl: VarDecl) { override fun visit(decl: VarDecl) {
if(decl.autoGenerated || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) { if(decl.autogeneratedDontRemove || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
// make sure autogenerated vardecls are in the used symbols // make sure autogenerated vardecls are in the used symbols
addNodeAndParentScopes(decl) addNodeAndParentScopes(decl)
} }
if(decl.datatype==DataType.STRUCT)
addNodeAndParentScopes(decl)
super.visit(decl) super.visit(decl)
} }
@ -153,6 +167,11 @@ class CallGraph(private val program: Program): IAstVisitor {
super.visit(jump) super.visit(jump)
} }
override fun visit(structDecl: StructDecl) {
usedSymbols.add(structDecl)
usedSymbols.addAll(structDecl.statements)
}
override fun visit(inlineAssembly: InlineAssembly) { override fun visit(inlineAssembly: InlineAssembly) {
// parse inline asm for subroutine calls (jmp, jsr) // parse inline asm for subroutine calls (jmp, jsr)
val scope = inlineAssembly.definingScope() val scope = inlineAssembly.definingScope()
@ -160,7 +179,7 @@ class CallGraph(private val program: Program): IAstVisitor {
super.visit(inlineAssembly) super.visit(inlineAssembly)
} }
private fun scanAssemblyCode(asm: String, context: IStatement, scope: INameScope) { private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE) val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
asm.lines().forEach { line -> asm.lines().forEach { line ->
@ -186,6 +205,7 @@ class CallGraph(private val program: Program): IAstVisitor {
if (matches2 != null) { if (matches2 != null) {
val target= matches2.groups[2]?.value val target= matches2.groups[2]?.value
if (target != null && (target[0].isLetter() || target[0] == '_')) { if (target != null && (target[0].isLetter() || target[0] == '_')) {
if(target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context) val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) { if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node) subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
@ -197,3 +217,4 @@ class CallGraph(private val program: Program): IAstVisitor {
} }
} }
} }
}

View File

@ -1,17 +1,14 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.* import prog8.ast.base.*
import prog8.ast.base.DataType import prog8.ast.expressions.Expression
import prog8.ast.base.ExpressionError import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.expressions.LiteralValue
import kotlin.math.pow import kotlin.math.pow
class ConstExprEvaluator { class ConstExprEvaluator {
fun evaluate(left: LiteralValue, operator: String, right: LiteralValue): IExpression { fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression {
return when(operator) { return when(operator) {
"+" -> plus(left, right) "+" -> plus(left, right)
"-" -> minus(left, right) "-" -> minus(left, right)
@ -25,200 +22,188 @@ class ConstExprEvaluator {
"and" -> logicaland(left, right) "and" -> logicaland(left, right)
"or" -> logicalor(left, right) "or" -> logicalor(left, right)
"xor" -> logicalxor(left, right) "xor" -> logicalxor(left, right)
"<" -> LiteralValue.fromBoolean(left < right, left.position) "<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
">" -> LiteralValue.fromBoolean(left > right, left.position) ">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
"<=" -> LiteralValue.fromBoolean(left <= right, left.position) "<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
">=" -> LiteralValue.fromBoolean(left >= right, left.position) ">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
"==" -> LiteralValue.fromBoolean(left == right, left.position) "==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
"!=" -> LiteralValue.fromBoolean(left != right, left.position) "!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
"<<" -> shiftedleft(left, right) "<<" -> shiftedleft(left, right)
">>" -> shiftedright(left, right) ">>" -> shiftedright(left, right)
else -> throw FatalAstException("const evaluation for invalid operator $operator") else -> throw FatalAstException("const evaluation for invalid operator $operator")
} }
} }
private fun shiftedright(left: LiteralValue, amount: LiteralValue): IExpression { private fun shiftedright(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
if(left.asIntegerValue==null || amount.asIntegerValue==null) if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left >> $amount", left.position) throw ExpressionError("cannot compute $left >> $amount", left.position)
val result = val result =
if(left.type== DataType.UBYTE || left.type== DataType.UWORD) if(left.type== DataType.UBYTE || left.type== DataType.UWORD)
left.asIntegerValue.ushr(amount.asIntegerValue) left.number.toInt().ushr(amount.number.toInt())
else else
left.asIntegerValue.shr(amount.asIntegerValue) left.number.toInt().shr(amount.number.toInt())
return LiteralValue.fromNumber(result, left.type, left.position) return NumericLiteralValue(left.type, result, left.position)
} }
private fun shiftedleft(left: LiteralValue, amount: LiteralValue): IExpression { private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
if(left.asIntegerValue==null || amount.asIntegerValue==null) if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left << $amount", left.position) throw ExpressionError("cannot compute $left << $amount", left.position)
val result = left.asIntegerValue.shl(amount.asIntegerValue) val result = left.number.toInt().shl(amount.number.toInt())
return LiteralValue.fromNumber(result, left.type, left.position) return NumericLiteralValue(left.type, result, left.position)
} }
private fun logicalxor(left: LiteralValue, right: LiteralValue): LiteralValue { private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-bitxor $right" val error = "cannot compute $left locical-bitxor $right"
return when { return when {
left.asIntegerValue!=null -> when { left.type in IntegerDatatypes -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean((left.asIntegerValue != 0) xor (right.asIntegerValue != 0), left.position) right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean((left.asIntegerValue != 0) xor (right.floatvalue != 0.0), left.position) right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.floatvalue!=null -> when { left.type == DataType.FLOAT -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean((left.floatvalue != 0.0) xor (right.asIntegerValue != 0), left.position) right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean((left.floatvalue != 0.0) xor (right.floatvalue != 0.0), left.position) right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun logicalor(left: LiteralValue, right: LiteralValue): LiteralValue { private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-or $right" val error = "cannot compute $left locical-or $right"
return when { return when {
left.asIntegerValue!=null -> when { left.type in IntegerDatatypes -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 || right.asIntegerValue != 0, left.position) right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 || right.floatvalue != 0.0, left.position) right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.floatvalue!=null -> when { left.type == DataType.FLOAT -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 || right.asIntegerValue != 0, left.position) right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 || right.floatvalue != 0.0, left.position) right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun logicaland(left: LiteralValue, right: LiteralValue): LiteralValue { private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-and $right" val error = "cannot compute $left locical-and $right"
return when { return when {
left.asIntegerValue!=null -> when { left.type in IntegerDatatypes -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 && right.asIntegerValue != 0, left.position) right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 && right.floatvalue != 0.0, left.position) right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.floatvalue!=null -> when { left.type == DataType.FLOAT -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 && right.asIntegerValue != 0, left.position) right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 && right.floatvalue != 0.0, left.position) right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun bitwisexor(left: LiteralValue, right: LiteralValue): LiteralValue { private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) { if(left.type== DataType.UBYTE) {
if(right.asIntegerValue!=null) { if(right.type in IntegerDatatypes) {
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() xor (right.asIntegerValue and 255)).toShort(), position = left.position) return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toShort(), left.position)
} }
} else if(left.type== DataType.UWORD) { } else if(left.type== DataType.UWORD) {
if(right.asIntegerValue!=null) { if(right.type in IntegerDatatypes) {
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! xor right.asIntegerValue, position = left.position) return NumericLiteralValue(DataType.UWORD, left.number.toInt() xor right.number.toInt(), left.position)
} }
} }
throw ExpressionError("cannot calculate $left ^ $right", left.position) throw ExpressionError("cannot calculate $left ^ $right", left.position)
} }
private fun bitwiseor(left: LiteralValue, right: LiteralValue): LiteralValue { private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) { if(left.type== DataType.UBYTE) {
if(right.asIntegerValue!=null) { if(right.type in IntegerDatatypes) {
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position) return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
} }
} else if(left.type== DataType.UWORD) { } else if(left.type== DataType.UWORD) {
if(right.asIntegerValue!=null) { if(right.type in IntegerDatatypes) {
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position) return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
} }
} }
throw ExpressionError("cannot calculate $left | $right", left.position) throw ExpressionError("cannot calculate $left | $right", left.position)
} }
private fun bitwiseand(left: LiteralValue, right: LiteralValue): LiteralValue { private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) { if(left.type== DataType.UBYTE) {
if(right.asIntegerValue!=null) { if(right.type in IntegerDatatypes) {
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position) return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
} }
} else if(left.type== DataType.UWORD) { } else if(left.type== DataType.UWORD) {
if(right.asIntegerValue!=null) { if(right.type in IntegerDatatypes) {
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position) return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), left.position)
} }
} }
throw ExpressionError("cannot calculate $left & $right", left.position) throw ExpressionError("cannot calculate $left & $right", left.position)
} }
private fun power(left: LiteralValue, right: LiteralValue): LiteralValue { private fun power(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot calculate $left ** $right" val error = "cannot calculate $left ** $right"
return when { return when {
left.asIntegerValue!=null -> when { left.type in IntegerDatatypes -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue.toDouble().pow(right.asIntegerValue), left.position) right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue.toDouble().pow(right.floatvalue), position = left.position) right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.floatvalue!=null -> when { left.type == DataType.FLOAT -> when {
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue.pow(right.asIntegerValue), position = left.position) right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue.pow(right.floatvalue), position = left.position) right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun plus(left: LiteralValue, right: LiteralValue): LiteralValue { private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot add $left and $right" val error = "cannot add $left and $right"
return when { return when {
left.asIntegerValue!=null -> when { left.type in IntegerDatatypes -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue + right.asIntegerValue, left.position) right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue + right.floatvalue, position = left.position) right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.floatvalue!=null -> when { left.type == DataType.FLOAT -> when {
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue + right.asIntegerValue, position = left.position) right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue + right.floatvalue, position = left.position) right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.isString -> when {
right.isString -> {
val newStr = left.strvalue!! + right.strvalue!!
if(newStr.length > 255) throw ExpressionError("string too long", left.position)
LiteralValue(DataType.STR, strvalue = newStr, position = left.position)
}
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun minus(left: LiteralValue, right: LiteralValue): LiteralValue { private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot subtract $left and $right" val error = "cannot subtract $left and $right"
return when { return when {
left.asIntegerValue!=null -> when { left.type in IntegerDatatypes -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue - right.asIntegerValue, left.position) right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue - right.floatvalue, position = left.position) right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.floatvalue!=null -> when { left.type == DataType.FLOAT -> when {
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue - right.asIntegerValue, position = left.position) right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue - right.floatvalue, position = left.position) right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun multiply(left: LiteralValue, right: LiteralValue): LiteralValue { private fun multiply(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot multiply ${left.type} and ${right.type}" val error = "cannot multiply ${left.type} and ${right.type}"
return when { return when {
left.asIntegerValue!=null -> when { left.type in IntegerDatatypes -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue * right.asIntegerValue, left.position) right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue * right.floatvalue, position = left.position) right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
right.isString -> {
if(right.strvalue!!.length * left.asIntegerValue > 255) throw ExpressionError("string too long", left.position)
LiteralValue(DataType.STR, strvalue = right.strvalue.repeat(left.asIntegerValue), position = left.position)
}
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.floatvalue!=null -> when { left.type == DataType.FLOAT -> when {
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.asIntegerValue, position = left.position) right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.floatvalue, position = left.position) right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
@ -228,29 +213,29 @@ class ConstExprEvaluator {
private fun divideByZeroError(pos: Position): Unit = private fun divideByZeroError(pos: Position): Unit =
throw ExpressionError("division by zero", pos) throw ExpressionError("division by zero", pos)
private fun divide(left: LiteralValue, right: LiteralValue): LiteralValue { private fun divide(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot divide $left by $right" val error = "cannot divide $left by $right"
return when { return when {
left.asIntegerValue!=null -> when { left.type in IntegerDatatypes -> when {
right.asIntegerValue!=null -> { right.type in IntegerDatatypes -> {
if(right.asIntegerValue==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.asIntegerValue / right.asIntegerValue val result: Int = left.number.toInt() / right.number.toInt()
LiteralValue.optimalNumeric(result, left.position) NumericLiteralValue.optimalNumeric(result, left.position)
} }
right.floatvalue!=null -> { right.type == DataType.FLOAT -> {
if(right.floatvalue==0.0) divideByZeroError(right.position) if(right.number.toDouble()==0.0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue / right.floatvalue, position = left.position) NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.floatvalue!=null -> when { left.type == DataType.FLOAT -> when {
right.asIntegerValue!=null -> { right.type in IntegerDatatypes -> {
if(right.asIntegerValue==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue / right.asIntegerValue, position = left.position) NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
} }
right.floatvalue!=null -> { right.type == DataType.FLOAT -> {
if(right.floatvalue==0.0) divideByZeroError(right.position) if(right.number.toDouble()==0.0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue / right.floatvalue, position = left.position) NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
@ -258,28 +243,28 @@ class ConstExprEvaluator {
} }
} }
private fun remainder(left: LiteralValue, right: LiteralValue): LiteralValue { private fun remainder(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute remainder of $left by $right" val error = "cannot compute remainder of $left by $right"
return when { return when {
left.asIntegerValue!=null -> when { left.type in IntegerDatatypes -> when {
right.asIntegerValue!=null -> { right.type in IntegerDatatypes -> {
if(right.asIntegerValue==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
LiteralValue.optimalNumeric(left.asIntegerValue.toDouble() % right.asIntegerValue.toDouble(), left.position) NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
} }
right.floatvalue!=null -> { right.type == DataType.FLOAT -> {
if(right.floatvalue==0.0) divideByZeroError(right.position) if(right.number.toDouble()==0.0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue % right.floatvalue, position = left.position) NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.floatvalue!=null -> when { left.type == DataType.FLOAT -> when {
right.asIntegerValue!=null -> { right.type in IntegerDatatypes -> {
if(right.asIntegerValue==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue % right.asIntegerValue, position = left.position) NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
} }
right.floatvalue!=null -> { right.type == DataType.FLOAT -> {
if(right.floatvalue==0.0) divideByZeroError(right.position) if(right.number.toDouble()==0.0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue % right.floatvalue, position = left.position) NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }

View File

@ -1,21 +1,21 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.* import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import kotlin.math.floor import kotlin.math.floor
class ConstantFolding(private val program: Program) : IAstModifyingVisitor { class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0 var optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf() var errors : MutableList<AstException> = mutableListOf()
private val reportedErrorMessages = mutableSetOf<String>() private val reportedErrorMessages = mutableSetOf<String>()
fun addError(x: AstException) { fun addError(x: AstException) {
@ -26,30 +26,31 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} }
} }
override fun visit(decl: VarDecl): IStatement { override fun visit(decl: VarDecl): Statement {
// the initializer value can't refer to the variable itself (recursive definition) // the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) { // TODO: use call tree for this?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
errors.add(ExpressionError("recursive var declaration", decl.position)) errors.add(ExpressionError("recursive var declaration", decl.position))
return decl return decl
} }
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) { if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
val litval = decl.value as? LiteralValue val refLv = decl.value as? ReferenceLiteralValue
if(litval!=null && litval.isArray && litval.heapId!=null) if(refLv!=null && refLv.isArray && refLv.heapId!=null)
fixupArrayTypeOnHeap(decl, litval) fixupArrayTypeOnHeap(decl, refLv)
if(decl.isArray){ if(decl.isArray){
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size // for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
if(decl.arraysize==null) { if(decl.arraysize==null) {
val arrayval = (decl.value as? LiteralValue)?.arrayvalue val arrayval = (decl.value as? ReferenceLiteralValue)?.array
if(arrayval!=null) { if(arrayval!=null) {
decl.arraysize = ArrayIndex(LiteralValue.optimalInteger(arrayval.size, decl.position), decl.position) decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.size, decl.position), decl.position)
optimizationsDone++ optimizationsDone++
} }
} }
else if(decl.arraysize?.size()==null) { else if(decl.arraysize?.size()==null) {
val size = decl.arraysize!!.index.accept(this) val size = decl.arraysize!!.index.accept(this)
if(size is LiteralValue) { if(size is NumericLiteralValue) {
decl.arraysize = ArrayIndex(size, decl.position) decl.arraysize = ArrayIndex(size, decl.position)
optimizationsDone++ optimizationsDone++
} }
@ -59,14 +60,22 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
when(decl.datatype) { when(decl.datatype) {
DataType.FLOAT -> { DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats // vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) { if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = LiteralValue(DataType.FLOAT, floatvalue = litval.asNumericValue!!.toDouble(), position = litval.position) val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
decl.value = newValue decl.value = newValue
optimizationsDone++ optimizationsDone++
return decl return super.visit(decl)
} }
} }
in StringDatatypes -> {
// nothing to do for strings
}
DataType.STRUCT -> {
// struct defintions don't have anything else in them
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) { if(rangeExpr!=null) {
// convert the initializer range expression to an actual array (will be put on heap later) // convert the initializer range expression to an actual array (will be put on heap later)
@ -77,69 +86,66 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
if(constRange!=null) { if(constRange!=null) {
val eltType = rangeExpr.inferType(program)!! val eltType = rangeExpr.inferType(program)!!
if(eltType in ByteDatatypes) { if(eltType in ByteDatatypes) {
decl.value = LiteralValue(decl.datatype, decl.value = ReferenceLiteralValue(decl.datatype,
arrayvalue = constRange.map { LiteralValue(eltType, bytevalue = it.toShort(), position = decl.value!!.position) } array = constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }
.toTypedArray(), position = decl.value!!.position) .toTypedArray(), position = decl.value!!.position)
} else { } else {
decl.value = LiteralValue(decl.datatype, decl.value = ReferenceLiteralValue(decl.datatype,
arrayvalue = constRange.map { LiteralValue(eltType, wordvalue = it, position = decl.value!!.position) } array = constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }
.toTypedArray(), position = decl.value!!.position) .toTypedArray(), position = decl.value!!.position)
} }
decl.value!!.linkParents(decl) decl.value!!.linkParents(decl)
optimizationsDone++ optimizationsDone++
return decl return super.visit(decl)
} }
} }
if(litval?.type== DataType.FLOAT) if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.add(ExpressionError("arraysize requires only integers here", litval.position)) errors.add(ExpressionError("arraysize requires only integers here", numericLv.position))
val size = decl.arraysize?.size() ?: return decl val size = decl.arraysize?.size() ?: return decl
if ((litval==null || !litval.isArray) && rangeExpr==null) { if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize. // arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = if (litval == null) 0 else litval.asIntegerValue ?: 0 val fillvalue = numericLv.number.toInt()
when(decl.datatype){ when(decl.datatype){
DataType.ARRAY_UB -> { DataType.ARRAY_UB -> {
if(fillvalue !in 0..255) if(fillvalue !in 0..255)
errors.add(ExpressionError("ubyte value overflow", litval?.position errors.add(ExpressionError("ubyte value overflow", numericLv.position))
?: decl.position))
} }
DataType.ARRAY_B -> { DataType.ARRAY_B -> {
if(fillvalue !in -128..127) if(fillvalue !in -128..127)
errors.add(ExpressionError("byte value overflow", litval?.position errors.add(ExpressionError("byte value overflow", numericLv.position))
?: decl.position))
} }
DataType.ARRAY_UW -> { DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535) if(fillvalue !in 0..65535)
errors.add(ExpressionError("uword value overflow", litval?.position errors.add(ExpressionError("uword value overflow", numericLv.position))
?: decl.position))
} }
DataType.ARRAY_W -> { DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767) if(fillvalue !in -32768..32767)
errors.add(ExpressionError("word value overflow", litval?.position errors.add(ExpressionError("word value overflow", numericLv.position))
?: decl.position))
} }
else -> {} else -> {}
} }
val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) }) val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) })
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval?.position decl.value = ReferenceLiteralValue(decl.datatype, initHeapId = heapId, position = numericLv.position)
?: decl.position)
optimizationsDone++ optimizationsDone++
return decl return super.visit(decl)
} }
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
val size = decl.arraysize?.size() ?: return decl val size = decl.arraysize?.size() ?: return decl
if (litval==null || !litval.isArray) { val litval = decl.value as? NumericLiteralValue
// arraysize initializer is empty or a single int, and we know the size; create the arraysize. if(litval==null) {
val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0 // there's no initialization value, but the size is known, so we're ok.
return super.visit(decl)
} else {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < FLOAT_MAX_NEGATIVE || fillvalue > FLOAT_MAX_POSITIVE) if (fillvalue < FLOAT_MAX_NEGATIVE || fillvalue > FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("float value overflow", litval?.position errors.add(ExpressionError("float value overflow", litval.position))
?: decl.position))
else { else {
val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue }) val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue })
decl.value = LiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval?.position decl.value = ReferenceLiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval.position)
?: decl.position)
optimizationsDone++ optimizationsDone++
return decl return super.visit(decl)
} }
} }
} }
@ -152,7 +158,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
return super.visit(decl) return super.visit(decl)
} }
private fun fixupArrayTypeOnHeap(decl: VarDecl, litval: LiteralValue) { private fun fixupArrayTypeOnHeap(decl: VarDecl, litval: ReferenceLiteralValue) {
// fix the type of the array value that's on the heap, to match the vardecl. // fix the type of the array value that's on the heap, to match the vardecl.
// notice that checking the bounds of the actual values is not done here, but in the AstChecker later. // notice that checking the bounds of the actual values is not done here, but in the AstChecker later.
@ -164,7 +170,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
if(array.array!=null) { if(array.array!=null) {
program.heap.update(heapId, HeapValues.HeapValue(decl.datatype, null, array.array, null)) program.heap.update(heapId, HeapValues.HeapValue(decl.datatype, null, array.array, null))
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval.position) decl.value = ReferenceLiteralValue(decl.datatype, initHeapId = heapId, position = litval.position)
} }
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
@ -172,9 +178,12 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
// convert a non-float array to floats // convert a non-float array to floats
val doubleArray = array.array.map { it.integer!!.toDouble() }.toDoubleArray() val doubleArray = array.array.map { it.integer!!.toDouble() }.toDoubleArray()
program.heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray)) program.heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray))
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval.position) decl.value = ReferenceLiteralValue(decl.datatype, initHeapId = heapId, position = litval.position)
} }
} }
DataType.STRUCT -> {
// leave it alone for structs.
}
else -> throw FatalAstException("invalid array vardecl type ${decl.datatype}") else -> throw FatalAstException("invalid array vardecl type ${decl.datatype}")
} }
} }
@ -182,22 +191,25 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
/** /**
* replace identifiers that refer to const value, with the value itself (if it's a simple type) * replace identifiers that refer to const value, with the value itself (if it's a simple type)
*/ */
override fun visit(identifier: IdentifierReference): IExpression { override fun visit(identifier: IdentifierReference): Expression {
return try { return try {
val cval = identifier.constValue(program) ?: return identifier val cval = identifier.constValue(program) ?: return identifier
return if(cval.isNumeric) { return when {
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, null, cval.arrayvalue, position = identifier.position) cval.type in NumericDatatypes -> {
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
copy.parent = identifier.parent copy.parent = identifier.parent
copy copy
} else }
identifier cval.type in PassByReferenceDatatypes -> TODO("ref type $identifier")
else -> identifier
}
} catch (ax: AstException) { } catch (ax: AstException) {
addError(ax) addError(ax)
identifier identifier
} }
} }
override fun visit(functionCall: FunctionCall): IExpression { override fun visit(functionCall: FunctionCall): Expression {
return try { return try {
super.visit(functionCall) super.visit(functionCall)
typeCastConstArguments(functionCall) typeCastConstArguments(functionCall)
@ -208,7 +220,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} }
} }
override fun visit(functionCallStatement: FunctionCallStatement): IStatement { override fun visit(functionCallStatement: FunctionCallStatement): Statement {
super.visit(functionCallStatement) super.visit(functionCallStatement)
typeCastConstArguments(functionCallStatement) typeCastConstArguments(functionCallStatement)
return functionCallStatement return functionCallStatement
@ -232,7 +244,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} }
} }
override fun visit(memread: DirectMemoryRead): IExpression { override fun visit(memread: DirectMemoryRead): Expression {
// @( &thing ) --> thing // @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null) if(addrOf!=null)
@ -245,43 +257,36 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
* Compile-time constant sub expressions will be evaluated on the spot. * Compile-time constant sub expressions will be evaluated on the spot.
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5 * For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
*/ */
override fun visit(expr: PrefixExpression): IExpression { override fun visit(expr: PrefixExpression): Expression {
return try { return try {
super.visit(expr) super.visit(expr)
val subexpr = expr.expression val subexpr = expr.expression
if (subexpr is LiteralValue) { if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true) // accept prefixed literal values (such as -3, not true)
return when { return when {
expr.operator == "+" -> subexpr expr.operator == "+" -> subexpr
expr.operator == "-" -> when { expr.operator == "-" -> when {
subexpr.asIntegerValue!= null -> { subexpr.type in IntegerDatatypes -> {
optimizationsDone++ optimizationsDone++
LiteralValue.optimalNumeric(-subexpr.asIntegerValue, subexpr.position) NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
} }
subexpr.floatvalue != null -> { subexpr.type == DataType.FLOAT -> {
optimizationsDone++ optimizationsDone++
LiteralValue(DataType.FLOAT, floatvalue = -subexpr.floatvalue, position = subexpr.position) NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position)
} }
else -> throw ExpressionError("can only take negative of int or float", subexpr.position) else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
} }
expr.operator == "~" -> when { expr.operator == "~" -> when {
subexpr.asIntegerValue != null -> { subexpr.type in IntegerDatatypes -> {
optimizationsDone++ optimizationsDone++
LiteralValue.optimalNumeric(subexpr.asIntegerValue.inv(), subexpr.position) NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
} }
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position) else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
} }
expr.operator == "not" -> when { expr.operator == "not" -> {
subexpr.asIntegerValue != null -> {
optimizationsDone++ optimizationsDone++
LiteralValue.fromBoolean(subexpr.asIntegerValue == 0, subexpr.position) NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
}
subexpr.floatvalue != null -> {
optimizationsDone++
LiteralValue.fromBoolean(subexpr.floatvalue == 0.0, subexpr.position)
}
else -> throw ExpressionError("can not take logical not of $subexpr", subexpr.position)
} }
else -> throw ExpressionError(expr.operator, subexpr.position) else -> throw ExpressionError(expr.operator, subexpr.position)
} }
@ -310,9 +315,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
* (X / c1) * c2 -> X / (c2/c1) * (X / c1) * c2 -> X / (c2/c1)
* (X + c1) - c2 -> X + (c1-c2) * (X + c1) - c2 -> X + (c1-c2)
*/ */
override fun visit(expr: BinaryExpression): IExpression { override fun visit(expr: BinaryExpression): Expression {
return try { return try {
super.visit(expr) super.visit(expr)
if(expr.left is ReferenceLiteralValue || expr.right is ReferenceLiteralValue)
TODO("binexpr with reference litval")
val leftconst = expr.left.constValue(program) val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program) val rightconst = expr.right.constValue(program)
@ -333,12 +342,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} }
// const fold when both operands are a const // const fold when both operands are a const
val evaluator = ConstExprEvaluator()
return when { return when {
leftconst != null && rightconst != null -> { leftconst != null && rightconst != null -> {
optimizationsDone++ optimizationsDone++
val evaluator = ConstExprEvaluator()
evaluator.evaluate(leftconst, expr.operator, rightconst) evaluator.evaluate(leftconst, expr.operator, rightconst)
} }
else -> expr else -> expr
} }
} catch (ax: AstException) { } catch (ax: AstException) {
@ -352,7 +362,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
leftIsConst: Boolean, leftIsConst: Boolean,
rightIsConst: Boolean, rightIsConst: Boolean,
subleftIsConst: Boolean, subleftIsConst: Boolean,
subrightIsConst: Boolean): IExpression subrightIsConst: Boolean): Expression
{ {
// @todo this implements only a small set of possible reorderings for now // @todo this implements only a small set of possible reorderings for now
if(expr.operator==subExpr.operator) { if(expr.operator==subExpr.operator) {
@ -539,13 +549,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} }
} }
override fun visit(forLoop: ForLoop): IStatement { override fun visit(forLoop: ForLoop): Statement {
fun adjustRangeDt(rangeFrom: LiteralValue, targetDt: DataType, rangeTo: LiteralValue, stepLiteral: LiteralValue?, range: RangeExpr): RangeExpr { fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
val newFrom = rangeFrom.cast(targetDt) val newFrom = rangeFrom.cast(targetDt)
val newTo = rangeTo.cast(targetDt) val newTo = rangeTo.cast(targetDt)
if (newFrom != null && newTo != null) { if (newFrom != null && newTo != null) {
val newStep: IExpression = val newStep: Expression =
if (stepLiteral != null) (stepLiteral.cast(targetDt) ?: stepLiteral) else range.step if (stepLiteral != null) (stepLiteral.cast(targetDt) ?: stepLiteral) else range.step
return RangeExpr(newFrom, newTo, newStep, range.position) return RangeExpr(newFrom, newTo, newStep, range.position)
} }
@ -555,13 +565,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
// adjust the datatype of a range expression in for loops to the loop variable. // adjust the datatype of a range expression in for loops to the loop variable.
val resultStmt = super.visit(forLoop) as ForLoop val resultStmt = super.visit(forLoop) as ForLoop
val iterableRange = resultStmt.iterable as? RangeExpr ?: return resultStmt val iterableRange = resultStmt.iterable as? RangeExpr ?: return resultStmt
val rangeFrom = iterableRange.from as? LiteralValue val rangeFrom = iterableRange.from as? NumericLiteralValue
val rangeTo = iterableRange.to as? LiteralValue val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return resultStmt if(rangeFrom==null || rangeTo==null) return resultStmt
val loopvar = resultStmt.loopVar?.targetVarDecl(program.namespace) val loopvar = resultStmt.loopVar?.targetVarDecl(program.namespace)
if(loopvar!=null) { if(loopvar!=null) {
val stepLiteral = iterableRange.step as? LiteralValue val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) { when(loopvar.datatype) {
DataType.UBYTE -> { DataType.UBYTE -> {
if(rangeFrom.type!= DataType.UBYTE) { if(rangeFrom.type!= DataType.UBYTE) {
@ -593,39 +603,46 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
return resultStmt return resultStmt
} }
override fun visit(literalValue: LiteralValue): LiteralValue { override fun visit(refLiteral: ReferenceLiteralValue): Expression {
val litval = super.visit(literalValue) val litval = super.visit(refLiteral)
if(litval is ReferenceLiteralValue) {
if (litval.isString) { if (litval.isString) {
// intern the string; move it into the heap // intern the string; move it into the heap
if(litval.strvalue!!.length !in 1..255) if (litval.str!!.length !in 1..255)
addError(ExpressionError("string literal length must be between 1 and 255", litval.position)) addError(ExpressionError("string literal length must be between 1 and 255", litval.position))
else { else {
litval.addToHeap(program.heap) // TODO: we don't know the actual string type yet, STR != STR_S etc... litval.addToHeap(program.heap) // TODO: we don't know the actual string type yet, STR != STR_S etc...
} }
} else if(litval.arrayvalue!=null) { } else if (litval.isArray) {
// first, adjust the array datatype // first, adjust the array datatype
val litval2 = adjustArrayValDatatype(litval) val litval2 = adjustArrayValDatatype(litval)
litval2.addToHeap(program.heap) litval2.addToHeap(program.heap)
return litval2 return litval2
} }
}
return litval return litval
} }
private fun adjustArrayValDatatype(litval: LiteralValue): LiteralValue { private fun adjustArrayValDatatype(litval: ReferenceLiteralValue): ReferenceLiteralValue {
val array = litval.arrayvalue!! if(litval.array==null) {
val typesInArray = array.mapNotNull { it.inferType(program) }.toSet() if(litval.heapId!=null)
return litval // thing is already on the heap, assume it's the right type
throw FatalAstException("missing array value")
}
val typesInArray = litval.array.mapNotNull { it.inferType(program) }.toSet()
val arrayDt = val arrayDt =
when { when {
array.any { it is AddressOf } -> DataType.ARRAY_UW litval.array.any { it is AddressOf } -> DataType.ARRAY_UW
DataType.FLOAT in typesInArray -> DataType.ARRAY_F DataType.FLOAT in typesInArray -> DataType.ARRAY_F
DataType.WORD in typesInArray -> DataType.ARRAY_W DataType.WORD in typesInArray -> DataType.ARRAY_W
else -> { else -> {
val allElementsAreConstantOrAddressOf = array.fold(true) { c, expr-> c and (expr is LiteralValue || expr is AddressOf)} val allElementsAreConstantOrAddressOf = litval.array.fold(true) { c, expr-> c and (expr is NumericLiteralValue|| expr is AddressOf)}
if(!allElementsAreConstantOrAddressOf) { if(!allElementsAreConstantOrAddressOf) {
addError(ExpressionError("array literal can only consist of constant primitive numerical values or memory pointers", litval.position)) addError(ExpressionError("array literal can only consist of constant primitive numerical values or memory pointers", litval.position))
return litval return litval
} else { } else {
val integerArray = array.map { it.constValue(program)!!.asIntegerValue!! } val integerArray = litval.array.map { it.constValue(program)!!.number.toInt() }
val maxValue = integerArray.max()!! val maxValue = integerArray.max()!!
val minValue = integerArray.min()!! val minValue = integerArray.min()!!
if (minValue >= 0) { if (minValue >= 0) {
@ -646,70 +663,69 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} }
if(arrayDt!=litval.type) { if(arrayDt!=litval.type) {
return LiteralValue(arrayDt, arrayvalue = litval.arrayvalue, position = litval.position) return ReferenceLiteralValue(arrayDt, array = litval.array, position = litval.position)
} }
return litval return litval
} }
override fun visit(assignment: Assignment): IStatement { override fun visit(assignment: Assignment): Statement {
super.visit(assignment) super.visit(assignment)
val lv = assignment.value as? LiteralValue val lv = assignment.value as? NumericLiteralValue
if(lv!=null) { if(lv!=null) {
// see if we can promote/convert a literal value to the required datatype // see if we can promote/convert a literal value to the required datatype
when(assignment.singleTarget?.inferType(program, assignment)) { when(assignment.target.inferType(program, assignment)) {
DataType.UWORD -> { DataType.UWORD -> {
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535, // we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
if(lv.type== DataType.UBYTE) if(lv.type== DataType.UBYTE)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position) assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.BYTE && lv.bytevalue!!>=0) else if(lv.type== DataType.BYTE && lv.number.toInt()>=0)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position) assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.WORD && lv.wordvalue!!>=0) else if(lv.type== DataType.WORD && lv.number.toInt()>=0)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position) assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.FLOAT) { else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!! val d = lv.number.toDouble()
if(floor(d)==d && d>=0 && d<=65535) if(floor(d)==d && d>=0 && d<=65535)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = floor(d).toInt(), position = lv.position) assignment.value = NumericLiteralValue(DataType.UWORD, floor(d).toInt(), lv.position)
} }
} }
DataType.UBYTE -> { DataType.UBYTE -> {
// we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255, // we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255,
if(lv.type== DataType.UWORD && lv.wordvalue!! <= 255) if(lv.type== DataType.UWORD && lv.number.toInt() <= 255)
assignment.value = LiteralValue(DataType.UBYTE, lv.wordvalue.toShort(), position = lv.position) assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.BYTE && lv.bytevalue!! >=0) else if(lv.type== DataType.BYTE && lv.number.toInt() >=0)
assignment.value = LiteralValue(DataType.UBYTE, lv.bytevalue.toShort(), position = lv.position) assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.FLOAT) { else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!! val d = lv.number.toDouble()
if(floor(d)==d && d >=0 && d<=255) if(floor(d)==d && d >=0 && d<=255)
assignment.value = LiteralValue(DataType.UBYTE, floor(d).toShort(), position = lv.position) assignment.value = NumericLiteralValue(DataType.UBYTE, floor(d).toShort(), lv.position)
} }
} }
DataType.BYTE -> { DataType.BYTE -> {
// we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127 // we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127
if(lv.type== DataType.UWORD && lv.wordvalue!! <= 127) if(lv.type== DataType.UWORD && lv.number.toInt() <= 127)
assignment.value = LiteralValue(DataType.BYTE, lv.wordvalue.toShort(), position = lv.position) assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.UBYTE && lv.bytevalue!! <= 127) else if(lv.type== DataType.UBYTE && lv.number.toInt() <= 127)
assignment.value = LiteralValue(DataType.BYTE, lv.bytevalue, position = lv.position) assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.FLOAT) { else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!! val d = lv.number.toDouble()
if(floor(d)==d && d>=0 && d<=127) if(floor(d)==d && d>=0 && d<=127)
assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position = lv.position) assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
} }
} }
DataType.WORD -> { DataType.WORD -> {
// we can convert to WORD: any UBYTE/BYTE, UWORD <= 32767, FLOAT that's an integer -32768..32767, // we can convert to WORD: any UBYTE/BYTE, UWORD <= 32767, FLOAT that's an integer -32768..32767,
if(lv.type== DataType.UBYTE || lv.type== DataType.BYTE) if(lv.type== DataType.UBYTE || lv.type== DataType.BYTE)
assignment.value = LiteralValue(DataType.WORD, wordvalue = lv.bytevalue!!.toInt(), position = lv.position) assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.UWORD && lv.wordvalue!! <= 32767) else if(lv.type== DataType.UWORD && lv.number.toInt() <= 32767)
assignment.value = LiteralValue(DataType.WORD, wordvalue = lv.wordvalue, position = lv.position) assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.FLOAT) { else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!! val d = lv.number.toDouble()
if(floor(d)==d && d>=-32768 && d<=32767) if(floor(d)==d && d>=-32768 && d<=32767)
assignment.value = LiteralValue(DataType.BYTE, floor(d).toShort(), position = lv.position) assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
} }
} }
DataType.FLOAT -> { DataType.FLOAT -> {
if(lv.isNumeric) assignment.value = NumericLiteralValue(DataType.FLOAT, lv.number.toDouble(), lv.position)
assignment.value = LiteralValue(DataType.FLOAT, floatvalue = lv.asNumericValue?.toDouble(), position = lv.position)
} }
else -> {} else -> {}
} }

View File

@ -1,8 +1,7 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.* import prog8.ast.Program
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.ast.statements.NopStatement
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
@ -31,14 +30,6 @@ internal fun Program.constantFold() {
internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int { internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int {
val optimizer = StatementOptimizer(this, optimizeInlining) val optimizer = StatementOptimizer(this, optimizeInlining)
optimizer.visit(this) optimizer.visit(this)
for(scope in optimizer.scopesToFlatten.reversed()) {
val namescope = scope.parent as INameScope
val idx = namescope.statements.indexOf(scope as IStatement)
if(idx>=0) {
namescope.statements[idx] = NopStatement(scope.position)
namescope.statements.addAll(idx, scope.statements)
}
}
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
return optimizer.optimizationsDone return optimizer.optimizationsDone

View File

@ -1,6 +1,6 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.* import prog8.ast.Program
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.IntegerDatatypes import prog8.ast.base.IntegerDatatypes
@ -8,6 +8,7 @@ import prog8.ast.base.NumericDatatypes
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.statements.Statement
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.log2 import kotlin.math.log2
@ -21,13 +22,13 @@ import kotlin.math.log2
internal class SimplifyExpressions(private val program: Program) : IAstModifyingVisitor { internal class SimplifyExpressions(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0 var optimizationsDone: Int = 0
override fun visit(assignment: Assignment): IStatement { override fun visit(assignment: Assignment): Statement {
if (assignment.aug_op != null) if (assignment.aug_op != null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer") throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
return super.visit(assignment) return super.visit(assignment)
} }
override fun visit(memread: DirectMemoryRead): IExpression { override fun visit(memread: DirectMemoryRead): Expression {
// @( &thing ) --> thing // @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null) if(addrOf!=null)
@ -35,28 +36,53 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return super.visit(memread) return super.visit(memread)
} }
override fun visit(typecast: TypecastExpression): IExpression { override fun visit(typecast: TypecastExpression): Expression {
// remove redundant typecasts
var tc = typecast var tc = typecast
// try to statically convert a literal value into one of the desired type
val literal = tc.expression as? NumericLiteralValue
if(literal!=null) {
val newLiteral = literal.cast(tc.type)
if(newLiteral!=null && newLiteral!==literal) {
optimizationsDone++
return newLiteral
}
}
// remove redundant typecasts
while(true) { while(true) {
val expr = tc.expression val expr = tc.expression
if(expr !is TypecastExpression || expr.type!=tc.type) { if(expr !is TypecastExpression || expr.type!=tc.type) {
val assignment = typecast.parent as? Assignment val assignment = typecast.parent as? Assignment
if(assignment!=null) { if(assignment!=null) {
val targetDt = assignment.singleTarget?.inferType(program, assignment) val targetDt = assignment.target.inferType(program, assignment)
if(tc.expression.inferType(program)==targetDt) { if(tc.expression.inferType(program)==targetDt) {
optimizationsDone++ optimizationsDone++
return tc.expression return tc.expression
} }
} }
val subTc = tc.expression as? TypecastExpression
if(subTc!=null) {
// if the previous typecast was casting to a 'bigger' type, just ignore that one
// if the previous typecast was casting to a similar type, ignore that one
if(subTc.type largerThan tc.type || subTc.type equalsSize tc.type) {
subTc.type = tc.type
subTc.parent = tc.parent
optimizationsDone++
return subTc
}
}
return super.visit(tc) return super.visit(tc)
} }
optimizationsDone++ optimizationsDone++
tc = expr tc = expr
} }
} }
override fun visit(expr: PrefixExpression): IExpression { override fun visit(expr: PrefixExpression): Expression {
if (expr.operator == "+") { if (expr.operator == "+") {
// +X --> X // +X --> X
optimizationsDone++ optimizationsDone++
@ -103,12 +129,12 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return super.visit(expr) return super.visit(expr)
} }
override fun visit(expr: BinaryExpression): IExpression { override fun visit(expr: BinaryExpression): Expression {
super.visit(expr) super.visit(expr)
val leftVal = expr.left.constValue(program) val leftVal = expr.left.constValue(program)
val rightVal = expr.right.constValue(program) val rightVal = expr.right.constValue(program)
val constTrue = LiteralValue.fromBoolean(true, expr.position) val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
val constFalse = LiteralValue.fromBoolean(false, expr.position) val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val leftDt = expr.left.inferType(program) val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program) val rightDt = expr.right.inferType(program)
@ -149,10 +175,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// X + (-value) --> X - value // X + (-value) --> X - value
if (expr.operator == "+" && rightVal != null) { if (expr.operator == "+" && rightVal != null) {
val rv = rightVal.asNumericValue?.toDouble() val rv = rightVal.number.toDouble()
if (rv != null && rv < 0.0) { if (rv < 0.0) {
expr.operator = "-" expr.operator = "-"
expr.right = LiteralValue.fromNumber(-rv, rightVal.type, rightVal.position) expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
optimizationsDone++ optimizationsDone++
return expr return expr
} }
@ -160,10 +186,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// (-value) + X --> X - value // (-value) + X --> X - value
if (expr.operator == "+" && leftVal != null) { if (expr.operator == "+" && leftVal != null) {
val lv = leftVal.asNumericValue?.toDouble() val lv = leftVal.number.toDouble()
if (lv != null && lv < 0.0) { if (lv < 0.0) {
expr.operator = "-" expr.operator = "-"
expr.right = LiteralValue.fromNumber(-lv, leftVal.type, leftVal.position) expr.right = NumericLiteralValue(leftVal.type, -lv, leftVal.position)
optimizationsDone++ optimizationsDone++
return expr return expr
} }
@ -179,10 +205,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// X - (-value) --> X + value // X - (-value) --> X + value
if (expr.operator == "-" && rightVal != null) { if (expr.operator == "-" && rightVal != null) {
val rv = rightVal.asNumericValue?.toDouble() val rv = rightVal.number.toDouble()
if (rv != null && rv < 0.0) { if (rv < 0.0) {
expr.operator = "+" expr.operator = "+"
expr.right = LiteralValue.fromNumber(-rv, rightVal.type, rightVal.position) expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
optimizationsDone++ optimizationsDone++
return expr return expr
} }
@ -200,7 +226,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.right val x = expr.right
val y = determineY(x, leftBinExpr) val y = determineY(x, leftBinExpr)
if(y!=null) { if(y!=null) {
val yPlus1 = BinaryExpression(y, "+", LiteralValue.fromNumber(1, leftDt!!, y.position), y.position) val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt!!, 1, y.position), y.position)
return BinaryExpression(x, "*", yPlus1, x.position) return BinaryExpression(x, "*", yPlus1, x.position)
} }
} else { } else {
@ -209,7 +235,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.right val x = expr.right
val y = determineY(x, leftBinExpr) val y = determineY(x, leftBinExpr)
if(y!=null) { if(y!=null) {
val yMinus1 = BinaryExpression(y, "-", LiteralValue.fromNumber(1, leftDt!!, y.position), y.position) val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt!!, 1, y.position), y.position)
return BinaryExpression(x, "*", yMinus1, x.position) return BinaryExpression(x, "*", yMinus1, x.position)
} }
} }
@ -221,7 +247,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.left val x = expr.left
val y = determineY(x, rightBinExpr) val y = determineY(x, rightBinExpr)
if(y!=null) { if(y!=null) {
val yPlus1 = BinaryExpression(y, "+", LiteralValue.optimalInteger(1, y.position), y.position) val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue.optimalInteger(1, y.position), y.position)
return BinaryExpression(x, "*", yPlus1, x.position) return BinaryExpression(x, "*", yPlus1, x.position)
} }
} else { } else {
@ -230,7 +256,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.left val x = expr.left
val y = determineY(x, rightBinExpr) val y = determineY(x, rightBinExpr)
if(y!=null) { if(y!=null) {
val oneMinusY = BinaryExpression(LiteralValue.optimalInteger(1, y.position), "-", y, y.position) val oneMinusY = BinaryExpression(NumericLiteralValue.optimalInteger(1, y.position), "-", y, y.position)
return BinaryExpression(x, "*", oneMinusY, x.position) return BinaryExpression(x, "*", oneMinusY, x.position)
} }
} }
@ -316,7 +342,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr return expr
} }
private fun determineY(x: IExpression, subBinExpr: BinaryExpression): IExpression? { private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when { return when {
subBinExpr.left isSameAs x -> subBinExpr.right subBinExpr.left isSameAs x -> subBinExpr.right
subBinExpr.right isSameAs x -> subBinExpr.left subBinExpr.right isSameAs x -> subBinExpr.left
@ -325,58 +351,58 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
} }
private fun adjustDatatypes(expr: BinaryExpression, private fun adjustDatatypes(expr: BinaryExpression,
leftConstVal: LiteralValue?, leftDt: DataType, leftConstVal: NumericLiteralValue?, leftDt: DataType,
rightConstVal: LiteralValue?, rightDt: DataType): Boolean { rightConstVal: NumericLiteralValue?, rightDt: DataType): Boolean {
fun adjust(value: LiteralValue, targetDt: DataType): Pair<Boolean, LiteralValue>{ fun adjust(value: NumericLiteralValue, targetDt: DataType): Pair<Boolean, NumericLiteralValue>{
if(value.type==targetDt) if(value.type==targetDt)
return Pair(false, value) return Pair(false, value)
when(value.type) { when(value.type) {
DataType.UBYTE -> { DataType.UBYTE -> {
if (targetDt == DataType.BYTE) { if (targetDt == DataType.BYTE) {
if(value.bytevalue!! < 127) if(value.number.toInt() < 127)
return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position)) return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
} }
else if (targetDt == DataType.UWORD || targetDt == DataType.WORD) else if (targetDt == DataType.UWORD || targetDt == DataType.WORD)
return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue!!.toInt(), position = value.position)) return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
} }
DataType.BYTE -> { DataType.BYTE -> {
if (targetDt == DataType.UBYTE) { if (targetDt == DataType.UBYTE) {
if(value.bytevalue!! >= 0) if(value.number.toInt() >= 0)
return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position)) return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
} }
else if (targetDt == DataType.UWORD) { else if (targetDt == DataType.UWORD) {
if(value.bytevalue!! >= 0) if(value.number.toInt() >= 0)
return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue.toInt(), position = value.position)) return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
} }
else if (targetDt == DataType.WORD) return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue!!.toInt(), position = value.position)) else if (targetDt == DataType.WORD) return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
} }
DataType.UWORD -> { DataType.UWORD -> {
if (targetDt == DataType.UBYTE) { if (targetDt == DataType.UBYTE) {
if(value.wordvalue!! <= 255) if(value.number.toInt() <= 255)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position)) return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
} }
else if (targetDt == DataType.BYTE) { else if (targetDt == DataType.BYTE) {
if(value.wordvalue!! <= 127) if(value.number.toInt() <= 127)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position)) return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
} }
else if (targetDt == DataType.WORD) { else if (targetDt == DataType.WORD) {
if(value.wordvalue!! <= 32767) if(value.number.toInt() <= 32767)
return Pair(true, LiteralValue(targetDt, wordvalue = value.wordvalue, position = value.position)) return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
} }
} }
DataType.WORD -> { DataType.WORD -> {
if (targetDt == DataType.UBYTE) { if (targetDt == DataType.UBYTE) {
if(value.wordvalue!! in 0..255) if(value.number.toInt() in 0..255)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position)) return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
} }
else if (targetDt == DataType.BYTE) { else if (targetDt == DataType.BYTE) {
if(value.wordvalue!! in -128..127) if(value.number.toInt() in -128..127)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position)) return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
} }
else if (targetDt == DataType.UWORD) { else if (targetDt == DataType.UWORD) {
if(value.wordvalue!! >= 0) if(value.number.toInt() >= 0)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position)) return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
} }
} }
else -> {} else -> {}
@ -385,7 +411,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
} }
if(leftConstVal==null && rightConstVal!=null) { if(leftConstVal==null && rightConstVal!=null) {
if(leftDt biggerThan rightDt) { if(leftDt largerThan rightDt) {
val (adjusted, newValue) = adjust(rightConstVal, leftDt) val (adjusted, newValue) = adjust(rightConstVal, leftDt)
if (adjusted) { if (adjusted) {
expr.right = newValue expr.right = newValue
@ -395,7 +421,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
} }
return false return false
} else if(leftConstVal!=null && rightConstVal==null) { } else if(leftConstVal!=null && rightConstVal==null) {
if(rightDt biggerThan leftDt) { if(rightDt largerThan leftDt) {
val (adjusted, newValue) = adjust(leftConstVal, rightDt) val (adjusted, newValue) = adjust(leftConstVal, rightDt)
if (adjusted) { if (adjusted) {
expr.left = newValue expr.left = newValue
@ -409,9 +435,9 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
} }
} }
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: LiteralValue?, val rightVal: LiteralValue?) private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
private fun reorderAssociative(expr: BinaryExpression, leftVal: LiteralValue?): ReorderedAssociativeBinaryExpr { private fun reorderAssociative(expr: BinaryExpression, leftVal: NumericLiteralValue?): ReorderedAssociativeBinaryExpr {
if(expr.operator in associativeOperators && leftVal!=null) { if(expr.operator in associativeOperators && leftVal!=null) {
// swap left and right so that right is always the constant // swap left and right so that right is always the constant
val tmp = expr.left val tmp = expr.left
@ -423,15 +449,15 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program)) return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
} }
private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression { private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: NumericLiteralValue?, prightVal: NumericLiteralValue?): Expression {
if(pleftVal==null && prightVal==null) if(pleftVal==null && prightVal==null)
return pexpr return pexpr
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal) val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
if(rightVal!=null) { if(rightVal!=null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: LiteralValue = rightVal val rightConst: NumericLiteralValue = rightVal
when(rightConst.asNumericValue?.toDouble()) { when(rightConst.number.toDouble()) {
0.0 -> { 0.0 -> {
// left // left
optimizationsDone++ optimizationsDone++
@ -444,14 +470,14 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr return expr
} }
private fun optimizeSub(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression { private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null) if(leftVal==null && rightVal==null)
return expr return expr
if(rightVal!=null) { if(rightVal!=null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: LiteralValue = rightVal val rightConst: NumericLiteralValue = rightVal
when(rightConst.asNumericValue?.toDouble()) { when(rightConst.number.toDouble()) {
0.0 -> { 0.0 -> {
// left // left
optimizationsDone++ optimizationsDone++
@ -461,7 +487,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
} }
if(leftVal!=null) { if(leftVal!=null) {
// left value is a constant, see if we can optimize // left value is a constant, see if we can optimize
when(leftVal.asNumericValue?.toDouble()) { when(leftVal.number.toDouble()) {
0.0 -> { 0.0 -> {
// -right // -right
optimizationsDone++ optimizationsDone++
@ -473,38 +499,38 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr return expr
} }
private fun optimizePower(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression { private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null) if(leftVal==null && rightVal==null)
return expr return expr
if(rightVal!=null) { if(rightVal!=null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: LiteralValue = rightVal val rightConst: NumericLiteralValue = rightVal
when(rightConst.asNumericValue?.toDouble()) { when(rightConst.number.toDouble()) {
-3.0 -> { -3.0 -> {
// -1/(left*left*left) // -1/(left*left*left)
optimizationsDone++ optimizationsDone++
return BinaryExpression(LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position), "/", return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position), BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
expr.position) expr.position)
} }
-2.0 -> { -2.0 -> {
// -1/(left*left) // -1/(left*left)
optimizationsDone++ optimizationsDone++
return BinaryExpression(LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position), "/", return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", expr.left, expr.position), BinaryExpression(expr.left, "*", expr.left, expr.position),
expr.position) expr.position)
} }
-1.0 -> { -1.0 -> {
// -1/left // -1/left
optimizationsDone++ optimizationsDone++
return BinaryExpression(LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position), "/", return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
expr.left, expr.position) expr.left, expr.position)
} }
0.0 -> { 0.0 -> {
// 1 // 1
optimizationsDone++ optimizationsDone++
return LiteralValue.fromNumber(1, rightConst.type, expr.position) return NumericLiteralValue(rightConst.type, 1, expr.position)
} }
0.5 -> { 0.5 -> {
// sqrt(left) // sqrt(left)
@ -530,21 +556,21 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
} }
if(leftVal!=null) { if(leftVal!=null) {
// left value is a constant, see if we can optimize // left value is a constant, see if we can optimize
when(leftVal.asNumericValue?.toDouble()) { when(leftVal.number.toDouble()) {
-1.0 -> { -1.0 -> {
// -1 // -1
optimizationsDone++ optimizationsDone++
return LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position) return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
} }
0.0 -> { 0.0 -> {
// 0 // 0
optimizationsDone++ optimizationsDone++
return LiteralValue.fromNumber(0, leftVal.type, expr.position) return NumericLiteralValue(leftVal.type, 0, expr.position)
} }
1.0 -> { 1.0 -> {
//1 //1
optimizationsDone++ optimizationsDone++
return LiteralValue.fromNumber(1, leftVal.type, expr.position) return NumericLiteralValue(leftVal.type, 1, expr.position)
} }
} }
@ -553,22 +579,22 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr return expr
} }
private fun optimizeRemainder(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression { private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null) if(leftVal==null && rightVal==null)
return expr return expr
// simplify assignments A = B <operator> C // simplify assignments A = B <operator> C
val cv = rightVal?.asIntegerValue?.toDouble() val cv = rightVal?.number?.toInt()?.toDouble()
when(expr.operator) { when(expr.operator) {
"%" -> { "%" -> {
if (cv == 1.0) { if (cv == 1.0) {
optimizationsDone++ optimizationsDone++
return LiteralValue.fromNumber(0, expr.inferType(program)!!, expr.position) return NumericLiteralValue(expr.inferType(program)!!, 0, expr.position)
} else if (cv == 2.0) { } else if (cv == 2.0) {
optimizationsDone++ optimizationsDone++
expr.operator = "&" expr.operator = "&"
expr.right = LiteralValue.optimalInteger(1, expr.position) expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
return expr return expr
} }
} }
@ -577,7 +603,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
} }
private fun optimizeDivision(expr: BinaryExpression, leftVal: LiteralValue?, rightVal: LiteralValue?): IExpression { private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null) if(leftVal==null && rightVal==null)
return expr return expr
@ -585,8 +611,8 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
if(rightVal!=null) { if(rightVal!=null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: LiteralValue = rightVal val rightConst: NumericLiteralValue = rightVal
val cv = rightConst.asNumericValue?.toDouble() val cv = rightConst.number.toDouble()
val leftDt = expr.left.inferType(program) val leftDt = expr.left.inferType(program)
when(cv) { when(cv) {
-1.0 -> { -1.0 -> {
@ -608,7 +634,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// divided by a power of two => shift right // divided by a power of two => shift right
optimizationsDone++ optimizationsDone++
val numshifts = log2(cv).toInt() val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, ">>", LiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
-2.0, -4.0, -8.0, -16.0, -32.0, -64.0, -128.0, -256.0, -512.0, -1024.0, -2048.0, -4096.0, -8192.0, -16384.0, -32768.0, -65536.0 -> { -2.0, -4.0, -8.0, -16.0, -32.0, -64.0, -128.0, -256.0, -512.0, -1024.0, -2048.0, -4096.0, -8192.0, -16384.0, -32768.0, -65536.0 -> {
@ -616,32 +642,32 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// divided by a negative power of two => negate, then shift right // divided by a negative power of two => negate, then shift right
optimizationsDone++ optimizationsDone++
val numshifts = log2(-cv).toInt() val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", LiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
} }
if (leftDt == DataType.UBYTE) { if (leftDt == DataType.UBYTE) {
if(abs(rightConst.asNumericValue!!.toDouble()) >= 256.0) { if(abs(rightConst.number.toDouble()) >= 256.0) {
optimizationsDone++ optimizationsDone++
return LiteralValue(DataType.UBYTE, 0, position = expr.position) return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
} }
} }
else if (leftDt == DataType.UWORD) { else if (leftDt == DataType.UWORD) {
if(abs(rightConst.asNumericValue!!.toDouble()) >= 65536.0) { if(abs(rightConst.number.toDouble()) >= 65536.0) {
optimizationsDone++ optimizationsDone++
return LiteralValue(DataType.UBYTE, 0, position = expr.position) return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
} }
} }
} }
if(leftVal!=null) { if(leftVal!=null) {
// left value is a constant, see if we can optimize // left value is a constant, see if we can optimize
when(leftVal.asNumericValue?.toDouble()) { when(leftVal.number.toDouble()) {
0.0 -> { 0.0 -> {
// 0 // 0
optimizationsDone++ optimizationsDone++
return LiteralValue.fromNumber(0, leftVal.type, expr.position) return NumericLiteralValue(leftVal.type, 0, expr.position)
} }
} }
} }
@ -649,16 +675,16 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr return expr
} }
private fun optimizeMultiplication(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression { private fun optimizeMultiplication(pexpr: BinaryExpression, pleftVal: NumericLiteralValue?, prightVal: NumericLiteralValue?): Expression {
if(pleftVal==null && prightVal==null) if(pleftVal==null && prightVal==null)
return pexpr return pexpr
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal) val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
if(rightVal!=null) { if(rightVal!=null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val leftValue: IExpression = expr.left val leftValue: Expression = expr.left
val rightConst: LiteralValue = rightVal val rightConst: NumericLiteralValue = rightVal
when(val cv = rightConst.asNumericValue?.toDouble()) { when(val cv = rightConst.number.toDouble()) {
-1.0 -> { -1.0 -> {
// -left // -left
optimizationsDone++ optimizationsDone++
@ -667,7 +693,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
0.0 -> { 0.0 -> {
// 0 // 0
optimizationsDone++ optimizationsDone++
return LiteralValue.fromNumber(0, rightConst.type, expr.position) return NumericLiteralValue(rightConst.type, 0, expr.position)
} }
1.0 -> { 1.0 -> {
// left // left
@ -679,7 +705,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// times a power of two => shift left // times a power of two => shift left
optimizationsDone++ optimizationsDone++
val numshifts = log2(cv).toInt() val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, "<<", LiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(expr.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
-2.0, -4.0, -8.0, -16.0, -32.0, -64.0, -128.0, -256.0, -512.0, -1024.0, -2048.0, -4096.0, -8192.0, -16384.0, -32768.0, -65536.0 -> { -2.0, -4.0, -8.0, -16.0, -32.0, -64.0, -128.0, -256.0, -512.0, -1024.0, -2048.0, -4096.0, -8192.0, -16384.0, -32768.0, -65536.0 -> {
@ -687,7 +713,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// times a negative power of two => negate, then shift left // times a negative power of two => negate, then shift left
optimizationsDone++ optimizationsDone++
val numshifts = log2(-cv).toInt() val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), "<<", LiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(PrefixExpression("-", expr.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
} }

View File

@ -4,6 +4,7 @@ import prog8.ast.*
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
@ -15,11 +16,10 @@ import kotlin.math.floor
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this
*/ */
internal class StatementOptimizer(private val program: Program, private val optimizeInlining: Boolean) : IAstModifyingVisitor { internal class StatementOptimizer(private val program: Program, private val optimizeInlining: Boolean) : IAstModifyingVisitor {
var optimizationsDone: Int = 0 var optimizationsDone: Int = 0
private set private set
var scopesToFlatten = mutableListOf<INameScope>()
val nopStatements = mutableListOf<NopStatement>()
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure } private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
private val callgraph = CallGraph(program) private val callgraph = CallGraph(program)
@ -34,9 +34,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
inlineSubroutines(callgraph) inlineSubroutines(callgraph)
} }
super.visit(program) super.visit(program)
// at the end, remove the encountered NOP statements
this.nopStatements.forEach { it.definingScope().remove(it) }
} }
private fun inlineSubroutines(callgraph: CallGraph) { private fun inlineSubroutines(callgraph: CallGraph) {
@ -62,7 +59,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
val scope = caller.definingScope() val scope = caller.definingScope()
if(sub.calledBy.count { it.definingScope()===scope } > 1) if(sub.calledBy.count { it.definingScope()===scope } > 1)
return return
if(caller !is IFunctionCall || caller !is IStatement || sub.statements.any { it is Subroutine }) if(caller !is IFunctionCall || caller !is Statement || sub.statements.any { it is Subroutine })
return return
if(sub.parameters.isEmpty() && sub.returntypes.isEmpty()) { if(sub.parameters.isEmpty() && sub.returntypes.isEmpty()) {
@ -81,7 +78,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
} }
val returns = inlined.statements.withIndex().filter { iv -> iv.value is Return }.map { iv -> Pair(iv.index, iv.value as Return)} val returns = inlined.statements.withIndex().filter { iv -> iv.value is Return }.map { iv -> Pair(iv.index, iv.value as Return)}
for(returnIdx in returns) { for(returnIdx in returns) {
assert(returnIdx.second.values.isEmpty())
val jump = Jump(null, IdentifierReference(listOf(endlabel.name), returnIdx.second.position), null, returnIdx.second.position) val jump = Jump(null, IdentifierReference(listOf(endlabel.name), returnIdx.second.position), null, returnIdx.second.position)
inlined.statements[returnIdx.first] = jump inlined.statements[returnIdx.first] = jump
endLabelUsed = true endLabelUsed = true
@ -140,32 +136,32 @@ internal class StatementOptimizer(private val program: Program, private val opti
} }
} }
override fun visit(block: Block): IStatement { override fun visit(block: Block): Statement {
if("force_output" !in block.options()) { if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) { if (block.containsNoCodeNorVars()) {
optimizationsDone++ optimizationsDone++
printWarning("removing empty block '${block.name}'", block.position) printWarning("removing empty block '${block.name}'", block.position)
return NopStatement(block.position) return NopStatement.insteadOf(block)
} }
if (block !in callgraph.usedSymbols) { if (block !in callgraph.usedSymbols) {
optimizationsDone++ optimizationsDone++
printWarning("removing unused block '${block.name}'", block.position) printWarning("removing unused block '${block.name}'", block.position)
return NopStatement(block.position) // remove unused block return NopStatement.insteadOf(block) // remove unused block
} }
} }
return super.visit(block) return super.visit(block)
} }
override fun visit(subroutine: Subroutine): IStatement { override fun visit(subroutine: Subroutine): Statement {
super.visit(subroutine) super.visit(subroutine)
val forceOutput = "force_output" in subroutine.definingBlock().options() val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null && !forceOutput) { if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.containsNoCodeNorVars()) { if(subroutine.containsNoCodeNorVars()) {
printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position) printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++ optimizationsDone++
return NopStatement(subroutine.position) return NopStatement.insteadOf(subroutine)
} }
} }
@ -189,39 +185,39 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(subroutine !in callgraph.usedSymbols && !forceOutput) { if(subroutine !in callgraph.usedSymbols && !forceOutput) {
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position) printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++ optimizationsDone++
return NopStatement(subroutine.position) // remove unused subroutine return NopStatement.insteadOf(subroutine)
} }
return subroutine return subroutine
} }
override fun visit(decl: VarDecl): IStatement { override fun visit(decl: VarDecl): Statement {
val forceOutput = "force_output" in decl.definingBlock().options() val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) { if(decl !in callgraph.usedSymbols && !forceOutput) {
if(decl.type!=VarDeclType.CONST) if(decl.type == VarDeclType.VAR)
printWarning("removing unused variable '${decl.name}'", decl.position) printWarning("removing unused variable ${decl.type} '${decl.name}'", decl.position)
optimizationsDone++ optimizationsDone++
return NopStatement(decl.position) // remove unused variable return NopStatement.insteadOf(decl)
} }
return super.visit(decl) return super.visit(decl)
} }
private fun deduplicateAssignments(statements: List<IStatement>): MutableList<Int> { private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
// removes 'duplicate' assignments that assign the isSameAs target // removes 'duplicate' assignments that assign the isSameAs target
val linesToRemove = mutableListOf<Int>() val linesToRemove = mutableListOf<Int>()
var previousAssignmentLine: Int? = null var previousAssignmentLine: Int? = null
for (i in 0 until statements.size) { for (i in 0 until statements.size) {
val stmt = statements[i] as? Assignment val stmt = statements[i] as? Assignment
if (stmt != null && stmt.value is LiteralValue) { if (stmt != null && stmt.value is NumericLiteralValue) {
if (previousAssignmentLine == null) { if (previousAssignmentLine == null) {
previousAssignmentLine = i previousAssignmentLine = i
continue continue
} else { } else {
val prev = statements[previousAssignmentLine] as Assignment val prev = statements[previousAssignmentLine] as Assignment
if (prev.targets.size == 1 && stmt.targets.size == 1 && prev.targets[0].isSameAs(stmt.targets[0], program)) { if (prev.target.isSameAs(stmt.target, program)) {
// get rid of the previous assignment, if the target is not MEMORY // get rid of the previous assignment, if the target is not MEMORY
if (prev.targets[0].isNotMemory(program.namespace)) if (prev.target.isNotMemory(program.namespace))
linesToRemove.add(previousAssignmentLine) linesToRemove.add(previousAssignmentLine)
} }
previousAssignmentLine = i previousAssignmentLine = i
@ -232,20 +228,20 @@ internal class StatementOptimizer(private val program: Program, private val opti
return linesToRemove return linesToRemove
} }
override fun visit(functionCallStatement: FunctionCallStatement): IStatement { override fun visit(functionCallStatement: FunctionCallStatement): Statement {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) { if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0] val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) { if (functionName in pureBuiltinFunctions) {
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position) printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
optimizationsDone++ optimizationsDone++
return NopStatement(functionCallStatement.position) return NopStatement.insteadOf(functionCallStatement)
} }
} }
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") || if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) { functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters // printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
if(functionCallStatement.arglist.single() is LiteralValue) if(functionCallStatement.arglist.single() is NumericLiteralValue)
throw AstException("string argument should be on heap already") throw AstException("string argument should be on heap already")
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference val stringVar = functionCallStatement.arglist.single() as? IdentifierReference
if(stringVar!=null) { if(stringVar!=null) {
@ -254,7 +250,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(string.length==1) { if(string.length==1) {
val petscii = Petscii.encodePetscii(string, true)[0] val petscii = Petscii.encodePetscii(string, true)[0]
functionCallStatement.arglist.clear() functionCallStatement.arglist.clear()
functionCallStatement.arglist.add(LiteralValue.optimalInteger(petscii, functionCallStatement.position)) functionCallStatement.arglist.add(NumericLiteralValue.optimalInteger(petscii.toInt(), functionCallStatement.position))
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position) functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
optimizationsDone++ optimizationsDone++
return functionCallStatement return functionCallStatement
@ -262,9 +258,9 @@ internal class StatementOptimizer(private val program: Program, private val opti
val petscii = Petscii.encodePetscii(string, true) val petscii = Petscii.encodePetscii(string, true)
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position) val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position), scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(LiteralValue.optimalInteger(petscii[0], functionCallStatement.position)), functionCallStatement.position)) mutableListOf(NumericLiteralValue.optimalInteger(petscii[0].toInt(), functionCallStatement.position)), functionCallStatement.position))
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position), scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(LiteralValue.optimalInteger(petscii[1], functionCallStatement.position)), functionCallStatement.position)) mutableListOf(NumericLiteralValue.optimalInteger(petscii[1].toInt(), functionCallStatement.position)), functionCallStatement.position))
optimizationsDone++ optimizationsDone++
return scope return scope
} }
@ -283,14 +279,14 @@ internal class StatementOptimizer(private val program: Program, private val opti
} }
if(first is ReturnFromIrq || first is Return) { if(first is ReturnFromIrq || first is Return) {
optimizationsDone++ optimizationsDone++
return NopStatement(functionCallStatement.position) return NopStatement.insteadOf(functionCallStatement)
} }
} }
return super.visit(functionCallStatement) return super.visit(functionCallStatement)
} }
override fun visit(functionCall: FunctionCall): IExpression { override fun visit(functionCall: FunctionCall): Expression {
// if it calls a subroutine, // if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead // and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value // if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
@ -301,8 +297,8 @@ internal class StatementOptimizer(private val program: Program, private val opti
optimizationsDone++ optimizationsDone++
return FunctionCall(first.identifier, functionCall.arglist, functionCall.position) return FunctionCall(first.identifier, functionCall.arglist, functionCall.position)
} }
if(first is Return && first.values.size==1) { if(first is Return && first.value!=null) {
val constval = first.values[0].constValue(program) val constval = first.value?.constValue(program)
if(constval!=null) if(constval!=null)
return constval return constval
} }
@ -310,12 +306,12 @@ internal class StatementOptimizer(private val program: Program, private val opti
return super.visit(functionCall) return super.visit(functionCall)
} }
override fun visit(ifStatement: IfStatement): IStatement { override fun visit(ifStatement: IfStatement): Statement {
super.visit(ifStatement) super.visit(ifStatement)
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) { if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
optimizationsDone++ optimizationsDone++
return NopStatement(ifStatement.position) return NopStatement.insteadOf(ifStatement)
} }
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) { if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
@ -344,18 +340,18 @@ internal class StatementOptimizer(private val program: Program, private val opti
return ifStatement return ifStatement
} }
override fun visit(forLoop: ForLoop): IStatement { override fun visit(forLoop: ForLoop): Statement {
super.visit(forLoop) super.visit(forLoop)
if(forLoop.body.containsNoCodeNorVars()) { if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop // remove empty for loop
optimizationsDone++ optimizationsDone++
return NopStatement(forLoop.position) return NopStatement.insteadOf(forLoop)
} else if(forLoop.body.statements.size==1) { } else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) { if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
// remove empty for loop // remove empty for loop
optimizationsDone++ optimizationsDone++
return NopStatement(forLoop.position) return NopStatement.insteadOf(forLoop)
} }
} }
@ -365,7 +361,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(range.size()==1) { if(range.size()==1) {
// for loop over a (constant) range of just a single value-- optimize the loop away // for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block // loopvar/reg = range value , follow by block
val assignment = Assignment(listOf(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position)), null, range.from, forLoop.position) val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position)
forLoop.body.statements.add(0, assignment) forLoop.body.statements.add(0, assignment)
optimizationsDone++ optimizationsDone++
return forLoop.body return forLoop.body
@ -374,7 +370,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return forLoop return forLoop
} }
override fun visit(whileLoop: WhileLoop): IStatement { override fun visit(whileLoop: WhileLoop): Statement {
super.visit(whileLoop) super.visit(whileLoop)
val constvalue = whileLoop.condition.constValue(program) val constvalue = whileLoop.condition.constValue(program)
if(constvalue!=null) { if(constvalue!=null) {
@ -394,13 +390,13 @@ internal class StatementOptimizer(private val program: Program, private val opti
// always false -> ditch whole statement // always false -> ditch whole statement
printWarning("condition is always false", whileLoop.position) printWarning("condition is always false", whileLoop.position)
optimizationsDone++ optimizationsDone++
NopStatement(whileLoop.position) NopStatement.insteadOf(whileLoop)
} }
} }
return whileLoop return whileLoop
} }
override fun visit(repeatLoop: RepeatLoop): IStatement { override fun visit(repeatLoop: RepeatLoop): Statement {
super.visit(repeatLoop) super.visit(repeatLoop)
val constvalue = repeatLoop.untilCondition.constValue(program) val constvalue = repeatLoop.untilCondition.constValue(program)
if(constvalue!=null) { if(constvalue!=null) {
@ -430,12 +426,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return repeatLoop return repeatLoop
} }
override fun visit(nopStatement: NopStatement): IStatement { override fun visit(whenStatement: WhenStatement): Statement {
this.nopStatements.add(nopStatement)
return nopStatement
}
override fun visit(whenStatement: WhenStatement): IStatement {
val choices = whenStatement.choices.toList() val choices = whenStatement.choices.toList()
for(choice in choices) { for(choice in choices) {
if(choice.statements.containsNoCodeNorVars()) if(choice.statements.containsNoCodeNorVars())
@ -450,12 +441,12 @@ internal class StatementOptimizer(private val program: Program, private val opti
{ {
var count=0 var count=0
override fun visit(breakStmt: Break): IStatement { override fun visit(breakStmt: Break): Statement {
count++ count++
return super.visit(breakStmt) return super.visit(breakStmt)
} }
override fun visit(contStmt: Continue): IStatement { override fun visit(contStmt: Continue): Statement {
count++ count++
return super.visit(contStmt) return super.visit(contStmt)
} }
@ -469,7 +460,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return s.count > 0 return s.count > 0
} }
override fun visit(jump: Jump): IStatement { override fun visit(jump: Jump): Statement {
val subroutine = jump.identifier?.targetSubroutine(program.namespace) val subroutine = jump.identifier?.targetSubroutine(program.namespace)
if(subroutine!=null) { if(subroutine!=null) {
// if the first instruction in the subroutine is another jump, shortcut this one // if the first instruction in the subroutine is another jump, shortcut this one
@ -486,53 +477,51 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(label!=null) { if(label!=null) {
if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) { if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) {
optimizationsDone++ optimizationsDone++
return NopStatement(jump.position) return NopStatement.insteadOf(jump)
} }
} }
return jump return jump
} }
override fun visit(assignment: Assignment): IStatement { override fun visit(assignment: Assignment): Statement {
if(assignment.aug_op!=null) if(assignment.aug_op!=null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer") throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
if(assignment.targets.size==1) { if(assignment.target isSameAs assignment.value) {
val target=assignment.targets[0]
if(target isSameAs assignment.value) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement.insteadOf(assignment)
} }
val targetDt = target.inferType(program, assignment) val targetDt = assignment.target.inferType(program, assignment)
val bexpr=assignment.value as? BinaryExpression val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) { if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.asNumericValue?.toDouble() val cv = bexpr.right.constValue(program)?.number?.toDouble()
if (cv == null) { if (cv == null) {
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) { if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
if (bexpr.left isSameAs bexpr.right && target isSameAs bexpr.left) { if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
bexpr.operator = "*" bexpr.operator = "*"
bexpr.right = LiteralValue.optimalInteger(2, assignment.value.position) bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
optimizationsDone++ optimizationsDone++
return assignment return assignment
} }
} }
} else { } else {
if (target isSameAs bexpr.left) { if (assignment.target isSameAs bexpr.left) {
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc // remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
// A = A <operator> B // A = A <operator> B
val vardeclDt = (target.identifier?.targetVarDecl(program.namespace))?.type val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) { when (bexpr.operator) {
"+" -> { "+" -> {
if (cv == 0.0) { if (cv == 0.0) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement.insteadOf(assignment)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) { } else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) { if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several INCs (a bit less when dealing with memory targets) // replace by several INCs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position) val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) { repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(target, "++", assignment.position)) decs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
} }
return decs return decs
} }
@ -541,13 +530,13 @@ internal class StatementOptimizer(private val program: Program, private val opti
"-" -> { "-" -> {
if (cv == 0.0) { if (cv == 0.0) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement.insteadOf(assignment)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) { } else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) { if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several DECs (a bit less when dealing with memory targets) // replace by several DECs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position) val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) { repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(target, "--", assignment.position)) decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
} }
return decs return decs
} }
@ -555,32 +544,32 @@ internal class StatementOptimizer(private val program: Program, private val opti
} }
"*" -> if (cv == 1.0) { "*" -> if (cv == 1.0) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement.insteadOf(assignment)
} }
"/" -> if (cv == 1.0) { "/" -> if (cv == 1.0) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement.insteadOf(assignment)
} }
"**" -> if (cv == 1.0) { "**" -> if (cv == 1.0) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement.insteadOf(assignment)
} }
"|" -> if (cv == 0.0) { "|" -> if (cv == 0.0) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement.insteadOf(assignment)
} }
"^" -> if (cv == 0.0) { "^" -> if (cv == 0.0) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement.insteadOf(assignment)
} }
"<<" -> { "<<" -> {
if (cv == 0.0) { if (cv == 0.0) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement.insteadOf(assignment)
} }
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) || if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) { ((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = LiteralValue.optimalInteger(0, assignment.value.position) assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment) assignment.value.linkParents(assignment)
optimizationsDone++ optimizationsDone++
} else { } else {
@ -598,11 +587,11 @@ internal class StatementOptimizer(private val program: Program, private val opti
">>" -> { ">>" -> {
if (cv == 0.0) { if (cv == 0.0) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement.insteadOf(assignment)
} }
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) || if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) { ((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = LiteralValue.optimalInteger(0, assignment.value.position) assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment) assignment.value.linkParents(assignment)
optimizationsDone++ optimizationsDone++
} else { } else {
@ -621,30 +610,25 @@ internal class StatementOptimizer(private val program: Program, private val opti
} }
} }
} }
}
return super.visit(assignment) return super.visit(assignment)
} }
override fun visit(scope: AnonymousScope): IStatement { override fun visit(scope: AnonymousScope): Statement {
val linesToRemove = deduplicateAssignments(scope.statements) val linesToRemove = deduplicateAssignments(scope.statements)
if(linesToRemove.isNotEmpty()) { if(linesToRemove.isNotEmpty()) {
linesToRemove.reversed().forEach{scope.statements.removeAt(it)} linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
} }
if(scope.parent is INameScope) {
scopesToFlatten.add(scope) // get rid of the anonymous scope
}
return super.visit(scope) return super.visit(scope)
} }
override fun visit(label: Label): IStatement { override fun visit(label: Label): Statement {
// remove duplicate labels // remove duplicate labels
val stmts = label.definingScope().statements val stmts = label.definingScope().statements
val startIdx = stmts.indexOf(label) val startIdx = stmts.indexOf(label)
if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label) if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label)
return NopStatement(label.position) return NopStatement.insteadOf(label)
return super.visit(label) return super.visit(label)
} }
@ -652,3 +636,39 @@ internal class StatementOptimizer(private val program: Program, private val opti
internal class FlattenAnonymousScopesAndRemoveNops: IAstVisitor {
private var scopesToFlatten = mutableListOf<INameScope>()
private val nopStatements = mutableListOf<NopStatement>()
override fun visit(program: Program) {
super.visit(program)
for(scope in scopesToFlatten.reversed()) {
val namescope = scope.parent as INameScope
val idx = namescope.statements.indexOf(scope as Statement)
if(idx>=0) {
val nop = NopStatement.insteadOf(namescope.statements[idx])
nop.parent = namescope as Node
namescope.statements[idx] = nop
namescope.statements.addAll(idx, scope.statements)
scope.statements.forEach { it.parent = namescope }
visit(nop)
}
}
this.nopStatements.forEach {
it.definingScope().remove(it)
}
}
override fun visit(scope: AnonymousScope) {
if(scope.parent is INameScope) {
scopesToFlatten.add(scope) // get rid of the anonymous scope
}
return super.visit(scope)
}
override fun visit(nopStatement: NopStatement) {
nopStatements.add(nopStatement)
}
}

View File

@ -1,7 +1,8 @@
package prog8.parser package prog8.parser
import org.antlr.v4.runtime.* import org.antlr.v4.runtime.*
import prog8.ast.* import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.antlr.toAst import prog8.ast.antlr.toAst
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.base.SyntaxError import prog8.ast.base.SyntaxError

View File

@ -1,7 +1,8 @@
package prog8.vm package prog8.vm
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.LiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import kotlin.math.abs import kotlin.math.abs
@ -9,11 +10,12 @@ import kotlin.math.pow
/** /**
* Rather than a literal value (LiteralValue) that occurs in the parsed source code, * Rather than a literal value (NumericLiteralValue) that occurs in the parsed source code,
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form) * this runtime value can be used to *execute* the parsed Ast (or another intermediary form)
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value. * It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
*/ */
open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null, val array: Array<Number>?=null, val heapId: Int?=null) { open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null,
val array: Array<Number>?=null, val heapId: Int?=null) {
val byteval: Short? val byteval: Short?
val wordval: Int? val wordval: Int?
@ -21,16 +23,19 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
val asBoolean: Boolean val asBoolean: Boolean
companion object { companion object {
fun from(literalValue: LiteralValue, heap: HeapValues): RuntimeValue { fun fromLv(literalValue: NumericLiteralValue): RuntimeValue {
return RuntimeValue(literalValue.type, num = literalValue.number)
}
fun fromLv(literalValue: ReferenceLiteralValue, heap: HeapValues): RuntimeValue {
return when(literalValue.type) { return when(literalValue.type) {
in NumericDatatypes -> RuntimeValue(literalValue.type, num = literalValue.asNumericValue!!) in StringDatatypes -> fromHeapId(literalValue.heapId!!, heap)
in StringDatatypes -> from(literalValue.heapId!!, heap) in ArrayDatatypes -> fromHeapId(literalValue.heapId!!, heap)
in ArrayDatatypes -> from(literalValue.heapId!!, heap) else -> throw IllegalArgumentException("weird source value $literalValue")
else -> TODO("type")
} }
} }
fun from(heapId: Int, heap: HeapValues): RuntimeValue { fun fromHeapId(heapId: Int, heap: HeapValues): RuntimeValue {
val value = heap.get(heapId) val value = heap.get(heapId)
return when { return when {
value.type in StringDatatypes -> value.type in StringDatatypes ->
@ -40,11 +45,18 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
RuntimeValue(value.type, array = value.doubleArray!!.toList().toTypedArray(), heapId = heapId) RuntimeValue(value.type, array = value.doubleArray!!.toList().toTypedArray(), heapId = heapId)
} else { } else {
val array = value.array!! val array = value.array!!
if (array.any { it.addressOf != null }) val resultArray = mutableListOf<Number>()
TODO("addressof values") for(elt in array.withIndex()){
RuntimeValue(value.type, array = array.map { it.integer!! }.toTypedArray(), heapId = heapId) if(elt.value.integer!=null)
resultArray.add(elt.value.integer!!)
else {
TODO("ADDRESSOF ${elt.value}")
} }
else -> TODO("weird type on heap") }
RuntimeValue(value.type, array = resultArray.toTypedArray(), heapId = heapId)
//RuntimeValue(value.type, array = array.map { it.integer!! }.toTypedArray(), heapId = heapId)
}
else -> throw IllegalArgumentException("weird value type on heap $value")
} }
} }
@ -53,27 +65,37 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
init { init {
when(type) { when(type) {
DataType.UBYTE -> { DataType.UBYTE -> {
byteval = (num!!.toInt() and 255).toShort() val inum = num!!.toInt()
if(inum !in 0 .. 255)
throw IllegalArgumentException("invalid value for ubyte: $inum")
byteval = inum.toShort()
wordval = null wordval = null
floatval = null floatval = null
asBoolean = byteval != 0.toShort() asBoolean = byteval != 0.toShort()
} }
DataType.BYTE -> { DataType.BYTE -> {
val v = num!!.toInt() and 255 val inum = num!!.toInt()
byteval = (if(v<128) v else v-256).toShort() if(inum !in -128 .. 127)
throw IllegalArgumentException("invalid value for byte: $inum")
byteval = inum.toShort()
wordval = null wordval = null
floatval = null floatval = null
asBoolean = byteval != 0.toShort() asBoolean = byteval != 0.toShort()
} }
DataType.UWORD -> { DataType.UWORD -> {
wordval = num!!.toInt() and 65535 val inum = num!!.toInt()
if(inum !in 0 .. 65535)
throw IllegalArgumentException("invalid value for uword: $inum")
wordval = inum
byteval = null byteval = null
floatval = null floatval = null
asBoolean = wordval != 0 asBoolean = wordval != 0
} }
DataType.WORD -> { DataType.WORD -> {
val v = num!!.toInt() and 65535 val inum = num!!.toInt()
wordval = if(v<32768) v else v - 65536 if(inum !in -32768 .. 32767)
throw IllegalArgumentException("invalid value for word: $inum")
wordval = inum
byteval = null byteval = null
floatval = null floatval = null
asBoolean = wordval != 0 asBoolean = wordval != 0
@ -95,19 +117,6 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
} }
} }
fun asLiteralValue(): LiteralValue {
return when(type) {
in ByteDatatypes -> LiteralValue(type, byteval, position = Position("", 0, 0, 0))
in WordDatatypes -> LiteralValue(type, wordvalue = wordval, position = Position("", 0, 0, 0))
DataType.FLOAT -> LiteralValue(type, floatvalue = floatval, position = Position("", 0, 0, 0))
in StringDatatypes -> LiteralValue(type, strvalue = str, position = Position("", 0, 0, 0))
in ArrayDatatypes -> LiteralValue(type,
arrayvalue = array?.map { LiteralValue.optimalNumeric(it, Position("", 0, 0, 0)) }?.toTypedArray(),
position = Position("", 0, 0, 0))
else -> TODO("strange type")
}
}
override fun toString(): String { override fun toString(): String {
return when(type) { return when(type) {
DataType.UBYTE -> "ub:%02x".format(byteval) DataType.UBYTE -> "ub:%02x".format(byteval)
@ -179,20 +188,44 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
if(leftDt== DataType.UBYTE) if(leftDt== DataType.UBYTE)
RuntimeValue(DataType.UBYTE, (number xor 255) + 1) RuntimeValue(DataType.UBYTE, (number xor 255) + 1)
else else
RuntimeValue(DataType.UBYTE, (number xor 65535) + 1) RuntimeValue(DataType.UWORD, (number xor 65535) + 1)
}
DataType.BYTE -> {
val v=result.toInt() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.WORD -> {
val v=result.toInt() and 65535
if(v<32768)
RuntimeValue(DataType.WORD, v)
else
RuntimeValue(DataType.WORD, v-65536)
} }
DataType.BYTE -> RuntimeValue(DataType.BYTE, result.toInt())
DataType.WORD -> RuntimeValue(DataType.WORD, result.toInt())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result) DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type") else -> throw ArithmeticException("$op on non-numeric type")
} }
} }
return when(leftDt) { return when(leftDt) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result.toInt()) DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result.toInt() and 255)
DataType.BYTE -> RuntimeValue(DataType.BYTE, result.toInt()) DataType.BYTE -> {
DataType.UWORD -> RuntimeValue(DataType.UWORD, result.toInt()) val v = result.toInt() and 255
DataType.WORD -> RuntimeValue(DataType.WORD, result.toInt()) if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.UWORD -> RuntimeValue(DataType.UWORD, result.toInt() and 65535)
DataType.WORD -> {
val v = result.toInt() and 65535
if(v<32768)
RuntimeValue(DataType.WORD, v)
else
RuntimeValue(DataType.WORD, v-65536)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result) DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type") else -> throw ArithmeticException("$op on non-numeric type")
} }
@ -268,10 +301,22 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
fun shl(): RuntimeValue { fun shl(): RuntimeValue {
val v = integerValue() val v = integerValue()
return when (type) { return when (type) {
DataType.UBYTE, DataType.UBYTE -> RuntimeValue(type, (v shl 1) and 255)
DataType.BYTE, DataType.UWORD -> RuntimeValue(type, (v shl 1) and 65535)
DataType.UWORD, DataType.BYTE -> {
DataType.WORD -> RuntimeValue(type, v shl 1) val value = v shl 1
if(value<128)
RuntimeValue(type, value)
else
RuntimeValue(type, value-256)
}
DataType.WORD -> {
val value = v shl 1
if(value<32768)
RuntimeValue(type, value)
else
RuntimeValue(type, value-65536)
}
else -> throw ArithmeticException("invalid type for shl: $type") else -> throw ArithmeticException("invalid type for shl: $type")
} }
} }
@ -409,16 +454,32 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
fun inv(): RuntimeValue { fun inv(): RuntimeValue {
return when(type) { return when(type) {
in ByteDatatypes -> RuntimeValue(type, byteval!!.toInt().inv()) DataType.UBYTE -> RuntimeValue(type, byteval!!.toInt().inv() and 255)
in WordDatatypes -> RuntimeValue(type, wordval!!.inv()) DataType.UWORD -> RuntimeValue(type, wordval!!.inv() and 65535)
DataType.BYTE -> RuntimeValue(type, byteval!!.toInt().inv())
DataType.WORD -> RuntimeValue(type, wordval!!.inv())
else -> throw ArithmeticException("inv can only work on byte/word") else -> throw ArithmeticException("inv can only work on byte/word")
} }
} }
fun inc(): RuntimeValue { fun inc(): RuntimeValue {
return when(type) { return when(type) {
in ByteDatatypes -> RuntimeValue(type, byteval!! + 1) DataType.UBYTE -> RuntimeValue(type, (byteval!! + 1) and 255)
in WordDatatypes -> RuntimeValue(type, wordval!! + 1) DataType.UWORD -> RuntimeValue(type, (wordval!! + 1) and 65535)
DataType.BYTE -> {
val newval = byteval!! + 1
if(newval == 128)
RuntimeValue(type, -128)
else
RuntimeValue(type, newval)
}
DataType.WORD -> {
val newval = wordval!! + 1
if(newval == 32768)
RuntimeValue(type, -32768)
else
RuntimeValue(type, newval)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! + 1) DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! + 1)
else -> throw ArithmeticException("inc can only work on numeric types") else -> throw ArithmeticException("inc can only work on numeric types")
} }
@ -426,8 +487,22 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
fun dec(): RuntimeValue { fun dec(): RuntimeValue {
return when(type) { return when(type) {
in ByteDatatypes -> RuntimeValue(type, byteval!! - 1) DataType.UBYTE -> RuntimeValue(type, (byteval!! - 1) and 255)
in WordDatatypes -> RuntimeValue(type, wordval!! - 1) DataType.UWORD -> RuntimeValue(type, (wordval!! - 1) and 65535)
DataType.BYTE -> {
val newval = byteval!! - 1
if(newval == -129)
RuntimeValue(type, 127)
else
RuntimeValue(type, newval)
}
DataType.WORD -> {
val newval = wordval!! - 1
if(newval == -32769)
RuntimeValue(type, 32767)
else
RuntimeValue(type, newval)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! - 1) DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! - 1)
else -> throw ArithmeticException("dec can only work on numeric types") else -> throw ArithmeticException("dec can only work on numeric types")
} }
@ -446,9 +521,21 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
DataType.UBYTE -> { DataType.UBYTE -> {
when (targetType) { when (targetType) {
DataType.UBYTE -> this DataType.UBYTE -> this
DataType.BYTE -> RuntimeValue(DataType.BYTE, byteval) DataType.BYTE -> {
val nval=byteval!!.toInt()
if(nval<128)
RuntimeValue(DataType.BYTE, nval)
else
RuntimeValue(DataType.BYTE, nval-256)
}
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue()) DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue())
DataType.WORD -> RuntimeValue(DataType.WORD, numericValue()) DataType.WORD -> {
val nval = numericValue().toInt()
if(nval<32768)
RuntimeValue(DataType.WORD, nval)
else
RuntimeValue(DataType.WORD, nval-65536)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue()) DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType") else -> throw ArithmeticException("invalid type cast from $type to $targetType")
} }
@ -456,8 +543,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
DataType.BYTE -> { DataType.BYTE -> {
when (targetType) { when (targetType) {
DataType.BYTE -> this DataType.BYTE -> this
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue()) DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue()) DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue()) DataType.WORD -> RuntimeValue(DataType.WORD, integerValue())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue()) DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType") else -> throw ArithmeticException("invalid type cast from $type to $targetType")
@ -465,18 +552,36 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
} }
DataType.UWORD -> { DataType.UWORD -> {
when (targetType) { when (targetType) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, integerValue()) DataType.BYTE -> {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue()) val v=integerValue()
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> this DataType.UWORD -> this
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue()) DataType.WORD -> {
val v=integerValue()
if(v<32768)
RuntimeValue(DataType.WORD, v)
else
RuntimeValue(DataType.WORD, v-65536)
}
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue()) DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType") else -> throw ArithmeticException("invalid type cast from $type to $targetType")
} }
} }
DataType.WORD -> { DataType.WORD -> {
when (targetType) { when (targetType) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, integerValue()) DataType.BYTE -> {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue()) val v = integerValue() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 65535)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue()) DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue())
DataType.WORD -> this DataType.WORD -> this
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue()) DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())

View File

@ -1,18 +1,23 @@
package prog8.vm.astvm package prog8.vm.astvm
import prog8.ast.* import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.base.initvarsSubName import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange import prog8.vm.RuntimeValueRange
import prog8.compiler.target.c64.Petscii
import java.awt.EventQueue import java.awt.EventQueue
import java.io.CharConversionException
import java.util.*
import kotlin.NoSuchElementException import kotlin.NoSuchElementException
import kotlin.concurrent.fixedRateTimer import kotlin.concurrent.fixedRateTimer
import kotlin.math.min import kotlin.math.*
import kotlin.random.Random
class VmExecutionException(msg: String?) : Exception(msg) class VmExecutionException(msg: String?) : Exception(msg)
@ -28,31 +33,31 @@ class StatusFlags {
var negative: Boolean = false var negative: Boolean = false
var irqd: Boolean = false var irqd: Boolean = false
private fun setFlags(value: LiteralValue?) { private fun setFlags(value: NumericLiteralValue?) {
if (value != null) { if (value != null) {
when (value.type) { when (value.type) {
DataType.UBYTE -> { DataType.UBYTE -> {
val v = value.bytevalue!!.toInt() val v = value.number.toInt()
negative = v > 127 negative = v > 127
zero = v == 0 zero = v == 0
} }
DataType.BYTE -> { DataType.BYTE -> {
val v = value.bytevalue!!.toInt() val v = value.number.toInt()
negative = v < 0 negative = v < 0
zero = v == 0 zero = v == 0
} }
DataType.UWORD -> { DataType.UWORD -> {
val v = value.wordvalue!! val v = value.number.toInt()
negative = v > 32767 negative = v > 32767
zero = v == 0 zero = v == 0
} }
DataType.WORD -> { DataType.WORD -> {
val v = value.wordvalue!! val v = value.number.toInt()
negative = v < 0 negative = v < 0
zero = v == 0 zero = v == 0
} }
DataType.FLOAT -> { DataType.FLOAT -> {
val flt = value.floatvalue!! val flt = value.number.toDouble()
negative = flt < 0.0 negative = flt < 0.0
zero = flt == 0.0 zero = flt == 0.0
} }
@ -94,14 +99,12 @@ class RuntimeVariables {
fun get(scope: INameScope, name: String): RuntimeValue { fun get(scope: INameScope, name: String): RuntimeValue {
val where = vars.getValue(scope) val where = vars.getValue(scope)
val value = where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name") return where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
return value
} }
fun getMemoryAddress(scope: INameScope, name: String): Int { fun getMemoryAddress(scope: INameScope, name: String): Int {
val where = memvars.getValue(scope) val where = memvars.getValue(scope)
val address = where[name] ?: throw NoSuchElementException("no such runtime memory-variable: ${scope.name}.$name") return where[name] ?: throw NoSuchElementException("no such runtime memory-variable: ${scope.name}.$name")
return address
} }
fun swap(a1: VarDecl, a2: VarDecl) = swap(a1.definingScope(), a1.name, a2.definingScope(), a2.name) fun swap(a1: VarDecl, a2: VarDecl) = swap(a1.definingScope(), a1.name, a2.definingScope(), a2.name)
@ -128,9 +131,18 @@ class AstVm(val program: Program) {
val bootTime = System.currentTimeMillis() val bootTime = System.currentTimeMillis()
var rtcOffset = bootTime var rtcOffset = bootTime
private val rnd = Random(0)
private val statusFlagsSave = Stack<StatusFlags>()
private val registerXsave = Stack<RuntimeValue>()
private val registerYsave = Stack<RuntimeValue>()
private val registerAsave = Stack<RuntimeValue>()
init { init {
// observe the jiffyclock // observe the jiffyclock and screen matrix
mem.observe(0xa0, 0xa1, 0xa2) mem.observe(0xa0, 0xa1, 0xa2)
for(i in 1024..2023)
mem.observe(i)
dialog.requestFocusInWindow() dialog.requestFocusInWindow()
@ -159,6 +171,11 @@ class AstVm(val program: Program) {
val jiffies = (time_hi.toInt() shl 16) + (time_mid.toInt() shl 8) + time_lo val jiffies = (time_hi.toInt() shl 16) + (time_mid.toInt() shl 8) + time_lo
rtcOffset = bootTime - (jiffies*1000/60) rtcOffset = bootTime - (jiffies*1000/60)
} }
if(address in 1024..2023) {
// write to the screen matrix
val scraddr = address-1024
dialog.canvas.setChar(scraddr % 40, scraddr / 40, value, 1)
}
return value return value
} }
@ -213,6 +230,7 @@ class AstVm(val program: Program) {
} }
} }
} }
dialog.canvas.printText("\n<program ended>", true)
println("PROGRAM EXITED!") println("PROGRAM EXITED!")
dialog.title = "PROGRAM EXITED" dialog.title = "PROGRAM EXITED"
} catch (tx: VmTerminationException) { } catch (tx: VmTerminationException) {
@ -228,7 +246,11 @@ class AstVm(val program: Program) {
if(statusflags.irqd) if(statusflags.irqd)
return // interrupt is disabled return // interrupt is disabled
val jiffies = min((timeStamp-rtcOffset)*60/1000, 24*3600*60-1) var jiffies = (timeStamp-rtcOffset)*60/1000
if(jiffies>24*3600*60-1) {
jiffies = 0
rtcOffset = timeStamp
}
// update the C-64 60hz jiffy clock in the ZP addresses: // update the C-64 60hz jiffy clock in the ZP addresses:
mem.setUByte_DMA(0x00a0, (jiffies ushr 16).toShort()) mem.setUByte_DMA(0x00a0, (jiffies ushr 16).toShort())
mem.setUByte_DMA(0x00a1, (jiffies ushr 8 and 255).toShort()) mem.setUByte_DMA(0x00a1, (jiffies ushr 8 and 255).toShort())
@ -236,17 +258,19 @@ class AstVm(val program: Program) {
} }
private val runtimeVariables = RuntimeVariables() private val runtimeVariables = RuntimeVariables()
private val functions = BuiltinFunctions() private val evalCtx = EvalContext(program, mem, statusflags, runtimeVariables, ::performBuiltinFunction, ::executeSubroutine)
private val evalCtx = EvalContext(program, mem, statusflags, runtimeVariables, functions, ::executeSubroutine)
class LoopControlBreak : Exception() class LoopControlBreak : Exception()
class LoopControlContinue : Exception() class LoopControlContinue : Exception()
class LoopControlReturn(val returnvalues: List<RuntimeValue>) : Exception() class LoopControlReturn(val returnvalue: RuntimeValue?) : Exception()
class LoopControlJump(val identifier: IdentifierReference?, val address: Int?, val generatedLabel: String?) : Exception() class LoopControlJump(val identifier: IdentifierReference?, val address: Int?, val generatedLabel: String?) : Exception()
internal fun executeSubroutine(sub: Subroutine, arguments: List<RuntimeValue>, startlabel: Label?=null): List<RuntimeValue> { internal fun executeSubroutine(sub: Subroutine, arguments: List<RuntimeValue>, startAtLabel: Label?=null): RuntimeValue? {
assert(!sub.isAsmSubroutine) if(sub.isAsmSubroutine) {
return performSyscall(sub, arguments)
}
if (sub.statements.isEmpty()) if (sub.statements.isEmpty())
throw VmTerminationException("scope contains no statements: $sub") throw VmTerminationException("scope contains no statements: $sub")
if (arguments.size != sub.parameters.size) if (arguments.size != sub.parameters.size)
@ -259,10 +283,10 @@ class AstVm(val program: Program) {
} }
val statements = sub.statements.iterator() val statements = sub.statements.iterator()
if(startlabel!=null) { if(startAtLabel!=null) {
do { do {
val stmt = statements.next() val stmt = statements.next()
} while(stmt!==startlabel) } while(stmt!==startAtLabel)
} }
try { try {
@ -277,7 +301,7 @@ class AstVm(val program: Program) {
} }
} }
} catch (r: LoopControlReturn) { } catch (r: LoopControlReturn) {
return r.returnvalues return r.returnvalue
} }
throw VmTerminationException("instruction pointer overflow, is a return missing? $sub") throw VmTerminationException("instruction pointer overflow, is a return missing? $sub")
} }
@ -289,7 +313,7 @@ class AstVm(val program: Program) {
} }
private fun executeStatement(sub: INameScope, stmt: IStatement) { private fun executeStatement(sub: INameScope, stmt: Statement) {
instructionCounter++ instructionCounter++
if (instructionCounter % 200 == 0) if (instructionCounter % 200 == 0)
Thread.sleep(1) Thread.sleep(1)
@ -324,7 +348,7 @@ class AstVm(val program: Program) {
executeSwap(stmt) executeSwap(stmt)
} else { } else {
val args = evaluate(stmt.arglist) val args = evaluate(stmt.arglist)
functions.performBuiltinFunction(target.name, args, statusflags) performBuiltinFunction(target.name, args, statusflags)
} }
} }
else -> { else -> {
@ -332,23 +356,29 @@ class AstVm(val program: Program) {
} }
} }
} }
is Return -> throw LoopControlReturn(stmt.values.map { evaluate(it, evalCtx) }) is Return -> {
val value =
if(stmt.value==null)
null
else
evaluate(stmt.value!!, evalCtx)
throw LoopControlReturn(value)
}
is Continue -> throw LoopControlContinue() is Continue -> throw LoopControlContinue()
is Break -> throw LoopControlBreak() is Break -> throw LoopControlBreak()
is Assignment -> { is Assignment -> {
if (stmt.aug_op != null) if (stmt.aug_op != null)
throw VmExecutionException("augmented assignment should have been converted into regular one $stmt") throw VmExecutionException("augmented assignment should have been converted into regular one $stmt")
val target = stmt.singleTarget
if (target != null) {
val value = evaluate(stmt.value, evalCtx) val value = evaluate(stmt.value, evalCtx)
performAssignment(target, value, stmt, evalCtx) performAssignment(stmt.target, value, stmt, evalCtx)
} else TODO("assign multitarget $stmt")
} }
is PostIncrDecr -> { is PostIncrDecr -> {
when { when {
stmt.target.identifier != null -> { stmt.target.identifier != null -> {
val ident = stmt.definingScope().lookup(stmt.target.identifier!!.nameInSource, stmt) as VarDecl val ident = stmt.definingScope().lookup(stmt.target.identifier!!.nameInSource, stmt) as VarDecl
val identScope = ident.definingScope() val identScope = ident.definingScope()
when(ident.type){
VarDeclType.VAR -> {
var value = runtimeVariables.get(identScope, ident.name) var value = runtimeVariables.get(identScope, ident.name)
value = when { value = when {
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1)) stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1))
@ -357,12 +387,50 @@ class AstVm(val program: Program) {
} }
runtimeVariables.set(identScope, ident.name, value) runtimeVariables.set(identScope, ident.name, value)
} }
VarDeclType.MEMORY -> {
val addr=ident.value!!.constValue(program)!!.number.toInt()
val newval = when {
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
mem.setUByte(addr,newval.toShort())
}
VarDeclType.CONST -> throw VmExecutionException("can't be const")
}
}
stmt.target.memoryAddress != null -> { stmt.target.memoryAddress != null -> {
TODO("postincrdecr memory $stmt") val addr = evaluate(stmt.target.memoryAddress!!.addressExpression, evalCtx).integerValue()
val newval = when {
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
mem.setUByte(addr,newval.toShort())
} }
stmt.target.arrayindexed != null -> { stmt.target.arrayindexed != null -> {
TODO("postincrdecr array $stmt") val arrayvar = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name)
val elementType = stmt.target.arrayindexed!!.inferType(program)!!
val index = evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx).integerValue()
var value = RuntimeValue(elementType, arrayvalue.array!![index].toInt())
value = when {
stmt.operator == "++" -> value.inc()
stmt.operator == "--" -> value.dec()
else -> throw VmExecutionException("strange postincdec operator $stmt")
} }
arrayvalue.array[index] = value.numericValue()
}
stmt.target.register != null -> {
var value = runtimeVariables.get(program.namespace, stmt.target.register!!.name)
value = when {
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1))
stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1))
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
runtimeVariables.set(program.namespace, stmt.target.register!!.name, value)
}
else -> throw VmExecutionException("empty postincrdecr? $stmt")
} }
} }
is Jump -> throw LoopControlJump(stmt.identifier, stmt.address, stmt.generatedLabel) is Jump -> throw LoopControlJump(stmt.identifier, stmt.address, stmt.generatedLabel)
@ -370,7 +438,7 @@ class AstVm(val program: Program) {
if (sub is Subroutine) { if (sub is Subroutine) {
val args = sub.parameters.map { runtimeVariables.get(sub, it.name) } val args = sub.parameters.map { runtimeVariables.get(sub, it.name) }
performSyscall(sub, args) performSyscall(sub, args)
throw LoopControlReturn(emptyList()) throw LoopControlReturn(null)
} }
throw VmExecutionException("can't execute inline assembly in $sub") throw VmExecutionException("can't execute inline assembly in $sub")
} }
@ -445,13 +513,13 @@ class AstVm(val program: Program) {
is WhenStatement -> { is WhenStatement -> {
val condition=evaluate(stmt.condition, evalCtx) val condition=evaluate(stmt.condition, evalCtx)
for(choice in stmt.choices) { for(choice in stmt.choices) {
if(choice.value==null) { if(choice.values==null) {
// the 'else' choice // the 'else' choice
executeAnonymousScope(choice.statements) executeAnonymousScope(choice.statements)
break break
} else { } else {
val value = choice.value.constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}") val value = choice.values.single().constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}")
val rtval = RuntimeValue.from(value, evalCtx.program.heap) val rtval = RuntimeValue.fromLv(value)
if(condition==rtval) { if(condition==rtval) {
executeAnonymousScope(choice.statements) executeAnonymousScope(choice.statements)
break break
@ -476,7 +544,7 @@ class AstVm(val program: Program) {
performAssignment(target2, value1, swap, evalCtx) performAssignment(target2, value1, swap, evalCtx)
} }
fun performAssignment(target: AssignTarget, value: RuntimeValue, contextStmt: IStatement, evalCtx: EvalContext) { fun performAssignment(target: AssignTarget, value: RuntimeValue, contextStmt: Statement, evalCtx: EvalContext) {
when { when {
target.identifier != null -> { target.identifier != null -> {
val decl = contextStmt.definingScope().lookup(target.identifier.nameInSource, contextStmt) as? VarDecl val decl = contextStmt.definingScope().lookup(target.identifier.nameInSource, contextStmt) as? VarDecl
@ -491,7 +559,7 @@ class AstVm(val program: Program) {
DataType.FLOAT -> mem.setFloat(address, value.floatval!!) DataType.FLOAT -> mem.setFloat(address, value.floatval!!)
DataType.STR -> mem.setString(address, value.str!!) DataType.STR -> mem.setString(address, value.str!!)
DataType.STR_S -> mem.setScreencodeString(address, value.str!!) DataType.STR_S -> mem.setScreencodeString(address, value.str!!)
else -> TODO("set memvar $decl") else -> throw VmExecutionException("weird memaddress type $decl")
} }
} else } else
runtimeVariables.set(decl.definingScope(), decl.name, value) runtimeVariables.set(decl.definingScope(), decl.name, value)
@ -501,6 +569,8 @@ class AstVm(val program: Program) {
evalCtx.mem.setUByte(address, value.byteval!!) evalCtx.mem.setUByte(address, value.byteval!!)
} }
target.arrayindexed != null -> { target.arrayindexed != null -> {
val vardecl = target.arrayindexed.identifier.targetVarDecl(program.namespace)!!
if(vardecl.type==VarDeclType.VAR) {
val array = evaluate(target.arrayindexed.identifier, evalCtx) val array = evaluate(target.arrayindexed.identifier, evalCtx)
val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx) val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx)
when (array.type) { when (array.type) {
@ -543,6 +613,20 @@ class AstVm(val program: Program) {
runtimeVariables.set(identScope, ident.name, RuntimeValue(array.type, str = newstr, heapId = array.heapId)) runtimeVariables.set(identScope, ident.name, RuntimeValue(array.type, str = newstr, heapId = array.heapId))
} }
} }
else {
val address = (vardecl.value as NumericLiteralValue).number.toInt()
val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx).integerValue()
val elementType = target.arrayindexed.inferType(program)!!
when(elementType) {
DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!)
DataType.BYTE -> mem.setSByte(address+index, value.byteval!!)
DataType.UWORD -> mem.setUWord(address+index*2, value.wordval!!)
DataType.WORD -> mem.setSWord(address+index*2, value.wordval!!)
DataType.FLOAT -> mem.setFloat(address+index* MachineDefinition.Mflpt5.MemorySize, value.floatval!!)
else -> throw VmExecutionException("strange array elt type $elementType")
}
}
}
target.register != null -> { target.register != null -> {
runtimeVariables.set(program.namespace, target.register.name, value) runtimeVariables.set(program.namespace, target.register.name, value)
} }
@ -557,56 +641,56 @@ class AstVm(val program: Program) {
executeAnonymousScope(stmt.body) executeAnonymousScope(stmt.body)
} }
private fun evaluate(args: List<IExpression>) = args.map { evaluate(it, evalCtx) } private fun evaluate(args: List<Expression>) = args.map { evaluate(it, evalCtx) }
private fun performSyscall(sub: Subroutine, args: List<RuntimeValue>) { private fun performSyscall(sub: Subroutine, args: List<RuntimeValue>): RuntimeValue? {
assert(sub.isAsmSubroutine) var result: RuntimeValue? = null
when (sub.scopedname) { when (sub.scopedname) {
"c64scr.print" -> { "c64scr.print" -> {
// if the argument is an UWORD, consider it to be the "address" of the string (=heapId) // if the argument is an UWORD, consider it to be the "address" of the string (=heapId)
if (args[0].wordval != null) { if (args[0].wordval != null) {
val str = program.heap.get(args[0].wordval!!).str!! val str = program.heap.get(args[0].wordval!!).str!!
dialog.canvas.printText(str, 1, true) dialog.canvas.printText(str, true)
} else } else
dialog.canvas.printText(args[0].str!!, 1, true) dialog.canvas.printText(args[0].str!!, true)
} }
"c64scr.print_ub" -> { "c64scr.print_ub" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), 1, true) dialog.canvas.printText(args[0].byteval!!.toString(), true)
} }
"c64scr.print_ub0" -> { "c64scr.print_ub0" -> {
dialog.canvas.printText("%03d".format(args[0].byteval!!), 1, true) dialog.canvas.printText("%03d".format(args[0].byteval!!), true)
} }
"c64scr.print_b" -> { "c64scr.print_b" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), 1, true) dialog.canvas.printText(args[0].byteval!!.toString(), true)
} }
"c64scr.print_uw" -> { "c64scr.print_uw" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), 1, true) dialog.canvas.printText(args[0].wordval!!.toString(), true)
} }
"c64scr.print_uw0" -> { "c64scr.print_uw0" -> {
dialog.canvas.printText("%05d".format(args[0].wordval!!), 1, true) dialog.canvas.printText("%05d".format(args[0].wordval!!), true)
} }
"c64scr.print_w" -> { "c64scr.print_w" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), 1, true) dialog.canvas.printText(args[0].wordval!!.toString(), true)
} }
"c64scr.print_ubhex" -> { "c64scr.print_ubhex" -> {
val prefix = if (args[0].asBoolean) "$" else "" val prefix = if (args[0].asBoolean) "$" else ""
val number = args[1].byteval!! val number = args[1].byteval!!
dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", 1, true) dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", true)
} }
"c64scr.print_uwhex" -> { "c64scr.print_uwhex" -> {
val prefix = if (args[0].asBoolean) "$" else "" val prefix = if (args[0].asBoolean) "$" else ""
val number = args[1].wordval!! val number = args[1].wordval!!
dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", 1, true) dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", true)
} }
"c64scr.print_uwbin" -> { "c64scr.print_uwbin" -> {
val prefix = if (args[0].asBoolean) "%" else "" val prefix = if (args[0].asBoolean) "%" else ""
val number = args[1].wordval!! val number = args[1].wordval!!
dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", 1, true) dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", true)
} }
"c64scr.print_ubbin" -> { "c64scr.print_ubbin" -> {
val prefix = if (args[0].asBoolean) "%" else "" val prefix = if (args[0].asBoolean) "%" else ""
val number = args[1].byteval!! val number = args[1].byteval!!
dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", 1, true) dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", true)
} }
"c64scr.clear_screenchars" -> { "c64scr.clear_screenchars" -> {
dialog.canvas.clearScreen(6) dialog.canvas.clearScreen(6)
@ -620,14 +704,252 @@ class AstVm(val program: Program) {
"c64scr.plot" -> { "c64scr.plot" -> {
dialog.canvas.setCursorPos(args[0].integerValue(), args[1].integerValue()) dialog.canvas.setCursorPos(args[0].integerValue(), args[1].integerValue())
} }
"c64.CHROUT" -> { "c64scr.input_chars" -> {
dialog.canvas.printChar(args[0].byteval!!) val input=mutableListOf<Char>()
for(i in 0 until 80) {
while(dialog.keyboardBuffer.isEmpty()) {
Thread.sleep(10)
}
val char=dialog.keyboardBuffer.pop()
if(char=='\n')
break
else {
input.add(char)
val printChar = try {
Petscii.encodePetscii("" + char, true).first()
} catch (cv: CharConversionException) {
0x3f.toShort()
}
dialog.canvas.printPetscii(printChar)
}
}
val inputStr = input.joinToString("")
val heapId = args[0].wordval!!
val origStr = program.heap.get(heapId).str!!
val paddedStr=inputStr.padEnd(origStr.length+1, '\u0000').substring(0, origStr.length)
program.heap.update(heapId, paddedStr)
result = RuntimeValue(DataType.UBYTE, paddedStr.indexOf('\u0000'))
} }
"c64flt.print_f" -> { "c64flt.print_f" -> {
dialog.canvas.printText(args[0].floatval.toString(), 1, true) dialog.canvas.printText(args[0].floatval.toString(), true)
}
"c64.CHROUT" -> {
dialog.canvas.printPetscii(args[0].byteval!!)
}
"c64.CLEARSCR" -> {
dialog.canvas.clearScreen(6)
}
"c64.CHRIN" -> {
while(dialog.keyboardBuffer.isEmpty()) {
Thread.sleep(10)
}
val char=dialog.keyboardBuffer.pop()
result = RuntimeValue(DataType.UBYTE, char.toShort())
}
"c64utils.str2uword" -> {
val heapId = args[0].wordval!!
val argString = program.heap.get(heapId).str!!
val numericpart = argString.takeWhile { it.isDigit() }
result = RuntimeValue(DataType.UWORD, numericpart.toInt() and 65535)
} }
else -> TODO("syscall ${sub.scopedname} $sub") else -> TODO("syscall ${sub.scopedname} $sub")
} }
return result
}
private fun performBuiltinFunction(name: String, args: List<RuntimeValue>, statusflags: StatusFlags): RuntimeValue? {
return when (name) {
"rnd" -> RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255)
"rndw" -> RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535)
"rndf" -> RuntimeValue(DataType.FLOAT, rnd.nextDouble())
"lsb" -> RuntimeValue(DataType.UBYTE, args[0].integerValue() and 255)
"msb" -> RuntimeValue(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
"sin" -> RuntimeValue(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
"sin8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * sin(rad)).toShort())
}
"sin8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
}
"sin16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * sin(rad)).toShort())
}
"sin16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort())
}
"cos" -> RuntimeValue(DataType.FLOAT, cos(args[0].numericValue().toDouble()))
"cos8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * cos(rad)).toShort())
}
"cos8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort())
}
"cos16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * cos(rad)).toShort())
}
"cos16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * cos(rad)).toShort())
}
"tan" -> RuntimeValue(DataType.FLOAT, tan(args[0].numericValue().toDouble()))
"atan" -> RuntimeValue(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
"ln" -> RuntimeValue(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
"log2" -> RuntimeValue(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
"sqrt" -> RuntimeValue(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
"sqrt16" -> RuntimeValue(DataType.UBYTE, sqrt(args[0].wordval!!.toDouble()).toInt())
"rad" -> RuntimeValue(DataType.FLOAT, Math.toRadians(args[0].numericValue().toDouble()))
"deg" -> RuntimeValue(DataType.FLOAT, Math.toDegrees(args[0].numericValue().toDouble()))
"round" -> RuntimeValue(DataType.FLOAT, round(args[0].numericValue().toDouble()))
"floor" -> RuntimeValue(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
"ceil" -> RuntimeValue(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
"rol" -> {
val (result, newCarry) = args[0].rol(statusflags.carry)
statusflags.carry = newCarry
return result
}
"rol2" -> args[0].rol2()
"ror" -> {
val (result, newCarry) = args[0].ror(statusflags.carry)
statusflags.carry = newCarry
return result
}
"ror2" -> args[0].ror2()
"lsl" -> args[0].shl()
"lsr" -> args[0].shr()
"abs" -> {
when (args[0].type) {
DataType.UBYTE -> args[0]
DataType.BYTE -> RuntimeValue(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
DataType.UWORD -> args[0]
DataType.WORD -> RuntimeValue(DataType.UWORD, abs(args[0].numericValue().toDouble()))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
else -> throw VmExecutionException("strange abs type ${args[0]}")
}
}
"max" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.max())
}
"min" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.min())
}
"avg" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.FLOAT, numbers.average())
}
"sum" -> {
val sum = args.single().array!!.map { it.toDouble() }.sum()
when (args[0].type) {
DataType.ARRAY_UB -> RuntimeValue(DataType.UWORD, sum)
DataType.ARRAY_B -> RuntimeValue(DataType.WORD, sum)
DataType.ARRAY_UW -> RuntimeValue(DataType.UWORD, sum)
DataType.ARRAY_W -> RuntimeValue(DataType.WORD, sum)
DataType.ARRAY_F -> RuntimeValue(DataType.FLOAT, sum)
else -> throw VmExecutionException("weird sum type ${args[0]}")
}
}
"any" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
}
"all" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
}
"swap" ->
throw VmExecutionException("swap() cannot be implemented as a function")
"strlen" -> {
val zeroIndex = args[0].str!!.indexOf(0.toChar())
if (zeroIndex >= 0)
RuntimeValue(DataType.UBYTE, zeroIndex)
else
RuntimeValue(DataType.UBYTE, args[0].str!!.length)
}
"memset" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount) {
target[i] = value
}
null
}
"memsetw" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount step 2) {
target[i * 2] = value and 255
target[i * 2 + 1] = value ushr 8
}
null
}
"memcopy" -> {
val source = args[0].array!!
val dest = args[1].array!!
val amount = args[2].integerValue()
for(i in 0 until amount) {
dest[i] = source[i]
}
null
}
"mkword" -> {
val result = (args[1].integerValue() shl 8) or args[0].integerValue()
RuntimeValue(DataType.UWORD, result)
}
"set_carry" -> {
statusflags.carry=true
null
}
"clear_carry" -> {
statusflags.carry=false
null
}
"set_irqd" -> {
statusflags.irqd=true
null
}
"clear_irqd" -> {
statusflags.irqd=false
null
}
"read_flags" -> {
val carry = if(statusflags.carry) 1 else 0
val zero = if(statusflags.zero) 2 else 0
val irqd = if(statusflags.irqd) 4 else 0
val negative = if(statusflags.negative) 128 else 0
RuntimeValue(DataType.UBYTE, carry or zero or irqd or negative)
}
"rsave" -> {
statusFlagsSave.push(statusflags)
registerAsave.push(runtimeVariables.get(program.namespace, Register.A.name))
registerXsave.push(runtimeVariables.get(program.namespace, Register.X.name))
registerYsave.push(runtimeVariables.get(program.namespace, Register.Y.name))
null
}
"rrestore" -> {
val flags = statusFlagsSave.pop()
statusflags.carry = flags.carry
statusflags.negative = flags.negative
statusflags.zero = flags.zero
statusflags.irqd = flags.irqd
runtimeVariables.set(program.namespace, Register.A.name, registerAsave.pop())
runtimeVariables.set(program.namespace, Register.X.name, registerXsave.pop())
runtimeVariables.set(program.namespace, Register.Y.name, registerYsave.pop())
null
}
else -> TODO("builtin function $name")
} }
} }
}

View File

@ -1,204 +0,0 @@
package prog8.vm.astvm
import prog8.ast.base.DataType
import prog8.vm.RuntimeValue
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import java.util.*
import kotlin.math.*
import kotlin.random.Random
class BuiltinFunctions {
private val rnd = Random(0)
private val statusFlagsSave = Stack<StatusFlags>()
fun performBuiltinFunction(name: String, args: List<RuntimeValue>, statusflags: StatusFlags): RuntimeValue? {
return when (name) {
"rnd" -> RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255)
"rndw" -> RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535)
"rndf" -> RuntimeValue(DataType.FLOAT, rnd.nextDouble())
"lsb" -> RuntimeValue(DataType.UBYTE, args[0].integerValue() and 255)
"msb" -> RuntimeValue(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
"sin" -> RuntimeValue(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
"sin8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * sin(rad)).toShort())
}
"sin8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
}
"sin16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * sin(rad)).toShort())
}
"sin16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort())
}
"cos" -> RuntimeValue(DataType.FLOAT, cos(args[0].numericValue().toDouble()))
"cos8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * cos(rad)).toShort())
}
"cos8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort())
}
"cos16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * cos(rad)).toShort())
}
"cos16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * cos(rad)).toShort())
}
"tan" -> RuntimeValue(DataType.FLOAT, tan(args[0].numericValue().toDouble()))
"atan" -> RuntimeValue(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
"ln" -> RuntimeValue(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
"log2" -> RuntimeValue(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
"sqrt" -> RuntimeValue(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
"sqrt16" -> RuntimeValue(DataType.UBYTE, sqrt(args[0].wordval!!.toDouble()).toInt())
"rad" -> RuntimeValue(DataType.FLOAT, toRadians(args[0].numericValue().toDouble()))
"deg" -> RuntimeValue(DataType.FLOAT, toDegrees(args[0].numericValue().toDouble()))
"round" -> RuntimeValue(DataType.FLOAT, round(args[0].numericValue().toDouble()))
"floor" -> RuntimeValue(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
"ceil" -> RuntimeValue(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
"rol" -> {
val (result, newCarry) = args[0].rol(statusflags.carry)
statusflags.carry = newCarry
return result
}
"rol2" -> args[0].rol2()
"ror" -> {
val (result, newCarry) = args[0].ror(statusflags.carry)
statusflags.carry = newCarry
return result
}
"ror2" -> args[0].ror2()
"lsl" -> args[0].shl()
"lsr" -> args[0].shr()
"abs" -> {
when (args[0].type) {
DataType.UBYTE -> args[0]
DataType.BYTE -> RuntimeValue(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
DataType.UWORD -> args[0]
DataType.WORD -> RuntimeValue(DataType.UWORD, abs(args[0].numericValue().toDouble()))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
else -> TODO("strange abs type")
}
}
"max" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(args[0].type, numbers.max())
}
"min" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(args[0].type, numbers.min())
}
"avg" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.FLOAT, numbers.average())
}
"sum" -> {
val sum = args.map { it.numericValue().toDouble() }.sum()
when (args[0].type) {
DataType.UBYTE -> RuntimeValue(DataType.UWORD, sum)
DataType.BYTE -> RuntimeValue(DataType.WORD, sum)
DataType.UWORD -> RuntimeValue(DataType.UWORD, sum)
DataType.WORD -> RuntimeValue(DataType.WORD, sum)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, sum)
else -> TODO("weird sum type")
}
}
"any" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
}
"all" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
}
"swap" ->
throw VmExecutionException("swap() cannot be implemented as a function")
"strlen" -> {
val zeroIndex = args[0].str!!.indexOf(0.toChar())
if (zeroIndex >= 0)
RuntimeValue(DataType.UBYTE, zeroIndex)
else
RuntimeValue(DataType.UBYTE, args[0].str!!.length)
}
"memset" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount) {
target[i] = value
}
null
}
"memsetw" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount step 2) {
target[i * 2] = value and 255
target[i * 2 + 1] = value ushr 8
}
null
}
"memcopy" -> {
val source = args[0].array!!
val dest = args[1].array!!
val amount = args[2].integerValue()
for(i in 0 until amount) {
dest[i] = source[i]
}
null
}
"mkword" -> {
val result = (args[1].integerValue() shl 8) or args[0].integerValue()
RuntimeValue(DataType.UWORD, result)
}
"set_carry" -> {
statusflags.carry=true
null
}
"clear_carry" -> {
statusflags.carry=false
null
}
"set_irqd" -> {
statusflags.irqd=true
null
}
"clear_irqd" -> {
statusflags.irqd=false
null
}
"read_flags" -> {
val carry = if(statusflags.carry) 1 else 0
val zero = if(statusflags.zero) 2 else 0
val irqd = if(statusflags.irqd) 4 else 0
val negative = if(statusflags.negative) 128 else 0
RuntimeValue(DataType.UBYTE, carry or zero or irqd or negative)
}
"rsave" -> {
statusFlagsSave.push(statusflags)
null
}
"rrestore" -> {
val flags = statusFlagsSave.pop()
statusflags.carry = flags.carry
statusflags.negative = flags.negative
statusflags.zero = flags.zero
statusflags.irqd = flags.irqd
null
}
else -> TODO("builtin function $name")
}
}
}

View File

@ -1,18 +0,0 @@
package prog8.vm.astvm
import prog8.ast.INameScope
import java.util.*
class CallStack {
private val stack = Stack<Pair<INameScope, Int>>()
fun pop(): Pair<INameScope, Int> {
return stack.pop()
}
fun push(scope: INameScope, index: Int) {
stack.push(Pair(scope, index))
}
}

View File

@ -1,8 +1,9 @@
package prog8.vm.astvm package prog8.vm.astvm
import prog8.ast.* import prog8.ast.Program
import prog8.ast.base.ArrayElementTypes import prog8.ast.base.ArrayElementTypes
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
@ -13,18 +14,27 @@ import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange import prog8.vm.RuntimeValueRange
import kotlin.math.abs import kotlin.math.abs
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
val runtimeVars: RuntimeVariables, val functions: BuiltinFunctions,
val executeSubroutine: (sub: Subroutine, args: List<RuntimeValue>, startlabel: Label?) -> List<RuntimeValue>)
fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue { typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValue>, flags: StatusFlags) -> RuntimeValue?
typealias SubroutineCaller = (sub: Subroutine, args: List<RuntimeValue>, startAtLabel: Label?) -> RuntimeValue?
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
val runtimeVars: RuntimeVariables,
val performBuiltinFunction: BuiltinfunctionCaller,
val executeSubroutine: SubroutineCaller)
fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
val constval = expr.constValue(ctx.program) val constval = expr.constValue(ctx.program)
if(constval!=null) if(constval!=null)
return RuntimeValue.from(constval, ctx.program.heap) return RuntimeValue.fromLv(constval)
when(expr) { when(expr) {
is LiteralValue -> { is NumericLiteralValue -> {
return RuntimeValue.from(expr, ctx.program.heap) return RuntimeValue.fromLv(expr)
}
is ReferenceLiteralValue -> {
return RuntimeValue.fromLv(expr, ctx.program.heap)
} }
is PrefixExpression -> { is PrefixExpression -> {
return when(expr.operator) { return when(expr.operator) {
@ -81,8 +91,14 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
} }
is AddressOf -> { is AddressOf -> {
// we support: address of heap var -> the heap id // we support: address of heap var -> the heap id
return try {
val heapId = expr.identifier.heapId(ctx.program.namespace) val heapId = expr.identifier.heapId(ctx.program.namespace)
return RuntimeValue(DataType.UWORD, heapId) RuntimeValue(DataType.UWORD, heapId)
} catch( f: FatalAstException) {
// fallback: use the hash of the name, so we have at least *a* value...
val address = expr.identifier.hashCode() and 65535
RuntimeValue(DataType.UWORD, address)
}
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
val address = evaluate(expr.addressExpression, ctx).wordval!! val address = evaluate(expr.addressExpression, ctx).wordval!!
@ -93,9 +109,10 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
val scope = expr.definingScope() val scope = expr.definingScope()
val variable = scope.lookup(expr.nameInSource, expr) val variable = scope.lookup(expr.nameInSource, expr)
if(variable is VarDecl) { if(variable is VarDecl) {
if(variable.type==VarDeclType.VAR) when {
return ctx.runtimeVars.get(variable.definingScope(), variable.name) variable.type==VarDeclType.VAR -> return ctx.runtimeVars.get(variable.definingScope(), variable.name)
else { variable.datatype==DataType.STRUCT -> throw VmExecutionException("cannot process structs by-value. at ${expr.position}")
else -> {
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name) val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
return when(variable.datatype) { return when(variable.datatype) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address)) DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
@ -108,6 +125,7 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
else -> throw VmExecutionException("unexpected datatype $variable") else -> throw VmExecutionException("unexpected datatype $variable")
} }
} }
}
} else } else
throw VmExecutionException("weird identifier reference $variable") throw VmExecutionException("weird identifier reference $variable")
} }
@ -116,13 +134,12 @@ fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
val args = expr.arglist.map { evaluate(it, ctx) } val args = expr.arglist.map { evaluate(it, ctx) }
return when(sub) { return when(sub) {
is Subroutine -> { is Subroutine -> {
val results = ctx.executeSubroutine(sub, args, null) val result = ctx.executeSubroutine(sub, args, null)
if(results.size!=1) ?: throw VmExecutionException("expected a result from functioncall $expr")
throw VmExecutionException("expected 1 result from functioncall $expr") result
results[0]
} }
is BuiltinFunctionStatementPlaceholder -> { is BuiltinFunctionStatementPlaceholder -> {
val result = ctx.functions.performBuiltinFunction(sub.name, args, ctx.statusflags) val result = ctx.performBuiltinFunction(sub.name, args, ctx.statusflags)
?: throw VmExecutionException("expected 1 result from functioncall $expr") ?: throw VmExecutionException("expected 1 result from functioncall $expr")
result result
} }

View File

@ -1,6 +1,6 @@
package prog8.vm.astvm package prog8.vm.astvm
import prog8.compiler.target.c64.Mflpt5 import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import kotlin.math.abs import kotlin.math.abs
@ -80,7 +80,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
} }
fun setFloat(address: Int, value: Double) { fun setFloat(address: Int, value: Double) {
val mflpt5 = Mflpt5.fromNumber(value) val mflpt5 = MachineDefinition.Mflpt5.fromNumber(value)
setUByte(address, mflpt5.b0) setUByte(address, mflpt5.b0)
setUByte(address+1, mflpt5.b1) setUByte(address+1, mflpt5.b1)
setUByte(address+2, mflpt5.b2) setUByte(address+2, mflpt5.b2)
@ -89,7 +89,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
} }
fun getFloat(address: Int): Double { fun getFloat(address: Int): Double {
return Mflpt5(getUByte(address), getUByte(address + 1), getUByte(address + 2), return MachineDefinition.Mflpt5(getUByte(address), getUByte(address + 1), getUByte(address + 2),
getUByte(address + 3), getUByte(address + 4)).toDouble() getUByte(address + 3), getUByte(address + 4)).toDouble()
} }

View File

@ -1,12 +1,12 @@
package prog8.vm.astvm package prog8.vm.astvm
import prog8.compiler.target.c64.Charset import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Colors
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import java.awt.* import java.awt.*
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import java.awt.event.KeyListener import java.awt.event.KeyListener
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.util.*
import javax.swing.JFrame import javax.swing.JFrame
import javax.swing.JPanel import javax.swing.JPanel
import javax.swing.Timer import javax.swing.Timer
@ -18,6 +18,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
private val g2d = image.graphics as Graphics2D private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0 private var cursorX: Int=0
private var cursorY: Int=0 private var cursorY: Int=0
val keyboardBuffer: Deque<Char> = LinkedList()
init { init {
val size = Dimension(image.width * SCALING, image.height * SCALING) val size = Dimension(image.width * SCALING, image.height * SCALING)
@ -30,14 +31,14 @@ class BitmapScreenPanel : KeyListener, JPanel() {
addKeyListener(this) addKeyListener(this)
} }
override fun keyTyped(p0: KeyEvent?) {} override fun keyTyped(p0: KeyEvent) {
keyboardBuffer.add(p0.keyChar)
}
override fun keyPressed(p0: KeyEvent?) { override fun keyPressed(p0: KeyEvent) {
println("pressed: $p0.k")
} }
override fun keyReleased(p0: KeyEvent?) { override fun keyReleased(p0: KeyEvent?) {
println("released: $p0")
} }
override fun paint(graphics: Graphics?) { override fun paint(graphics: Graphics?) {
@ -49,61 +50,82 @@ class BitmapScreenPanel : KeyListener, JPanel() {
} }
fun clearScreen(color: Short) { fun clearScreen(color: Short) {
g2d.background = Colors.palette[color % Colors.palette.size] g2d.background = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size]
g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT) g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT)
cursorX = 0 cursorX = 0
cursorY = 0 cursorY = 0
} }
fun setPixel(x: Int, y: Int, color: Short) { fun setPixel(x: Int, y: Int, color: Short) {
image.setRGB(x, y, Colors.palette[color % Colors.palette.size].rgb) image.setRGB(x, y, MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size].rgb)
} }
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) { fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) {
g2d.color = Colors.palette[color % Colors.palette.size] g2d.color = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size]
g2d.drawLine(x1, y1, x2, y2) g2d.drawLine(x1, y1, x2, y2)
} }
fun printText(text: String, color: Short, lowercase: Boolean) {
fun printText(text: String, lowercase: Boolean, inverseVideo: Boolean=false) {
val t2 = text.substringBefore(0.toChar()) val t2 = text.substringBefore(0.toChar())
val lines = t2.split('\n') val lines = t2.split('\n')
for(line in lines.withIndex()) { for(line in lines.withIndex()) {
printTextSingleLine(line.value, color, lowercase) val petscii = Petscii.encodePetscii(line.value, lowercase)
petscii.forEach { printPetscii(it, inverseVideo) }
if(line.index<lines.size-1) { if(line.index<lines.size-1) {
cursorX=0 printPetscii(13) // newline
cursorY++
}
}
}
private fun printTextSingleLine(text: String, color: Short, lowercase: Boolean) {
for(clearx in cursorX until cursorX+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(cursorX, cursorY, sc, color)
cursorX++
if(cursorX>=(SCREENWIDTH /8)) {
cursorY++
cursorX=0
} }
} }
} }
fun printChar(char: Short) { fun printPetscii(char: Short, inverseVideo: Boolean=false) {
if(char==13.toShort() || char==141.toShort()) { if(char==13.toShort() || char==141.toShort()) {
cursorX=0 cursorX=0
cursorY++ cursorY++
} else { } else {
setChar(cursorX, cursorY, char, 1) setPetscii(cursorX, cursorY, char, 1, inverseVideo)
cursorX++ cursorX++
if (cursorX >= (SCREENWIDTH / 8)) { if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++ cursorY++
cursorX = 0 cursorX = 0
} }
} }
while(cursorY>=(SCREENHEIGHT/8)) {
// scroll the screen up because the cursor went past the last line
Thread.sleep(10)
val screen = image.copy()
val graphics = image.graphics as Graphics2D
graphics.drawImage(screen, 0, -8, null)
val color = graphics.color
graphics.color = MachineDefinition.colorPalette[6]
graphics.fillRect(0, 24*8, SCREENWIDTH, 25*8)
graphics.color=color
cursorY--
}
} }
fun setChar(x: Int, y: Int, screenCode: Short, color: Short) { fun writeTextAt(x: Int, y: Int, text: String, color: Short, lowercase: Boolean, inverseVideo: Boolean=false) {
val colorIdx = (color % MachineDefinition.colorPalette.size).toShort()
var xx=x
for(clearx in xx until xx+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodePetscii(text, lowercase)) {
if(sc==0.toShort())
break
setPetscii(xx++, y, sc, colorIdx, inverseVideo)
}
}
fun setPetscii(x: Int, y: Int, petscii: Short, color: Short, inverseVideo: Boolean) {
g2d.clearRect(8*x, 8*y, 8, 8) g2d.clearRect(8*x, 8*y, 8, 8)
val colorIdx = (color % Colors.palette.size).toShort() val colorIdx = (color % MachineDefinition.colorPalette.size).toShort()
val coloredImage = Charset.getColoredChar(screenCode, colorIdx) val screencode = Petscii.petscii2scr(petscii, inverseVideo)
val coloredImage = MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null)
}
fun setChar(x: Int, y: Int, screencode: Short, color: Short) {
g2d.clearRect(8*x, 8*y, 8, 8)
val colorIdx = (color % MachineDefinition.colorPalette.size).toShort()
val coloredImage = MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null) g2d.drawImage(coloredImage, 8*x, 8*y , null)
} }
@ -116,20 +138,6 @@ class BitmapScreenPanel : KeyListener, JPanel() {
return Pair(cursorX, cursorY) return Pair(cursorX, cursorY)
} }
fun writeText(x: Int, y: Int, text: String, color: Short, lowercase: Boolean) {
val colorIdx = (color % Colors.palette.size).toShort()
var xx=x
for(clearx in xx until xx+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
if(sc==0.toShort())
break
setChar(xx++, y, sc, colorIdx)
}
}
companion object { companion object {
const val SCREENWIDTH = 320 const val SCREENWIDTH = 320
const val SCREENHEIGHT = 200 const val SCREENHEIGHT = 200
@ -140,29 +148,30 @@ class BitmapScreenPanel : KeyListener, JPanel() {
class ScreenDialog(title: String) : JFrame(title) { class ScreenDialog(title: String) : JFrame(title) {
val canvas = BitmapScreenPanel() val canvas = BitmapScreenPanel()
val keyboardBuffer = canvas.keyboardBuffer
init { init {
val borderWidth = 16 val borderWidth = 16
layout = GridBagLayout() layout = GridBagLayout()
defaultCloseOperation = JFrame.EXIT_ON_CLOSE defaultCloseOperation = EXIT_ON_CLOSE
isResizable = false isResizable = false
// the borders (top, left, right, bottom) // the borders (top, left, right, bottom)
val borderTop = JPanel().apply { val borderTop = JPanel().apply {
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth) preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = Colors.palette[14] background = MachineDefinition.colorPalette[14]
} }
val borderBottom = JPanel().apply { val borderBottom = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth) preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = Colors.palette[14] background = MachineDefinition.colorPalette[14]
} }
val borderLeft = JPanel().apply { val borderLeft = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT) preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = Colors.palette[14] background = MachineDefinition.colorPalette[14]
} }
val borderRight = JPanel().apply { val borderRight = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT) preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = Colors.palette[14] background = MachineDefinition.colorPalette[14]
} }
var c = GridBagConstraints() var c = GridBagConstraints()
c.gridx=0; c.gridy=1; c.gridwidth=3 c.gridx=0; c.gridy=1; c.gridwidth=3
@ -189,3 +198,12 @@ class ScreenDialog(title: String) : JFrame(title) {
repaintTimer.start() repaintTimer.start()
} }
} }
private fun BufferedImage.copy(): BufferedImage {
val bcopy = BufferedImage(this.width, this.height, this.type)
val g = bcopy.graphics
g.drawImage(this, 0, 0, null)
g.dispose()
return bcopy
}

View File

@ -1,10 +1,17 @@
package prog8.vm.astvm package prog8.vm.astvm
import prog8.ast.* import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.DataType
import prog8.ast.expressions.LiteralValue import prog8.ast.base.Position
import prog8.ast.base.Register
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.Statement
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.vm.RuntimeValue import prog8.vm.RuntimeValue
@ -17,9 +24,12 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValue(DataType.UBYTE, 0)) runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValue(DataType.UBYTE, 0))
val globalpos = Position("<<global>>", 0, 0, 0) val globalpos = Position("<<global>>", 0, 0, 0)
val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.A.name, LiteralValue.optimalInteger(0, globalpos), isArray = false, autoGenerated = true, position = globalpos) val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.A.name, null,
val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.X.name, LiteralValue.optimalInteger(255, globalpos), isArray = false, autoGenerated = true, position = globalpos) NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.Y.name, LiteralValue.optimalInteger(0, globalpos), isArray = false, autoGenerated = true, position = globalpos) val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.X.name, null,
NumericLiteralValue.optimalInteger(255, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.Y.name, null,
NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
vdA.linkParents(program.namespace) vdA.linkParents(program.namespace)
vdX.linkParents(program.namespace) vdX.linkParents(program.namespace)
vdY.linkParents(program.namespace) vdY.linkParents(program.namespace)
@ -30,24 +40,34 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
super.visit(program) super.visit(program)
} }
override fun visit(decl: VarDecl): IStatement { override fun visit(decl: VarDecl): Statement {
// if the decl is part of a struct, just skip it
if(decl.parent !is StructDecl) {
when (decl.type) { when (decl.type) {
// we can assume the value in the vardecl already has been converted into a constant LiteralValue here.
VarDeclType.VAR -> { VarDeclType.VAR -> {
val value = RuntimeValue.from(decl.value as LiteralValue, heap) if(decl.datatype!=DataType.STRUCT) {
val numericLv = decl.value as? NumericLiteralValue
val value = if(numericLv!=null) {
RuntimeValue.fromLv(numericLv)
} else {
val referenceLv = decl.value as ReferenceLiteralValue
RuntimeValue.fromLv(referenceLv, heap)
}
runtimeVariables.define(decl.definingScope(), decl.name, value) runtimeVariables.define(decl.definingScope(), decl.name, value)
} }
}
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as LiteralValue).asIntegerValue!!) runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as NumericLiteralValue).number.toInt())
} }
VarDeclType.CONST -> { VarDeclType.CONST -> {
// consts should have been const-folded away // consts should have been const-folded away
} }
} }
}
return super.visit(decl) return super.visit(decl)
} }
// override fun accept(assignment: Assignment): IStatement { // override fun accept(assignment: Assignment): Statement {
// if(assignment is VariableInitializationAssignment) { // if(assignment is VariableInitializationAssignment) {
// println("INIT VAR $assignment") // println("INIT VAR $assignment")
// } // }

View File

@ -4,10 +4,13 @@ import prog8.ast.antlr.unescape
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.AddressOf import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.vm.RuntimeValue
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.intermediate.* import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.intermediate.opcodesWithVarArgument
import prog8.vm.RuntimeValue
import java.io.File import java.io.File
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
@ -226,18 +229,18 @@ class Program (val name: String,
if(valueStr[0] !='"' && ':' !in valueStr) if(valueStr[0] !='"' && ':' !in valueStr)
throw VmExecutionException("missing value type character") throw VmExecutionException("missing value type character")
val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) { val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).toShort(16)) DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).toShort(16)) DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).toInt(16)) DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).toInt(16)) DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).toDouble()) DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).substringBefore(' ').toDouble())// TODO process ZP and struct info?
in StringDatatypes -> { in StringDatatypes -> {
if(valueStr.startsWith('"') && valueStr.endsWith('"')) if(valueStr.startsWith('"') && valueStr.endsWith('"'))
throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap") throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap")
else if(!valueStr.startsWith("heap:")) else if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid string value, should be a heap reference") throw VmExecutionException("invalid string value, should be a heap reference")
else { else {
val heapId = valueStr.substring(5).toInt() val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
RuntimeValue(type, heapId = heapId) RuntimeValue(type, heapId = heapId)
} }
} }
@ -245,7 +248,7 @@ class Program (val name: String,
if(!valueStr.startsWith("heap:")) if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid array value, should be a heap reference") throw VmExecutionException("invalid array value, should be a heap reference")
else { else {
val heapId = valueStr.substring(5).toInt() val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
RuntimeValue(type, heapId = heapId) RuntimeValue(type, heapId = heapId)
} }
} }

View File

@ -1,26 +1,22 @@
package prog8.vm.stackvm package prog8.vm.stackvm
import prog8.ast.base.DataType import prog8.ast.base.*
import prog8.ast.base.IterableDatatypes
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.Register
import prog8.ast.base.initvarsSubName
import prog8.vm.astvm.BitmapScreenPanel
import prog8.vm.astvm.Memory
import prog8.vm.RuntimeValue
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.intermediate.Instruction import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.Opcode import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.compiler.toHex import prog8.compiler.toHex
import prog8.vm.RuntimeValue
import prog8.vm.astvm.BitmapScreenPanel
import prog8.vm.astvm.Memory
import java.io.File import java.io.File
import java.io.PrintStream import java.io.PrintStream
import java.util.* import java.util.*
import kotlin.math.* import kotlin.math.*
enum class Syscall(val callNr: Short) { internal enum class Syscall(val callNr: Short) {
VM_WRITE_MEMCHR(10), // print a single char from the memory address popped from stack VM_WRITE_MEMCHR(10), // print a single char from the memory address popped from stack
VM_WRITE_MEMSTR(11), // print a 0-terminated petscii string from the memory address popped from stack VM_WRITE_MEMSTR(11), // print a 0-terminated petscii string from the memory address popped from stack
VM_WRITE_NUM(12), // pop from the evaluation stack and print it as a number VM_WRITE_NUM(12), // pop from the evaluation stack and print it as a number
@ -107,10 +103,9 @@ enum class Syscall(val callNr: Short) {
SYSASM_c64flt_print_f(214), SYSASM_c64flt_print_f(214),
} }
internal val syscallNames = enumValues<Syscall>().map { it.name }.toSet()
val syscallNames = enumValues<Syscall>().map { it.name }.toSet() internal val syscallsForStackVm = setOf(
val syscallsForStackVm = setOf(
Syscall.VM_WRITE_MEMCHR, Syscall.VM_WRITE_MEMCHR,
Syscall.VM_WRITE_MEMSTR, Syscall.VM_WRITE_MEMSTR,
Syscall.VM_WRITE_NUM, Syscall.VM_WRITE_NUM,
@ -123,13 +118,13 @@ val syscallsForStackVm = setOf(
Syscall.VM_GFX_LINE Syscall.VM_GFX_LINE
) )
class VmExecutionException(msg: String?) : Exception(msg) internal class VmExecutionException(msg: String?) : Exception(msg)
class VmTerminationException(msg: String?) : Exception(msg) internal class VmTerminationException(msg: String?) : Exception(msg)
class VmBreakpointException : Exception("breakpoint") internal class VmBreakpointException : Exception("breakpoint")
class MyStack<T> : Stack<T>() { internal class MyStack<T> : Stack<T>() {
fun peek(amount: Int) : List<T> { fun peek(amount: Int) : List<T> {
return this.toList().subList(max(0, size-amount), size) return this.toList().subList(max(0, size-amount), size)
} }
@ -141,7 +136,6 @@ class MyStack<T> : Stack<T>() {
} }
} }
class StackVm(private var traceOutputFile: String?) { class StackVm(private var traceOutputFile: String?) {
val mem = Memory(::memread, ::memwrite) val mem = Memory(::memread, ::memwrite)
var P_carry: Boolean = false var P_carry: Boolean = false
@ -156,9 +150,9 @@ class StackVm(private var traceOutputFile: String?) {
private set private set
var memoryPointers = mutableMapOf<String, Pair<Int, DataType>>() // all named pointers var memoryPointers = mutableMapOf<String, Pair<Int, DataType>>() // all named pointers
private set private set
var evalstack = MyStack<RuntimeValue>() internal var evalstack = MyStack<RuntimeValue>()
private set private set
var callstack = MyStack<Int>() internal var callstack = MyStack<Int>()
private set private set
private var program = listOf<Instruction>() private var program = listOf<Instruction>()
private var labels = emptyMap<String, Int>() private var labels = emptyMap<String, Int>()
@ -204,7 +198,7 @@ class StackVm(private var traceOutputFile: String?) {
throw VmExecutionException("program contains variable(s) for the reserved registers A/X/Y") throw VmExecutionException("program contains variable(s) for the reserved registers A/X/Y")
// define the 'registers' // define the 'registers'
variables["A"] = RuntimeValue(DataType.UBYTE, 0) variables["A"] = RuntimeValue(DataType.UBYTE, 0)
variables["X"] = RuntimeValue(DataType.UBYTE, 0) variables["X"] = RuntimeValue(DataType.UBYTE, 255)
variables["Y"] = RuntimeValue(DataType.UBYTE, 0) variables["Y"] = RuntimeValue(DataType.UBYTE, 0)
initMemory(program.memory) initMemory(program.memory)
@ -1865,8 +1859,8 @@ class StackVm(private var traceOutputFile: String?) {
} }
Opcode.RRESTORE -> { Opcode.RRESTORE -> {
variables["A"] = evalstack.pop() variables["A"] = evalstack.pop()
variables["X"] = evalstack.pop()
variables["Y"] = evalstack.pop() variables["Y"] = evalstack.pop()
variables["X"] = evalstack.pop()
P_carry = evalstack.pop().asBoolean P_carry = evalstack.pop().asBoolean
P_irqd = evalstack.pop().asBoolean P_irqd = evalstack.pop().asBoolean
} }
@ -1875,10 +1869,17 @@ class StackVm(private var traceOutputFile: String?) {
Opcode.INLINE_ASSEMBLY -> throw VmExecutionException("stackVm doesn't support executing inline assembly code $ins") Opcode.INLINE_ASSEMBLY -> throw VmExecutionException("stackVm doesn't support executing inline assembly code $ins")
Opcode.INCLUDE_FILE -> throw VmExecutionException("stackVm doesn't support including a file $ins") Opcode.INCLUDE_FILE -> throw VmExecutionException("stackVm doesn't support including a file $ins")
Opcode.PUSH_ADDR_HEAPVAR -> { Opcode.PUSH_ADDR_HEAPVAR -> {
val heapId = variables.getValue(ins.callLabel!!).heapId!! val variable = variables.getValue(ins.callLabel!!)
if(variable.heapId!=null) {
val heapId = variable.heapId
if (heapId < 0) if (heapId < 0)
throw VmExecutionException("expected variable on heap") throw VmExecutionException("expected variable on heap")
evalstack.push(RuntimeValue(DataType.UWORD, heapId)) // push the "address" of the string or array variable (this is taken care of properly in the assembly code generator) evalstack.push(RuntimeValue(DataType.UWORD, heapId)) // push the "address" of the string or array variable (this is taken care of properly in the assembly code generator)
} else {
// hack: return hash of the name, so we have at least *a* value...
val addr = ins.callLabel.hashCode() and 65535
evalstack.push(RuntimeValue(DataType.UWORD, addr))
}
} }
Opcode.CAST_UB_TO_B -> typecast(DataType.UBYTE, DataType.BYTE) Opcode.CAST_UB_TO_B -> typecast(DataType.UBYTE, DataType.BYTE)
Opcode.CAST_W_TO_B -> typecast(DataType.WORD, DataType.BYTE) Opcode.CAST_W_TO_B -> typecast(DataType.WORD, DataType.BYTE)
@ -1928,7 +1929,7 @@ class StackVm(private var traceOutputFile: String?) {
} }
"c64.CHROUT" -> { "c64.CHROUT" -> {
val sc=variables.getValue("A").integerValue() val sc=variables.getValue("A").integerValue()
canvas?.printChar(sc.toShort()) canvas?.printPetscii(sc.toShort())
callstack.pop() callstack.pop()
} }
"c64.GETIN" -> { "c64.GETIN" -> {
@ -2029,7 +2030,7 @@ class StackVm(private var traceOutputFile: String?) {
val color = evalstack.pop() val color = evalstack.pop()
val (cy, cx) = evalstack.pop2() val (cy, cx) = evalstack.pop2()
val text = heap.get(textPtr) val text = heap.get(textPtr)
canvas?.writeText(cx.integerValue(), cy.integerValue(), text.str!!, color.integerValue().toShort(), true) canvas?.writeTextAt(cx.integerValue(), cy.integerValue(), text.str!!, color.integerValue().toShort(), true)
} }
Syscall.FUNC_RND -> evalstack.push(RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255)) Syscall.FUNC_RND -> evalstack.push(RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255))
Syscall.FUNC_RNDW -> evalstack.push(RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535)) Syscall.FUNC_RNDW -> evalstack.push(RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535))
@ -2311,54 +2312,54 @@ class StackVm(private var traceOutputFile: String?) {
Syscall.SYSASM_c64scr_print -> { Syscall.SYSASM_c64scr_print -> {
val straddr = variables.getValue("A").integerValue() + 256*variables.getValue("Y").integerValue() val straddr = variables.getValue("A").integerValue() + 256*variables.getValue("Y").integerValue()
val str = heap.get(straddr).str!! val str = heap.get(straddr).str!!
canvas?.printText(str, 1, true) canvas?.printText(str, true)
} }
Syscall.SYSASM_c64scr_print_ub -> { Syscall.SYSASM_c64scr_print_ub -> {
val num = variables.getValue("A").integerValue() val num = variables.getValue("A").integerValue()
canvas?.printText(num.toString(), 1, true) canvas?.printText(num.toString(), true)
} }
Syscall.SYSASM_c64scr_print_ub0 -> { Syscall.SYSASM_c64scr_print_ub0 -> {
val num = variables.getValue("A").integerValue() val num = variables.getValue("A").integerValue()
canvas?.printText("%03d".format(num), 1, true) canvas?.printText("%03d".format(num), true)
} }
Syscall.SYSASM_c64scr_print_b -> { Syscall.SYSASM_c64scr_print_b -> {
val num = variables.getValue("A").integerValue() val num = variables.getValue("A").integerValue()
if(num<=127) if(num<=127)
canvas?.printText(num.toString(), 1, true) canvas?.printText(num.toString(), true)
else else
canvas?.printText("-${256-num}", 1, true) canvas?.printText("-${256-num}", true)
} }
Syscall.SYSASM_c64scr_print_uw -> { Syscall.SYSASM_c64scr_print_uw -> {
val lo = variables.getValue("A").integerValue() val lo = variables.getValue("A").integerValue()
val hi = variables.getValue("Y").integerValue() val hi = variables.getValue("Y").integerValue()
val number = lo+256*hi val number = lo+256*hi
canvas?.printText(number.toString(), 1, true) canvas?.printText(number.toString(), true)
} }
Syscall.SYSASM_c64scr_print_uw0 -> { Syscall.SYSASM_c64scr_print_uw0 -> {
val lo = variables.getValue("A").integerValue() val lo = variables.getValue("A").integerValue()
val hi = variables.getValue("Y").integerValue() val hi = variables.getValue("Y").integerValue()
val number = lo+256*hi val number = lo+256*hi
canvas?.printText("%05d".format(number), 1, true) canvas?.printText("%05d".format(number), true)
} }
Syscall.SYSASM_c64scr_print_uwhex -> { Syscall.SYSASM_c64scr_print_uwhex -> {
val prefix = if(this.P_carry) "$" else "" val prefix = if(this.P_carry) "$" else ""
val lo = variables.getValue("A").integerValue() val lo = variables.getValue("A").integerValue()
val hi = variables.getValue("Y").integerValue() val hi = variables.getValue("Y").integerValue()
val number = lo+256*hi val number = lo+256*hi
canvas?.printText("$prefix${number.toString(16).padStart(4, '0')}", 1, true) canvas?.printText("$prefix${number.toString(16).padStart(4, '0')}", true)
} }
Syscall.SYSASM_c64scr_print_w -> { Syscall.SYSASM_c64scr_print_w -> {
val lo = variables.getValue("A").integerValue() val lo = variables.getValue("A").integerValue()
val hi = variables.getValue("Y").integerValue() val hi = variables.getValue("Y").integerValue()
val number = lo+256*hi val number = lo+256*hi
if(number<=32767) if(number<=32767)
canvas?.printText(number.toString(), 1, true) canvas?.printText(number.toString(), true)
else else
canvas?.printText("-${65536-number}", 1, true) canvas?.printText("-${65536-number}", true)
} }
Syscall.SYSASM_c64flt_print_f -> { Syscall.SYSASM_c64flt_print_f -> {
val number = variables.getValue("c64flt.print_f.value").numericValue() val number = variables.getValue("c64flt.print_f.value").numericValue()
canvas?.printText(number.toString(), 1, true) canvas?.printText(number.toString(), true)
} }
Syscall.SYSASM_c64scr_setcc -> { Syscall.SYSASM_c64scr_setcc -> {
val x = variables.getValue("c64scr.setcc.column").integerValue() val x = variables.getValue("c64scr.setcc.column").integerValue()
@ -2412,12 +2413,12 @@ class StackVm(private var traceOutputFile: String?) {
irqStoredCarry = P_carry irqStoredCarry = P_carry
irqStoredTraceOutputFile = traceOutputFile irqStoredTraceOutputFile = traceOutputFile
if(irqStartInstructionPtr>=0) currentInstructionPtr = if(irqStartInstructionPtr>=0)
currentInstructionPtr = irqStartInstructionPtr irqStartInstructionPtr
else { else {
if(program.last().opcode!=Opcode.RETURN) if(program.last().opcode!=Opcode.RETURN)
throw VmExecutionException("last instruction in program should be RETURN for irq handler") throw VmExecutionException("last instruction in program should be RETURN for irq handler")
currentInstructionPtr = program.size-1 program.size-1
} }
callstack = MyStack() callstack = MyStack()
evalstack = MyStack() evalstack = MyStack()

View File

@ -3,24 +3,32 @@ package prog8tests
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.expressions.LiteralValue
import prog8.ast.base.Position import prog8.ast.base.Position
import kotlin.test.* import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
import kotlin.test.assertTrue
private fun sameValueAndType(lv1: LiteralValue, lv2: LiteralValue): Boolean { private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue): Boolean {
return lv1.type==lv2.type && lv1==lv2 return lv1.type==lv2.type && lv1==lv2
} }
private fun sameValueAndType(rv1: ReferenceLiteralValue, rv2: ReferenceLiteralValue): Boolean {
return rv1.type==rv2.type && rv1==rv2
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestParserLiteralValue { class TestParserNumericLiteralValue {
private val dummyPos = Position("test", 0, 0, 0) private val dummyPos = Position("test", 0, 0, 0)
@Test @Test
fun testIdentity() { fun testIdentity() {
val v = LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos) val v = NumericLiteralValue(DataType.UWORD, 12345, dummyPos)
assertEquals(v, v) assertEquals(v, v)
assertFalse(v != v) assertFalse(v != v)
assertTrue(v <= v) assertTrue(v <= v)
@ -28,104 +36,109 @@ class TestParserLiteralValue {
assertFalse(v < v) assertFalse(v < v)
assertFalse(v > v) assertFalse(v > v)
assertTrue(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos))) assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
} }
@Test @Test
fun testEqualsAndNotEquals() { fun testEqualsAndNotEquals() {
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos)) assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos)) assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos))
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)) assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos)) assertEquals(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos)) assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos)) assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos)) assertEquals(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos)) assertEquals(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos)) assertEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos))
assertTrue(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))) assertTrue(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos))) assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos))) assertTrue(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos)))
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 101, position = dummyPos)) assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos)) assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos))
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos)) assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos)) assertNotEquals(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos)) assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos)) assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos)) assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos)) assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos)) assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 101, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos))) assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.FLOAT, 9.0, dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos), LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.STR, strvalue = "hello", position = dummyPos), LiteralValue(DataType.STR, strvalue = "bye", position = dummyPos)))
val lvOne = LiteralValue(DataType.UBYTE, 1, position = dummyPos) }
val lvTwo = LiteralValue(DataType.UBYTE, 2, position = dummyPos)
val lvThree = LiteralValue(DataType.UBYTE, 3, position = dummyPos) @Test
val lvOneR = LiteralValue(DataType.UBYTE, 1, position = dummyPos) fun testEqualsRef() {
val lvTwoR = LiteralValue(DataType.UBYTE, 2, position = dummyPos) assertTrue(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos)))
val lvThreeR = LiteralValue(DataType.UBYTE, 3, position = dummyPos) assertFalse(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "bye", position = dummyPos)))
val lvFour= LiteralValue(DataType.UBYTE, 4, position = dummyPos)
val lv1 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos) val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
val lv2 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos) val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
val lv3 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos) val lvThree = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
val lvOneR = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
val lv1 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos)
val lv2 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos)
val lv3 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos)
assertEquals(lv1, lv2) assertEquals(lv1, lv2)
assertNotEquals(lv1, lv3) assertNotEquals(lv1, lv3)
} }
@Test @Test
fun testGreaterThan(){ fun testGreaterThan(){
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) > LiteralValue(DataType.UBYTE, 99, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) >= LiteralValue(DataType.UBYTE, 100, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) > LiteralValue(DataType.UBYTE, 100, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) > NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) >= LiteralValue(DataType.UBYTE, 101, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
} }
@Test @Test
fun testLessThan() { fun testLessThan() {
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) < LiteralValue(DataType.UBYTE, 101, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) <= LiteralValue(DataType.UBYTE, 100, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)) assertTrue(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) < LiteralValue(DataType.UBYTE, 100, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) < NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
assertFalse(LiteralValue(DataType.UBYTE, 100, position = dummyPos) <= LiteralValue(DataType.UBYTE, 99, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
assertFalse(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos)) assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
} }
} }

View File

@ -17,39 +17,27 @@ class TestRuntimeValue {
@Test @Test
fun testValueRanges() { fun testValueRanges() {
assertEquals(100, RuntimeValue(DataType.UBYTE, 100).integerValue()) assertEquals(0, RuntimeValue(DataType.UBYTE, 0).integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 100).integerValue()) assertEquals(255, RuntimeValue(DataType.UBYTE, 255).integerValue())
assertEquals(10000, RuntimeValue(DataType.UWORD, 10000).integerValue()) assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UBYTE, -1)}
assertEquals(10000, RuntimeValue(DataType.WORD, 10000).integerValue()) assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UBYTE, 256)}
assertEquals(100.11, RuntimeValue(DataType.FLOAT, 100.11).numericValue())
assertEquals(200, RuntimeValue(DataType.UBYTE, 200).integerValue()) assertEquals(0, RuntimeValue(DataType.BYTE, 0).integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 200).integerValue()) assertEquals(-128, RuntimeValue(DataType.BYTE, -128).integerValue())
assertEquals(50000, RuntimeValue(DataType.UWORD, 50000).integerValue()) assertEquals(127, RuntimeValue(DataType.BYTE, 127).integerValue())
assertEquals(-15536, RuntimeValue(DataType.WORD, 50000).integerValue()) assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.BYTE, -129)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.BYTE, 128)}
assertEquals(44, RuntimeValue(DataType.UBYTE, 300).integerValue()) assertEquals(0, RuntimeValue(DataType.UWORD, 0).integerValue())
assertEquals(44, RuntimeValue(DataType.BYTE, 300).integerValue()) assertEquals(65535, RuntimeValue(DataType.UWORD, 65535).integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 400).integerValue()) assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UWORD, -1)}
assertEquals(-112, RuntimeValue(DataType.BYTE, 400).integerValue()) assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UWORD, 65536)}
assertEquals(34463, RuntimeValue(DataType.UWORD, 99999).integerValue())
assertEquals(-31073, RuntimeValue(DataType.WORD, 99999).integerValue())
assertEquals(156, RuntimeValue(DataType.UBYTE, -100).integerValue()) assertEquals(0, RuntimeValue(DataType.WORD, 0).integerValue())
assertEquals(-100, RuntimeValue(DataType.BYTE, -100).integerValue()) assertEquals(-32768, RuntimeValue(DataType.WORD, -32768).integerValue())
assertEquals(55536, RuntimeValue(DataType.UWORD, -10000).integerValue()) assertEquals(32767, RuntimeValue(DataType.WORD, 32767).integerValue())
assertEquals(-10000, RuntimeValue(DataType.WORD, -10000).integerValue()) assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.WORD, -32769)}
assertEquals(-100.11, RuntimeValue(DataType.FLOAT, -100.11).numericValue()) assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.WORD, 32768)}
assertEquals(56, RuntimeValue(DataType.UBYTE, -200).integerValue())
assertEquals(56, RuntimeValue(DataType.BYTE, -200).integerValue())
assertEquals(45536, RuntimeValue(DataType.UWORD, -20000).integerValue())
assertEquals(-20000, RuntimeValue(DataType.WORD, -20000).integerValue())
assertEquals(212, RuntimeValue(DataType.UBYTE, -300).integerValue())
assertEquals(-44, RuntimeValue(DataType.BYTE, -300).integerValue())
assertEquals(42184, RuntimeValue(DataType.UWORD, -88888).integerValue())
assertEquals(-23352, RuntimeValue(DataType.WORD, -88888).integerValue())
} }
@Test @Test
@ -60,10 +48,6 @@ class TestRuntimeValue {
assertFalse(RuntimeValue(DataType.WORD, 0).asBoolean) assertFalse(RuntimeValue(DataType.WORD, 0).asBoolean)
assertFalse(RuntimeValue(DataType.UWORD, 0).asBoolean) assertFalse(RuntimeValue(DataType.UWORD, 0).asBoolean)
assertFalse(RuntimeValue(DataType.FLOAT, 0.0).asBoolean) assertFalse(RuntimeValue(DataType.FLOAT, 0.0).asBoolean)
assertFalse(RuntimeValue(DataType.BYTE, 256).asBoolean)
assertFalse(RuntimeValue(DataType.UBYTE, 256).asBoolean)
assertFalse(RuntimeValue(DataType.WORD, 65536).asBoolean)
assertFalse(RuntimeValue(DataType.UWORD, 65536).asBoolean)
assertTrue(RuntimeValue(DataType.BYTE, 42).asBoolean) assertTrue(RuntimeValue(DataType.BYTE, 42).asBoolean)
assertTrue(RuntimeValue(DataType.UBYTE, 42).asBoolean) assertTrue(RuntimeValue(DataType.UBYTE, 42).asBoolean)
@ -71,9 +55,7 @@ class TestRuntimeValue {
assertTrue(RuntimeValue(DataType.UWORD, 42).asBoolean) assertTrue(RuntimeValue(DataType.UWORD, 42).asBoolean)
assertTrue(RuntimeValue(DataType.FLOAT, 42.0).asBoolean) assertTrue(RuntimeValue(DataType.FLOAT, 42.0).asBoolean)
assertTrue(RuntimeValue(DataType.BYTE, -42).asBoolean) assertTrue(RuntimeValue(DataType.BYTE, -42).asBoolean)
assertTrue(RuntimeValue(DataType.UBYTE, -42).asBoolean)
assertTrue(RuntimeValue(DataType.WORD, -42).asBoolean) assertTrue(RuntimeValue(DataType.WORD, -42).asBoolean)
assertTrue(RuntimeValue(DataType.UWORD, -42).asBoolean)
assertTrue(RuntimeValue(DataType.FLOAT, -42.0).asBoolean) assertTrue(RuntimeValue(DataType.FLOAT, -42.0).asBoolean)
} }
@ -245,4 +227,155 @@ class TestRuntimeValue {
} }
val result = RuntimeValue(DataType.FLOAT, 233.333).add(RuntimeValue(DataType.FLOAT, 1.234)) val result = RuntimeValue(DataType.FLOAT, 233.333).add(RuntimeValue(DataType.FLOAT, 1.234))
} }
@Test
fun arithmetictestUbyte() {
assertEquals(255, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 55)).integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 56)).integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 57)).integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 2)).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 3)).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 254).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 255).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 1).dec().integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 0).dec().integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 0).inv().integerValue())
assertEquals(0b00110011, RuntimeValue(DataType.UBYTE, 0b11001100).inv().integerValue())
// assertEquals(0, RuntimeValue(DataType.UBYTE, 0).neg().integerValue())
// assertEquals(0, RuntimeValue(DataType.UBYTE, 0).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 255).not().integerValue())
assertEquals(200, RuntimeValue(DataType.UBYTE, 20).mul(RuntimeValue(DataType.UBYTE, 10)).integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 20).mul(RuntimeValue(DataType.UBYTE, 20)).integerValue())
assertEquals(25, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 2)).integerValue())
assertEquals(125, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 3)).integerValue())
assertEquals(113, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 4)).integerValue())
assertEquals(100, RuntimeValue(DataType.UBYTE, 50).shl().integerValue())
assertEquals(200, RuntimeValue(DataType.UBYTE, 100).shl().integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 200).shl().integerValue())
}
@Test
fun arithmetictestUWord() {
assertEquals(65535, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5535)).integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5536)).integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5537)).integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 2)).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 3)).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 65534).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 65535).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 1).dec().integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 0).dec().integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 0).inv().integerValue())
assertEquals(0b0011001101010101, RuntimeValue(DataType.UWORD, 0b1100110010101010).inv().integerValue())
// assertEquals(0, RuntimeValue(DataType.UWORD, 0).neg().integerValue())
// assertEquals(0, RuntimeValue(DataType.UWORD, 0).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 11111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 65535).not().integerValue())
assertEquals(2000, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 10)).integerValue())
assertEquals(40000, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 200)).integerValue())
assertEquals(14464, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 400)).integerValue())
assertEquals(15625, RuntimeValue(DataType.UWORD, 5).pow(RuntimeValue(DataType.UWORD, 6)).integerValue())
assertEquals(12589, RuntimeValue(DataType.UWORD, 5).pow(RuntimeValue(DataType.UWORD, 7)).integerValue())
assertEquals(10000, RuntimeValue(DataType.UWORD, 5000).shl().integerValue())
assertEquals(60000, RuntimeValue(DataType.UWORD, 30000).shl().integerValue())
assertEquals(14464, RuntimeValue(DataType.UWORD, 40000).shl().integerValue())
}
@Test
fun arithmetictestByte() {
assertEquals(127, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 27)).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 28)).integerValue())
assertEquals(-127, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 29)).integerValue())
assertEquals(1, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 2)).integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 3)).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -100).sub(RuntimeValue(DataType.BYTE, 28)).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, -100).sub(RuntimeValue(DataType.BYTE, 29)).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, 126).inc().integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, 127).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 1).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 0).dec().integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -127).dec().integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, -128).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 0).inv().integerValue())
assertEquals(-103, RuntimeValue(DataType.BYTE, 0b01100110).inv().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 0).neg().integerValue())
assertEquals(-2, RuntimeValue(DataType.BYTE, 2).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.BYTE, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, -33).not().integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 10).mul(RuntimeValue(DataType.BYTE, 10)).integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 20).mul(RuntimeValue(DataType.BYTE, 10)).integerValue())
assertEquals(25, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 2)).integerValue())
assertEquals(125, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 3)).integerValue())
assertEquals(113, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 4)).integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 50).shl().integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 100).shl().integerValue())
assertEquals(-2, RuntimeValue(DataType.BYTE, -1).shl().integerValue())
}
@Test
fun arithmetictestWorrd() {
assertEquals(32767, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 67)).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 68)).integerValue())
assertEquals(-32767, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 69)).integerValue())
assertEquals(1, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 2)).integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 3)).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32700).sub(RuntimeValue(DataType.WORD, 68)).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, -32700).sub(RuntimeValue(DataType.WORD, 69)).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, 32766).inc().integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, 32767).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 1).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 0).dec().integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32767).dec().integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, -32768).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 0).inv().integerValue())
assertEquals(-103, RuntimeValue(DataType.WORD, 0b01100110).inv().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 0).neg().integerValue())
assertEquals(-2, RuntimeValue(DataType.WORD, 2).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.WORD, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, -33).not().integerValue())
assertEquals(10000, RuntimeValue(DataType.WORD, 100).mul(RuntimeValue(DataType.WORD, 100)).integerValue())
assertEquals(-25536, RuntimeValue(DataType.WORD, 200).mul(RuntimeValue(DataType.WORD, 200)).integerValue())
assertEquals(15625, RuntimeValue(DataType.WORD, 5).pow(RuntimeValue(DataType.WORD, 6)).integerValue())
assertEquals(-6487, RuntimeValue(DataType.WORD, 9).pow(RuntimeValue(DataType.WORD, 5)).integerValue())
assertEquals(18000, RuntimeValue(DataType.WORD, 9000).shl().integerValue())
assertEquals(-25536, RuntimeValue(DataType.WORD, 20000).shl().integerValue())
assertEquals(-2, RuntimeValue(DataType.WORD, -1).shl().integerValue())
}
} }

View File

@ -8,10 +8,10 @@ import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.IterableDatatypes import prog8.ast.base.IterableDatatypes
import prog8.ast.base.WordDatatypes import prog8.ast.base.WordDatatypes
import prog8.vm.RuntimeValue
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.intermediate.Instruction import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.Opcode import prog8.compiler.intermediate.Opcode
import prog8.vm.RuntimeValue
import prog8.vm.stackvm.* import prog8.vm.stackvm.*
import kotlin.test.* import kotlin.test.*
@ -413,10 +413,10 @@ class TestStackVmOpcodes {
testUnaryOperator(RuntimeValue(DataType.UBYTE, 123), Opcode.INV_BYTE, RuntimeValue(DataType.UBYTE, 0x84)) testUnaryOperator(RuntimeValue(DataType.UBYTE, 123), Opcode.INV_BYTE, RuntimeValue(DataType.UBYTE, 0x84))
testUnaryOperator(RuntimeValue(DataType.UWORD, 4044), Opcode.INV_WORD, RuntimeValue(DataType.UWORD, 0xf033)) testUnaryOperator(RuntimeValue(DataType.UWORD, 4044), Opcode.INV_WORD, RuntimeValue(DataType.UWORD, 0xf033))
assertFailsWith<VmExecutionException> { assertFailsWith<VmExecutionException> {
testUnaryOperator(RuntimeValue(DataType.BYTE, 123), Opcode.INV_BYTE, RuntimeValue(DataType.BYTE, 0x84)) testUnaryOperator(RuntimeValue(DataType.BYTE, 123), Opcode.INV_BYTE, RuntimeValue(DataType.BYTE, -124))
} }
assertFailsWith<VmExecutionException> { assertFailsWith<VmExecutionException> {
testUnaryOperator(RuntimeValue(DataType.WORD, 4044), Opcode.INV_WORD, RuntimeValue(DataType.WORD, 0xf033)) testUnaryOperator(RuntimeValue(DataType.WORD, 4044), Opcode.INV_WORD, RuntimeValue(DataType.WORD, -4043))
} }
} }

View File

@ -7,10 +7,15 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.LiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.vm.RuntimeValue import prog8.ast.expressions.ReferenceLiteralValue
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.c64.* import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import java.io.CharConversionException import java.io.CharConversionException
import kotlin.test.* import kotlin.test.*
@ -336,8 +341,8 @@ class TestPetscii {
@Test @Test
fun testLiteralValueComparisons() { fun testLiteralValueComparisons() {
val ten = LiteralValue(DataType.UWORD, wordvalue = 10, position = Position("", 0, 0, 0)) val ten = NumericLiteralValue(DataType.UWORD, 10, Position("", 0, 0, 0))
val nine = LiteralValue(DataType.UBYTE, bytevalue = 9, position = Position("", 0, 0, 0)) val nine = NumericLiteralValue(DataType.UBYTE, 9, Position("", 0, 0, 0))
assertEquals(ten, ten) assertEquals(ten, ten)
assertNotEquals(ten, nine) assertNotEquals(ten, nine)
assertFalse(ten != ten) assertFalse(ten != ten)
@ -353,17 +358,11 @@ class TestPetscii {
assertTrue(ten <= ten) assertTrue(ten <= ten)
assertFalse(ten < ten) assertFalse(ten < ten)
val abc = LiteralValue(DataType.STR, strvalue = "abc", position = Position("", 0, 0, 0)) val abc = ReferenceLiteralValue(DataType.STR, str = "abc", position = Position("", 0, 0, 0))
val abd = LiteralValue(DataType.STR, strvalue = "abd", position = Position("", 0, 0, 0)) val abd = ReferenceLiteralValue(DataType.STR, str = "abd", position = Position("", 0, 0, 0))
assertEquals(abc, abc) assertEquals(abc, abc)
assertTrue(abc!=abd) assertTrue(abc!=abd)
assertFalse(abc!=abc) assertFalse(abc!=abc)
assertTrue(abc < abd)
assertTrue(abc <= abd)
assertFalse(abd <= abc)
assertTrue(abd >= abc)
assertTrue(abd > abc)
assertFalse(abc > abd)
} }
@Test @Test

View File

@ -2,7 +2,9 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="Python 3.7 (py3)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.7 (py3)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>

View File

@ -2,6 +2,8 @@
Writing and building a program Writing and building a program
============================== ==============================
.. _building_compiler:
First, getting a working compiler First, getting a working compiler
--------------------------------- ---------------------------------
@ -11,20 +13,38 @@ Then you can choose a few ways to get a compiler:
**Download a precompiled version from github:** **Download a precompiled version from github:**
#. download a recent "prog8compiler.jar" from `the releases on Github <https://github.com/irmen/prog8/releases>`_ #. download a recent "fat-jar" (called something like "prog8compiler-all.jar") from `the releases on Github <https://github.com/irmen/prog8/releases>`_
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it. #. run the compiler with "java -jar prog8compiler-all.jar" to see how you can use it.
**Using the shell scripts:** **using the Gradle build system to make it yourself:**
#. run the "create_compiler_jar.sh" shell script and have a little patience while everything is built The Gradle build system is used to build the compiler.
#. it will output "prog8compiler.jar" file which contains everything. The most interesting gradle commands to run are probably:
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it.
**using the Gradle build system directly:** ``./gradlew check``
Builds the compiler code and runs all available checks and unit-tests.
``./gradlew installDist``
Builds the compiler and installs it with scripts to run it, in the directory
``./compiler/build/install/p8compile``
``./gradlew installShadowDist``
Creates a 'fat-jar' that contains the compiler and all dependencies, in a single
executable .jar file, and includes few start scripts to run it.
The output can be found in ``.compiler/build/install/compiler-shadow/``
``./gradlew shadowDistZip``
Creates a zipfile with the above in it, for easy distribution.
This file can be found in ``./compiler/build/distributions/``
#. run the command "./gradlew installDist" and have a little patience while everything is built For normal use, the ``installDist`` target should suffice and ater succesful completion
#. it will create the commands and required libraries in the "./compiler/build/install/p8compile/" directory of that build task, you can start the compiler with:
#. run the compiler with the "./compiler/build/install/p8compile/bin/p8compile" command to see how you can use it.
``./compiler/build/install/p8compile/bin/p8compile <options> <sourcefile>``
(You should probably make an alias...)
.. note::
Development and testing is done on Linux, but the compiler should run on most
operating systems. If you do have trouble building or running
the compiler on another operating system, please let me know!
@ -54,14 +74,10 @@ The compiler will link everything together into one output program at the end.
If you start the compiler without arguments, it will print a short usage text. If you start the compiler without arguments, it will print a short usage text.
For normal use the compiler is invoked with the command: For normal use the compiler is invoked with the command:
``$ java -jar prog8compiler.jar sourcefile.p8`` if you're using the packaged release version, or ``$ java -jar prog8compiler.jar sourcefile.p8``
``$ ./p8compile.sh sourcefile.p8`` if you're running an unpackaged development version. Other options are also available, see the introduction page about how
to build and run the compiler.
If you tell it to save the intermediate code for the prog8 virtual machine, you can
actually run this code with the command:
``$ ./p8vm.sh sourcefile.vm.txt``
By default, assembly code is generated and written to ``sourcefile.asm``. By default, assembly code is generated and written to ``sourcefile.asm``.
@ -131,3 +147,24 @@ or::
$ ./p8compile.sh -emu examples/rasterbars.p8 $ ./p8compile.sh -emu examples/rasterbars.p8
Virtual Machine
---------------
You may have noticed the ``-avm`` and ``-vm`` command line options for the compiler:
-avm
Launches the "AST virtual machine" that directly executes the parsed program.
No compilation steps will be performed.
Allows for very fast testing and debugging before actually compiling programs
to machine code.
It simulates a bare minimum of features from the target platform, so most stuff
that calls ROM routines or writes into hardware registers won't work. But basic
system routines are emulated.
-vm <vm bytecode file>
Launches the "intermediate code VM"
it interprets the intermediate code that the compiler can write when using the ``-writevm``
option. This is the code that will be fed to the assembly code generator,
so you'll skip that last step.

View File

@ -37,8 +37,8 @@ This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/license
:alt: Fully playable tetris clone :alt: Fully playable tetris clone
Code example Code examples
------------ -------------
This code calculates prime numbers using the Sieve of Eratosthenes algorithm:: This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
@ -89,13 +89,47 @@ This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
} }
when compiled an ran on a C-64 you'll get: when compiled an ran on a C-64 you get this:
.. image:: _static/primes_example.png .. image:: _static/primes_example.png
:align: center :align: center
:alt: result when run on C-64 :alt: result when run on C-64
The following programs shows a use of the high level ``struct`` type::
%import c64utils
%zeropage basicsafe
~ main {
struct Color {
ubyte red
ubyte green
ubyte blue
}
sub start() {
Color purple = {255, 0, 255}
Color other
other = purple
other.red /= 2
other.green = 10 + other.green / 2
other.blue = 99
c64scr.print_ub(other.red)
c64.CHROUT(',')
c64scr.print_ub(other.green)
c64.CHROUT(',')
c64scr.print_ub(other.blue)
c64.CHROUT('\n')
}
}
when compiled and ran, it prints ``127,10,99`` on the screen.
Design principles and features Design principles and features
------------------------------ ------------------------------
@ -143,37 +177,14 @@ Finally: a **C-64 emulator** (or a real C-64 ofcourse) to run the programs on. T
of the `Vice emulator <http://vice-emu.sourceforge.net/>`_. of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
.. important:: .. important::
**Building the compiler itself:** **Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
(re)building the compiler itself requires a recent Kotlin SDK. (re)building the compiler itself requires a recent Kotlin SDK.
The compiler is developed using the `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_ The compiler is developed using the `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_
IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available). IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available).
But a bare Kotlin SDK installation should work just as well. But a bare Kotlin SDK installation should work just as well.
A shell script (``create_compiler_jar.sh``) is provided to build and package the compiler from the command line. Instructions on how to obtain a working compiler are in :ref:`building_compiler`.
If you have the 'fat-jar' you can run it with ``java -jar prog8compiler.jar``.
You can also use the Gradle build system to build the compiler (it will take care of
downloading all required libraries for you) by typing ``gradle build`` for instance.
The output of this gradle build will appear in the "./compiler/build/install/p8compile/" directory.
The most interesting gradle commands to run are probably:
``./gradlew check``
Builds the compiler code and runs all available checks and unit-tests.
``./gradlew installShadowDist``
Creates a 'fat-jar' that contains the compiler and all dependencies,
and a few start scripts to run it.
The output can be found in ``.compiler/build/install/compiler-shadow/``
and you can launch the compiler with the script
``./compiler/build/install/compiler-shadow/bin/p8compile``.
``./gradlew shadowDistZip``
Creates a zipfile with the above in it, for easy distribution.
This file can be found in ``./compiler/build/distributions/``
.. note::
Development and testing is done on Linux, but the compiler should run on most
operating systems. If you do have trouble building or running
the compiler on another operating system, please let me know!
.. toctree:: .. toctree::

View File

@ -237,6 +237,7 @@ Arrays
^^^^^^ ^^^^^^
Array types are also supported. They can be made of bytes, words or floats:: Array types are also supported. They can be made of bytes, words or floats::
byte[10] array ; array of 10 bytes, initially set to 0
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...] byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199] byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
@ -266,6 +267,13 @@ Strings in your source code files will be encoded (translated from ASCII/UTF-8)
PETSCII is the default choice. If you need screencodes (also called 'poke' codes) instead, PETSCII is the default choice. If you need screencodes (also called 'poke' codes) instead,
you have to use the ``str_s`` variants of the string type identifier. you have to use the ``str_s`` variants of the string type identifier.
You can concatenate two string literals using '+' (not very useful though) or repeat
a string literal a given number of times using '*'::
str string1 = "first part" + "second part"
str string2 = "hello!" * 10
.. caution:: .. caution::
It's probably best that you don't change strings after they're created. It's probably best that you don't change strings after they're created.
This is because if your program exits and is restarted (without loading it again), This is because if your program exits and is restarted (without loading it again),
@ -273,6 +281,39 @@ you have to use the ``str_s`` variants of the string type identifier.
The same is true for arrays by the way. The same is true for arrays by the way.
Structs
^^^^^^^
A struct is a group of one or more other variables.
This allows you to reuse the definition and manipulate it as a whole.
Individual variables in the struct are accessed as you would expect, just
use a scoped name to refer to them: ``structvariable.membername``.
Structs are a bit limited in Prog8: you can only use numerical variables
as member of a struct, so strings and arrays and other structs can not be part of a struct.
Also, it is not possible to use a struct itself inside an array.
Structs are mainly syntactic sugar for repeated groups of vardecls
and assignments that belong together. However,
*they are layed out in sequence in memory as the members are defined*
which may be usefulif you want to pass pointers around.
To create a variable of a struct type you need to define the struct itself,
and then create a variable with it::
struct Color {
ubyte red
ubyte green
ubyte blue
}
Color rgb = {255,122,0} ; note the curly braces here instead of brackets
Color another ; the init value is optional, like arrays
another = rgb ; assign all of the values of rgb to another
another.blue = 255 ; set a single member
Special types: const and memory-mapped Special types: const and memory-mapped
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -321,10 +362,17 @@ Initial values across multiple runs of the program
When declaring values with an initial value, this value will be set into the variable each time When declaring values with an initial value, this value will be set into the variable each time
the program reaches the declaration again. This can be in loops, multiple subroutine calls, the program reaches the declaration again. This can be in loops, multiple subroutine calls,
or even multiple invocations of the entire program. If you omit an initial value, it will or even multiple invocations of the entire program. If you omit an initial value, it will
be set to zero only for the first run of the program. A second run will utilize the last value be set to zero *but only for the first run of the program*. A second run will utilize the last value
where it left off (but your code will be a bit smaller because no initialization instructions where it left off (but your code will be a bit smaller because no initialization instructions
are generated) are generated)
This only works for simple types, *and not for string variables and arrays*.
It is assumed these are left unchanged by the program; they are not re-initialized on
a second run.
If you do modify them in-place, you should take care yourself that they work as
expected when the program is restarted.
(This is an optimization choice to avoid having to store two copies of every string and array)
.. caution:: .. caution::
variables that get allocated in zero-page will *not* have a zero starting value when you omit variables that get allocated in zero-page will *not* have a zero starting value when you omit
the variable's initialization. They'll be whatever the last value in that zero page the variable's initialization. They'll be whatever the last value in that zero page
@ -334,12 +382,6 @@ are generated)
this behavior may change in a future version so that subsequent runs always this behavior may change in a future version so that subsequent runs always
use the same initial values use the same initial values
This only works for simple types, *and not for string variables and arrays*.
It is assumed these are left unchanged by the program.
If you do modify them in-place, you should take care yourself that they work as
expected when the program is restarted.
(This is an optimization choice to avoid having to store two copies of every string and array)
Loops Loops
----- -----
@ -416,15 +458,26 @@ So ``if_cc goto target`` will directly translate into the single CPU instruction
Maybe in the future this will be a separate nested scope, but for now, that is Maybe in the future this will be a separate nested scope, but for now, that is
only possible when defining a subroutine. only possible when defining a subroutine.
when statement (jumptable) when statement ('jump table')
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. attention:: Instead of writing a bunch of sequential if-elseif statements, it is more readable to
TODO: docs for this this must still be written. use a ``when`` statement. (It will also result in greatly improved assembly code generation)
TODO: the code generator for this is not yet working. Use a ``when`` statement if you have a set of fixed choices that each should result in a certain
action. It is possible to combine several choices to result in the same action::
Use a ``when`` statement if you have a set of choices that each should result in a certain when value {
action. It's more readable (and results in faster code) than using a lot of if / else statements. 4 -> c64scr.print("four")
5 -> c64scr.print("five")
10,20,30 -> {
c64scr.print("ten or twenty or thirty")
}
else -> c64scr.print("don't know")
}
The when-*value* can be any expression but the choice values have to evaluate to
compile-time constant integers (bytes or words). They also have to be the same
datatype as the when-value, otherwise no efficient comparison can be done.
Assignments Assignments

View File

@ -215,7 +215,7 @@ Variable declarations
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
Variables should be declared with their exact type and size so the compiler can allocate storage Variables should be declared with their exact type and size so the compiler can allocate storage
for them. You must give them an initial value as well. That value can be a simple literal value, for them. You can give them an initial value as well. That value can be a simple literal value,
or an expression. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it or an expression. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
when selecting variables to be put into zeropage. when selecting variables to be put into zeropage.
The syntax is:: The syntax is::
@ -231,10 +231,11 @@ Various examples::
str name = "my name is Irmen" str name = "my name is Irmen"
uword address = &counter uword address = &counter
byte[] values = [11, 22, 33, 44, 55] byte[] values = [11, 22, 33, 44, 55]
byte[5] values ; array of 5 bytes, initially set to zero
byte[5] values = 255 ; initialize with five 255 bytes byte[5] values = 255 ; initialize with five 255 bytes
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
Color rgb = {1,255,0} ; a struct variable with initial values
Data types Data types
@ -358,6 +359,26 @@ Syntax is familiar with brackets: ``arrayvar[x]`` ::
string[4] ; the fifth character (=byte) in the string string[4] ; the fifth character (=byte) in the string
Struct
^^^^^^
A *struct* has to be defined to specify what its member variables are.
There are one or more members::
struct <structname> {
<vardecl>
[ <vardecl> ...]
}
You can only use numerical variables as member of a struct, so strings and arrays
and other structs can not be part of a struct. Vice versa, a struct can not occur in an array.
After defining a struct you can use the name of the struct as a data type to declare variables with.
Struct variables can be assigned a struct literal value (also in their declaration as initial value)::
Color rgb = {255, 100, 0} ; curly braces instead of brackets
Operators Operators
--------- ---------
@ -444,11 +465,12 @@ Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code or referencing However, the special ``asmsub`` routines (implemented in assembly code or referencing
a routine in kernel ROM) can return more than one return values, for instance a status a routine in kernel ROM) can return more than one return values, for instance a status
in the carry bit and a number in A, or a 16-bit value in A/Y registers. in the carry bit and a number in A, or a 16-bit value in A/Y registers.
Only for these kind of subroutines it is possible to write a multi value assignment to It is not possible to process the results of a call to these kind of routines
store the resulting values:: directly from the language, because only single value assignments are possible.
You can still call the subroutine and not store the results.
var1, var2, var3 = asmsubroutine() But if you want to do something with the values it returns, you'll have to write
a small block of custom inline assembly that does the call and stores the values
appropriately. Don't forget to save/restore the registers if required.
Subroutine definitions Subroutine definitions
@ -610,28 +632,28 @@ The XX corresponds to one of the eigth branching instructions so the possibiliti
``if_cs``, ``if_cc``, ``if_eq``, ``if_ne``, ``if_pl``, ``if_mi``, ``if_vs`` and ``if_vc``. ``if_cs``, ``if_cc``, ``if_eq``, ``if_ne``, ``if_pl``, ``if_mi``, ``if_vs`` and ``if_vc``.
It can also be one of the four aliases that are easier to read: ``if_z``, ``if_nz``, ``if_pos`` and ``if_neg``. It can also be one of the four aliases that are easier to read: ``if_z``, ``if_nz``, ``if_pos`` and ``if_neg``.
when statement (jumptable) when statement ('jump table')
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. attention:: The structure of a when statement is like this::
TODO: docs for this this must still be written.
TODO: the code generator for this is not yet working.
The condition value can only be an integer datatype. when <expression> {
The choice values must be constant integer values. <value(s)> -> <statement(s)>
<value(s)> -> <statement(s)>
code example:: ...
[ else -> <statement(s)> ]
when 4+A+Y {
10 -> {
c64scr.print("ten")
} }
The when-*value* can be any expression but the choice values have to evaluate to
compile-time constant integers (bytes or words).
The else part is optional.
Choices can result in a single statement or a block of multiple statements in which
case you have to use { } to enclose them::
when value {
4 -> c64scr.print("four")
5 -> c64scr.print("five") 5 -> c64scr.print("five")
30 -> c64scr.print("thirty") 10,20,30 -> {
99 -> c64scr.print("nn") c64scr.print("ten or twenty or thirty")
55 -> {
; will be optimized away
}
else -> {
c64scr.print("!??!\n")
} }
else -> c64scr.print("don't know")
} }

View File

@ -52,25 +52,6 @@ Allocate a fixed word in ZP that is the TOS so we can operate on TOS directly
without having to to index into the stack? without having to to index into the stack?
structs?
^^^^^^^^
A user defined struct type would be nice to group a bunch
of values together (and use it multiple times). Something like::
struct Point {
ubyte color
word[] vec = [0,0,0]
}
Point p1
Point p2
Point p3
p1.color = 3
p1.vec[2] = 2
Misc Misc
^^^^ ^^^^

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,8 +3,6 @@
%zeropage basicsafe %zeropage basicsafe
; The classic number guessing game. ; The classic number guessing game.
; This version uses mostly high level subroutine calls and loops.
; It's more readable than the low-level version, but produces a slightly larger program.
~ main { ~ main {
@ -32,7 +30,8 @@
ubyte guess = lsb(c64utils.str2uword(input)) ubyte guess = lsb(c64utils.str2uword(input))
if guess==secretnumber { if guess==secretnumber {
return ending(true) ending(true)
return
} else { } else {
c64scr.print("\n\nThat is too ") c64scr.print("\n\nThat is too ")
if guess<secretnumber if guess<secretnumber
@ -42,7 +41,8 @@
} }
} }
return ending(false) ending(false)
return
sub ending(ubyte success) { sub ending(ubyte success) {

View File

@ -43,8 +43,6 @@
while multiple < len(sieve) { while multiple < len(sieve) {
sieve[lsb(multiple)] = true sieve[lsb(multiple)] = true
multiple += candidate_prime multiple += candidate_prime
; c64scr.print_uw(multiple) ; TODO
; c4.CHROUT('\n') ; TODO
} }
return candidate_prime return candidate_prime
} }

View File

@ -6,9 +6,10 @@
sub start() { sub start() {
c64.SCROLY &= %11101111 ; blank the screen c64.SCROLY &= %11101111 ; blank the screen
c64utils.set_rasterirq_excl(40) c64utils.set_rasterirq_excl(40) ; register exclusive raster irq handler
while(true) { while(true) {
; enjoy the moving bars :)
} }
} }
@ -20,22 +21,20 @@
const ubyte barheight = 4 const ubyte barheight = 4
ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9] ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]
ubyte color = 0 ubyte color = 0
ubyte ypos = 0 ubyte yanim = 0
sub irq() { sub irq() {
Y++ ; slight timing delay to avoid rasterline transition issues
ubyte rasterpos = c64.RASTER
if color!=len(colors) { if color!=len(colors) {
c64.EXTCOL = colors[color] c64.EXTCOL = colors[color]
c64.RASTER += barheight ; next raster Irq for next color
color++ color++
c64.RASTER = rasterpos+barheight
} }
else { else {
ypos += 2
c64.EXTCOL = 0 c64.EXTCOL = 0
color = 0 color = 0
c64.RASTER = sin8u(ypos)/2+40 yanim += 2
} c64.RASTER = sin8u(yanim)/2+30 ; new start of raster Irq
}
c64.SCROLY &= $7f ; set high bit of the raster pos to zero
} }
} }

View File

@ -53,6 +53,7 @@
~ irq { ~ irq {
sub irq() { sub irq() {
c64.EXTCOL-- c64.EXTCOL--

31
examples/structs.p8 Normal file
View File

@ -0,0 +1,31 @@
%import c64utils
%zeropage basicsafe
~ main {
struct Color {
ubyte red
ubyte green
ubyte blue
}
sub start() {
Color purple = {255, 0, 255}
Color other
other = purple
other.red /= 2
other.green = 10 + other.green / 2
other.blue = 99
c64scr.print_ub(other.red)
c64.CHROUT(',')
c64scr.print_ub(other.green)
c64.CHROUT(',')
c64scr.print_ub(other.blue)
c64.CHROUT('\n')
}
}

View File

@ -5,20 +5,24 @@
const uword width = 40 const uword width = 40
const uword height = 25 const uword height = 25
sub start() { struct Ball {
uword anglex uword anglex
uword angley uword angley
ubyte color ubyte color
}
sub start() {
Ball ball
while true { while true {
ubyte x = msb(sin8u(msb(anglex)) as uword * width) ubyte x = msb(sin8u(msb(ball.anglex)) as uword * width)
ubyte y = msb(cos8u(msb(angley)) as uword * height) ubyte y = msb(cos8u(msb(ball.angley)) as uword * height)
c64scr.setcc(x, y, 81, color) c64scr.setcc(x, y, 81, ball.color)
anglex+=800 ball.anglex+=800
angley+=947 ball.angley+=947
color++ ball.color++
} }
} }
} }

View File

@ -75,33 +75,31 @@ waitkey:
} }
sub keypress(ubyte key) { sub move_left() {
if key==157 or key==',' {
; move left
drawBlock(xpos, ypos, 32) drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos-1, ypos) { if blocklogic.noCollision(xpos-1, ypos) {
xpos-- xpos--
} }
drawBlock(xpos, ypos, 160) drawBlock(xpos, ypos, 160)
} }
else if key==29 or key=='/' {
; move right sub move_right() {
drawBlock(xpos, ypos, 32) drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos+1, ypos) { if blocklogic.noCollision(xpos+1, ypos) {
xpos++ xpos++
} }
drawBlock(xpos, ypos, 160) drawBlock(xpos, ypos, 160)
} }
else if key==17 or key=='.' {
; move down faster sub move_down_faster() {
drawBlock(xpos, ypos, 32) drawBlock(xpos, ypos, 32)
if blocklogic.noCollision(xpos, ypos+1) { if blocklogic.noCollision(xpos, ypos+1) {
ypos++ ypos++
} }
drawBlock(xpos, ypos, 160) drawBlock(xpos, ypos, 160)
} }
else if key==145 or key==' ' {
; drop down immediately sub drop_down_immediately() {
drawBlock(xpos, ypos, 32) drawBlock(xpos, ypos, 32)
ubyte dropypos ubyte dropypos
for dropypos in ypos+1 to boardOffsetY+boardHeight-1 { for dropypos in ypos+1 to boardOffsetY+boardHeight-1 {
@ -120,7 +118,15 @@ waitkey:
drawScore() drawScore()
} }
} }
else if key=='z' { ; no joystick equivalent (there is only 1 fire button)
sub keypress(ubyte key) {
when key {
157, ',' -> move_left()
29, '/' -> move_right()
17, '.' -> move_down_faster()
145, ' ' -> drop_down_immediately()
'z' -> {
; no joystick equivalent (there is only 1 fire button)
; rotate counter clockwise ; rotate counter clockwise
drawBlock(xpos, ypos, 32) drawBlock(xpos, ypos, 32)
if blocklogic.canRotateCCW(xpos, ypos) { if blocklogic.canRotateCCW(xpos, ypos) {
@ -139,7 +145,7 @@ waitkey:
} }
drawBlock(xpos, ypos, 160) drawBlock(xpos, ypos, 160)
} }
else if key=='x' { 'x' -> {
; rotate clockwise ; rotate clockwise
drawBlock(xpos, ypos, 32) drawBlock(xpos, ypos, 32)
if blocklogic.canRotateCW(xpos, ypos) { if blocklogic.canRotateCW(xpos, ypos) {
@ -158,7 +164,7 @@ waitkey:
} }
drawBlock(xpos, ypos, 160) drawBlock(xpos, ypos, 160)
} }
else if key=='c' { 'c' -> {
; hold ; hold
if holdingAllowed { if holdingAllowed {
sound.swapping() sound.swapping()
@ -177,6 +183,7 @@ waitkey:
} }
} }
} }
}
sub checkForLines() { sub checkForLines() {
; check if line(s) are full -> flash/clear line(s) + add score + move rest down ; check if line(s) are full -> flash/clear line(s) + add score + move rest down

View File

@ -1,56 +1,75 @@
%import c64utils %import c64utils
%import c64flt
%zeropage basicsafe %zeropage basicsafe
%option enable_floats %option enable_floats
~ main { ~ main {
sub start() { sub start() {
A=10
Y=22
uword uw = A*Y
str teststring = "hello" word w1
c64scr.print(&teststring) word w2
word w3
word w4
word w5
word w6
word w7
word w8
word w9
word w10
word w11
word w12
word w13
word w14
word w15
word w16
word w17
word w18
word w19
word @zp w20
word @zp w21
word w22
word w23
word w24
word w25
word w26
word w27
word w28
word w29
word w30
when uw { ubyte[] blaat = 10 to 20
12345 -> {
A=44 for ubyte c in 'a' to 'z' {
} c64.CHROUT(blaat[c])
12346 -> {
A=44
}
12347 -> {
A=44
}
else -> {
A=0
}
} }
when 4+A+Y { word q
10 -> { q=w26
c64scr.print("ten") q+=w27
} q+=w28
5 -> c64scr.print("five") q+=w29
30 -> c64scr.print("thirty") q+=w30
31 -> c64scr.print("thirty1") q+=w10
32 -> c64scr.print("thirty2") q+=w11
33 -> c64scr.print("thirty3") q+=w12
99 -> c64scr.print("nn") q+=w13
55 -> { q+=w14
; should be optimized away q+=w15
} q+=w16
56 -> { q+=w17
; should be optimized away q+=w18
} q+=w19
57243 -> { q+=w20
; should be optimized away q+=w21
} q+=w22
else -> { q+=w23
c64scr.print("!??!\n") q+=w24
c64scr.print("!??!!??!\n") q+=w25
c64scr.print("!??!!??!!?!\n") q+=w26
} q+=w27
}
} }
} }

View File

@ -1,33 +0,0 @@
#!/usr/bin/env bash
# compile using regular Kotlin sdk command line tool
echo "Compiling the parser..."
java -jar ./parser/antlr/lib/antlr-4.7.2-complete.jar -o ./parser/src/prog8/parser -Xexact-output-dir -no-listener -no-visitor ./parser/antlr/prog8.g4
PARSER_CLASSES=./out/production/parser
COMPILER_JAR=prog8compiler.jar
ANTLR_RUNTIME=./parser/antlr/lib/antlr-runtime-4.7.2.jar
mkdir -p ${PARSER_CLASSES}
javac -d ${PARSER_CLASSES} -cp ${ANTLR_RUNTIME} ./parser/src/prog8/parser/prog8Lexer.java ./parser/src/prog8/parser/prog8Parser.java
echo "Compiling the compiler itself..."
JAVA_OPTS="-Xmx3G -Xms300M" kotlinc -verbose -include-runtime -d ${COMPILER_JAR} -jvm-target 1.8 -cp ${ANTLR_RUNTIME}:${PARSER_CLASSES} ./compiler/src/prog8
echo "Finalizing the compiler jar file..."
# add the antlr parser classes
jar ufe ${COMPILER_JAR} prog8.CompilerMainKt -C ${PARSER_CLASSES} prog8
# add the resources
jar uf ${COMPILER_JAR} -C ./compiler/res .
# add the antlr runtime classes
rm -rf antlr_runtime_extraction
mkdir antlr_runtime_extraction
(cd antlr_runtime_extraction; jar xf ../${ANTLR_RUNTIME})
jar uf ${COMPILER_JAR} -C antlr_runtime_extraction org
rm -rf antlr_runtime_extraction
echo "Done!"

View File

@ -1,9 +0,0 @@
@echo off
set PROG8CLASSPATH=./compiler/build/classes/kotlin/main;./compiler/build/resources/main;./parser/build/classes/java/main
set KOTLINPATH=%USERPROFILE%/.IdeaIC2019.1/config/plugins/Kotlin
set LIBJARS=%KOTLINPATH%/lib/kotlin-stdlib.jar;%KOTLINPATH%/lib/kotlin-reflect.jar;./parser/antlr/lib/antlr-runtime-4.7.2.jar
java -cp %PROG8CLASSPATH%;%LIBJARS% prog8.CompilerMainKt %*
@REM if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar

View File

@ -1,9 +0,0 @@
#!/usr/bin/env sh
PROG8CLASSPATH=./compiler/build/classes/kotlin/main:./compiler/build/resources/main:./parser/build/classes/java/main
KOTLINPATH=${HOME}/.IntelliJIdea2019.1/config/plugins/Kotlin
LIBJARS=${KOTLINPATH}/lib/kotlin-stdlib.jar:${KOTLINPATH}/lib/kotlin-reflect.jar:./parser/antlr/lib/antlr-runtime-4.7.2.jar
java -cp ${PROG8CLASSPATH}:${LIBJARS} prog8.CompilerMainKt $*
# if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar

View File

@ -1,9 +0,0 @@
@echo off
set PROG8CLASSPATH=./compiler/build/classes/kotlin/main;./compiler/build/resources/main
set KOTLINPATH=%USERPROFILE%/.IdeaIC2019.1/config/plugins/Kotlin
set LIBJARS=%KOTLINPATH%/lib/kotlin-stdlib.jar;%KOTLINPATH%/lib/kotlin-reflect.jar
java -cp %PROG8CLASSPATH%;%LIBJARS% prog8.vm.stackvm.MainKt %*
@REM if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar -vm

View File

@ -1,9 +0,0 @@
#!/usr/bin/env sh
PROG8CLASSPATH=./compiler/build/classes/kotlin/main:./compiler/build/resources/main
KOTLINPATH=${HOME}/.IntelliJIdea2019.1/config/plugins/Kotlin
LIBJARS=${KOTLINPATH}/lib/kotlin-stdlib.jar:${KOTLINPATH}/lib/kotlin-reflect.jar
java -cp ${PROG8CLASSPATH}:${LIBJARS} prog8.vm.stackvm.MainKt $*
# if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar -vm

View File

@ -73,9 +73,12 @@ block: '~' identifier integerliteral? statement_block EOL ;
statement : statement :
directive directive
| varinitializer | varinitializer
| structvarinitializer
| vardecl | vardecl
| structvardecl
| constdecl | constdecl
| memoryvardecl | memoryvardecl
| structdecl
| assignment | assignment
| augassignment | augassignment
| unconditionaljump | unconditionaljump
@ -109,21 +112,25 @@ directive :
directivearg : stringliteral | identifier | integerliteral ; directivearg : stringliteral | identifier | integerliteral ;
vardecl: datatype ZEROPAGE? (arrayindex | ARRAYSIG) ? identifier ; vardecl: datatype ZEROPAGE? (arrayindex | ARRAYSIG) ? varname=identifier ;
structvardecl: structname=identifier varname=identifier ;
varinitializer : vardecl '=' expression ; varinitializer : vardecl '=' expression ;
structvarinitializer : structvardecl '=' expression ;
constdecl: 'const' varinitializer ; constdecl: 'const' varinitializer ;
memoryvardecl: ADDRESS_OF varinitializer; memoryvardecl: ADDRESS_OF varinitializer;
structdecl: 'struct' identifier '{' EOL vardecl ( EOL vardecl)* EOL? '}' EOL;
datatype: 'ubyte' | 'byte' | 'uword' | 'word' | 'float' | 'str' | 'str_s' ; datatype: 'ubyte' | 'byte' | 'uword' | 'word' | 'float' | 'str' | 'str_s' ;
arrayindex: '[' expression ']' ; arrayindex: '[' expression ']' ;
assignment : assign_targets '=' expression ; assignment : assign_target '=' expression ;
assign_targets : assign_target (',' assign_target)* ;
augassignment : augassignment :
assign_target operator=('+=' | '-=' | '/=' | '*=' | '**=' | '&=' | '|=' | '^=' | '%=' | '<<=' | '>>=' ) expression assign_target operator=('+=' | '-=' | '/=' | '*=' | '**=' | '&=' | '|=' | '^=' | '%=' | '<<=' | '>>=' ) expression
@ -185,7 +192,7 @@ expression_list :
expression (',' EOL? expression)* // you can split the expression list over several lines expression (',' EOL? expression)* // you can split the expression list over several lines
; ;
returnstmt : 'return' expression_list? ; returnstmt : 'return' expression? ;
breakstmt : 'break'; breakstmt : 'break';
@ -207,7 +214,9 @@ wordsuffix : '.w' ;
booleanliteral : 'true' | 'false' ; booleanliteral : 'true' | 'false' ;
arrayliteral : '[' EOL? expression (',' EOL? expression)* EOL? ']' ; // you can split the array list over several lines arrayliteral : '[' EOL? expression (',' EOL? expression)* EOL? ']' ; // you can split the values over several lines
structliteral : '{' EOL? expression (',' EOL? expression)* EOL? '}' ; // you can split the values over several lines
stringliteral : STRING ; stringliteral : STRING ;
@ -215,6 +224,7 @@ charliteral : SINGLECHAR ;
floatliteral : FLOAT_NUMBER ; floatliteral : FLOAT_NUMBER ;
literalvalue : literalvalue :
integerliteral integerliteral
| booleanliteral | booleanliteral
@ -222,6 +232,7 @@ literalvalue :
| stringliteral | stringliteral
| charliteral | charliteral
| floatliteral | floatliteral
| structliteral
; ;
inlineasm : '%asm' INLINEASMBLOCK; inlineasm : '%asm' INLINEASMBLOCK;
@ -282,4 +293,5 @@ repeatloop: 'repeat' (statement | statement_block) EOL? 'until' expression ;
whenstmt: 'when' expression '{' EOL (when_choice | EOL) * '}' EOL? ; whenstmt: 'when' expression '{' EOL (when_choice | EOL) * '}' EOL? ;
when_choice: (expression | 'else' ) '->' (statement | statement_block ) ; when_choice: (expression_list | 'else' ) '->' (statement | statement_block ) ;

View File

@ -1,512 +0,0 @@
// Generated from prog8.g4 by ANTLR 4.7.2
package prog8.parser;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.atn.*;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.misc.*;
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
public class prog8Lexer extends Lexer {
static { RuntimeMetaData.checkVersion("4.7.2", RuntimeMetaData.VERSION); }
protected static final DFA[] _decisionToDFA;
protected static final PredictionContextCache _sharedContextCache =
new PredictionContextCache();
public static final int
T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9,
T__9=10, T__10=11, T__11=12, T__12=13, T__13=14, T__14=15, T__15=16, T__16=17,
T__17=18, T__18=19, T__19=20, T__20=21, T__21=22, T__22=23, T__23=24,
T__24=25, T__25=26, T__26=27, T__27=28, T__28=29, T__29=30, T__30=31,
T__31=32, T__32=33, T__33=34, T__34=35, T__35=36, T__36=37, T__37=38,
T__38=39, T__39=40, T__40=41, T__41=42, T__42=43, T__43=44, T__44=45,
T__45=46, T__46=47, T__47=48, T__48=49, T__49=50, T__50=51, T__51=52,
T__52=53, T__53=54, T__54=55, T__55=56, T__56=57, T__57=58, T__58=59,
T__59=60, T__60=61, T__61=62, T__62=63, T__63=64, T__64=65, T__65=66,
T__66=67, T__67=68, T__68=69, T__69=70, T__70=71, T__71=72, T__72=73,
T__73=74, T__74=75, T__75=76, T__76=77, T__77=78, T__78=79, T__79=80,
T__80=81, T__81=82, T__82=83, T__83=84, T__84=85, T__85=86, T__86=87,
T__87=88, T__88=89, T__89=90, T__90=91, T__91=92, T__92=93, T__93=94,
T__94=95, T__95=96, T__96=97, T__97=98, T__98=99, T__99=100, T__100=101,
T__101=102, T__102=103, T__103=104, T__104=105, T__105=106, T__106=107,
T__107=108, T__108=109, LINECOMMENT=110, COMMENT=111, WS=112, EOL=113,
NAME=114, DEC_INTEGER=115, HEX_INTEGER=116, BIN_INTEGER=117, ADDRESS_OF=118,
FLOAT_NUMBER=119, STRING=120, INLINEASMBLOCK=121, SINGLECHAR=122, ZEROPAGE=123,
ARRAYSIG=124;
public static String[] channelNames = {
"DEFAULT_TOKEN_CHANNEL", "HIDDEN"
};
public static String[] modeNames = {
"DEFAULT_MODE"
};
private static String[] makeRuleNames() {
return new String[] {
"T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8",
"T__9", "T__10", "T__11", "T__12", "T__13", "T__14", "T__15", "T__16",
"T__17", "T__18", "T__19", "T__20", "T__21", "T__22", "T__23", "T__24",
"T__25", "T__26", "T__27", "T__28", "T__29", "T__30", "T__31", "T__32",
"T__33", "T__34", "T__35", "T__36", "T__37", "T__38", "T__39", "T__40",
"T__41", "T__42", "T__43", "T__44", "T__45", "T__46", "T__47", "T__48",
"T__49", "T__50", "T__51", "T__52", "T__53", "T__54", "T__55", "T__56",
"T__57", "T__58", "T__59", "T__60", "T__61", "T__62", "T__63", "T__64",
"T__65", "T__66", "T__67", "T__68", "T__69", "T__70", "T__71", "T__72",
"T__73", "T__74", "T__75", "T__76", "T__77", "T__78", "T__79", "T__80",
"T__81", "T__82", "T__83", "T__84", "T__85", "T__86", "T__87", "T__88",
"T__89", "T__90", "T__91", "T__92", "T__93", "T__94", "T__95", "T__96",
"T__97", "T__98", "T__99", "T__100", "T__101", "T__102", "T__103", "T__104",
"T__105", "T__106", "T__107", "T__108", "LINECOMMENT", "COMMENT", "WS",
"EOL", "NAME", "DEC_INTEGER", "HEX_INTEGER", "BIN_INTEGER", "ADDRESS_OF",
"FLOAT_NUMBER", "FNUMBER", "STRING_ESCAPE_SEQ", "STRING", "INLINEASMBLOCK",
"SINGLECHAR", "ZEROPAGE", "ARRAYSIG"
};
}
public static final String[] ruleNames = makeRuleNames();
private static String[] makeLiteralNames() {
return new String[] {
null, "'~'", "':'", "'goto'", "'%output'", "'%launcher'", "'%zeropage'",
"'%zpreserved'", "'%address'", "'%import'", "'%breakpoint'", "'%asminclude'",
"'%asmbinary'", "'%option'", "','", "'='", "'const'", "'ubyte'", "'byte'",
"'uword'", "'word'", "'float'", "'str'", "'str_s'", "'['", "']'", "'+='",
"'-='", "'/='", "'*='", "'**='", "'&='", "'|='", "'^='", "'%='", "'<<='",
"'>>='", "'++'", "'--'", "'+'", "'-'", "'**'", "'*'", "'/'", "'%'", "'<<'",
"'>>'", "'<'", "'>'", "'<='", "'>='", "'=='", "'!='", "'^'", "'|'", "'to'",
"'step'", "'and'", "'or'", "'xor'", "'not'", "'('", "')'", "'as'", "'@'",
"'return'", "'break'", "'continue'", "'.'", "'A'", "'X'", "'Y'", "'AX'",
"'AY'", "'XY'", "'Pc'", "'Pz'", "'Pn'", "'Pv'", "'.w'", "'true'", "'false'",
"'%asm'", "'sub'", "'->'", "'{'", "'}'", "'asmsub'", "'stack'", "'clobbers'",
"'if'", "'else'", "'if_cs'", "'if_cc'", "'if_eq'", "'if_z'", "'if_ne'",
"'if_nz'", "'if_pl'", "'if_pos'", "'if_mi'", "'if_neg'", "'if_vs'", "'if_vc'",
"'for'", "'in'", "'while'", "'repeat'", "'until'", "'when'", null, null,
null, null, null, null, null, null, "'&'", null, null, null, null, "'@zp'",
"'[]'"
};
}
private static final String[] _LITERAL_NAMES = makeLiteralNames();
private static String[] makeSymbolicNames() {
return new String[] {
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null,
null, null, "LINECOMMENT", "COMMENT", "WS", "EOL", "NAME", "DEC_INTEGER",
"HEX_INTEGER", "BIN_INTEGER", "ADDRESS_OF", "FLOAT_NUMBER", "STRING",
"INLINEASMBLOCK", "SINGLECHAR", "ZEROPAGE", "ARRAYSIG"
};
}
private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames();
public static final Vocabulary VOCABULARY = new VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES);
/**
* @deprecated Use {@link #VOCABULARY} instead.
*/
@Deprecated
public static final String[] tokenNames;
static {
tokenNames = new String[_SYMBOLIC_NAMES.length];
for (int i = 0; i < tokenNames.length; i++) {
tokenNames[i] = VOCABULARY.getLiteralName(i);
if (tokenNames[i] == null) {
tokenNames[i] = VOCABULARY.getSymbolicName(i);
}
if (tokenNames[i] == null) {
tokenNames[i] = "<INVALID>";
}
}
}
@Override
@Deprecated
public String[] getTokenNames() {
return tokenNames;
}
@Override
public Vocabulary getVocabulary() {
return VOCABULARY;
}
public prog8Lexer(CharStream input) {
super(input);
_interp = new LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache);
}
@Override
public String getGrammarFileName() { return "prog8.g4"; }
@Override
public String[] getRuleNames() { return ruleNames; }
@Override
public String getSerializedATN() { return _serializedATN; }
@Override
public String[] getChannelNames() { return channelNames; }
@Override
public String[] getModeNames() { return modeNames; }
@Override
public ATN getATN() { return _ATN; }
@Override
public void action(RuleContext _localctx, int ruleIndex, int actionIndex) {
switch (ruleIndex) {
case 121:
STRING_action((RuleContext)_localctx, actionIndex);
break;
case 122:
INLINEASMBLOCK_action((RuleContext)_localctx, actionIndex);
break;
case 123:
SINGLECHAR_action((RuleContext)_localctx, actionIndex);
break;
}
}
private void STRING_action(RuleContext _localctx, int actionIndex) {
switch (actionIndex) {
case 0:
// get rid of the enclosing quotes
String s = getText();
setText(s.substring(1, s.length() - 1));
break;
}
}
private void INLINEASMBLOCK_action(RuleContext _localctx, int actionIndex) {
switch (actionIndex) {
case 1:
// get rid of the enclosing double braces
String s = getText();
setText(s.substring(2, s.length() - 2));
break;
}
}
private void SINGLECHAR_action(RuleContext _localctx, int actionIndex) {
switch (actionIndex) {
case 2:
// get rid of the enclosing quotes
String s = getText();
setText(s.substring(1, s.length() - 1));
break;
}
}
public static final String _serializedATN =
"\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2~\u0365\b\1\4\2\t"+
"\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13"+
"\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+
"\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31\t\31"+
"\4\32\t\32\4\33\t\33\4\34\t\34\4\35\t\35\4\36\t\36\4\37\t\37\4 \t \4!"+
"\t!\4\"\t\"\4#\t#\4$\t$\4%\t%\4&\t&\4\'\t\'\4(\t(\4)\t)\4*\t*\4+\t+\4"+
",\t,\4-\t-\4.\t.\4/\t/\4\60\t\60\4\61\t\61\4\62\t\62\4\63\t\63\4\64\t"+
"\64\4\65\t\65\4\66\t\66\4\67\t\67\48\t8\49\t9\4:\t:\4;\t;\4<\t<\4=\t="+
"\4>\t>\4?\t?\4@\t@\4A\tA\4B\tB\4C\tC\4D\tD\4E\tE\4F\tF\4G\tG\4H\tH\4I"+
"\tI\4J\tJ\4K\tK\4L\tL\4M\tM\4N\tN\4O\tO\4P\tP\4Q\tQ\4R\tR\4S\tS\4T\tT"+
"\4U\tU\4V\tV\4W\tW\4X\tX\4Y\tY\4Z\tZ\4[\t[\4\\\t\\\4]\t]\4^\t^\4_\t_\4"+
"`\t`\4a\ta\4b\tb\4c\tc\4d\td\4e\te\4f\tf\4g\tg\4h\th\4i\ti\4j\tj\4k\t"+
"k\4l\tl\4m\tm\4n\tn\4o\to\4p\tp\4q\tq\4r\tr\4s\ts\4t\tt\4u\tu\4v\tv\4"+
"w\tw\4x\tx\4y\ty\4z\tz\4{\t{\4|\t|\4}\t}\4~\t~\4\177\t\177\3\2\3\2\3\3"+
"\3\3\3\4\3\4\3\4\3\4\3\4\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\6\3\6\3\6\3"+
"\6\3\6\3\6\3\6\3\6\3\6\3\6\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\b"+
"\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\t\3\t\3\t\3\t\3\t\3\t\3"+
"\t\3\t\3\t\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\13\3\13\3\13\3\13\3\13\3"+
"\13\3\13\3\13\3\13\3\13\3\13\3\13\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f"+
"\3\f\3\f\3\f\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\16\3\16\3\16"+
"\3\16\3\16\3\16\3\16\3\16\3\17\3\17\3\20\3\20\3\21\3\21\3\21\3\21\3\21"+
"\3\21\3\22\3\22\3\22\3\22\3\22\3\22\3\23\3\23\3\23\3\23\3\23\3\24\3\24"+
"\3\24\3\24\3\24\3\24\3\25\3\25\3\25\3\25\3\25\3\26\3\26\3\26\3\26\3\26"+
"\3\26\3\27\3\27\3\27\3\27\3\30\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3\32"+
"\3\32\3\33\3\33\3\33\3\34\3\34\3\34\3\35\3\35\3\35\3\36\3\36\3\36\3\37"+
"\3\37\3\37\3\37\3 \3 \3 \3!\3!\3!\3\"\3\"\3\"\3#\3#\3#\3$\3$\3$\3$\3%"+
"\3%\3%\3%\3&\3&\3&\3\'\3\'\3\'\3(\3(\3)\3)\3*\3*\3*\3+\3+\3,\3,\3-\3-"+
"\3.\3.\3.\3/\3/\3/\3\60\3\60\3\61\3\61\3\62\3\62\3\62\3\63\3\63\3\63\3"+
"\64\3\64\3\64\3\65\3\65\3\65\3\66\3\66\3\67\3\67\38\38\38\39\39\39\39"+
"\39\3:\3:\3:\3:\3;\3;\3;\3<\3<\3<\3<\3=\3=\3=\3=\3>\3>\3?\3?\3@\3@\3@"+
"\3A\3A\3B\3B\3B\3B\3B\3B\3B\3C\3C\3C\3C\3C\3C\3D\3D\3D\3D\3D\3D\3D\3D"+
"\3D\3E\3E\3F\3F\3G\3G\3H\3H\3I\3I\3I\3J\3J\3J\3K\3K\3K\3L\3L\3L\3M\3M"+
"\3M\3N\3N\3N\3O\3O\3O\3P\3P\3P\3Q\3Q\3Q\3Q\3Q\3R\3R\3R\3R\3R\3R\3S\3S"+
"\3S\3S\3S\3T\3T\3T\3T\3U\3U\3U\3V\3V\3W\3W\3X\3X\3X\3X\3X\3X\3X\3Y\3Y"+
"\3Y\3Y\3Y\3Y\3Z\3Z\3Z\3Z\3Z\3Z\3Z\3Z\3Z\3[\3[\3[\3\\\3\\\3\\\3\\\3\\\3"+
"]\3]\3]\3]\3]\3]\3^\3^\3^\3^\3^\3^\3_\3_\3_\3_\3_\3_\3`\3`\3`\3`\3`\3"+
"a\3a\3a\3a\3a\3a\3b\3b\3b\3b\3b\3b\3c\3c\3c\3c\3c\3c\3d\3d\3d\3d\3d\3"+
"d\3d\3e\3e\3e\3e\3e\3e\3f\3f\3f\3f\3f\3f\3f\3g\3g\3g\3g\3g\3g\3h\3h\3"+
"h\3h\3h\3h\3i\3i\3i\3i\3j\3j\3j\3k\3k\3k\3k\3k\3k\3l\3l\3l\3l\3l\3l\3"+
"l\3m\3m\3m\3m\3m\3m\3n\3n\3n\3n\3n\3o\3o\7o\u02eb\no\fo\16o\u02ee\13o"+
"\3o\3o\3o\3o\3p\3p\7p\u02f6\np\fp\16p\u02f9\13p\3p\3p\3q\3q\3q\3q\3r\6"+
"r\u0302\nr\rr\16r\u0303\3s\3s\7s\u0308\ns\fs\16s\u030b\13s\3t\3t\3t\6"+
"t\u0310\nt\rt\16t\u0311\5t\u0314\nt\3u\3u\6u\u0318\nu\ru\16u\u0319\3v"+
"\3v\6v\u031e\nv\rv\16v\u031f\3w\3w\3x\3x\3x\5x\u0327\nx\3x\5x\u032a\n"+
"x\3y\6y\u032d\ny\ry\16y\u032e\3y\3y\6y\u0333\ny\ry\16y\u0334\5y\u0337"+
"\ny\3z\3z\3z\3z\5z\u033d\nz\3{\3{\3{\7{\u0342\n{\f{\16{\u0345\13{\3{\3"+
"{\3{\3|\3|\3|\3|\6|\u034e\n|\r|\16|\u034f\3|\3|\3|\3|\3|\3}\3}\3}\5}\u035a"+
"\n}\3}\3}\3}\3~\3~\3~\3~\3\177\3\177\3\177\3\u034f\2\u0080\3\3\5\4\7\5"+
"\t\6\13\7\r\b\17\t\21\n\23\13\25\f\27\r\31\16\33\17\35\20\37\21!\22#\23"+
"%\24\'\25)\26+\27-\30/\31\61\32\63\33\65\34\67\359\36;\37= ?!A\"C#E$G"+
"%I&K\'M(O)Q*S+U,W-Y.[/]\60_\61a\62c\63e\64g\65i\66k\67m8o9q:s;u<w=y>{"+
"?}@\177A\u0081B\u0083C\u0085D\u0087E\u0089F\u008bG\u008dH\u008fI\u0091"+
"J\u0093K\u0095L\u0097M\u0099N\u009bO\u009dP\u009fQ\u00a1R\u00a3S\u00a5"+
"T\u00a7U\u00a9V\u00abW\u00adX\u00afY\u00b1Z\u00b3[\u00b5\\\u00b7]\u00b9"+
"^\u00bb_\u00bd`\u00bfa\u00c1b\u00c3c\u00c5d\u00c7e\u00c9f\u00cbg\u00cd"+
"h\u00cfi\u00d1j\u00d3k\u00d5l\u00d7m\u00d9n\u00dbo\u00ddp\u00dfq\u00e1"+
"r\u00e3s\u00e5t\u00e7u\u00e9v\u00ebw\u00edx\u00efy\u00f1\2\u00f3\2\u00f5"+
"z\u00f7{\u00f9|\u00fb}\u00fd~\3\2\n\4\2\f\f\17\17\4\2\13\13\"\"\5\2C\\"+
"aac|\6\2\62;C\\aac|\5\2\62;CHch\4\2GGgg\4\2--//\6\2\f\f\16\17$$^^\2\u0374"+
"\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2"+
"\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3\2\2\2"+
"\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2\2#\3\2"+
"\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2/\3\2\2"+
"\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3\2\2\2\2;\3"+
"\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2\2\2G\3\2\2"+
"\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2Q\3\2\2\2\2S\3\2\2\2\2"+
"U\3\2\2\2\2W\3\2\2\2\2Y\3\2\2\2\2[\3\2\2\2\2]\3\2\2\2\2_\3\2\2\2\2a\3"+
"\2\2\2\2c\3\2\2\2\2e\3\2\2\2\2g\3\2\2\2\2i\3\2\2\2\2k\3\2\2\2\2m\3\2\2"+
"\2\2o\3\2\2\2\2q\3\2\2\2\2s\3\2\2\2\2u\3\2\2\2\2w\3\2\2\2\2y\3\2\2\2\2"+
"{\3\2\2\2\2}\3\2\2\2\2\177\3\2\2\2\2\u0081\3\2\2\2\2\u0083\3\2\2\2\2\u0085"+
"\3\2\2\2\2\u0087\3\2\2\2\2\u0089\3\2\2\2\2\u008b\3\2\2\2\2\u008d\3\2\2"+
"\2\2\u008f\3\2\2\2\2\u0091\3\2\2\2\2\u0093\3\2\2\2\2\u0095\3\2\2\2\2\u0097"+
"\3\2\2\2\2\u0099\3\2\2\2\2\u009b\3\2\2\2\2\u009d\3\2\2\2\2\u009f\3\2\2"+
"\2\2\u00a1\3\2\2\2\2\u00a3\3\2\2\2\2\u00a5\3\2\2\2\2\u00a7\3\2\2\2\2\u00a9"+
"\3\2\2\2\2\u00ab\3\2\2\2\2\u00ad\3\2\2\2\2\u00af\3\2\2\2\2\u00b1\3\2\2"+
"\2\2\u00b3\3\2\2\2\2\u00b5\3\2\2\2\2\u00b7\3\2\2\2\2\u00b9\3\2\2\2\2\u00bb"+
"\3\2\2\2\2\u00bd\3\2\2\2\2\u00bf\3\2\2\2\2\u00c1\3\2\2\2\2\u00c3\3\2\2"+
"\2\2\u00c5\3\2\2\2\2\u00c7\3\2\2\2\2\u00c9\3\2\2\2\2\u00cb\3\2\2\2\2\u00cd"+
"\3\2\2\2\2\u00cf\3\2\2\2\2\u00d1\3\2\2\2\2\u00d3\3\2\2\2\2\u00d5\3\2\2"+
"\2\2\u00d7\3\2\2\2\2\u00d9\3\2\2\2\2\u00db\3\2\2\2\2\u00dd\3\2\2\2\2\u00df"+
"\3\2\2\2\2\u00e1\3\2\2\2\2\u00e3\3\2\2\2\2\u00e5\3\2\2\2\2\u00e7\3\2\2"+
"\2\2\u00e9\3\2\2\2\2\u00eb\3\2\2\2\2\u00ed\3\2\2\2\2\u00ef\3\2\2\2\2\u00f5"+
"\3\2\2\2\2\u00f7\3\2\2\2\2\u00f9\3\2\2\2\2\u00fb\3\2\2\2\2\u00fd\3\2\2"+
"\2\3\u00ff\3\2\2\2\5\u0101\3\2\2\2\7\u0103\3\2\2\2\t\u0108\3\2\2\2\13"+
"\u0110\3\2\2\2\r\u011a\3\2\2\2\17\u0124\3\2\2\2\21\u0130\3\2\2\2\23\u0139"+
"\3\2\2\2\25\u0141\3\2\2\2\27\u014d\3\2\2\2\31\u0159\3\2\2\2\33\u0164\3"+
"\2\2\2\35\u016c\3\2\2\2\37\u016e\3\2\2\2!\u0170\3\2\2\2#\u0176\3\2\2\2"+
"%\u017c\3\2\2\2\'\u0181\3\2\2\2)\u0187\3\2\2\2+\u018c\3\2\2\2-\u0192\3"+
"\2\2\2/\u0196\3\2\2\2\61\u019c\3\2\2\2\63\u019e\3\2\2\2\65\u01a0\3\2\2"+
"\2\67\u01a3\3\2\2\29\u01a6\3\2\2\2;\u01a9\3\2\2\2=\u01ac\3\2\2\2?\u01b0"+
"\3\2\2\2A\u01b3\3\2\2\2C\u01b6\3\2\2\2E\u01b9\3\2\2\2G\u01bc\3\2\2\2I"+
"\u01c0\3\2\2\2K\u01c4\3\2\2\2M\u01c7\3\2\2\2O\u01ca\3\2\2\2Q\u01cc\3\2"+
"\2\2S\u01ce\3\2\2\2U\u01d1\3\2\2\2W\u01d3\3\2\2\2Y\u01d5\3\2\2\2[\u01d7"+
"\3\2\2\2]\u01da\3\2\2\2_\u01dd\3\2\2\2a\u01df\3\2\2\2c\u01e1\3\2\2\2e"+
"\u01e4\3\2\2\2g\u01e7\3\2\2\2i\u01ea\3\2\2\2k\u01ed\3\2\2\2m\u01ef\3\2"+
"\2\2o\u01f1\3\2\2\2q\u01f4\3\2\2\2s\u01f9\3\2\2\2u\u01fd\3\2\2\2w\u0200"+
"\3\2\2\2y\u0204\3\2\2\2{\u0208\3\2\2\2}\u020a\3\2\2\2\177\u020c\3\2\2"+
"\2\u0081\u020f\3\2\2\2\u0083\u0211\3\2\2\2\u0085\u0218\3\2\2\2\u0087\u021e"+
"\3\2\2\2\u0089\u0227\3\2\2\2\u008b\u0229\3\2\2\2\u008d\u022b\3\2\2\2\u008f"+
"\u022d\3\2\2\2\u0091\u022f\3\2\2\2\u0093\u0232\3\2\2\2\u0095\u0235\3\2"+
"\2\2\u0097\u0238\3\2\2\2\u0099\u023b\3\2\2\2\u009b\u023e\3\2\2\2\u009d"+
"\u0241\3\2\2\2\u009f\u0244\3\2\2\2\u00a1\u0247\3\2\2\2\u00a3\u024c\3\2"+
"\2\2\u00a5\u0252\3\2\2\2\u00a7\u0257\3\2\2\2\u00a9\u025b\3\2\2\2\u00ab"+
"\u025e\3\2\2\2\u00ad\u0260\3\2\2\2\u00af\u0262\3\2\2\2\u00b1\u0269\3\2"+
"\2\2\u00b3\u026f\3\2\2\2\u00b5\u0278\3\2\2\2\u00b7\u027b\3\2\2\2\u00b9"+
"\u0280\3\2\2\2\u00bb\u0286\3\2\2\2\u00bd\u028c\3\2\2\2\u00bf\u0292\3\2"+
"\2\2\u00c1\u0297\3\2\2\2\u00c3\u029d\3\2\2\2\u00c5\u02a3\3\2\2\2\u00c7"+
"\u02a9\3\2\2\2\u00c9\u02b0\3\2\2\2\u00cb\u02b6\3\2\2\2\u00cd\u02bd\3\2"+
"\2\2\u00cf\u02c3\3\2\2\2\u00d1\u02c9\3\2\2\2\u00d3\u02cd\3\2\2\2\u00d5"+
"\u02d0\3\2\2\2\u00d7\u02d6\3\2\2\2\u00d9\u02dd\3\2\2\2\u00db\u02e3\3\2"+
"\2\2\u00dd\u02e8\3\2\2\2\u00df\u02f3\3\2\2\2\u00e1\u02fc\3\2\2\2\u00e3"+
"\u0301\3\2\2\2\u00e5\u0305\3\2\2\2\u00e7\u0313\3\2\2\2\u00e9\u0315\3\2"+
"\2\2\u00eb\u031b\3\2\2\2\u00ed\u0321\3\2\2\2\u00ef\u0323\3\2\2\2\u00f1"+
"\u032c\3\2\2\2\u00f3\u033c\3\2\2\2\u00f5\u033e\3\2\2\2\u00f7\u0349\3\2"+
"\2\2\u00f9\u0356\3\2\2\2\u00fb\u035e\3\2\2\2\u00fd\u0362\3\2\2\2\u00ff"+
"\u0100\7\u0080\2\2\u0100\4\3\2\2\2\u0101\u0102\7<\2\2\u0102\6\3\2\2\2"+
"\u0103\u0104\7i\2\2\u0104\u0105\7q\2\2\u0105\u0106\7v\2\2\u0106\u0107"+
"\7q\2\2\u0107\b\3\2\2\2\u0108\u0109\7\'\2\2\u0109\u010a\7q\2\2\u010a\u010b"+
"\7w\2\2\u010b\u010c\7v\2\2\u010c\u010d\7r\2\2\u010d\u010e\7w\2\2\u010e"+
"\u010f\7v\2\2\u010f\n\3\2\2\2\u0110\u0111\7\'\2\2\u0111\u0112\7n\2\2\u0112"+
"\u0113\7c\2\2\u0113\u0114\7w\2\2\u0114\u0115\7p\2\2\u0115\u0116\7e\2\2"+
"\u0116\u0117\7j\2\2\u0117\u0118\7g\2\2\u0118\u0119\7t\2\2\u0119\f\3\2"+
"\2\2\u011a\u011b\7\'\2\2\u011b\u011c\7|\2\2\u011c\u011d\7g\2\2\u011d\u011e"+
"\7t\2\2\u011e\u011f\7q\2\2\u011f\u0120\7r\2\2\u0120\u0121\7c\2\2\u0121"+
"\u0122\7i\2\2\u0122\u0123\7g\2\2\u0123\16\3\2\2\2\u0124\u0125\7\'\2\2"+
"\u0125\u0126\7|\2\2\u0126\u0127\7r\2\2\u0127\u0128\7t\2\2\u0128\u0129"+
"\7g\2\2\u0129\u012a\7u\2\2\u012a\u012b\7g\2\2\u012b\u012c\7t\2\2\u012c"+
"\u012d\7x\2\2\u012d\u012e\7g\2\2\u012e\u012f\7f\2\2\u012f\20\3\2\2\2\u0130"+
"\u0131\7\'\2\2\u0131\u0132\7c\2\2\u0132\u0133\7f\2\2\u0133\u0134\7f\2"+
"\2\u0134\u0135\7t\2\2\u0135\u0136\7g\2\2\u0136\u0137\7u\2\2\u0137\u0138"+
"\7u\2\2\u0138\22\3\2\2\2\u0139\u013a\7\'\2\2\u013a\u013b\7k\2\2\u013b"+
"\u013c\7o\2\2\u013c\u013d\7r\2\2\u013d\u013e\7q\2\2\u013e\u013f\7t\2\2"+
"\u013f\u0140\7v\2\2\u0140\24\3\2\2\2\u0141\u0142\7\'\2\2\u0142\u0143\7"+
"d\2\2\u0143\u0144\7t\2\2\u0144\u0145\7g\2\2\u0145\u0146\7c\2\2\u0146\u0147"+
"\7m\2\2\u0147\u0148\7r\2\2\u0148\u0149\7q\2\2\u0149\u014a\7k\2\2\u014a"+
"\u014b\7p\2\2\u014b\u014c\7v\2\2\u014c\26\3\2\2\2\u014d\u014e\7\'\2\2"+
"\u014e\u014f\7c\2\2\u014f\u0150\7u\2\2\u0150\u0151\7o\2\2\u0151\u0152"+
"\7k\2\2\u0152\u0153\7p\2\2\u0153\u0154\7e\2\2\u0154\u0155\7n\2\2\u0155"+
"\u0156\7w\2\2\u0156\u0157\7f\2\2\u0157\u0158\7g\2\2\u0158\30\3\2\2\2\u0159"+
"\u015a\7\'\2\2\u015a\u015b\7c\2\2\u015b\u015c\7u\2\2\u015c\u015d\7o\2"+
"\2\u015d\u015e\7d\2\2\u015e\u015f\7k\2\2\u015f\u0160\7p\2\2\u0160\u0161"+
"\7c\2\2\u0161\u0162\7t\2\2\u0162\u0163\7{\2\2\u0163\32\3\2\2\2\u0164\u0165"+
"\7\'\2\2\u0165\u0166\7q\2\2\u0166\u0167\7r\2\2\u0167\u0168\7v\2\2\u0168"+
"\u0169\7k\2\2\u0169\u016a\7q\2\2\u016a\u016b\7p\2\2\u016b\34\3\2\2\2\u016c"+
"\u016d\7.\2\2\u016d\36\3\2\2\2\u016e\u016f\7?\2\2\u016f \3\2\2\2\u0170"+
"\u0171\7e\2\2\u0171\u0172\7q\2\2\u0172\u0173\7p\2\2\u0173\u0174\7u\2\2"+
"\u0174\u0175\7v\2\2\u0175\"\3\2\2\2\u0176\u0177\7w\2\2\u0177\u0178\7d"+
"\2\2\u0178\u0179\7{\2\2\u0179\u017a\7v\2\2\u017a\u017b\7g\2\2\u017b$\3"+
"\2\2\2\u017c\u017d\7d\2\2\u017d\u017e\7{\2\2\u017e\u017f\7v\2\2\u017f"+
"\u0180\7g\2\2\u0180&\3\2\2\2\u0181\u0182\7w\2\2\u0182\u0183\7y\2\2\u0183"+
"\u0184\7q\2\2\u0184\u0185\7t\2\2\u0185\u0186\7f\2\2\u0186(\3\2\2\2\u0187"+
"\u0188\7y\2\2\u0188\u0189\7q\2\2\u0189\u018a\7t\2\2\u018a\u018b\7f\2\2"+
"\u018b*\3\2\2\2\u018c\u018d\7h\2\2\u018d\u018e\7n\2\2\u018e\u018f\7q\2"+
"\2\u018f\u0190\7c\2\2\u0190\u0191\7v\2\2\u0191,\3\2\2\2\u0192\u0193\7"+
"u\2\2\u0193\u0194\7v\2\2\u0194\u0195\7t\2\2\u0195.\3\2\2\2\u0196\u0197"+
"\7u\2\2\u0197\u0198\7v\2\2\u0198\u0199\7t\2\2\u0199\u019a\7a\2\2\u019a"+
"\u019b\7u\2\2\u019b\60\3\2\2\2\u019c\u019d\7]\2\2\u019d\62\3\2\2\2\u019e"+
"\u019f\7_\2\2\u019f\64\3\2\2\2\u01a0\u01a1\7-\2\2\u01a1\u01a2\7?\2\2\u01a2"+
"\66\3\2\2\2\u01a3\u01a4\7/\2\2\u01a4\u01a5\7?\2\2\u01a58\3\2\2\2\u01a6"+
"\u01a7\7\61\2\2\u01a7\u01a8\7?\2\2\u01a8:\3\2\2\2\u01a9\u01aa\7,\2\2\u01aa"+
"\u01ab\7?\2\2\u01ab<\3\2\2\2\u01ac\u01ad\7,\2\2\u01ad\u01ae\7,\2\2\u01ae"+
"\u01af\7?\2\2\u01af>\3\2\2\2\u01b0\u01b1\7(\2\2\u01b1\u01b2\7?\2\2\u01b2"+
"@\3\2\2\2\u01b3\u01b4\7~\2\2\u01b4\u01b5\7?\2\2\u01b5B\3\2\2\2\u01b6\u01b7"+
"\7`\2\2\u01b7\u01b8\7?\2\2\u01b8D\3\2\2\2\u01b9\u01ba\7\'\2\2\u01ba\u01bb"+
"\7?\2\2\u01bbF\3\2\2\2\u01bc\u01bd\7>\2\2\u01bd\u01be\7>\2\2\u01be\u01bf"+
"\7?\2\2\u01bfH\3\2\2\2\u01c0\u01c1\7@\2\2\u01c1\u01c2\7@\2\2\u01c2\u01c3"+
"\7?\2\2\u01c3J\3\2\2\2\u01c4\u01c5\7-\2\2\u01c5\u01c6\7-\2\2\u01c6L\3"+
"\2\2\2\u01c7\u01c8\7/\2\2\u01c8\u01c9\7/\2\2\u01c9N\3\2\2\2\u01ca\u01cb"+
"\7-\2\2\u01cbP\3\2\2\2\u01cc\u01cd\7/\2\2\u01cdR\3\2\2\2\u01ce\u01cf\7"+
",\2\2\u01cf\u01d0\7,\2\2\u01d0T\3\2\2\2\u01d1\u01d2\7,\2\2\u01d2V\3\2"+
"\2\2\u01d3\u01d4\7\61\2\2\u01d4X\3\2\2\2\u01d5\u01d6\7\'\2\2\u01d6Z\3"+
"\2\2\2\u01d7\u01d8\7>\2\2\u01d8\u01d9\7>\2\2\u01d9\\\3\2\2\2\u01da\u01db"+
"\7@\2\2\u01db\u01dc\7@\2\2\u01dc^\3\2\2\2\u01dd\u01de\7>\2\2\u01de`\3"+
"\2\2\2\u01df\u01e0\7@\2\2\u01e0b\3\2\2\2\u01e1\u01e2\7>\2\2\u01e2\u01e3"+
"\7?\2\2\u01e3d\3\2\2\2\u01e4\u01e5\7@\2\2\u01e5\u01e6\7?\2\2\u01e6f\3"+
"\2\2\2\u01e7\u01e8\7?\2\2\u01e8\u01e9\7?\2\2\u01e9h\3\2\2\2\u01ea\u01eb"+
"\7#\2\2\u01eb\u01ec\7?\2\2\u01ecj\3\2\2\2\u01ed\u01ee\7`\2\2\u01eel\3"+
"\2\2\2\u01ef\u01f0\7~\2\2\u01f0n\3\2\2\2\u01f1\u01f2\7v\2\2\u01f2\u01f3"+
"\7q\2\2\u01f3p\3\2\2\2\u01f4\u01f5\7u\2\2\u01f5\u01f6\7v\2\2\u01f6\u01f7"+
"\7g\2\2\u01f7\u01f8\7r\2\2\u01f8r\3\2\2\2\u01f9\u01fa\7c\2\2\u01fa\u01fb"+
"\7p\2\2\u01fb\u01fc\7f\2\2\u01fct\3\2\2\2\u01fd\u01fe\7q\2\2\u01fe\u01ff"+
"\7t\2\2\u01ffv\3\2\2\2\u0200\u0201\7z\2\2\u0201\u0202\7q\2\2\u0202\u0203"+
"\7t\2\2\u0203x\3\2\2\2\u0204\u0205\7p\2\2\u0205\u0206\7q\2\2\u0206\u0207"+
"\7v\2\2\u0207z\3\2\2\2\u0208\u0209\7*\2\2\u0209|\3\2\2\2\u020a\u020b\7"+
"+\2\2\u020b~\3\2\2\2\u020c\u020d\7c\2\2\u020d\u020e\7u\2\2\u020e\u0080"+
"\3\2\2\2\u020f\u0210\7B\2\2\u0210\u0082\3\2\2\2\u0211\u0212\7t\2\2\u0212"+
"\u0213\7g\2\2\u0213\u0214\7v\2\2\u0214\u0215\7w\2\2\u0215\u0216\7t\2\2"+
"\u0216\u0217\7p\2\2\u0217\u0084\3\2\2\2\u0218\u0219\7d\2\2\u0219\u021a"+
"\7t\2\2\u021a\u021b\7g\2\2\u021b\u021c\7c\2\2\u021c\u021d\7m\2\2\u021d"+
"\u0086\3\2\2\2\u021e\u021f\7e\2\2\u021f\u0220\7q\2\2\u0220\u0221\7p\2"+
"\2\u0221\u0222\7v\2\2\u0222\u0223\7k\2\2\u0223\u0224\7p\2\2\u0224\u0225"+
"\7w\2\2\u0225\u0226\7g\2\2\u0226\u0088\3\2\2\2\u0227\u0228\7\60\2\2\u0228"+
"\u008a\3\2\2\2\u0229\u022a\7C\2\2\u022a\u008c\3\2\2\2\u022b\u022c\7Z\2"+
"\2\u022c\u008e\3\2\2\2\u022d\u022e\7[\2\2\u022e\u0090\3\2\2\2\u022f\u0230"+
"\7C\2\2\u0230\u0231\7Z\2\2\u0231\u0092\3\2\2\2\u0232\u0233\7C\2\2\u0233"+
"\u0234\7[\2\2\u0234\u0094\3\2\2\2\u0235\u0236\7Z\2\2\u0236\u0237\7[\2"+
"\2\u0237\u0096\3\2\2\2\u0238\u0239\7R\2\2\u0239\u023a\7e\2\2\u023a\u0098"+
"\3\2\2\2\u023b\u023c\7R\2\2\u023c\u023d\7|\2\2\u023d\u009a\3\2\2\2\u023e"+
"\u023f\7R\2\2\u023f\u0240\7p\2\2\u0240\u009c\3\2\2\2\u0241\u0242\7R\2"+
"\2\u0242\u0243\7x\2\2\u0243\u009e\3\2\2\2\u0244\u0245\7\60\2\2\u0245\u0246"+
"\7y\2\2\u0246\u00a0\3\2\2\2\u0247\u0248\7v\2\2\u0248\u0249\7t\2\2\u0249"+
"\u024a\7w\2\2\u024a\u024b\7g\2\2\u024b\u00a2\3\2\2\2\u024c\u024d\7h\2"+
"\2\u024d\u024e\7c\2\2\u024e\u024f\7n\2\2\u024f\u0250\7u\2\2\u0250\u0251"+
"\7g\2\2\u0251\u00a4\3\2\2\2\u0252\u0253\7\'\2\2\u0253\u0254\7c\2\2\u0254"+
"\u0255\7u\2\2\u0255\u0256\7o\2\2\u0256\u00a6\3\2\2\2\u0257\u0258\7u\2"+
"\2\u0258\u0259\7w\2\2\u0259\u025a\7d\2\2\u025a\u00a8\3\2\2\2\u025b\u025c"+
"\7/\2\2\u025c\u025d\7@\2\2\u025d\u00aa\3\2\2\2\u025e\u025f\7}\2\2\u025f"+
"\u00ac\3\2\2\2\u0260\u0261\7\177\2\2\u0261\u00ae\3\2\2\2\u0262\u0263\7"+
"c\2\2\u0263\u0264\7u\2\2\u0264\u0265\7o\2\2\u0265\u0266\7u\2\2\u0266\u0267"+
"\7w\2\2\u0267\u0268\7d\2\2\u0268\u00b0\3\2\2\2\u0269\u026a\7u\2\2\u026a"+
"\u026b\7v\2\2\u026b\u026c\7c\2\2\u026c\u026d\7e\2\2\u026d\u026e\7m\2\2"+
"\u026e\u00b2\3\2\2\2\u026f\u0270\7e\2\2\u0270\u0271\7n\2\2\u0271\u0272"+
"\7q\2\2\u0272\u0273\7d\2\2\u0273\u0274\7d\2\2\u0274\u0275\7g\2\2\u0275"+
"\u0276\7t\2\2\u0276\u0277\7u\2\2\u0277\u00b4\3\2\2\2\u0278\u0279\7k\2"+
"\2\u0279\u027a\7h\2\2\u027a\u00b6\3\2\2\2\u027b\u027c\7g\2\2\u027c\u027d"+
"\7n\2\2\u027d\u027e\7u\2\2\u027e\u027f\7g\2\2\u027f\u00b8\3\2\2\2\u0280"+
"\u0281\7k\2\2\u0281\u0282\7h\2\2\u0282\u0283\7a\2\2\u0283\u0284\7e\2\2"+
"\u0284\u0285\7u\2\2\u0285\u00ba\3\2\2\2\u0286\u0287\7k\2\2\u0287\u0288"+
"\7h\2\2\u0288\u0289\7a\2\2\u0289\u028a\7e\2\2\u028a\u028b\7e\2\2\u028b"+
"\u00bc\3\2\2\2\u028c\u028d\7k\2\2\u028d\u028e\7h\2\2\u028e\u028f\7a\2"+
"\2\u028f\u0290\7g\2\2\u0290\u0291\7s\2\2\u0291\u00be\3\2\2\2\u0292\u0293"+
"\7k\2\2\u0293\u0294\7h\2\2\u0294\u0295\7a\2\2\u0295\u0296\7|\2\2\u0296"+
"\u00c0\3\2\2\2\u0297\u0298\7k\2\2\u0298\u0299\7h\2\2\u0299\u029a\7a\2"+
"\2\u029a\u029b\7p\2\2\u029b\u029c\7g\2\2\u029c\u00c2\3\2\2\2\u029d\u029e"+
"\7k\2\2\u029e\u029f\7h\2\2\u029f\u02a0\7a\2\2\u02a0\u02a1\7p\2\2\u02a1"+
"\u02a2\7|\2\2\u02a2\u00c4\3\2\2\2\u02a3\u02a4\7k\2\2\u02a4\u02a5\7h\2"+
"\2\u02a5\u02a6\7a\2\2\u02a6\u02a7\7r\2\2\u02a7\u02a8\7n\2\2\u02a8\u00c6"+
"\3\2\2\2\u02a9\u02aa\7k\2\2\u02aa\u02ab\7h\2\2\u02ab\u02ac\7a\2\2\u02ac"+
"\u02ad\7r\2\2\u02ad\u02ae\7q\2\2\u02ae\u02af\7u\2\2\u02af\u00c8\3\2\2"+
"\2\u02b0\u02b1\7k\2\2\u02b1\u02b2\7h\2\2\u02b2\u02b3\7a\2\2\u02b3\u02b4"+
"\7o\2\2\u02b4\u02b5\7k\2\2\u02b5\u00ca\3\2\2\2\u02b6\u02b7\7k\2\2\u02b7"+
"\u02b8\7h\2\2\u02b8\u02b9\7a\2\2\u02b9\u02ba\7p\2\2\u02ba\u02bb\7g\2\2"+
"\u02bb\u02bc\7i\2\2\u02bc\u00cc\3\2\2\2\u02bd\u02be\7k\2\2\u02be\u02bf"+
"\7h\2\2\u02bf\u02c0\7a\2\2\u02c0\u02c1\7x\2\2\u02c1\u02c2\7u\2\2\u02c2"+
"\u00ce\3\2\2\2\u02c3\u02c4\7k\2\2\u02c4\u02c5\7h\2\2\u02c5\u02c6\7a\2"+
"\2\u02c6\u02c7\7x\2\2\u02c7\u02c8\7e\2\2\u02c8\u00d0\3\2\2\2\u02c9\u02ca"+
"\7h\2\2\u02ca\u02cb\7q\2\2\u02cb\u02cc\7t\2\2\u02cc\u00d2\3\2\2\2\u02cd"+
"\u02ce\7k\2\2\u02ce\u02cf\7p\2\2\u02cf\u00d4\3\2\2\2\u02d0\u02d1\7y\2"+
"\2\u02d1\u02d2\7j\2\2\u02d2\u02d3\7k\2\2\u02d3\u02d4\7n\2\2\u02d4\u02d5"+
"\7g\2\2\u02d5\u00d6\3\2\2\2\u02d6\u02d7\7t\2\2\u02d7\u02d8\7g\2\2\u02d8"+
"\u02d9\7r\2\2\u02d9\u02da\7g\2\2\u02da\u02db\7c\2\2\u02db\u02dc\7v\2\2"+
"\u02dc\u00d8\3\2\2\2\u02dd\u02de\7w\2\2\u02de\u02df\7p\2\2\u02df\u02e0"+
"\7v\2\2\u02e0\u02e1\7k\2\2\u02e1\u02e2\7n\2\2\u02e2\u00da\3\2\2\2\u02e3"+
"\u02e4\7y\2\2\u02e4\u02e5\7j\2\2\u02e5\u02e6\7g\2\2\u02e6\u02e7\7p\2\2"+
"\u02e7\u00dc\3\2\2\2\u02e8\u02ec\t\2\2\2\u02e9\u02eb\t\3\2\2\u02ea\u02e9"+
"\3\2\2\2\u02eb\u02ee\3\2\2\2\u02ec\u02ea\3\2\2\2\u02ec\u02ed\3\2\2\2\u02ed"+
"\u02ef\3\2\2\2\u02ee\u02ec\3\2\2\2\u02ef\u02f0\5\u00dfp\2\u02f0\u02f1"+
"\3\2\2\2\u02f1\u02f2\bo\2\2\u02f2\u00de\3\2\2\2\u02f3\u02f7\7=\2\2\u02f4"+
"\u02f6\n\2\2\2\u02f5\u02f4\3\2\2\2\u02f6\u02f9\3\2\2\2\u02f7\u02f5\3\2"+
"\2\2\u02f7\u02f8\3\2\2\2\u02f8\u02fa\3\2\2\2\u02f9\u02f7\3\2\2\2\u02fa"+
"\u02fb\bp\2\2\u02fb\u00e0\3\2\2\2\u02fc\u02fd\t\3\2\2\u02fd\u02fe\3\2"+
"\2\2\u02fe\u02ff\bq\3\2\u02ff\u00e2\3\2\2\2\u0300\u0302\t\2\2\2\u0301"+
"\u0300\3\2\2\2\u0302\u0303\3\2\2\2\u0303\u0301\3\2\2\2\u0303\u0304\3\2"+
"\2\2\u0304\u00e4\3\2\2\2\u0305\u0309\t\4\2\2\u0306\u0308\t\5\2\2\u0307"+
"\u0306\3\2\2\2\u0308\u030b\3\2\2\2\u0309\u0307\3\2\2\2\u0309\u030a\3\2"+
"\2\2\u030a\u00e6\3\2\2\2\u030b\u0309\3\2\2\2\u030c\u0314\4\62;\2\u030d"+
"\u030f\4\63;\2\u030e\u0310\4\62;\2\u030f\u030e\3\2\2\2\u0310\u0311\3\2"+
"\2\2\u0311\u030f\3\2\2\2\u0311\u0312\3\2\2\2\u0312\u0314\3\2\2\2\u0313"+
"\u030c\3\2\2\2\u0313\u030d\3\2\2\2\u0314\u00e8\3\2\2\2\u0315\u0317\7&"+
"\2\2\u0316\u0318\t\6\2\2\u0317\u0316\3\2\2\2\u0318\u0319\3\2\2\2\u0319"+
"\u0317\3\2\2\2\u0319\u031a\3\2\2\2\u031a\u00ea\3\2\2\2\u031b\u031d\7\'"+
"\2\2\u031c\u031e\4\62\63\2\u031d\u031c\3\2\2\2\u031e\u031f\3\2\2\2\u031f"+
"\u031d\3\2\2\2\u031f\u0320\3\2\2\2\u0320\u00ec\3\2\2\2\u0321\u0322\7("+
"\2\2\u0322\u00ee\3\2\2\2\u0323\u0329\5\u00f1y\2\u0324\u0326\t\7\2\2\u0325"+
"\u0327\t\b\2\2\u0326\u0325\3\2\2\2\u0326\u0327\3\2\2\2\u0327\u0328\3\2"+
"\2\2\u0328\u032a\5\u00f1y\2\u0329\u0324\3\2\2\2\u0329\u032a\3\2\2\2\u032a"+
"\u00f0\3\2\2\2\u032b\u032d\4\62;\2\u032c\u032b\3\2\2\2\u032d\u032e\3\2"+
"\2\2\u032e\u032c\3\2\2\2\u032e\u032f\3\2\2\2\u032f\u0336\3\2\2\2\u0330"+
"\u0332\7\60\2\2\u0331\u0333\4\62;\2\u0332\u0331\3\2\2\2\u0333\u0334\3"+
"\2\2\2\u0334\u0332\3\2\2\2\u0334\u0335\3\2\2\2\u0335\u0337\3\2\2\2\u0336"+
"\u0330\3\2\2\2\u0336\u0337\3\2\2\2\u0337\u00f2\3\2\2\2\u0338\u0339\7^"+
"\2\2\u0339\u033d\13\2\2\2\u033a\u033b\7^\2\2\u033b\u033d\5\u00e3r\2\u033c"+
"\u0338\3\2\2\2\u033c\u033a\3\2\2\2\u033d\u00f4\3\2\2\2\u033e\u0343\7$"+
"\2\2\u033f\u0342\5\u00f3z\2\u0340\u0342\n\t\2\2\u0341\u033f\3\2\2\2\u0341"+
"\u0340\3\2\2\2\u0342\u0345\3\2\2\2\u0343\u0341\3\2\2\2\u0343\u0344\3\2"+
"\2\2\u0344\u0346\3\2\2\2\u0345\u0343\3\2\2\2\u0346\u0347\7$\2\2\u0347"+
"\u0348\b{\4\2\u0348\u00f6\3\2\2\2\u0349\u034a\7}\2\2\u034a\u034b\7}\2"+
"\2\u034b\u034d\3\2\2\2\u034c\u034e\13\2\2\2\u034d\u034c\3\2\2\2\u034e"+
"\u034f\3\2\2\2\u034f\u0350\3\2\2\2\u034f\u034d\3\2\2\2\u0350\u0351\3\2"+
"\2\2\u0351\u0352\7\177\2\2\u0352\u0353\7\177\2\2\u0353\u0354\3\2\2\2\u0354"+
"\u0355\b|\5\2\u0355\u00f8\3\2\2\2\u0356\u0359\7)\2\2\u0357\u035a\5\u00f3"+
"z\2\u0358\u035a\n\t\2\2\u0359\u0357\3\2\2\2\u0359\u0358\3\2\2\2\u035a"+
"\u035b\3\2\2\2\u035b\u035c\7)\2\2\u035c\u035d\b}\6\2\u035d\u00fa\3\2\2"+
"\2\u035e\u035f\7B\2\2\u035f\u0360\7|\2\2\u0360\u0361\7r\2\2\u0361\u00fc"+
"\3\2\2\2\u0362\u0363\7]\2\2\u0363\u0364\7_\2\2\u0364\u00fe\3\2\2\2\26"+
"\2\u02ec\u02f7\u0303\u0309\u0311\u0313\u0317\u0319\u031f\u0326\u0329\u032e"+
"\u0334\u0336\u033c\u0341\u0343\u034f\u0359\7\2\3\2\b\2\2\3{\2\3|\3\3}"+
"\4";
public static final ATN _ATN =
new ATNDeserializer().deserialize(_serializedATN.toCharArray());
static {
_decisionToDFA = new DFA[_ATN.getNumberOfDecisions()];
for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) {
_decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), i);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
#!/usr/bin/env sh #!/usr/bin/env sh
rm *.jar *.asm *.prg *.vm.txt *.vice-mon-list rm *.jar *.asm *.prg *.vm.txt *.vice-mon-list
rm -r build rm -rf build out