Compare commits

..
17 Commits
69 changed files with 2277 additions and 2189 deletions

No files matched your search

-2
View File
@@ -24,8 +24,6 @@ __pycache__/
parser.out
parsetab.py
.pytest_cache/
compiler/src/prog8_kotlin.jar
compiler/src/compiled_java
.attach_pid*
.gradle
+1 -1
View File
@@ -1 +1 @@
1.11
1.20
+1 -1
View File
@@ -1,8 +1,8 @@
package prog8
import prog8.compiler.compileProgram
import prog8.vm.astvm.AstVm
import prog8.vm.stackvm.stackVmMain
import prog8.compiler.*
import java.nio.file.Paths
import kotlin.system.exitProcess
+161 -10
View File
@@ -1,15 +1,167 @@
package prog8.ast
import prog8.ast.base.*
import prog8.ast.statements.Block
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctions
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 *************/
class Program(val name: String, val modules: MutableList<Module>) {
@@ -32,7 +184,7 @@ class Program(val name: String, val modules: MutableList<Module>) {
}
class Module(override val name: String,
override var statements: MutableList<IStatement>,
override var statements: MutableList<Statement>,
override val position: Position,
val isLibraryModule: Boolean,
val source: Path) : Node, INameScope {
@@ -56,14 +208,14 @@ class Module(override val name: String,
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<IStatement>()
override val statements = mutableListOf<Statement>()
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {
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) {
// builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
@@ -80,8 +232,7 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
if(struct.statements.any { (it as VarDecl).name == scopedName.last()}) {
// return ref to the mangled name variable
val mangled = mangledStructMemberName(thing.name, scopedName.last())
val mangledVar = thing.definingScope().getLabelOrVariable(mangled)
return mangledVar
return thing.definingScope().getLabelOrVariable(mangled)
}
}
}
@@ -98,7 +249,7 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
object BuiltinFunctionScopePlaceholder : INameScope {
override val name = "<<builtin-functions-scope-placeholder>>"
override val position = Position("<<placeholder>>", 0, 0, 0)
override var statements = mutableListOf<IStatement>()
override var statements = mutableListOf<Statement>()
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
}
-220
View File
@@ -1,220 +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) {
// 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
}
}
return subscopes
}
fun getLabelOrVariable(name: String): IStatement? {
// 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, 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) {
// 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())
val mangledVar = thing.definingScope().getLabelOrVariable(mangled)
return mangledVar
}
}
// 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 referencesIdentifiers(vararg 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
}
}
+64 -30
View File
@@ -3,16 +3,16 @@ package prog8.ast.antlr
import org.antlr.v4.runtime.IntStream
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.tree.TerminalNode
import prog8.ast.*
import prog8.ast.Module
import prog8.ast.base.*
import prog8.ast.expressions.*
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.parser.CustomLexer
import prog8.parser.prog8Parser
import java.io.CharConversionException
import java.io.File
import java.nio.file.Path
/***************** 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()
if(directive!=null) return directive
@@ -50,15 +50,15 @@ 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())
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<IStatement> =
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList()
private fun prog8Parser.StatementContext.toAst() : IStatement {
private fun prog8Parser.StatementContext.toAst() : Statement {
vardecl()?.let { return it.toAst() }
varinitializer()?.let {
@@ -69,7 +69,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
vd.structname?.text,
null,
it.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
@@ -77,6 +77,37 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
)
}
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 {
val cvarinit = it.varinitializer()
val vd = cvarinit.vardecl()
@@ -86,7 +117,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
vd.structname?.text,
null,
cvarinit.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
@@ -103,7 +134,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
vd.structname?.text,
null,
mvarinit.expression().toAst(),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
@@ -183,7 +214,7 @@ private fun prog8Parser.StatementContext.toAst() : IStatement {
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun prog8Parser.AsmsubroutineContext.toAst(): IStatement {
private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
val name = identifier().text
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
val params = asmsub_params()?.toAst() ?: emptyList()
@@ -232,7 +263,7 @@ private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParamete
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()
return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), toPosition())
@@ -265,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())
@@ -378,30 +409,30 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
}
private fun prog8Parser.ExpressionContext.toAst() : IExpression {
private fun prog8Parser.ExpressionContext.toAst() : Expression {
val litval = literalvalue()
if(litval!=null) {
val booleanlit = litval.booleanliteral()?.toAst()
return if(booleanlit!=null) {
LiteralValue.fromBoolean(booleanlit, litval.toPosition())
NumericLiteralValue.fromBoolean(booleanlit, litval.toPosition())
}
else {
val intLit = litval.integerliteral()?.toAst()
when {
intLit!=null -> when(intLit.datatype) {
DataType.UBYTE -> LiteralValue(DataType.UBYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition())
DataType.BYTE -> LiteralValue(DataType.BYTE, bytevalue = intLit.number.toShort(), position = litval.toPosition())
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = intLit.number.toInt(), position = litval.toPosition())
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = intLit.number.toInt(), position = litval.toPosition())
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = intLit.number.toDouble(), position = litval.toPosition())
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, intLit.number.toShort(), litval.toPosition())
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, intLit.number.toShort(), litval.toPosition())
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, intLit.number.toInt(), litval.toPosition())
DataType.WORD -> NumericLiteralValue(DataType.WORD, intLit.number.toInt(), litval.toPosition())
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, intLit.number.toDouble(), litval.toPosition())
else -> throw FatalAstException("invalid datatype for numeric literal")
}
litval.floatliteral()!=null -> LiteralValue(DataType.FLOAT, floatvalue = litval.floatliteral().toAst(), position = litval.toPosition())
litval.stringliteral()!=null -> LiteralValue(DataType.STR, strvalue = unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
litval.stringliteral()!=null -> ReferenceLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition())
litval.charliteral()!=null -> {
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) {
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
}
@@ -410,7 +441,11 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
val array = litval.arrayliteral()?.toAst()
// 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.
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")
}
@@ -433,7 +468,7 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
if(funcall!=null) return funcall
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())
}
@@ -484,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()
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
val condition = expression().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@@ -498,7 +532,7 @@ private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
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())
}
@@ -576,7 +610,7 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
arrayindex()?.toAst(),
varname.text,
structname?.text,
null,
null,
ARRAYSIG() != null || arrayindex() != null,
false,
+4 -35
View File
@@ -1,10 +1,8 @@
package prog8.ast.base
import prog8.ast.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.compiler.CompilationOptions
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
@@ -31,7 +29,7 @@ internal fun Program.checkValid(compilerOptions: CompilationOptions) {
internal fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace, heap)
initvalueCreator.visit(this)
val checker = StatementReorderer(this)
@@ -52,41 +50,12 @@ internal fun Program.checkRecursion() {
internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(namespace)
val checker = AstIdentifiersChecker(this)
checker.visit(this)
if(modules.map {it.name}.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
}
// add any anonymous variables for heap values that are used,
// and replace an iterable literalvalue by identifierref to new local variable
// TODO: this is't doing anything anymore?
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)
}
@@ -1,11 +1,17 @@
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.processing.IAstModifyingVisitor
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.IntegerOrAddressOf
import prog8.compiler.target.c64.Petscii
@@ -18,7 +24,38 @@ import kotlin.math.abs
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 fun linkParents(parent: Node) {
@@ -26,7 +63,7 @@ class PrefixExpression(val operator: String, var expression: IExpression, overri
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: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
@@ -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 fun linkParents(parent: Node) {
@@ -51,7 +88,7 @@ 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...
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: IAstVisitor) = visitor.visit(this)
@@ -148,7 +185,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
}
fun commonDatatype(leftDt: DataType, rightDt: DataType,
left: IExpression, right: IExpression): Pair<DataType, IExpression?> {
left: Expression, right: Expression): Pair<DataType, Expression?> {
// byte + byte -> byte
// byte + word -> 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.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.BYTE -> {
@@ -179,7 +216,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
DataType.UWORD -> Pair(DataType.WORD, left)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.UWORD -> {
@@ -189,7 +226,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
DataType.UWORD -> Pair(DataType.UWORD, null)
DataType.WORD -> Pair(DataType.WORD, left)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.WORD -> {
@@ -199,20 +236,20 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
DataType.UWORD -> Pair(DataType.WORD, right)
DataType.WORD -> Pair(DataType.WORD, null)
DataType.FLOAT -> Pair(DataType.FLOAT, left)
else -> throw FatalAstException("non-numeric datatype $rightDt")
else -> Pair(leftDt, null) // non-numeric datatype
}
}
DataType.FLOAT -> {
Pair(DataType.FLOAT, right)
}
else -> throw FatalAstException("non-numeric datatype $leftDt")
else -> Pair(leftDt, null) // non-numeric datatype
}
}
}
class ArrayIndexedExpression(val identifier: IdentifierReference,
var arrayspec: ArrayIndex,
override val position: Position) : IExpression {
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
@@ -220,7 +257,7 @@ class ArrayIndexedExpression(val identifier: IdentifierReference,
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: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
@@ -243,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 fun linkParents(parent: Node) {
@@ -255,7 +292,7 @@ class TypecastExpression(var expression: IExpression, var type: DataType, val im
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
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
return cv.cast(type)
// val value = RuntimeValue(cv.type, cv.asNumericValue!!).cast(type)
@@ -267,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 fun linkParents(parent: Node) {
@@ -276,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
override fun constValue(program: Program): LiteralValue? = null
override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program) = DataType.UWORD
override fun accept(visitor: IAstModifyingVisitor) = 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 fun linkParents(parent: Node) {
@@ -295,163 +332,216 @@ class DirectMemoryRead(var addressExpression: IExpression, override val position
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = false
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 {
return "DirectMemoryRead($addressExpression)"
}
}
open class LiteralValue(val type: DataType,
val bytevalue: Short? = null,
val wordvalue: Int? = null,
val floatvalue: Double? = null,
val strvalue: String? = null,
val arrayvalue: Array<IExpression>? = null,
initHeapId: Int? =null,
override val position: Position) : IExpression {
class NumericLiteralValue(val type: DataType, // only numerical types allowed
val number: Number, // can be byte, word or float depending on the type
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun referencesIdentifiers(vararg name: String) = arrayvalue?.any { it.referencesIdentifiers(*name) } ?: false
val isString = type in StringDatatypes
val isNumeric = type in NumericDatatypes
val isArray = type in ArrayDatatypes
var heapId = initHeapId
private set
companion object {
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 {
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 {
fun optimalNumeric(value: Number, position: Position): NumericLiteralValue {
return if(value is Double) {
LiteralValue(DataType.FLOAT, floatvalue = value, position = position)
NumericLiteralValue(DataType.FLOAT, value, position)
} else {
when (val intval = value.toInt()) {
in 0..255 -> LiteralValue(DataType.UBYTE, bytevalue = intval.toShort(), position = position)
in -128..127 -> LiteralValue(DataType.BYTE, bytevalue = intval.toShort(), position = position)
in 0..65535 -> LiteralValue(DataType.UWORD, wordvalue = intval, position = position)
in -32768..32767 -> LiteralValue(DataType.WORD, wordvalue = intval, position = position)
else -> LiteralValue(DataType.FLOAT, floatvalue = intval.toDouble(), position = position)
in 0..255 -> NumericLiteralValue(DataType.UBYTE, intval, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, intval, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, intval, position)
in -32768..32767 -> NumericLiteralValue(DataType.WORD, intval, position)
else -> NumericLiteralValue(DataType.FLOAT, intval.toDouble(), position)
}
}
}
fun optimalInteger(value: Number, position: Position): LiteralValue {
val intval = value.toInt()
if(intval.toDouble() != value.toDouble())
throw FatalAstException("value is not an integer: $value")
return when (intval) {
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)
fun optimalInteger(value: Int, position: Position): NumericLiteralValue {
return when (value) {
in 0..255 -> NumericLiteralValue(DataType.UBYTE, value, position)
in -128..127 -> NumericLiteralValue(DataType.BYTE, value, position)
in 0..65535 -> NumericLiteralValue(DataType.UWORD, value, position)
else -> throw FatalAstException("integer overflow: $value")
}
}
}
init {
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())
val asBooleanValue: Boolean = number!=0
override fun linkParents(parent: Node) {
this.parent = parent
arrayvalue?.forEach {it.linkParents(this)}
}
override fun constValue(program: Program): LiteralValue? {
if(arrayvalue!=null) {
for(v in arrayvalue) {
if(v.constValue(program)==null) return null
override fun referencesIdentifiers(vararg name: String) = false
override fun constValue(program: Program) = this
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
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 this
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: IAstVisitor) = visitor.visit(this)
override fun toString(): String {
val vstr = when(type) {
DataType.UBYTE -> "ubyte:$bytevalue"
DataType.BYTE -> "byte:$bytevalue"
DataType.UWORD -> "uword:$wordvalue"
DataType.WORD -> "word:$wordvalue"
DataType.FLOAT -> "float:$floatvalue"
in StringDatatypes -> "str:'${escape(strvalue?:"")}'"
in ArrayDatatypes -> "array:$arrayvalue"
else -> throw FatalAstException("weird datatype")
val valueStr = when(type) {
in StringDatatypes -> "'${escape(str!!)}'"
in ArrayDatatypes -> "$array"
else -> throw FatalAstException("weird ref type")
}
return "LiteralValue($vstr)"
return "ReferenceValueLiteral($type, $valueStr)"
}
override fun inferType(program: Program) = type
override fun hashCode(): Int {
val bh = bytevalue?.hashCode() ?: 0x10001234
val wh = wordvalue?.hashCode() ?: 0x01002345
val fh = floatvalue?.hashCode() ?: 0x00103456
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
val sh = str?.hashCode() ?: 0x00014567
val ah = array?.hashCode() ?: 0x11119876
return sh * 31 xor ah xor type.hashCode()
}
override fun equals(other: Any?): Boolean {
if(other==null || other !is LiteralValue)
if(other==null || other !is ReferenceLiteralValue)
return false
if(isNumeric && other.isNumeric)
return asNumericValue?.toDouble()==other.asNumericValue?.toDouble()
if(isArray && other.isArray)
return arrayvalue!!.contentEquals(other.arrayvalue!!) && heapId==other.heapId
return array!!.contentEquals(other.array!!) && heapId==other.heapId
if(isString && other.isString)
return strvalue==other.strvalue && heapId==other.heapId
return str==other.str && heapId==other.heapId
if(type!=other.type)
return false
@@ -459,74 +549,17 @@ open class LiteralValue(val type: DataType,
return compareTo(other) == 0
}
operator fun compareTo(other: LiteralValue): 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)
operator fun compareTo(other: ReferenceLiteralValue): Int {
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)
return this
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 -> {
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 -> {
if(targettype in StringDatatypes)
return this
return ReferenceLiteralValue(targettype, str, initHeapId = heapId, position = position)
}
else -> {}
}
@@ -534,39 +567,38 @@ open class LiteralValue(val type: DataType,
}
fun addToHeap(heap: HeapValues) {
if(heapId==null) {
if (strvalue != null) {
heapId = heap.addString(type, strvalue)
}
else if (arrayvalue!=null) {
if(arrayvalue.any {it is AddressOf }) {
val intArrayWithAddressOfs = arrayvalue.map {
when (it) {
is AddressOf -> IntegerOrAddressOf(null, it)
is LiteralValue -> IntegerOrAddressOf(it.asIntegerValue, null)
else -> throw FatalAstException("invalid datatype in array")
}
if (heapId != null) return
if (str != null) {
heapId = heap.addString(type, str)
}
else if (array!=null) {
if(array.any {it is AddressOf }) {
val intArrayWithAddressOfs = array.map {
when (it) {
is AddressOf -> IntegerOrAddressOf(null, it)
is NumericLiteralValue -> IntegerOrAddressOf(it.number.toInt(), null)
else -> throw FatalAstException("invalid datatype in array")
}
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
}
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
} else {
val valuesInArray = array.map { (it as NumericLiteralValue).number }
heapId = if(type== DataType.ARRAY_F) {
val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray()
heap.addDoublesArray(doubleArray)
} else {
val valuesInArray = arrayvalue.map { (it as LiteralValue).asNumericValue!! }
heapId = if(type== DataType.ARRAY_F) {
val doubleArray = valuesInArray.map { it.toDouble() }.toDoubleArray()
heap.addDoublesArray(doubleArray)
} else {
val integerArray = valuesInArray.map { it.toInt() }
heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
}
val integerArray = valuesInArray.map { it.toInt() }
heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
}
}
}
}
}
class RangeExpr(var from: IExpression,
var to: IExpression,
var step: IExpression,
override val position: Position) : IExpression {
class RangeExpr(var from: Expression,
var to: Expression,
var step: Expression,
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -576,7 +608,7 @@ class RangeExpr(var from: IExpression,
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: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
@@ -599,30 +631,32 @@ class RangeExpr(var from: IExpression,
}
fun size(): Int? {
val fromLv = (from as? LiteralValue)
val toLv = (to as? LiteralValue)
val fromLv = (from as? NumericLiteralValue)
val toLv = (to as? NumericLiteralValue)
if(fromLv==null || toLv==null)
return null
return toConstantIntegerRange()?.count()
}
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 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
fromVal = Petscii.encodePetscii(fromLv.strvalue!!, true)[0].toInt()
toVal = Petscii.encodePetscii(toLv.strvalue!!, true)[0].toInt()
fromVal = Petscii.encodePetscii(fromRlv.str!!, true)[0].toInt()
toVal = Petscii.encodePetscii(toRlv.str!!, true)[0].toInt()
} else {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
if(fromLv==null || toLv==null)
return null // non-constant range
// integer range
fromVal = (from as LiteralValue).asIntegerValue!!
toVal = (to as LiteralValue).asIntegerValue!!
fromVal = fromLv.number.toInt()
toVal = toLv.number.toInt()
}
val stepVal = (step as? LiteralValue)?.asIntegerValue ?: 1
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
@@ -638,14 +672,14 @@ 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 fun linkParents(parent: Node) {
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: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
@@ -656,7 +690,7 @@ class RegisterExpr(val register: Register, override val position: Position) : IE
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
fun targetStatement(namespace: INameScope) =
@@ -672,7 +706,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
this.parent = parent
}
override fun constValue(program: Program): LiteralValue? {
override fun constValue(program: Program): NumericLiteralValue? {
val node = program.namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolError(this)
val vardecl = node as? VarDecl
@@ -703,13 +737,18 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
fun heapId(namespace: INameScope): Int {
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,
override var arglist: MutableList<IExpression>,
override val position: Position) : IExpression, IFunctionCall {
override var arglist: MutableList<Expression>,
override val position: Position) : Expression(), IFunctionCall {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
@@ -720,12 +759,12 @@ class FunctionCall(override var target: IdentifierReference,
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!
// lenghts of arrays and strings are constants that are determined at compile time!
if(target.nameInSource.size>1) return null
try {
var resultValue: LiteralValue? = null
var resultValue: NumericLiteralValue? = null
val func = BuiltinFunctions[target.nameInSource[0]]
if(func!=null) {
val exprfunc = func.constExpressionFunc
@@ -778,8 +817,7 @@ class FunctionCall(override var target: IdentifierReference,
return stmt.returntypes[0]
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
@@ -1,33 +1,41 @@
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.autoHeapValuePrefix
import prog8.ast.expressions.*
import prog8.ast.statements.*
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 var blocks = mutableMapOf<String, Block>()
internal val anonymousVariablesFromHeap = mutableMapOf<String, Pair<LiteralValue, VarDecl>>()
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
internal fun result(): List<AstException> {
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))
}
override fun visit(module: Module) {
vardeclsToAdd.clear()
blocks.clear() // blocks may be redefined within a different 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]
if(existing!=null)
nameError(block.name, block.position, existing)
@@ -37,7 +45,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
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") {
// 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)
@@ -47,7 +55,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
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
decl.datatypeErrors.forEach { checkResult.add(it) }
@@ -60,10 +68,20 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT) {
if(decl.structHasBeenFlattened)
return decl // don't do this multiple times
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 decl // a non-numeric member, not supported. proper error is given by AstChecker later
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)
@@ -72,14 +90,14 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
return result
}
val existing = namespace.lookup(listOf(decl.name), decl)
val existing = program.namespace.lookup(listOf(decl.name), decl)
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
return super.visit(decl)
}
override fun visit(subroutine: Subroutine): IStatement {
override fun visit(subroutine: Subroutine): Statement {
if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
@@ -87,7 +105,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
if (subroutine.parameters.any { it.name in BuiltinFunctions })
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)
nameError(subroutine.name, subroutine.position, existing)
@@ -116,7 +134,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
.filter { it.name !in namesInSub }
.forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.DONTCARE, null, it.name, null, null,
isArray = false, hiddenButDoNotRemove = true, position = subroutine.position)
isArray = false, autogeneratedDontRemove = true, position = subroutine.position)
vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl)
}
@@ -126,19 +144,19 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
return super.visit(subroutine)
}
override fun visit(label: Label): IStatement {
override fun visit(label: Label): Statement {
if(label.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position))
} else {
val existing = namespace.lookup(listOf(label.name), label)
val existing = program.namespace.lookup(listOf(label.name), label)
if (existing != null && existing !== label)
nameError(label.name, label.position, existing)
}
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
// 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
@@ -155,7 +173,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
if(existing==null) {
// create the local scoped for loop variable itself
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, null,
isArray = false, hiddenButDoNotRemove = true, position = forLoop.loopVar.position)
isArray = false, autogeneratedDontRemove = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@@ -168,7 +186,7 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
if(existing==null) {
// create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, ForLoop.iteratorLoopcounterVarname, null, null,
isArray = false, hiddenButDoNotRemove = true, position = forLoop.loopVar.position)
isArray = false, autogeneratedDontRemove = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@@ -184,55 +202,53 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
return super.visit(assignTarget)
}
override fun visit(returnStmt: Return): IStatement {
override fun visit(returnStmt: Return): Statement {
if(returnStmt.value!=null) {
// possibly adjust any literal values returned, into the desired returning data type
val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size!=1)
return returnStmt // mismatch in number of return values, error will be printed later.
val newValue: IExpression
val lval = returnStmt.value as? LiteralValue
val newValue: Expression
val lval = returnStmt.value as? NumericLiteralValue
if(lval!=null) {
val adjusted = lval.cast(subroutine.returntypes.single())
if(adjusted!=null && adjusted !== lval)
newValue = adjusted
else
newValue = lval
} else
newValue = if(adjusted!=null && adjusted !== lval) adjusted else lval
} else {
newValue = returnStmt.value!!
}
returnStmt.value = newValue
}
return super.visit(returnStmt)
}
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!
// (note: ususally, this has been taken care of already when the var was created)
val declaredType = if(literalValue.isArray) ArrayElementTypes.getValue(literalValue.type) else literalValue.type
val variable = VarDecl(VarDeclType.VAR,
declaredType,
ZeropageWish.NOT_IN_ZEROPAGE,
null,
"$autoHeapValuePrefix${literalValue.heapId}",
null,
literalValue,
isArray = literalValue.isArray, hiddenButDoNotRemove = true, position = literalValue.position)
anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable)
override fun visit(refLiteral: ReferenceLiteralValue): Expression {
if(refLiteral.parent !is VarDecl) {
return makeIdentifierFromRefLv(refLiteral)
}
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
val variable= addressOf.identifier.targetVarDecl(namespace) ?: return addressOf
val variable= addressOf.identifier.targetVarDecl(program.namespace) ?: return addressOf
addressOf.scopedname = variable.scopedname
return super.visit(addressOf)
}
override fun visit(structDecl: StructDecl): IStatement {
override fun visit(structDecl: StructDecl): Statement {
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl!=null && decl.datatype !in NumericDatatypes)
@@ -242,4 +258,44 @@ internal class AstIdentifiersChecker(private val namespace: INameScope) : IAstMo
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)
}
}
@@ -1,6 +1,6 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.INameScope
import prog8.ast.base.AstException
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.FunctionCallStatement
@@ -1,6 +1,7 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.*
@@ -13,38 +14,38 @@ interface IAstModifyingVisitor {
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)
return expr
}
fun visit(expr: BinaryExpression): IExpression {
fun visit(expr: BinaryExpression): Expression {
expr.left = expr.left.accept(this)
expr.right = expr.right.accept(this)
return expr
}
fun visit(directive: Directive): IStatement {
fun visit(directive: Directive): Statement {
return directive
}
fun visit(block: Block): IStatement {
fun visit(block: Block): Statement {
block.statements = block.statements.asSequence().map { it.accept(this) }.toMutableList()
return block
}
fun visit(decl: VarDecl): IStatement {
fun visit(decl: VarDecl): Statement {
decl.value = decl.value?.accept(this)
decl.arraysize?.accept(this)
return decl
}
fun visit(subroutine: Subroutine): IStatement {
fun visit(subroutine: Subroutine): Statement {
subroutine.statements = subroutine.statements.asSequence().map { it.accept(this) }.toMutableList()
return subroutine
}
fun visit(functionCall: FunctionCall): IExpression {
fun visit(functionCall: FunctionCall): Expression {
val newtarget = functionCall.target.accept(this)
if(newtarget is IdentifierReference)
functionCall.target = newtarget
@@ -52,7 +53,7 @@ interface IAstModifyingVisitor {
return functionCall
}
fun visit(functionCallStatement: FunctionCallStatement): IStatement {
fun visit(functionCallStatement: FunctionCallStatement): Statement {
val newtarget = functionCallStatement.target.accept(this)
if(newtarget is IdentifierReference)
functionCallStatement.target = newtarget
@@ -60,13 +61,13 @@ interface IAstModifyingVisitor {
return functionCallStatement
}
fun visit(identifier: IdentifierReference): IExpression {
fun visit(identifier: IdentifierReference): 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)
return identifier
}
fun visit(jump: Jump): IStatement {
fun visit(jump: Jump): Statement {
if(jump.identifier!=null) {
val ident = jump.identifier.accept(this)
if(ident is IdentifierReference && ident!==jump.identifier) {
@@ -76,84 +77,88 @@ interface IAstModifyingVisitor {
return jump
}
fun visit(ifStatement: IfStatement): IStatement {
fun visit(ifStatement: IfStatement): Statement {
ifStatement.condition = ifStatement.condition.accept(this)
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
return ifStatement
}
fun visit(branchStatement: BranchStatement): IStatement {
fun visit(branchStatement: BranchStatement): Statement {
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
return branchStatement
}
fun visit(range: RangeExpr): IExpression {
fun visit(range: RangeExpr): Expression {
range.from = range.from.accept(this)
range.to = range.to.accept(this)
range.step = range.step.accept(this)
return range
}
fun visit(label: Label): IStatement {
fun visit(label: Label): Statement {
return label
}
fun visit(literalValue: LiteralValue): LiteralValue {
if(literalValue.arrayvalue!=null) {
for(av in literalValue.arrayvalue.withIndex()) {
val newvalue = av.value.accept(this)
literalValue.arrayvalue[av.index] = newvalue
}
}
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
return literalValue
}
fun visit(assignment: Assignment): IStatement {
fun visit(refLiteral: ReferenceLiteralValue): Expression {
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)
return assignment
}
fun visit(postIncrDecr: PostIncrDecr): IStatement {
fun visit(postIncrDecr: PostIncrDecr): Statement {
postIncrDecr.target = postIncrDecr.target.accept(this)
return postIncrDecr
}
fun visit(contStmt: Continue): IStatement {
fun visit(contStmt: Continue): Statement {
return contStmt
}
fun visit(breakStmt: Break): IStatement {
fun visit(breakStmt: Break): Statement {
return breakStmt
}
fun visit(forLoop: ForLoop): IStatement {
fun visit(forLoop: ForLoop): Statement {
forLoop.loopVar?.accept(this)
forLoop.iterable = forLoop.iterable.accept(this)
forLoop.body = forLoop.body.accept(this) as AnonymousScope
return forLoop
}
fun visit(whileLoop: WhileLoop): IStatement {
fun visit(whileLoop: WhileLoop): Statement {
whileLoop.condition = whileLoop.condition.accept(this)
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
return whileLoop
}
fun visit(repeatLoop: RepeatLoop): IStatement {
fun visit(repeatLoop: RepeatLoop): Statement {
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
return repeatLoop
}
fun visit(returnStmt: Return): IStatement {
fun visit(returnStmt: Return): Statement {
returnStmt.value = returnStmt.value?.accept(this)
return returnStmt
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
fun visit(arrayIndexedExpression: ArrayIndexedExpression): Expression {
arrayIndexedExpression.identifier.accept(this)
arrayIndexedExpression.arrayspec.accept(this)
return arrayIndexedExpression
@@ -166,17 +171,17 @@ interface IAstModifyingVisitor {
return assignTarget
}
fun visit(scope: AnonymousScope): IStatement {
fun visit(scope: AnonymousScope): Statement {
scope.statements = scope.statements.asSequence().map { it.accept(this) }.toMutableList()
return scope
}
fun visit(typecast: TypecastExpression): IExpression {
fun visit(typecast: TypecastExpression): Expression {
typecast.expression = typecast.expression.accept(this)
return typecast
}
fun visit(memread: DirectMemoryRead): IExpression {
fun visit(memread: DirectMemoryRead): Expression {
memread.addressExpression = memread.addressExpression.accept(this)
return memread
}
@@ -185,28 +190,28 @@ interface IAstModifyingVisitor {
memwrite.addressExpression = memwrite.addressExpression.accept(this)
}
fun visit(addressOf: AddressOf): IExpression {
fun visit(addressOf: AddressOf): Expression {
addressOf.identifier.accept(this)
return addressOf
}
fun visit(inlineAssembly: InlineAssembly): IStatement {
fun visit(inlineAssembly: InlineAssembly): Statement {
return inlineAssembly
}
fun visit(registerExpr: RegisterExpr): IExpression {
fun visit(registerExpr: RegisterExpr): Expression {
return registerExpr
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): IStatement {
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
return builtinFunctionStatementPlaceholder
}
fun visit(nopStatement: NopStatement): IStatement {
fun visit(nopStatement: NopStatement): Statement {
return nopStatement
}
fun visit(whenStatement: WhenStatement): IStatement {
fun visit(whenStatement: WhenStatement): Statement {
whenStatement.condition.accept(this)
whenStatement.choices.forEach { it.accept(this) }
return whenStatement
@@ -217,8 +222,13 @@ interface IAstModifyingVisitor {
whenChoice.statements.accept(this)
}
fun visit(structDecl: StructDecl): IStatement {
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
}
}
@@ -1,6 +1,7 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.statements.*
@@ -75,8 +76,11 @@ interface IAstVisitor {
fun visit(label: Label) {
}
fun visit(literalValue: LiteralValue) {
literalValue.arrayvalue?.let { it.forEach { v->v.accept(this) }}
fun visit(numLiteral: NumericLiteralValue) {
}
fun visit(refLiteral: ReferenceLiteralValue) {
refLiteral.array?.let { it.forEach { v->v.accept(this) }}
}
fun visit(assignment: Assignment) {
@@ -170,4 +174,8 @@ interface IAstVisitor {
fun visit(structDecl: StructDecl) {
structDecl.statements.forEach { it.accept(this) }
}
fun visit(structLv: StructLiteralValue) {
structLv.values.forEach { it.accept(this) }
}
}
@@ -1,9 +1,10 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.Module
import prog8.ast.base.SyntaxError
import prog8.ast.base.printWarning
import prog8.ast.statements.Directive
import prog8.ast.statements.Statement
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
private val checkResult: MutableList<SyntaxError> = mutableListOf()
@@ -17,7 +18,7 @@ internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
*/
override fun visit(module: Module) {
super.visit(module)
val newStatements : MutableList<IStatement> = mutableListOf()
val newStatements : MutableList<Statement> = mutableListOf()
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
for (sourceStmt in module.statements) {
@@ -1,55 +1,50 @@
package prog8.ast.processing
import kotlin.comparisons.nullsLast
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName
import prog8.ast.base.printWarning
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
fun flattenStructAssignment(structAssignment: Assignment, program: Program): List<Assignment> {
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!!
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if(!sourceVar.isArray && sourceVar.struct==null)
throw FatalAstException("can only assign arrays or structs to structs")
if(sourceVar.isArray) {
val sourceArray = (sourceVar.value as LiteralValue).arrayvalue!!
return struct.statements.zip(sourceArray).map { member ->
val decl = member.first as VarDecl
val mangled = mangledStructMemberName(identifierName, decl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
null, member.second, member.second.position)
assign.linkParents(structAssignment)
assign
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
}
}
}
else {
// 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")
}
}
@@ -102,7 +97,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
module.statements.addAll(0, directives)
}
override fun visit(block: Block): IStatement {
override fun visit(block: Block): Statement {
val subroutines = block.statements.filterIsInstance<Subroutine>()
var numSubroutinesAtEnd = 0
@@ -166,7 +161,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return super.visit(block)
}
override fun visit(subroutine: Subroutine): IStatement {
override fun visit(subroutine: Subroutine): Statement {
super.visit(subroutine)
val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
@@ -191,7 +186,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return subroutine
}
override fun visit(expr: BinaryExpression): IExpression {
override fun visit(expr: BinaryExpression): Expression {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if(leftDt!=null && rightDt!=null && leftDt!=rightDt) {
@@ -214,43 +209,68 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return super.visit(expr)
}
override fun visit(assignment: Assignment): IStatement {
override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment)
if(assg !is Assignment)
return assg
// see if a typecast is needed to convert the value's type into the proper target type
val valuetype = assignment.value.inferType(program)
val targettype = assignment.target.inferType(program, assignment)
val valuetype = assg.value.inferType(program)
val targettype = assg.target.inferType(program, assg)
if(targettype!=null && valuetype!=null) {
if(valuetype!=targettype) {
if (valuetype isAssignableTo targettype) {
assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position)
assignment.value.linkParents(assignment)
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
assg.value.linkParents(assg)
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
// struct assignments will be flattened
// struct assignments will be flattened (if it's not a struct literal)
if(valuetype==DataType.STRUCT && targettype==DataType.STRUCT) {
val assignments = flattenStructAssignment(assignment, program)
if(assignments.isEmpty()) {
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
return assignment
assg
} else {
val scope = AnonymousScope(assignments.toMutableList(), assignment.position)
scope.linkParents(assignment.parent)
return scope
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
scope.linkParents(assg.parent)
scope
}
}
return super.visit(assignment)
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): IStatement {
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
return super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall): IExpression {
override fun visit(functionCall: FunctionCall): Expression {
checkFunctionCallArguments(functionCall, functionCall.definingScope())
return super.visit(functionCall)
}
@@ -301,29 +321,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
}
private fun sortConstantAssignmentSequence(first: Assignment, stmtIter: MutableIterator<IStatement>): Pair<List<Assignment>, IStatement?> {
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.target.shortString(true)}))
return Pair(sorted, trailing)
}
override fun visit(typecast: TypecastExpression): IExpression {
override fun visit(typecast: TypecastExpression): Expression {
// 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)) {
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)
@@ -331,7 +329,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return super.visit(typecast)
}
override fun visit(whenStatement: WhenStatement): IStatement {
override fun visit(whenStatement: WhenStatement): Statement {
// make sure all choices are just for one single value
val choices = whenStatement.choices.toList()
for(choice in choices) {
@@ -347,15 +345,15 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
// sort the choices in low-to-high value order (nulls last)
whenStatement.choices
.sortWith(compareBy<WhenChoice, Int?>(nullsLast(), {it.values?.single()?.constValue(program)?.asIntegerValue}))
.sortWith(compareBy<WhenChoice, Int?>(nullsLast(), {it.values?.single()?.constValue(program)?.number?.toInt()}))
return super.visit(whenStatement)
}
override fun visit(memread: DirectMemoryRead): IExpression {
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? LiteralValue
val literaladdr = memread.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)!!
} else {
@@ -369,7 +367,7 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
override fun visit(memwrite: DirectMemoryWrite) {
val dt = memwrite.addressExpression.inferType(program)
if(dt!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? LiteralValue
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)!!
} else {
@@ -379,4 +377,42 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
}
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
}
}
}
@@ -1,17 +1,15 @@
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.autoHeapValuePrefix
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.CompilerException
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:
// 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.
@@ -28,7 +26,6 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
override fun visit(module: Module) {
vardeclsToAdd.clear()
super.visit(module)
// add any new vardecls to the various scopes
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
@@ -36,8 +33,19 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
}
}
override fun visit(decl: VarDecl): IStatement {
override fun visit(decl: VarDecl): Statement {
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)
return decl
@@ -46,13 +54,13 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!!
val value =
if(declvalue is LiteralValue) {
if(declvalue is NumericLiteralValue) {
val converted = declvalue.cast(decl.datatype)
converted ?: declvalue
}
else
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(
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
null,
@@ -60,35 +68,36 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
decl.position
)
}
return decl
}
override fun visit(functionCall: FunctionCall): IExpression {
override fun visit(functionCall: FunctionCall): Expression {
val targetStatement = functionCall.target.targetSubroutine(namespace)
if(targetStatement!=null) {
var node: Node = functionCall
while(node !is IStatement)
while(node !is Statement)
node=node.parent
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node)
}
return functionCall
}
override fun visit(functionCallStatement: FunctionCallStatement): IStatement {
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
val targetStatement = functionCallStatement.target.targetSubroutine(namespace)
if(targetStatement!=null)
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, 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.
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) {
if(argparam.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? LiteralValue
val strvalue = argparam.second as? ReferenceLiteralValue
if(idref!=null) {
val variable = idref.targetVarDecl(namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
@@ -100,17 +109,15 @@ internal class VarInitValueAndAddressOfCreator(private val namespace: INameScope
}
else if(strvalue!=null) {
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
val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}"
val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position)
val autoHeapvarRef = IdentifierReference(listOf(variable.name), 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)
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, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, strvalue,
isArray = false, hiddenButDoNotRemove = false, position = strvalue.position)
addVarDecl(strvalue.definingScope(), variable)
}
}
}
@@ -6,10 +6,40 @@ import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.MachineDefinition
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 fun linkParents(parent: Node) {}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -24,9 +54,9 @@ data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusf
class Block(override val name: String,
val address: Int?,
override var statements: MutableList<IStatement>,
override var statements: MutableList<Statement>,
val isInLibrary: Boolean,
override val position: Position) : IStatement, INameScope {
override val position: Position) : Statement(), INameScope {
override lateinit var parent: Node
override val expensiveToInline
get() = statements.any { it.expensiveToInline }
@@ -46,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()
}
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 val expensiveToInline = false
@@ -67,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 val expensiveToInline = false
@@ -85,9 +115,9 @@ data class Label(val name: String, override val position: Position) : IStatement
val scopedname: String by lazy { makeScopedName(name) }
}
open class Return(var value: IExpression?, override val position: Position) : IStatement {
open class Return(var value: Expression?, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = value!=null && value !is LiteralValue
override val expensiveToInline = value!=null && value !is NumericLiteralValue
override fun linkParents(parent: Node) {
this.parent = parent
@@ -111,7 +141,7 @@ class ReturnFromIrq(override val position: Position) : Return(null, position) {
}
}
class Continue(override val position: Position) : IStatement {
class Continue(override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
@@ -123,7 +153,7 @@ class Continue(override val position: Position) : IStatement {
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 val expensiveToInline = false
@@ -149,10 +179,10 @@ class VarDecl(val type: VarDeclType,
var arraysize: ArrayIndex?,
val name: String,
private val structName: String?,
var value: IExpression?,
var value: Expression?,
val isArray: Boolean,
val hiddenButDoNotRemove: Boolean,
override val position: Position) : IStatement {
val autogeneratedDontRemove: Boolean,
override val position: Position) : Statement() {
override lateinit var parent: Node
var struct: StructDecl? = null // set later (because at parse time, we only know the name)
private set
@@ -160,7 +190,26 @@ class VarDecl(val type: VarDeclType,
private set
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 datatype =
@@ -182,7 +231,9 @@ class VarDecl(val type: VarDeclType,
arraysize?.linkParents(this)
value?.linkParents(this)
if(structName!=null) {
struct = definingScope().lookup(listOf(structName), this) as StructDecl
val structStmt = definingScope().lookup(listOf(structName), this)
if(structStmt!=null)
struct = definingScope().lookup(listOf(structName), this) as StructDecl
}
}
@@ -197,23 +248,23 @@ class VarDecl(val type: VarDeclType,
fun asDefaultValueDecl(parent: Node?): VarDecl {
val constValue = when(declaredDatatype) {
DataType.UBYTE -> LiteralValue(DataType.UBYTE, 0, position = position)
DataType.BYTE -> LiteralValue(DataType.BYTE, 0, position = position)
DataType.UWORD -> LiteralValue(DataType.UWORD, wordvalue = 0, position = position)
DataType.WORD -> LiteralValue(DataType.WORD, wordvalue = 0, position = position)
DataType.FLOAT -> LiteralValue(DataType.FLOAT, floatvalue = 0.0, position = position)
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only set a default value for a numeric type")
}
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, true, position)
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, false, position)
if(parent!=null)
decl.linkParents(parent)
return decl
}
fun flattenStructMembers(): MutableList<IStatement> {
fun flattenStructMembers(): MutableList<Statement> {
val result = struct!!.statements.withIndex().map {
val member = it.value as VarDecl
val initvalue = if(value!=null) (value as LiteralValue).arrayvalue!![it.index] else null
val initvalue = if(value!=null) (value as StructLiteralValue).values[it.index] else null
VarDecl(
VarDeclType.VAR,
member.datatype,
@@ -225,14 +276,14 @@ class VarDecl(val type: VarDeclType,
member.isArray,
true,
member.position
) as IStatement
) 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 fun linkParents(parent: Node) {
@@ -241,9 +292,9 @@ class ArrayIndex(var index: IExpression, override val position: Position) : Node
}
companion object {
fun forArray(v: LiteralValue, heap: HeapValues): ArrayIndex {
val arraySize = v.arrayvalue?.size ?: heap.get(v.heapId!!).arraysize
return ArrayIndex(LiteralValue.optimalNumeric(arraySize, v.position), v.position)
fun forArray(v: ReferenceLiteralValue, heap: HeapValues): ArrayIndex {
val arraySize = v.array?.size ?: heap.get(v.heapId!!).arraysize
return ArrayIndex(NumericLiteralValue.optimalNumeric(arraySize, v.position), v.position)
}
}
@@ -258,13 +309,13 @@ class ArrayIndex(var index: IExpression, override val position: Position) : Node
return("ArrayIndex($index, pos=$position)")
}
fun size() = (index as? LiteralValue)?.asIntegerValue
fun size() = (index as? NumericLiteralValue)?.number?.toInt()
}
open class Assignment(var target: 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 val expensiveToInline
get() = value !is LiteralValue
get() = value !is NumericLiteralValue
override fun linkParents(parent: Node) {
this.parent = parent
@@ -282,7 +333,7 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
// 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.
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: IExpression, position: Position)
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: Expression, position: Position)
: Assignment(target, aug_op, value, position)
data class AssignTarget(val register: Register?,
@@ -303,7 +354,7 @@ data class AssignTarget(val register: Register?,
fun accept(visitor: IAstVisitor) = visitor.visit(this)
companion object {
fun fromExpr(expr: IExpression): AssignTarget {
fun fromExpr(expr: Expression): AssignTarget {
return when (expr) {
is RegisterExpr -> AssignTarget(expr.register, null, null, null, expr.position)
is IdentifierReference -> AssignTarget(null, expr, null, null, expr.position)
@@ -314,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)
return DataType.UBYTE
@@ -335,23 +386,7 @@ data class AssignTarget(val register: Register?,
return null
}
fun shortString(withTypePrefix: Boolean=false): String {
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 {
infix fun isSameAs(value: Expression): Boolean {
return when {
this.memoryAddress!=null -> false
this.register!=null -> value is RegisterExpr && value.register==register
@@ -406,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 val expensiveToInline = false
@@ -426,7 +461,7 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
class Jump(val address: Int?,
val identifier: IdentifierReference?,
val generatedLabel: String?, // used in code generation scenarios
override val position: Position) : IStatement {
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = false
@@ -444,11 +479,11 @@ class Jump(val address: Int?,
}
class FunctionCallStatement(override var target: IdentifierReference,
override var arglist: MutableList<IExpression>,
override val position: Position) : IStatement, IFunctionCall {
override var arglist: MutableList<Expression>,
override val position: Position) : Statement(), IFunctionCall {
override lateinit var parent: Node
override val expensiveToInline
get() = arglist.any { it !is LiteralValue }
get() = arglist.any { it !is NumericLiteralValue }
override fun linkParents(parent: Node) {
this.parent = parent
@@ -464,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 val expensiveToInline = true
@@ -476,8 +511,8 @@ class InlineAssembly(val assembly: String, override val position: Position) : IS
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
}
class AnonymousScope(override var statements: MutableList<IStatement>,
override val position: Position) : INameScope, IStatement {
class AnonymousScope(override var statements: MutableList<Statement>,
override val position: Position) : INameScope, Statement() {
override val name: String
override lateinit var parent: Node
override val expensiveToInline
@@ -501,7 +536,7 @@ class AnonymousScope(override var statements: MutableList<IStatement>,
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 val expensiveToInline = false
@@ -513,7 +548,7 @@ class NopStatement(override val position: Position): IStatement {
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
companion object {
fun insteadOf(stmt: IStatement): NopStatement {
fun insteadOf(stmt: Statement): NopStatement {
val nop = NopStatement(stmt.position)
nop.parent = stmt.parent
return nop
@@ -532,8 +567,8 @@ class Subroutine(override val name: String,
val asmClobbers: Set<Register>,
val asmAddress: Int?,
val isAsmSubroutine: Boolean,
override var statements: MutableList<IStatement>,
override val position: Position) : IStatement, INameScope {
override var statements: MutableList<Statement>,
override val position: Position) : Statement(), INameScope {
var keepAlways: Boolean = false
override val expensiveToInline
@@ -609,10 +644,10 @@ open class SubroutineParameter(val name: String,
}
}
class IfStatement(var condition: IExpression,
class IfStatement(var condition: Expression,
var truepart: AnonymousScope,
var elsepart: AnonymousScope,
override val position: Position) : IStatement {
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
@@ -631,7 +666,7 @@ class IfStatement(var condition: IExpression,
class BranchStatement(var condition: BranchCondition,
var truepart: AnonymousScope,
var elsepart: AnonymousScope,
override val position: Position) : IStatement {
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline: Boolean
get() = truepart.expensiveToInline || elsepart.expensiveToInline
@@ -650,9 +685,9 @@ class ForLoop(val loopRegister: Register?,
val decltype: DataType?,
val zeropage: ZeropageWish,
val loopVar: IdentifierReference?,
var iterable: IExpression,
var iterable: Expression,
var body: AnonymousScope,
override val position: Position) : IStatement {
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
@@ -675,9 +710,9 @@ class ForLoop(val loopRegister: Register?,
}
}
class WhileLoop(var condition: IExpression,
class WhileLoop(var condition: Expression,
var body: AnonymousScope,
override val position: Position) : IStatement {
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
@@ -692,8 +727,8 @@ class WhileLoop(var condition: IExpression,
}
class RepeatLoop(var body: AnonymousScope,
var untilCondition: IExpression,
override val position: Position) : IStatement {
var untilCondition: Expression,
override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
@@ -707,9 +742,9 @@ class RepeatLoop(var body: AnonymousScope,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
}
class WhenStatement(val condition: IExpression,
class WhenStatement(val condition: Expression,
val choices: MutableList<WhenChoice>,
override val position: Position): IStatement {
override val position: Position): Statement() {
override lateinit var parent: Node
override val expensiveToInline: Boolean = true
@@ -726,7 +761,7 @@ class WhenStatement(val condition: IExpression,
if(choice.values==null)
result.add(null to choice)
else {
val values = choice.values.map { it.constValue(program)?.asNumericValue?.toInt() }
val values = choice.values.map { it.constValue(program)?.number?.toInt() }
if(values.contains(null))
result.add(null to choice)
else
@@ -740,7 +775,7 @@ class WhenStatement(val condition: IExpression,
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
}
class WhenChoice(val values: List<IExpression>?, // if null, this is the 'else' part
class WhenChoice(val values: List<Expression>?, // if null, this is the 'else' part
val statements: AnonymousScope,
override val position: Position) : Node {
override lateinit var parent: Node
@@ -761,8 +796,8 @@ class WhenChoice(val values: List<IExpression>?, // if null, this is
class StructDecl(override val name: String,
override var statements: MutableList<IStatement>, // actually, only vardecls here
override val position: Position): IStatement, INameScope {
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
@@ -774,25 +809,12 @@ class StructDecl(override val name: String,
val numberOfElements: Int
get() = this.statements.size
val memorySize: Int
get() = this.statements.map {
val decl = it as VarDecl
when {
decl.datatype in ByteDatatypes -> 8
decl.datatype in WordDatatypes -> 16
decl.datatype==DataType.FLOAT -> MachineDefinition.Mflpt5.MemorySize
decl.datatype in StringDatatypes -> TODO("stringvalue size")
decl.datatype in ArrayDatatypes -> decl.arraysize!!.size()!!
decl.datatype==DataType.STRUCT -> decl.struct!!.memorySize
else -> throw FatalAstException("can't get size for $decl")
}
}.sum()
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
}
class DirectMemoryWrite(var addressExpression: IExpression, override val position: Position) : Node {
class DirectMemoryWrite(var addressExpression: Expression, override val position: Position) : Node {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
+56 -34
View File
@@ -1,22 +1,24 @@
package prog8.compiler
import prog8.ast.antlr.escape
import prog8.ast.IFunctionCall
import prog8.ast.IStatement
import prog8.ast.Module
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.processing.IAstVisitor
import prog8.ast.statements.*
class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
var scopelevel = 0
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
private var scopelevel = 0
fun indent(s: String) = " ".repeat(scopelevel) + s
fun outputln(text: String) = output(text + "\n")
fun outputlni(s: Any) = outputln(indent(s.toString()))
fun outputi(s: Any) = output(indent(s.toString()))
private fun indent(s: String) = " ".repeat(scopelevel) + s
private fun outputln(text: String) = output(text + "\n")
private fun outputlni(s: Any) = outputln(indent(s.toString()))
private fun outputi(s: Any) = output(indent(s.toString()))
override fun visit(program: Program) {
outputln("============= PROGRAM ${program.name} (FROM AST) ===============")
@@ -76,7 +78,7 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
output("\n")
}
fun datatypeString(dt: DataType): String {
private fun datatypeString(dt: DataType): String {
return when(dt) {
in NumericDatatypes -> dt.toString().toLowerCase()
in StringDatatypes -> dt.toString().toLowerCase()
@@ -176,9 +178,9 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
}
}
private fun outputStatements(statements: List<IStatement>) {
private fun outputStatements(statements: List<Statement>) {
for(stmt in statements) {
if(stmt is VarDecl && stmt.hiddenButDoNotRemove)
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
continue // skip autogenerated decls (to avoid generating a newline)
outputi("")
stmt.accept(this)
@@ -252,36 +254,51 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
output("${label.name}:")
}
override fun visit(literalValue: LiteralValue) {
override fun visit(numLiteral: NumericLiteralValue) {
output(numLiteral.number.toString())
}
override fun visit(refLiteral: ReferenceLiteralValue) {
when {
literalValue.isNumeric -> output(literalValue.asNumericValue.toString())
literalValue.isString -> output("\"${escape(literalValue.strvalue!!)}\"")
literalValue.isArray -> {
if(literalValue.arrayvalue!=null) {
var counter = 0
output("[")
scopelevel++
for (v in literalValue.arrayvalue) {
v.accept(this)
if (v !== literalValue.arrayvalue.last())
output(", ")
counter++
if(counter > 16) {
outputln("")
outputi("")
counter=0
}
}
scopelevel--
output("]")
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) {
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} ")
else
output(" = ")
@@ -429,6 +446,11 @@ class AstToSourceCode(val output: (text: String) -> Unit): IAstVisitor {
whenChoice.statements.accept(this)
outputln("")
}
override fun visit(structLv: StructLiteralValue) {
outputListMembers(structLv.values.asSequence(), '{', '}')
}
override fun visit(nopStatement: NopStatement) {
output("; NOP @ ${nopStatement.position} $nopStatement")
}
+103 -92
View File
@@ -1,10 +1,11 @@
package prog8.compiler
import prog8.ast.*
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.base.RegisterOrPair.*
import prog8.ast.expressions.*
import prog8.ast.processing.flattenStructAssignment
import prog8.ast.mangledStructMemberName
import prog8.ast.statements.*
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.Opcode
@@ -116,7 +117,7 @@ class HeapValues {
fun get(heapId: Int): HeapValue {
return heap[heapId] ?:
throw IllegalArgumentException("heapId not found in heap")
throw IllegalArgumentException("heapId $heapId not found in heap")
}
fun allEntries() = heap.entries
@@ -180,8 +181,8 @@ internal class Compiler(private val program: Program) {
processVariables(subscope.value)
}
private fun translate(statements: List<IStatement>) {
for (stmt: IStatement in statements) {
private fun translate(statements: List<Statement>) {
for (stmt: Statement in statements) {
generatedLabelSequenceNumber++
when (stmt) {
is Label -> translate(stmt)
@@ -487,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++
return "${scopeStmt.makeScopedName("")}.<s-$generatedLabelSequenceNumber-$postfix>"
}
@@ -552,7 +553,7 @@ internal class Compiler(private val program: Program) {
prog.instr(Opcode.NOP)
}
private fun translate(expr: IExpression) {
private fun translate(expr: Expression) {
when(expr) {
is RegisterExpr -> {
prog.instr(Opcode.PUSH_VAR_BYTE, callLabel = expr.register.name)
@@ -593,22 +594,24 @@ internal class Compiler(private val program: Program) {
is TypecastExpression -> translate(expr)
is DirectMemoryRead -> translate(expr)
is AddressOf -> translate(expr)
is StructLiteralValue -> throw CompilerException("a struct Lv should have been flattened as assignments")
else -> {
val lv = expr.constValue(program) ?: throw CompilerException("constant expression required, not $expr")
when(lv.type) {
in ByteDatatypes -> prog.instr(Opcode.PUSH_BYTE, RuntimeValue(lv.type, lv.bytevalue!!))
in WordDatatypes -> prog.instr(Opcode.PUSH_WORD, RuntimeValue(lv.type, lv.wordvalue!!))
DataType.FLOAT -> prog.instr(Opcode.PUSH_FLOAT, RuntimeValue(lv.type, lv.floatvalue!!))
in StringDatatypes -> {
if(lv.heapId==null)
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)
throw CompilerException("array should have been moved into heap ${lv.position}")
TODO("push address of array with PUSH_ADDR_HEAPVAR")
}
in ByteDatatypes -> prog.instr(Opcode.PUSH_BYTE, RuntimeValue(lv.type, lv.number.toShort()))
in WordDatatypes -> prog.instr(Opcode.PUSH_WORD, RuntimeValue(lv.type, lv.number.toInt()))
DataType.FLOAT -> prog.instr(Opcode.PUSH_FLOAT, RuntimeValue(lv.type, lv.number.toDouble()))
// TODO what about these ref types:
// in StringDatatypes -> {
// if(lv.heapId==null)
// 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)
// 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")
}
}
@@ -675,11 +678,11 @@ internal class Compiler(private val program: Program) {
throw CompilerException("const ref should have been const-folded away")
VarDeclType.MEMORY -> {
when (target.datatype) {
DataType.UBYTE -> prog.instr(Opcode.PUSH_MEM_UB, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.BYTE-> prog.instr(Opcode.PUSH_MEM_B, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.UWORD -> prog.instr(Opcode.PUSH_MEM_UW, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.WORD -> prog.instr(Opcode.PUSH_MEM_W, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.FLOAT -> prog.instr(Opcode.PUSH_MEM_FLOAT, 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 NumericLiteralValue).number))
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 NumericLiteralValue).number))
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")
}
}
@@ -715,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
if(funcname == "swap") {
@@ -745,7 +748,8 @@ internal class Compiler(private val program: Program) {
val arg=args.single()
when (arg.inferType(program)) {
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" -> {
@@ -898,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
if (args.size != 2)
throw AstException("swap requires 2 arguments")
@@ -921,7 +925,7 @@ internal class Compiler(private val program: Program) {
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.
var restoreX = Register.X in subroutine.asmClobbers
if(restoreX)
@@ -968,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
if (subroutine.parameters.size != subroutine.asmParameterRegisters.size)
TODO("no support yet for mix of register and non-register subroutine arguments")
@@ -1007,13 +1011,13 @@ internal class Compiler(private val program: Program) {
prog.instr(Opcode.RSAVEX)
restoreX = true
}
val valueA: IExpression
val valueX: IExpression
val valueA: Expression
val valueX: Expression
val paramDt = arg.first.inferType(program)
when (paramDt) {
DataType.UBYTE -> {
valueA = arg.first
valueX = LiteralValue.optimalInteger(0, callPosition)
valueX = NumericLiteralValue.optimalInteger(0, callPosition)
val assignA = Assignment(AssignTarget(Register.A, null, null, null, callPosition), null, valueA, callPosition)
val assignX = Assignment(AssignTarget(Register.X, null, null, null, callPosition), null, valueX, callPosition)
assignA.linkParents(arguments[0].parent)
@@ -1030,13 +1034,13 @@ internal class Compiler(private val program: Program) {
}
}
AY -> {
val valueA: IExpression
val valueY: IExpression
val valueA: Expression
val valueY: Expression
val paramDt = arg.first.inferType(program)
when (paramDt) {
DataType.UBYTE -> {
valueA = arg.first
valueY = LiteralValue.optimalInteger(0, callPosition)
valueY = NumericLiteralValue.optimalInteger(0, callPosition)
val assignA = Assignment(AssignTarget(Register.A, null, null, null, callPosition), null, valueA, callPosition)
val assignY = Assignment(AssignTarget(Register.Y, null, null, null, callPosition), null, valueY, callPosition)
assignA.linkParents(arguments[0].parent)
@@ -1057,13 +1061,13 @@ internal class Compiler(private val program: Program) {
prog.instr(Opcode.RSAVEX)
restoreX = true
}
val valueX: IExpression
val valueY: IExpression
val valueX: Expression
val valueY: Expression
val paramDt = arg.first.inferType(program)
when (paramDt) {
DataType.UBYTE -> {
valueX = arg.first
valueY = LiteralValue.optimalInteger(0, callPosition)
valueY = NumericLiteralValue.optimalInteger(0, callPosition)
val assignX = Assignment(AssignTarget(Register.X, null, null, null, callPosition), null, valueX, callPosition)
val assignY = Assignment(AssignTarget(Register.Y, null, null, null, callPosition), null, valueY, callPosition)
assignX.linkParents(arguments[0].parent)
@@ -1253,10 +1257,10 @@ internal class Compiler(private val program: Program) {
prog.instr(opcode)
}
private fun translateBitshiftedOperator(operator: String, leftDt: DataType, amount: LiteralValue?) {
if(amount?.asIntegerValue == null)
private fun translateBitshiftedOperator(operator: String, leftDt: DataType, amount: NumericLiteralValue?) {
if(amount?.number?.toInt() == null)
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)
throw FatalAstException("bitshift value should be >= 0")
@@ -1382,7 +1386,7 @@ internal class Compiler(private val program: Program) {
}
}
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) {
when(stmt.operator) {
"++" -> prog.instr(Opcode.INC_MEMORY, RuntimeValue(DataType.UWORD, address))
@@ -1402,6 +1406,26 @@ internal class Compiler(private val program: Program) {
private fun translate(stmt: Assignment) {
prog.line(stmt.position)
if(stmt.value is StructLiteralValue) {
// flatten into individual struct member assignments
val identifier = stmt.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
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
}
translate(stmt.value)
val valueDt = stmt.value.inferType(program)
@@ -1446,11 +1470,6 @@ internal class Compiler(private val program: Program) {
else -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
}
}
DataType.STRUCT -> {
// Assume the value is an array. Flatten the struct assignment into memberwise assignments.
flattenStructAssignment(stmt, program).forEach { translate(it) }
return
}
in StringDatatypes -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
in ArrayDatatypes -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt")
else -> throw CompilerException("weird/unknown targetdt")
@@ -1465,43 +1484,34 @@ internal class Compiler(private val program: Program) {
popValueIntoTarget(stmt.target, datatype)
}
private fun pushHeapVarAddress(value: IExpression, removeLastOpcode: Boolean) {
when (value) {
is LiteralValue -> throw CompilerException("can only push address of string or array (value on the heap)")
is IdentifierReference -> {
val vardecl = value.targetVarDecl(program.namespace)!!
if(removeLastOpcode) prog.removeLastInstruction()
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")
private fun pushHeapVarAddress(value: Expression, removeLastOpcode: Boolean) {
if (value is IdentifierReference) {
val vardecl = value.targetVarDecl(program.namespace)!!
if(removeLastOpcode) prog.removeLastInstruction()
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")
}
private fun pushFloatAddress(value: IExpression) {
when (value) {
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)!!
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")
private fun pushFloatAddress(value: Expression) {
if (value is IdentifierReference) {
val vardecl = value.targetVarDecl(program.namespace)!!
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")
}
private fun pushStructAddress(value: IExpression) {
when (value) {
is LiteralValue -> throw CompilerException("can only push address of struct that is a variable on the heap")
is IdentifierReference -> {
// notice that the mangled name of the first struct member is the start address of this struct var
val vardecl = value.targetVarDecl(program.namespace)!!
val firstStructMember = (vardecl.struct!!.statements.first() as VarDecl).name
val firstVarName = listOf(vardecl.name, firstStructMember)
// find the flattened var that belongs to this first struct member
val firstVar = value.definingScope().lookup(firstVarName, value) as VarDecl
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = firstVar.scopedname) // TODO
}
else -> throw CompilerException("can only take address of a the float as constant literal or variable")
private fun pushStructAddress(value: Expression) {
if (value is IdentifierReference) {
// notice that the mangled name of the first struct member is the start address of this struct var
val vardecl = value.targetVarDecl(program.namespace)!!
val firstStructMember = (vardecl.struct!!.statements.first() as VarDecl).name
val firstVarName = listOf(vardecl.name, firstStructMember)
// find the flattened var that belongs to this first struct member
val firstVar = value.definingScope().lookup(firstVarName, value) as VarDecl
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = firstVar.scopedname) // TODO
}
else throw CompilerException("can only take address of a the float as constant literal or variable")
}
private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) {
@@ -1516,7 +1526,7 @@ internal class Compiler(private val program: Program) {
}
VarDeclType.MEMORY -> {
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))
}
VarDeclType.CONST -> throw CompilerException("cannot assign to const")
@@ -1526,7 +1536,7 @@ internal class Compiler(private val program: Program) {
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.memoryAddress != null -> {
val address = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue
val address = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.number?.toInt()
if(address!=null) {
// const integer address given
prog.instr(Opcode.POP_MEM_BYTE, arg= RuntimeValue(DataType.UWORD, address))
@@ -1613,18 +1623,19 @@ internal class Compiler(private val program: Program) {
loop.iterable is IdentifierReference -> {
val idRef = loop.iterable as IdentifierReference
val vardecl = idRef.targetVarDecl(program.namespace)!!
val iterableValue = vardecl.value as LiteralValue
if(iterableValue.type !in IterableDatatypes)
throw CompilerException("loop over something that isn't iterable ${loop.iterable}")
translateForOverIterableVar(loop, loopvalueDt, iterableValue)
// TODO check loop over iterable or not
// val iterableValue = vardecl.value as LiteralValue
// if(iterableValue.type !in IterableDatatypes)
// 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}")
}
}
}
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))
throw CompilerException("loop variable type doesn't match iterableValue type")
else if(loopvarDt== DataType.UWORD && iterableValue.type != DataType.ARRAY_UW)
@@ -1636,16 +1647,16 @@ internal class Compiler(private val program: Program) {
when(iterableValue.type) {
!in IterableDatatypes -> throw CompilerException("non-iterableValue type")
DataType.STR, DataType.STR_S -> {
numElements = iterableValue.strvalue!!.length
numElements = iterableValue.str!!.length
if(numElements>255) throw CompilerException("string length > 255")
}
DataType.ARRAY_UB, DataType.ARRAY_B,
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")
}
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")
}
else -> throw CompilerException("weird datatype")
@@ -1839,7 +1850,7 @@ internal class Compiler(private val program: Program) {
val loopLabel = makeLabel(body, "loop")
val continueLabel = makeLabel(body, "continue")
val breakLabel = makeLabel(body, "break")
val literalStepValue = (range.step as? LiteralValue)?.asNumericValue?.toInt()
val literalStepValue = (range.step as? NumericLiteralValue)?.number?.toInt()
continueStmtLabelStack.push(continueLabel)
breakStmtLabelStack.push(breakLabel)
@@ -2053,7 +2064,7 @@ internal class Compiler(private val program: Program) {
private fun translate(memread: DirectMemoryRead) {
// 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) {
prog.instr(Opcode.PUSH_MEM_UB, arg = RuntimeValue(DataType.UWORD, address))
} else {
@@ -2065,7 +2076,7 @@ internal class Compiler(private val program: Program) {
private fun translate(memwrite: DirectMemoryWrite) {
// for now, only a single memory location (ubyte) is written at a time.
// 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) {
prog.instr(Opcode.POP_MEM_BYTE, arg = RuntimeValue(DataType.UWORD, address))
} else {
+6 -8
View File
@@ -2,9 +2,6 @@ package prog8.compiler
import prog8.ast.Program
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.compiler.target.c64.AsmGen
import prog8.compiler.target.c64.MachineDefinition
@@ -17,7 +14,6 @@ import prog8.parser.importModule
import prog8.parser.moduleName
import java.io.File
import java.io.PrintStream
import java.lang.Exception
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
@@ -64,6 +60,11 @@ fun compileProgram(filepath: Path,
//println(" time2: $time2")
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
}
//println(" time3: $time3")
@@ -89,9 +90,6 @@ fun compileProgram(filepath: Path,
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
// printAst(programAst)
// namespace.debugPrint()
if(generateVmCode) {
// compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(programAst)
@@ -146,7 +144,7 @@ fun compileProgram(filepath: Path,
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print)
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}
-1
View File
@@ -1,7 +1,6 @@
package prog8.compiler
import prog8.ast.base.*
import prog8.ast.base.printWarning
class ZeropageDepletedError(message: String) : Exception(message)
@@ -2,63 +2,52 @@ package prog8.compiler.intermediate
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.base.printWarning
import prog8.ast.expressions.LiteralValue
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
import prog8.vm.RuntimeValue
import prog8.compiler.CompilerException
import prog8.compiler.HeapValues
import prog8.compiler.Zeropage
import prog8.compiler.ZeropageDepletedError
import prog8.vm.RuntimeValue
import java.io.PrintStream
import java.nio.file.Path
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
data class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?)
class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?)
class Variable(val scopedname: String, val value: RuntimeValue, val params: VariableParameters)
class ProgramBlock(val name: String,
var address: Int?,
val instructions: MutableList<Instruction> = mutableListOf(),
val variables: MutableList<Triple<String, RuntimeValue, VariableParameters>> = mutableListOf(), // names are fully scoped
val variables: MutableList<Variable> = mutableListOf(),
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
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() // TODO maybe this can be removed now we have ValueParameters
}
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
val blocks = mutableListOf<ProgramBlock>()
val memory = mutableMapOf<Int, List<RuntimeValue>>()
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) {
// allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP)
var notAllocated = 0
for(block in blocks) {
val zpVariables = block.variables.filter { it.first in block.variablesMarkedForZeropage }
val zpVariables = block.variables.filter { it.params.zp==ZeropageWish.REQUIRE_ZEROPAGE || it.params.zp==ZeropageWish.PREFER_ZEROPAGE }
if (zpVariables.isNotEmpty()) {
for ((varname, value, varparams) in zpVariables) {
if(varparams.zp==ZeropageWish.NOT_IN_ZEROPAGE || varparams.memberOfStruct!=null)
for (variable in zpVariables) {
if(variable.params.zp==ZeropageWish.NOT_IN_ZEROPAGE || variable.params.memberOfStruct!=null)
throw CompilerException("zp conflict")
try {
val address = zeropage.allocate(varname, value.type, null)
allocatedZeropageVariables[varname] = Pair(address, value.type)
val address = zeropage.allocate(variable.scopedname, variable.value.type, null)
allocatedZeropageVariables[variable.scopedname] = Pair(address, variable.value.type)
} catch (x: ZeropageDepletedError) {
printWarning(x.toString() + " variable $varname type ${value.type}")
printWarning(x.toString() + " variable ${variable.scopedname} type ${variable.value.type}")
notAllocated++
}
}
@@ -407,19 +396,23 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
val valueparams = VariableParameters(decl.zeropage, decl.struct)
val value = when(decl.datatype) {
in NumericDatatypes -> {
RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
RuntimeValue(decl.datatype, (decl.value as NumericLiteralValue).number)
}
in StringDatatypes -> {
val litval = (decl.value as LiteralValue)
val litval = (decl.value as ReferenceLiteralValue)
if(litval.heapId==null)
throw CompilerException("string should already be in the heap")
RuntimeValue(decl.datatype, heapId = litval.heapId)
}
in ArrayDatatypes -> {
val litval = (decl.value as LiteralValue)
if(litval.heapId==null)
val litval = (decl.value as? ReferenceLiteralValue)
if(litval!=null && litval.heapId==null)
throw CompilerException("array should already be in the heap")
RuntimeValue(decl.datatype, heapId = litval.heapId)
if(litval!=null){
RuntimeValue(decl.datatype, heapId = litval.heapId)
} else {
throw CompilerException("initialization value expected")
}
}
DataType.STRUCT -> {
// struct variables have been flattened already
@@ -427,25 +420,21 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
}
else -> throw CompilerException("weird datatype")
}
currentBlock.variables.add(Triple(scopedname, value, valueparams))
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
currentBlock.variablesMarkedForZeropage.add(scopedname)
else if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
TODO("REQUIRE_ZEROPAGE not yet implemented")
currentBlock.variables.add(Variable(scopedname, value, valueparams))
}
VarDeclType.MEMORY -> {
// 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)
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 -> {
// 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!!
val lv = decl.value as LiteralValue
val lv = decl.value as NumericLiteralValue
if(lv.type in IntegerDatatypes)
currentBlock.memoryPointers[scopedname] = Pair(lv.asIntegerValue!!, decl.datatype)
currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), decl.datatype)
}
}
}
@@ -521,12 +510,12 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
out.println("%variables")
for ((vname, value, parameters) in blk.variables) {
if(parameters.zp==ZeropageWish.REQUIRE_ZEROPAGE)
for (variable in blk.variables) {
if(variable.params.zp==ZeropageWish.REQUIRE_ZEROPAGE)
throw CompilerException("zp conflict")
val valuestr = value.toString()
val struct = if(parameters.memberOfStruct==null) "" else "struct=${parameters.memberOfStruct.name}"
out.println("$vname ${value.type.name.toLowerCase()} $valuestr zp=${parameters.zp} $struct")
val valuestr = variable.value.toString()
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("%memorypointers")
@@ -7,9 +7,12 @@ import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.initvarsSubName
import prog8.ast.statements.ZeropageWish
import prog8.vm.RuntimeValue
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.util.*
import kotlin.math.abs
@@ -45,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.
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
for(block in program.blocks) {
val newvars = block.variables.map { Triple(symname(it.first, block), it.second, it.third) }.toMutableList()
val newvarsZeropaged = block.variablesMarkedForZeropage.map{symname(it, block)}.toMutableSet()
val newvars = block.variables.map { IntermediateProgram.Variable(symname(it.scopedname, block), it.value, it.params) }.toMutableList()
val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap()
val newinstructions = block.instructions.asSequence().map {
when {
@@ -67,8 +69,6 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
newMempointers,
newlabels,
force_output = block.force_output)
newblock.variablesMarkedForZeropage.clear()
newblock.variablesMarkedForZeropage.addAll(newvarsZeropaged)
newblocks.add(newblock)
}
program.blocks.clear()
@@ -238,20 +238,19 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
}
// deal with zeropage variables
for((varname, value, parameters) in blk.variables) {
val sym = symname(blk.name+"."+varname, null)
for(variable in blk.variables) {
val sym = symname(blk.name+"."+variable.scopedname, null)
val zpVar = program.allocatedZeropageVariables[sym]
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)
if(parameters.zp != ZeropageWish.NOT_IN_ZEROPAGE &&
value.type in zeropage.allowedDatatypes
&& value.type != DataType.FLOAT) {
if(variable.params.zp != ZeropageWish.NOT_IN_ZEROPAGE &&
variable.value.type in zeropage.allowedDatatypes
&& variable.value.type != DataType.FLOAT) {
try {
val address = zeropage.allocate(sym, value.type, null)
out("$varname = $address\t; auto zp ${value.type}")
val address = zeropage.allocate(sym, variable.value.type, null)
out("${variable.scopedname} = $address\t; auto zp ${variable.value.type}")
// make sure we add the var to the set of zpvars for this block
blk.variablesMarkedForZeropage.add(varname)
program.allocatedZeropageVariables[sym] = Pair(address, value.type)
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
} catch (x: ZeropageDepletedError) {
// leave it as it is.
}
@@ -259,7 +258,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
}
else {
// it was already allocated on the zp
out("$varname = ${zpVar.first}\t; zp ${zpVar.second}")
out("${variable.scopedname} = ${zpVar.first}\t; zp ${zpVar.second}")
}
}
@@ -293,23 +292,24 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
}
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
val uniqueNames = block.variables.map { it.first }.toSet()
val uniqueNames = block.variables.map { it.scopedname }.toSet()
if (uniqueNames.size != block.variables.size)
throw AssemblyError("not all variables have unique names")
// these are the non-zeropage variables.
// first get all the flattened struct members, they MUST remain in order
out("; flattened struct members")
val (structMembers, normalVars) = block.variables.partition { it.third.memberOfStruct!=null }
structMembers.forEach { vardecl2asm(it.first, it.second, it.third) }
val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null }
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.second.type }
for ((varname, value, parameters) in sortedVars) {
if(varname in block.variablesMarkedForZeropage)
continue // skip the ones that belong in the zero page
vardecl2asm(varname, value, parameters)
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)
}
}
@@ -376,7 +376,7 @@ class AsmGen(private val options: CompilationOptions, private val program: Inter
val array = heap.get(value.heapId!!).doubleArray!!
val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) }
out(varname)
for(f in array.zip(floatFills))
for (f in array.zip(floatFills))
out(" .byte ${f.second} ; float ${f.first}")
}
DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened")
@@ -3,11 +3,11 @@ package prog8.compiler.target.c64
import prog8.ast.base.printWarning
import prog8.compiler.intermediate.Instruction
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_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.toHex
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
@@ -1,6 +1,9 @@
package prog8.compiler.target.c64
import prog8.compiler.*
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
@@ -5,16 +5,15 @@ import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.toHex
import prog8.vm.stackvm.Syscall
import prog8.vm.stackvm.syscallsForStackVm
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.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS2_HEX
import prog8.compiler.toHex
import prog8.vm.stackvm.Syscall
import prog8.vm.stackvm.syscallsForStackVm
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
+88 -174
View File
@@ -1,11 +1,8 @@
package prog8.functions
import prog8.ast.*
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.VarDecl
import prog8.ast.expressions.*
import prog8.compiler.CompilerException
import kotlin.math.*
@@ -13,7 +10,7 @@ import kotlin.math.*
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<IExpression>, position: Position, program: Program) -> LiteralValue
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
class FunctionSignature(val pure: Boolean, // does it have side effects?
@@ -31,9 +28,9 @@ val BuiltinFunctions = mapOf(
"lsl" 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):
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.max()!! }}, // 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
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.sum() }}, // 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, _ -> collectionArgNeverConst(a, p) }, // 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
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
// normal functions follow:
@@ -55,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) },
"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) },
"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) },
"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) },
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.any { v -> v != 0.0} }},
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.all { 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, _ -> 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 }},
"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(
@@ -116,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 {
if(arglist is LiteralValue) {
fun datatypeFromIterableArg(arglist: Expression): DataType {
if(arglist is ReferenceLiteralValue) {
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}) {
throw FatalAstException("fuction $function only accepts arraysize of numeric values")
}
@@ -188,181 +185,98 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, program
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)
throw SyntaxError("built-in function requires one floating point argument", position)
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)
}
private fun getFloatArg(v: LiteralValue, position: Position): Double {
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 {
private fun oneDoubleArgOutputWord(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val float = getFloatArg(constval, args[0].position)
return LiteralValue(DataType.WORD, wordvalue = function(float).toInt(), position = args[0].position)
val float = constval.number.toDouble()
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)
throw SyntaxError("built-in function requires one integer argument", position)
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)
val integer = constval.asNumericValue?.toInt()!!
val integer = constval.number.toInt()
return numericLiteral(function(integer).toInt(), args[0].position)
}
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position,
program: Program,
function: (arg: Collection<Double>)->Number): LiteralValue {
private fun collectionArgNeverConst(args: List<Expression>, position: Position): NumericLiteralValue {
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() }).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)
// max/min/sum etc only work on arrays and these are never considered to be const for these functions
throw NotConstArgumentException()
}
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position,
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 {
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
// 1 arg, type = float or int, result type= isSameAs as argument type
if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return when (val number = constval.asNumericValue) {
is Int, is Byte, is Short -> numericLiteral(abs(number.toInt()), args[0].position)
is Double -> numericLiteral(abs(number.toDouble()), args[0].position)
return when (constval.type) {
in IntegerDatatypes -> numericLiteral(abs(constval.number.toInt()), args[0].position)
DataType.FLOAT -> numericLiteral(abs(constval.number.toDouble()), args[0].position)
else -> throw SyntaxError("abs requires one numeric argument", position)
}
}
private fun builtinAvg(args: List<IExpression>, position: Position, program: Program): LiteralValue {
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 {
private fun builtinStrlen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
if(argument.type !in StringDatatypes)
throw SyntaxError("strlen must have string argument", position)
val string = argument.strvalue!!
val zeroIdx = string.indexOf('\u0000')
return if(zeroIdx>=0)
LiteralValue.optimalInteger(zeroIdx, position=position)
else
LiteralValue.optimalInteger(string.length, position=position)
throw NotConstArgumentException() // this function is not considering the string argument a constant
}
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.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
var argument = args[0].constValue(program)
if(argument==null) {
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
val arraySize = directMemVar?.arraysize?.size()
if(arraySize != null)
return LiteralValue.optimalInteger(arraySize, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
val argValue = (target as? VarDecl)?.value
argument = argValue?.constValue(program)
?: throw NotConstArgumentException()
}
return when(argument.type) {
val constArg = args[0].constValue(program)
if(constArg!=null)
throw SyntaxError("len of weird argument ${args[0]}", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
var arraySize = directMemVar?.arraysize?.size()
if(arraySize != null)
return NumericLiteralValue.optimalInteger(arraySize, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
return when(target.datatype) {
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)
throw CompilerException("array length exceeds byte limit ${argument.position}")
LiteralValue.optimalInteger(arraySize, args[0].position)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.ARRAY_F -> {
val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize
arraySize = target.arraysize!!.size()!!
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${argument.position}")
LiteralValue.optimalInteger(arraySize, args[0].position)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
in StringDatatypes -> {
val str = argument.strvalue!!
if(str.length>255)
throw CompilerException("string length exceeds byte limit ${argument.position}")
LiteralValue.optimalInteger(str.length, args[0].position)
val refLv = target.value as ReferenceLiteralValue
if(refLv.str!!.length>255)
throw CompilerException("string length exceeds byte limit ${refLv.position}")
NumericLiteralValue.optimalInteger(refLv.str.length, args[0].position)
}
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
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)
throw SyntaxError("mkword requires lsb and msb arguments", position)
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsb.asIntegerValue!! shl 8) or constLsb.asIntegerValue!!
return LiteralValue(DataType.UWORD, wordvalue = result, position = position)
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
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)
throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.BYTE, bytevalue = (127.0 * sin(rad)).toShort(), position = position)
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
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)
throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * sin(rad)).toShort(), position = position)
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
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)
throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.BYTE, bytevalue = (127.0 * cos(rad)).toShort(), position = position)
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
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)
throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5 * cos(rad)).toShort(), position = position)
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
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)
throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.WORD, wordvalue = (32767.0 * sin(rad)).toInt(), position = position)
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
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)
throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * sin(rad)).toInt(), position = position)
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
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)
throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.WORD, wordvalue = (32767.0 * cos(rad)).toInt(), position = position)
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
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)
throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UWORD, wordvalue = (32768.0 + 32767.5 * cos(rad)).toInt(), position = position)
val rad = constval.number.toDouble() /256.0 * 2.0 * PI
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 tweakedValue: Number =
if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
@@ -452,11 +366,11 @@ private fun numericLiteral(value: Number, position: Position): LiteralValue {
floatNum
return when(tweakedValue) {
is Int -> LiteralValue.optimalNumeric(value.toInt(), position)
is Short -> LiteralValue.optimalNumeric(value.toInt(), position)
is Byte -> LiteralValue(DataType.UBYTE, bytevalue = value.toShort(), position = position)
is Double -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position)
is Float -> LiteralValue(DataType.FLOAT, floatvalue = value.toDouble(), position = position)
is Int -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
is Short -> NumericLiteralValue.optimalNumeric(value.toInt(), position)
is Byte -> NumericLiteralValue(DataType.UBYTE, value.toShort(), position)
is Double -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
is Float -> NumericLiteralValue(DataType.FLOAT, value.toDouble(), position)
else -> throw FatalAstException("invalid number type ${value::class}")
}
}
+10 -6
View File
@@ -1,6 +1,9 @@
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.VarDeclType
@@ -18,7 +21,8 @@ class CallGraph(private val program: Program): IAstVisitor {
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().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 {
visit(program)
@@ -93,11 +97,11 @@ class CallGraph(private val program: Program): IAstVisitor {
super.visit(identifier)
}
private fun addNodeAndParentScopes(stmt: IStatement) {
private fun addNodeAndParentScopes(stmt: Statement) {
usedSymbols.add(stmt)
var node: Node=stmt
do {
if(node is INameScope && node is IStatement) {
if(node is INameScope && node is Statement) {
usedSymbols.add(node)
}
node=node.parent
@@ -119,7 +123,7 @@ class CallGraph(private val program: Program): IAstVisitor {
}
override fun visit(decl: VarDecl) {
if(decl.hiddenButDoNotRemove || (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
addNodeAndParentScopes(decl)
}
@@ -175,7 +179,7 @@ class CallGraph(private val program: Program): IAstVisitor {
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 asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
asm.lines().forEach { line ->
+114 -129
View File
@@ -1,17 +1,14 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ExpressionError
import prog8.ast.base.FatalAstException
import prog8.ast.base.Position
import prog8.ast.expressions.LiteralValue
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue
import kotlin.math.pow
class ConstExprEvaluator {
fun evaluate(left: LiteralValue, operator: String, right: LiteralValue): IExpression {
fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression {
return when(operator) {
"+" -> plus(left, right)
"-" -> minus(left, right)
@@ -25,200 +22,188 @@ class ConstExprEvaluator {
"and" -> logicaland(left, right)
"or" -> logicalor(left, right)
"xor" -> logicalxor(left, right)
"<" -> LiteralValue.fromBoolean(left < right, left.position)
">" -> LiteralValue.fromBoolean(left > right, left.position)
"<=" -> LiteralValue.fromBoolean(left <= right, left.position)
">=" -> LiteralValue.fromBoolean(left >= right, left.position)
"==" -> LiteralValue.fromBoolean(left == right, left.position)
"!=" -> LiteralValue.fromBoolean(left != right, left.position)
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
"<<" -> shiftedleft(left, right)
">>" -> shiftedright(left, right)
else -> throw FatalAstException("const evaluation for invalid operator $operator")
}
}
private fun shiftedright(left: LiteralValue, amount: LiteralValue): IExpression {
if(left.asIntegerValue==null || amount.asIntegerValue==null)
private fun shiftedright(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left >> $amount", left.position)
val result =
if(left.type== DataType.UBYTE || left.type== DataType.UWORD)
left.asIntegerValue.ushr(amount.asIntegerValue)
left.number.toInt().ushr(amount.number.toInt())
else
left.asIntegerValue.shr(amount.asIntegerValue)
return LiteralValue.fromNumber(result, left.type, left.position)
left.number.toInt().shr(amount.number.toInt())
return NumericLiteralValue(left.type, result, left.position)
}
private fun shiftedleft(left: LiteralValue, amount: LiteralValue): IExpression {
if(left.asIntegerValue==null || amount.asIntegerValue==null)
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left << $amount", left.position)
val result = left.asIntegerValue.shl(amount.asIntegerValue)
return LiteralValue.fromNumber(result, left.type, left.position)
val result = left.number.toInt().shl(amount.number.toInt())
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"
return when {
left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean((left.asIntegerValue != 0) xor (right.asIntegerValue != 0), left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean((left.asIntegerValue != 0) xor (right.floatvalue != 0.0), left.position)
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 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)
}
left.floatvalue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean((left.floatvalue != 0.0) xor (right.asIntegerValue != 0), left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean((left.floatvalue != 0.0) xor (right.floatvalue != 0.0), left.position)
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 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)
}
}
private fun logicalor(left: LiteralValue, right: LiteralValue): LiteralValue {
private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-or $right"
return when {
left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 || right.asIntegerValue != 0, left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 || right.floatvalue != 0.0, left.position)
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 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)
}
left.floatvalue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 || right.asIntegerValue != 0, left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 || right.floatvalue != 0.0, left.position)
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 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)
}
}
private fun logicaland(left: LiteralValue, right: LiteralValue): LiteralValue {
private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-and $right"
return when {
left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 && right.asIntegerValue != 0, left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean(left.asIntegerValue != 0 && right.floatvalue != 0.0, left.position)
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 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)
}
left.floatvalue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 && right.asIntegerValue != 0, left.position)
right.floatvalue!=null -> LiteralValue.fromBoolean(left.floatvalue != 0.0 && right.floatvalue != 0.0, left.position)
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 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)
}
}
private fun bitwisexor(left: LiteralValue, right: LiteralValue): LiteralValue {
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
if(left.type== DataType.UBYTE) {
if(right.asIntegerValue!=null) {
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() xor (right.asIntegerValue and 255)).toShort(), position = left.position)
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toShort(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.asIntegerValue!=null) {
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! xor right.asIntegerValue, position = left.position)
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() xor right.number.toInt(), 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(right.asIntegerValue!=null) {
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position)
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.asIntegerValue!=null) {
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position)
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), 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(right.asIntegerValue!=null) {
return LiteralValue(DataType.UBYTE, bytevalue = (left.bytevalue!!.toInt() or (right.asIntegerValue and 255)).toShort(), position = left.position)
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toShort(), left.position)
}
} else if(left.type== DataType.UWORD) {
if(right.asIntegerValue!=null) {
return LiteralValue(DataType.UWORD, wordvalue = left.wordvalue!! or right.asIntegerValue, position = left.position)
if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, left.number.toInt() or right.number.toInt(), 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"
return when {
left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue.toDouble().pow(right.asIntegerValue), left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue.toDouble().pow(right.floatvalue), position = left.position)
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), 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)
}
left.floatvalue!=null -> when {
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue.pow(right.asIntegerValue), position = left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue.pow(right.floatvalue), position = left.position)
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), 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)
}
}
private fun plus(left: LiteralValue, right: LiteralValue): LiteralValue {
private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot add $left and $right"
return when {
left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue + right.asIntegerValue, left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue + right.floatvalue, position = left.position)
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.floatvalue!=null -> when {
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue + right.asIntegerValue, position = left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue + right.floatvalue, position = 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)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), 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)
}
}
private fun minus(left: LiteralValue, right: LiteralValue): LiteralValue {
private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot subtract $left and $right"
return when {
left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue - right.asIntegerValue, left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue - right.floatvalue, position = left.position)
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.floatvalue!=null -> when {
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue - right.asIntegerValue, position = left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue - right.floatvalue, position = left.position)
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), 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)
}
}
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}"
return when {
left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue * right.asIntegerValue, left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue * right.floatvalue, position = 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)
}
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.floatvalue!=null -> when {
right.asIntegerValue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.asIntegerValue, position = left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.floatvalue, position = left.position)
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), 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)
@@ -228,29 +213,29 @@ class ConstExprEvaluator {
private fun divideByZeroError(pos: Position): Unit =
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"
return when {
left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> {
if(right.asIntegerValue==0) divideByZeroError(right.position)
val result: Int = left.asIntegerValue / right.asIntegerValue
LiteralValue.optimalNumeric(result, left.position)
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteralValue.optimalNumeric(result, left.position)
}
right.floatvalue!=null -> {
if(right.floatvalue==0.0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue / right.floatvalue, position = left.position)
right.type == DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
}
else -> throw ExpressionError(error, left.position)
}
left.floatvalue!=null -> when {
right.asIntegerValue!=null -> {
if(right.asIntegerValue==0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue / right.asIntegerValue, position = left.position)
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
}
right.floatvalue!=null -> {
if(right.floatvalue==0.0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue / right.floatvalue, position = left.position)
right.type == DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), 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"
return when {
left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> {
if(right.asIntegerValue==0) divideByZeroError(right.position)
LiteralValue.optimalNumeric(left.asIntegerValue.toDouble() % right.asIntegerValue.toDouble(), left.position)
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
}
right.floatvalue!=null -> {
if(right.floatvalue==0.0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue % right.floatvalue, position = left.position)
right.type == DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
}
else -> throw ExpressionError(error, left.position)
}
left.floatvalue!=null -> when {
right.asIntegerValue!=null -> {
if(right.asIntegerValue==0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue % right.asIntegerValue, position = left.position)
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
}
right.floatvalue!=null -> {
if(right.floatvalue==0.0) divideByZeroError(right.position)
LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue % right.floatvalue, position = left.position)
right.type == DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
}
else -> throw ExpressionError(error, left.position)
}
+142 -129
View File
@@ -1,6 +1,7 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
@@ -15,7 +16,6 @@ import kotlin.math.floor
class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf()
private val reportedErrorMessages = mutableSetOf<String>()
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)
// 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))
return decl
}
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
val litval = decl.value as? LiteralValue
if(litval!=null && litval.isArray && litval.heapId!=null)
fixupArrayTypeOnHeap(decl, litval)
val refLv = decl.value as? ReferenceLiteralValue
if(refLv!=null && refLv.isArray && refLv.heapId!=null)
fixupArrayTypeOnHeap(decl, refLv)
if(decl.isArray){
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
if(decl.arraysize==null) {
val arrayval = (decl.value as? LiteralValue)?.arrayvalue
val arrayval = (decl.value as? ReferenceLiteralValue)?.array
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++
}
}
else if(decl.arraysize?.size()==null) {
val size = decl.arraysize!!.index.accept(this)
if(size is LiteralValue) {
if(size is NumericLiteralValue) {
decl.arraysize = ArrayIndex(size, decl.position)
optimizationsDone++
}
@@ -59,14 +60,22 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
when(decl.datatype) {
DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats
if (litval != null && litval.type in IntegerDatatypes) {
val newValue = LiteralValue(DataType.FLOAT, floatvalue = litval.asNumericValue!!.toDouble(), position = litval.position)
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
decl.value = newValue
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 -> {
val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// 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) {
val eltType = rangeExpr.inferType(program)!!
if(eltType in ByteDatatypes) {
decl.value = LiteralValue(decl.datatype,
arrayvalue = constRange.map { LiteralValue(eltType, bytevalue = it.toShort(), position = decl.value!!.position) }
decl.value = ReferenceLiteralValue(decl.datatype,
array = constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }
.toTypedArray(), position = decl.value!!.position)
} else {
decl.value = LiteralValue(decl.datatype,
arrayvalue = constRange.map { LiteralValue(eltType, wordvalue = it, position = decl.value!!.position) }
decl.value = ReferenceLiteralValue(decl.datatype,
array = constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }
.toTypedArray(), position = decl.value!!.position)
}
decl.value!!.linkParents(decl)
optimizationsDone++
return decl
return super.visit(decl)
}
}
if(litval?.type== DataType.FLOAT)
errors.add(ExpressionError("arraysize requires only integers here", litval.position))
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.add(ExpressionError("arraysize requires only integers here", numericLv.position))
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.
val fillvalue = if (litval == null) 0 else litval.asIntegerValue ?: 0
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.add(ExpressionError("ubyte value overflow", litval?.position
?: decl.position))
errors.add(ExpressionError("ubyte value overflow", numericLv.position))
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.add(ExpressionError("byte value overflow", litval?.position
?: decl.position))
errors.add(ExpressionError("byte value overflow", numericLv.position))
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.add(ExpressionError("uword value overflow", litval?.position
?: decl.position))
errors.add(ExpressionError("uword value overflow", numericLv.position))
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.add(ExpressionError("word value overflow", litval?.position
?: decl.position))
errors.add(ExpressionError("word value overflow", numericLv.position))
}
else -> {}
}
val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) })
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval?.position
?: decl.position)
decl.value = ReferenceLiteralValue(decl.datatype, initHeapId = heapId, position = numericLv.position)
optimizationsDone++
return decl
return super.visit(decl)
}
}
DataType.ARRAY_F -> {
val size = decl.arraysize?.size() ?: return decl
if (litval==null || !litval.isArray) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0
if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("float value overflow", litval?.position
?: decl.position))
val litval = decl.value as? NumericLiteralValue
if(litval==null) {
// 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)
errors.add(ExpressionError("float value overflow", litval.position))
else {
val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue })
decl.value = LiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval?.position
?: decl.position)
decl.value = ReferenceLiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval.position)
optimizationsDone++
return decl
return super.visit(decl)
}
}
}
@@ -152,7 +158,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
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.
// 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 -> {
if(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 -> {
@@ -172,7 +178,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
// convert a non-float array to floats
val doubleArray = array.array.map { it.integer!!.toDouble() }.toDoubleArray()
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 -> {
@@ -185,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)
*/
override fun visit(identifier: IdentifierReference): IExpression {
override fun visit(identifier: IdentifierReference): Expression {
return try {
val cval = identifier.constValue(program) ?: return identifier
return if(cval.isNumeric) {
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, null, cval.arrayvalue, position = identifier.position)
copy.parent = identifier.parent
copy
} else
identifier
return when {
cval.type in NumericDatatypes -> {
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
copy.parent = identifier.parent
copy
}
cval.type in PassByReferenceDatatypes -> TODO("ref type $identifier")
else -> identifier
}
} catch (ax: AstException) {
addError(ax)
identifier
}
}
override fun visit(functionCall: FunctionCall): IExpression {
override fun visit(functionCall: FunctionCall): Expression {
return try {
super.visit(functionCall)
typeCastConstArguments(functionCall)
@@ -211,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)
typeCastConstArguments(functionCallStatement)
return functionCallStatement
@@ -235,7 +244,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
}
override fun visit(memread: DirectMemoryRead): IExpression {
override fun visit(memread: DirectMemoryRead): Expression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
@@ -248,43 +257,36 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
* 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
*/
override fun visit(expr: PrefixExpression): IExpression {
override fun visit(expr: PrefixExpression): Expression {
return try {
super.visit(expr)
val subexpr = expr.expression
if (subexpr is LiteralValue) {
if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true)
return when {
expr.operator == "+" -> subexpr
expr.operator == "-" -> when {
subexpr.asIntegerValue!= null -> {
subexpr.type in IntegerDatatypes -> {
optimizationsDone++
LiteralValue.optimalNumeric(-subexpr.asIntegerValue, subexpr.position)
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
}
subexpr.floatvalue != null -> {
subexpr.type == DataType.FLOAT -> {
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)
}
expr.operator == "~" -> when {
subexpr.asIntegerValue != null -> {
subexpr.type in IntegerDatatypes -> {
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)
}
expr.operator == "not" -> when {
subexpr.asIntegerValue != null -> {
optimizationsDone++
LiteralValue.fromBoolean(subexpr.asIntegerValue == 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)
expr.operator == "not" -> {
optimizationsDone++
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
}
else -> throw ExpressionError(expr.operator, subexpr.position)
}
@@ -313,9 +315,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
* (X / c1) * c2 -> X / (c2/c1)
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun visit(expr: BinaryExpression): IExpression {
override fun visit(expr: BinaryExpression): Expression {
return try {
super.visit(expr)
if(expr.left is ReferenceLiteralValue || expr.right is ReferenceLiteralValue)
TODO("binexpr with reference litval")
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
@@ -336,12 +342,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
// const fold when both operands are a const
val evaluator = ConstExprEvaluator()
return when {
leftconst != null && rightconst != null -> {
optimizationsDone++
val evaluator = ConstExprEvaluator()
evaluator.evaluate(leftconst, expr.operator, rightconst)
}
else -> expr
}
} catch (ax: AstException) {
@@ -355,7 +362,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
subrightIsConst: Boolean): IExpression
subrightIsConst: Boolean): Expression
{
// @todo this implements only a small set of possible reorderings for now
if(expr.operator==subExpr.operator) {
@@ -542,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 newTo = rangeTo.cast(targetDt)
if (newFrom != null && newTo != null) {
val newStep: IExpression =
val newStep: Expression =
if (stepLiteral != null) (stepLiteral.cast(targetDt) ?: stepLiteral) else range.step
return RangeExpr(newFrom, newTo, newStep, range.position)
}
@@ -558,13 +565,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
// adjust the datatype of a range expression in for loops to the loop variable.
val resultStmt = super.visit(forLoop) as ForLoop
val iterableRange = resultStmt.iterable as? RangeExpr ?: return resultStmt
val rangeFrom = iterableRange.from as? LiteralValue
val rangeTo = iterableRange.to as? LiteralValue
val rangeFrom = iterableRange.from as? NumericLiteralValue
val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return resultStmt
val loopvar = resultStmt.loopVar?.targetVarDecl(program.namespace)
if(loopvar!=null) {
val stepLiteral = iterableRange.step as? LiteralValue
val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) {
DataType.UBYTE -> {
if(rangeFrom.type!= DataType.UBYTE) {
@@ -596,39 +603,46 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
return resultStmt
}
override fun visit(literalValue: LiteralValue): LiteralValue {
val litval = super.visit(literalValue)
if(litval.isString) {
// intern the string; move it into the heap
if(litval.strvalue!!.length !in 1..255)
addError(ExpressionError("string literal length must be between 1 and 255", litval.position))
else {
litval.addToHeap(program.heap) // TODO: we don't know the actual string type yet, STR != STR_S etc...
override fun visit(refLiteral: ReferenceLiteralValue): Expression {
val litval = super.visit(refLiteral)
if(litval is ReferenceLiteralValue) {
if (litval.isString) {
// intern the string; move it into the heap
if (litval.str!!.length !in 1..255)
addError(ExpressionError("string literal length must be between 1 and 255", litval.position))
else {
litval.addToHeap(program.heap) // TODO: we don't know the actual string type yet, STR != STR_S etc...
}
} else if (litval.isArray) {
// first, adjust the array datatype
val litval2 = adjustArrayValDatatype(litval)
litval2.addToHeap(program.heap)
return litval2
}
} else if(litval.arrayvalue!=null) {
// first, adjust the array datatype
val litval2 = adjustArrayValDatatype(litval)
litval2.addToHeap(program.heap)
return litval2
}
return litval
}
private fun adjustArrayValDatatype(litval: LiteralValue): LiteralValue {
val array = litval.arrayvalue!!
val typesInArray = array.mapNotNull { it.inferType(program) }.toSet()
private fun adjustArrayValDatatype(litval: ReferenceLiteralValue): ReferenceLiteralValue {
if(litval.array==null) {
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 =
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.WORD in typesInArray -> DataType.ARRAY_W
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) {
addError(ExpressionError("array literal can only consist of constant primitive numerical values or memory pointers", litval.position))
return litval
} else {
val integerArray = array.map { it.constValue(program)!!.asIntegerValue!! }
val integerArray = litval.array.map { it.constValue(program)!!.number.toInt() }
val maxValue = integerArray.max()!!
val minValue = integerArray.min()!!
if (minValue >= 0) {
@@ -649,70 +663,69 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
if(arrayDt!=litval.type) {
return LiteralValue(arrayDt, arrayvalue = litval.arrayvalue, position = litval.position)
return ReferenceLiteralValue(arrayDt, array = litval.array, position = litval.position)
}
return litval
}
override fun visit(assignment: Assignment): IStatement {
override fun visit(assignment: Assignment): Statement {
super.visit(assignment)
val lv = assignment.value as? LiteralValue
val lv = assignment.value as? NumericLiteralValue
if(lv!=null) {
// see if we can promote/convert a literal value to the required datatype
when(assignment.target.inferType(program, assignment)) {
DataType.UWORD -> {
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
if(lv.type== DataType.UBYTE)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position)
else if(lv.type== DataType.BYTE && lv.bytevalue!!>=0)
assignment.value = LiteralValue(DataType.UWORD, wordvalue = lv.asIntegerValue, position = lv.position)
else if(lv.type== DataType.WORD && lv.wordvalue!!>=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.BYTE && lv.number.toInt()>=0)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.WORD && lv.number.toInt()>=0)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!!
val d = lv.number.toDouble()
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 -> {
// we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255,
if(lv.type== DataType.UWORD && lv.wordvalue!! <= 255)
assignment.value = LiteralValue(DataType.UBYTE, lv.wordvalue.toShort(), position = lv.position)
else if(lv.type== DataType.BYTE && lv.bytevalue!! >=0)
assignment.value = LiteralValue(DataType.UBYTE, lv.bytevalue.toShort(), position = lv.position)
if(lv.type== DataType.UWORD && lv.number.toInt() <= 255)
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.BYTE && lv.number.toInt() >=0)
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!!
val d = lv.number.toDouble()
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 -> {
// we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127
if(lv.type== DataType.UWORD && lv.wordvalue!! <= 127)
assignment.value = LiteralValue(DataType.BYTE, lv.wordvalue.toShort(), position = lv.position)
else if(lv.type== DataType.UBYTE && lv.bytevalue!! <= 127)
assignment.value = LiteralValue(DataType.BYTE, lv.bytevalue, position = lv.position)
if(lv.type== DataType.UWORD && lv.number.toInt() <= 127)
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.UBYTE && lv.number.toInt() <= 127)
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!!
val d = lv.number.toDouble()
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 -> {
// 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)
assignment.value = LiteralValue(DataType.WORD, wordvalue = lv.bytevalue!!.toInt(), position = lv.position)
else if(lv.type== DataType.UWORD && lv.wordvalue!! <= 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.UWORD && lv.number.toInt() <= 32767)
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.floatvalue!!
val d = lv.number.toDouble()
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 -> {
if(lv.isNumeric)
assignment.value = LiteralValue(DataType.FLOAT, floatvalue = lv.asNumericValue?.toDouble(), position = lv.position)
assignment.value = NumericLiteralValue(DataType.FLOAT, lv.number.toDouble(), lv.position)
}
else -> {}
}
+1 -2
View File
@@ -1,8 +1,7 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.statements.NopStatement
import prog8.parser.ParsingFailedError
@@ -1,6 +1,6 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.DataType
import prog8.ast.base.IntegerDatatypes
@@ -8,6 +8,7 @@ import prog8.ast.base.NumericDatatypes
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.Assignment
import prog8.ast.statements.Statement
import kotlin.math.abs
import kotlin.math.log2
@@ -21,13 +22,13 @@ import kotlin.math.log2
internal class SimplifyExpressions(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
override fun visit(assignment: Assignment): IStatement {
override fun visit(assignment: Assignment): Statement {
if (assignment.aug_op != null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
return super.visit(assignment)
}
override fun visit(memread: DirectMemoryRead): IExpression {
override fun visit(memread: DirectMemoryRead): Expression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
@@ -35,11 +36,11 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return super.visit(memread)
}
override fun visit(typecast: TypecastExpression): IExpression {
override fun visit(typecast: TypecastExpression): Expression {
var tc = typecast
// try to statically convert a literal value into one of the desired type
val literal = tc.expression as? LiteralValue
val literal = tc.expression as? NumericLiteralValue
if(literal!=null) {
val newLiteral = literal.cast(tc.type)
if(newLiteral!=null && newLiteral!==literal) {
@@ -81,7 +82,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
}
}
override fun visit(expr: PrefixExpression): IExpression {
override fun visit(expr: PrefixExpression): Expression {
if (expr.operator == "+") {
// +X --> X
optimizationsDone++
@@ -128,12 +129,12 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return super.visit(expr)
}
override fun visit(expr: BinaryExpression): IExpression {
override fun visit(expr: BinaryExpression): Expression {
super.visit(expr)
val leftVal = expr.left.constValue(program)
val rightVal = expr.right.constValue(program)
val constTrue = LiteralValue.fromBoolean(true, expr.position)
val constFalse = LiteralValue.fromBoolean(false, expr.position)
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
@@ -174,10 +175,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// X + (-value) --> X - value
if (expr.operator == "+" && rightVal != null) {
val rv = rightVal.asNumericValue?.toDouble()
if (rv != null && rv < 0.0) {
val rv = rightVal.number.toDouble()
if (rv < 0.0) {
expr.operator = "-"
expr.right = LiteralValue.fromNumber(-rv, rightVal.type, rightVal.position)
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
optimizationsDone++
return expr
}
@@ -185,10 +186,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// (-value) + X --> X - value
if (expr.operator == "+" && leftVal != null) {
val lv = leftVal.asNumericValue?.toDouble()
if (lv != null && lv < 0.0) {
val lv = leftVal.number.toDouble()
if (lv < 0.0) {
expr.operator = "-"
expr.right = LiteralValue.fromNumber(-lv, leftVal.type, leftVal.position)
expr.right = NumericLiteralValue(leftVal.type, -lv, leftVal.position)
optimizationsDone++
return expr
}
@@ -204,10 +205,10 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// X - (-value) --> X + value
if (expr.operator == "-" && rightVal != null) {
val rv = rightVal.asNumericValue?.toDouble()
if (rv != null && rv < 0.0) {
val rv = rightVal.number.toDouble()
if (rv < 0.0) {
expr.operator = "+"
expr.right = LiteralValue.fromNumber(-rv, rightVal.type, rightVal.position)
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
optimizationsDone++
return expr
}
@@ -225,7 +226,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.right
val y = determineY(x, leftBinExpr)
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)
}
} else {
@@ -234,7 +235,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.right
val y = determineY(x, leftBinExpr)
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)
}
}
@@ -246,7 +247,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.left
val y = determineY(x, rightBinExpr)
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)
}
} else {
@@ -255,7 +256,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.left
val y = determineY(x, rightBinExpr)
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)
}
}
@@ -341,7 +342,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr
}
private fun determineY(x: IExpression, subBinExpr: BinaryExpression): IExpression? {
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when {
subBinExpr.left isSameAs x -> subBinExpr.right
subBinExpr.right isSameAs x -> subBinExpr.left
@@ -350,58 +351,58 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
}
private fun adjustDatatypes(expr: BinaryExpression,
leftConstVal: LiteralValue?, leftDt: DataType,
rightConstVal: LiteralValue?, rightDt: DataType): Boolean {
leftConstVal: NumericLiteralValue?, leftDt: DataType,
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)
return Pair(false, value)
when(value.type) {
DataType.UBYTE -> {
if (targetDt == DataType.BYTE) {
if(value.bytevalue!! < 127)
return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position))
if(value.number.toInt() < 127)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
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 -> {
if (targetDt == DataType.UBYTE) {
if(value.bytevalue!! >= 0)
return Pair(true, LiteralValue(targetDt, value.bytevalue, position = value.position))
if(value.number.toInt() >= 0)
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
else if (targetDt == DataType.UWORD) {
if(value.bytevalue!! >= 0)
return Pair(true, LiteralValue(targetDt, wordvalue = value.bytevalue.toInt(), position = value.position))
if(value.number.toInt() >= 0)
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 -> {
if (targetDt == DataType.UBYTE) {
if(value.wordvalue!! <= 255)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
if(value.number.toInt() <= 255)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.BYTE) {
if(value.wordvalue!! <= 127)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
if(value.number.toInt() <= 127)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.WORD) {
if(value.wordvalue!! <= 32767)
return Pair(true, LiteralValue(targetDt, wordvalue = value.wordvalue, position = value.position))
if(value.number.toInt() <= 32767)
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
}
DataType.WORD -> {
if (targetDt == DataType.UBYTE) {
if(value.wordvalue!! in 0..255)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
if(value.number.toInt() in 0..255)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.BYTE) {
if(value.wordvalue!! in -128..127)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
if(value.number.toInt() in -128..127)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.UWORD) {
if(value.wordvalue!! >= 0)
return Pair(true, LiteralValue(targetDt, value.wordvalue.toShort(), position = value.position))
if(value.number.toInt() >= 0)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
}
else -> {}
@@ -434,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) {
// swap left and right so that right is always the constant
val tmp = expr.left
@@ -448,15 +449,15 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
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)
return pexpr
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: LiteralValue = rightVal
when(rightConst.asNumericValue?.toDouble()) {
val rightConst: NumericLiteralValue = rightVal
when(rightConst.number.toDouble()) {
0.0 -> {
// left
optimizationsDone++
@@ -469,14 +470,14 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
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)
return expr
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: LiteralValue = rightVal
when(rightConst.asNumericValue?.toDouble()) {
val rightConst: NumericLiteralValue = rightVal
when(rightConst.number.toDouble()) {
0.0 -> {
// left
optimizationsDone++
@@ -486,7 +487,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
}
if(leftVal!=null) {
// left value is a constant, see if we can optimize
when(leftVal.asNumericValue?.toDouble()) {
when(leftVal.number.toDouble()) {
0.0 -> {
// -right
optimizationsDone++
@@ -498,38 +499,38 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
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)
return expr
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: LiteralValue = rightVal
when(rightConst.asNumericValue?.toDouble()) {
val rightConst: NumericLiteralValue = rightVal
when(rightConst.number.toDouble()) {
-3.0 -> {
// -1/(left*left*left)
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),
expr.position)
}
-2.0 -> {
// -1/(left*left)
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),
expr.position)
}
-1.0 -> {
// -1/left
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)
}
0.0 -> {
// 1
optimizationsDone++
return LiteralValue.fromNumber(1, rightConst.type, expr.position)
return NumericLiteralValue(rightConst.type, 1, expr.position)
}
0.5 -> {
// sqrt(left)
@@ -555,21 +556,21 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
}
if(leftVal!=null) {
// left value is a constant, see if we can optimize
when(leftVal.asNumericValue?.toDouble()) {
when(leftVal.number.toDouble()) {
-1.0 -> {
// -1
optimizationsDone++
return LiteralValue(DataType.FLOAT, floatvalue = -1.0, position = expr.position)
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
}
0.0 -> {
// 0
optimizationsDone++
return LiteralValue.fromNumber(0, leftVal.type, expr.position)
return NumericLiteralValue(leftVal.type, 0, expr.position)
}
1.0 -> {
//1
optimizationsDone++
return LiteralValue.fromNumber(1, leftVal.type, expr.position)
return NumericLiteralValue(leftVal.type, 1, expr.position)
}
}
@@ -578,22 +579,22 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
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)
return expr
// simplify assignments A = B <operator> C
val cv = rightVal?.asIntegerValue?.toDouble()
val cv = rightVal?.number?.toInt()?.toDouble()
when(expr.operator) {
"%" -> {
if (cv == 1.0) {
optimizationsDone++
return LiteralValue.fromNumber(0, expr.inferType(program)!!, expr.position)
return NumericLiteralValue(expr.inferType(program)!!, 0, expr.position)
} else if (cv == 2.0) {
optimizationsDone++
expr.operator = "&"
expr.right = LiteralValue.optimalInteger(1, expr.position)
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
return expr
}
}
@@ -602,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)
return expr
@@ -610,8 +611,8 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: LiteralValue = rightVal
val cv = rightConst.asNumericValue?.toDouble()
val rightConst: NumericLiteralValue = rightVal
val cv = rightConst.number.toDouble()
val leftDt = expr.left.inferType(program)
when(cv) {
-1.0 -> {
@@ -633,7 +634,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// divided by a power of two => shift right
optimizationsDone++
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 -> {
@@ -641,32 +642,32 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// divided by a negative power of two => negate, then shift right
optimizationsDone++
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(abs(rightConst.asNumericValue!!.toDouble()) >= 256.0) {
if(abs(rightConst.number.toDouble()) >= 256.0) {
optimizationsDone++
return LiteralValue(DataType.UBYTE, 0, position = expr.position)
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
}
}
else if (leftDt == DataType.UWORD) {
if(abs(rightConst.asNumericValue!!.toDouble()) >= 65536.0) {
if(abs(rightConst.number.toDouble()) >= 65536.0) {
optimizationsDone++
return LiteralValue(DataType.UBYTE, 0, position = expr.position)
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
}
}
}
if(leftVal!=null) {
// left value is a constant, see if we can optimize
when(leftVal.asNumericValue?.toDouble()) {
when(leftVal.number.toDouble()) {
0.0 -> {
// 0
optimizationsDone++
return LiteralValue.fromNumber(0, leftVal.type, expr.position)
return NumericLiteralValue(leftVal.type, 0, expr.position)
}
}
}
@@ -674,16 +675,16 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
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)
return pexpr
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val leftValue: IExpression = expr.left
val rightConst: LiteralValue = rightVal
when(val cv = rightConst.asNumericValue?.toDouble()) {
val leftValue: Expression = expr.left
val rightConst: NumericLiteralValue = rightVal
when(val cv = rightConst.number.toDouble()) {
-1.0 -> {
// -left
optimizationsDone++
@@ -692,7 +693,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
0.0 -> {
// 0
optimizationsDone++
return LiteralValue.fromNumber(0, rightConst.type, expr.position)
return NumericLiteralValue(rightConst.type, 0, expr.position)
}
1.0 -> {
// left
@@ -704,7 +705,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// times a power of two => shift left
optimizationsDone++
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 -> {
@@ -712,7 +713,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
// times a negative power of two => negate, then shift left
optimizationsDone++
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)
}
}
}
@@ -59,7 +59,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
val scope = caller.definingScope()
if(sub.calledBy.count { it.definingScope()===scope } > 1)
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
if(sub.parameters.isEmpty() && sub.returntypes.isEmpty()) {
@@ -136,7 +136,7 @@ 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 (block.containsNoCodeNorVars()) {
optimizationsDone++
@@ -154,7 +154,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return super.visit(block)
}
override fun visit(subroutine: Subroutine): IStatement {
override fun visit(subroutine: Subroutine): Statement {
super.visit(subroutine)
val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null && !forceOutput) {
@@ -191,7 +191,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return subroutine
}
override fun visit(decl: VarDecl): IStatement {
override fun visit(decl: VarDecl): Statement {
val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) {
if(decl.type == VarDeclType.VAR)
@@ -203,13 +203,13 @@ internal class StatementOptimizer(private val program: Program, private val opti
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
val linesToRemove = mutableListOf<Int>()
var previousAssignmentLine: Int? = null
for (i in 0 until statements.size) {
val stmt = statements[i] as? Assignment
if (stmt != null && stmt.value is LiteralValue) {
if (stmt != null && stmt.value is NumericLiteralValue) {
if (previousAssignmentLine == null) {
previousAssignmentLine = i
continue
@@ -228,7 +228,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
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) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
@@ -241,7 +241,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
// 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")
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference
if(stringVar!=null) {
@@ -250,7 +250,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(string.length==1) {
val petscii = Petscii.encodePetscii(string, true)[0]
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)
optimizationsDone++
return functionCallStatement
@@ -258,9 +258,9 @@ internal class StatementOptimizer(private val program: Program, private val opti
val petscii = Petscii.encodePetscii(string, true)
val scope = AnonymousScope(mutableListOf(), functionCallStatement.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),
mutableListOf(LiteralValue.optimalInteger(petscii[1], functionCallStatement.position)), functionCallStatement.position))
mutableListOf(NumericLiteralValue.optimalInteger(petscii[1].toInt(), functionCallStatement.position)), functionCallStatement.position))
optimizationsDone++
return scope
}
@@ -286,7 +286,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall): IExpression {
override fun visit(functionCall: FunctionCall): Expression {
// if it calls a subroutine,
// 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
@@ -306,7 +306,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return super.visit(functionCall)
}
override fun visit(ifStatement: IfStatement): IStatement {
override fun visit(ifStatement: IfStatement): Statement {
super.visit(ifStatement)
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
@@ -340,7 +340,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return ifStatement
}
override fun visit(forLoop: ForLoop): IStatement {
override fun visit(forLoop: ForLoop): Statement {
super.visit(forLoop)
if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop
@@ -370,7 +370,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return forLoop
}
override fun visit(whileLoop: WhileLoop): IStatement {
override fun visit(whileLoop: WhileLoop): Statement {
super.visit(whileLoop)
val constvalue = whileLoop.condition.constValue(program)
if(constvalue!=null) {
@@ -396,7 +396,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return whileLoop
}
override fun visit(repeatLoop: RepeatLoop): IStatement {
override fun visit(repeatLoop: RepeatLoop): Statement {
super.visit(repeatLoop)
val constvalue = repeatLoop.untilCondition.constValue(program)
if(constvalue!=null) {
@@ -426,7 +426,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return repeatLoop
}
override fun visit(whenStatement: WhenStatement): IStatement {
override fun visit(whenStatement: WhenStatement): Statement {
val choices = whenStatement.choices.toList()
for(choice in choices) {
if(choice.statements.containsNoCodeNorVars())
@@ -441,12 +441,12 @@ internal class StatementOptimizer(private val program: Program, private val opti
{
var count=0
override fun visit(breakStmt: Break): IStatement {
override fun visit(breakStmt: Break): Statement {
count++
return super.visit(breakStmt)
}
override fun visit(contStmt: Continue): IStatement {
override fun visit(contStmt: Continue): Statement {
count++
return super.visit(contStmt)
}
@@ -460,7 +460,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return s.count > 0
}
override fun visit(jump: Jump): IStatement {
override fun visit(jump: Jump): Statement {
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
if(subroutine!=null) {
// if the first instruction in the subroutine is another jump, shortcut this one
@@ -484,7 +484,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return jump
}
override fun visit(assignment: Assignment): IStatement {
override fun visit(assignment: Assignment): Statement {
if(assignment.aug_op!=null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
@@ -495,12 +495,12 @@ internal class StatementOptimizer(private val program: Program, private val opti
val targetDt = assignment.target.inferType(program, assignment)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.asNumericValue?.toDouble()
val cv = bexpr.right.constValue(program)?.number?.toDouble()
if (cv == null) {
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
bexpr.operator = "*"
bexpr.right = LiteralValue.optimalInteger(2, assignment.value.position)
bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
optimizationsDone++
return assignment
}
@@ -569,7 +569,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
}
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.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)
optimizationsDone++
} else {
@@ -591,7 +591,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
}
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.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)
optimizationsDone++
} else {
@@ -615,7 +615,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return super.visit(assignment)
}
override fun visit(scope: AnonymousScope): IStatement {
override fun visit(scope: AnonymousScope): Statement {
val linesToRemove = deduplicateAssignments(scope.statements)
if(linesToRemove.isNotEmpty()) {
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
@@ -623,7 +623,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
return super.visit(scope)
}
override fun visit(label: Label): IStatement {
override fun visit(label: Label): Statement {
// remove duplicate labels
val stmts = label.definingScope().statements
val startIdx = stmts.indexOf(label)
@@ -644,7 +644,7 @@ internal class FlattenAnonymousScopesAndRemoveNops: IAstVisitor {
super.visit(program)
for(scope in scopesToFlatten.reversed()) {
val namescope = scope.parent as INameScope
val idx = namescope.statements.indexOf(scope as IStatement)
val idx = namescope.statements.indexOf(scope as Statement)
if(idx>=0) {
val nop = NopStatement.insteadOf(namescope.statements[idx])
nop.parent = namescope as Node
+2 -1
View File
@@ -1,7 +1,8 @@
package prog8.parser
import org.antlr.v4.runtime.*
import prog8.ast.*
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.antlr.toAst
import prog8.ast.base.Position
import prog8.ast.base.SyntaxError
+11 -22
View File
@@ -1,9 +1,8 @@
package prog8.vm
import prog8.ast.base.*
import prog8.ast.expressions.LiteralValue
import prog8.ast.statements.StructDecl
import prog8.ast.statements.ZeropageWish
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii
import kotlin.math.abs
@@ -11,7 +10,7 @@ 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)
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
*/
@@ -24,16 +23,19 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
val asBoolean: Boolean
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) {
in NumericDatatypes -> RuntimeValue(literalValue.type, num = literalValue.asNumericValue!!)
in StringDatatypes -> from(literalValue.heapId!!, heap)
in ArrayDatatypes -> from(literalValue.heapId!!, heap)
in StringDatatypes -> fromHeapId(literalValue.heapId!!, heap)
in ArrayDatatypes -> fromHeapId(literalValue.heapId!!, heap)
else -> throw IllegalArgumentException("weird source value $literalValue")
}
}
fun from(heapId: Int, heap: HeapValues): RuntimeValue {
fun fromHeapId(heapId: Int, heap: HeapValues): RuntimeValue {
val value = heap.get(heapId)
return when {
value.type in StringDatatypes ->
@@ -115,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 -> throw IllegalArgumentException("weird source value $this")
}
}
override fun toString(): String {
return when(type) {
DataType.UBYTE -> "ub:%02x".format(byteval)
+33 -32
View File
@@ -1,15 +1,16 @@
package prog8.vm.astvm
import prog8.ast.*
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.base.initvarsSubName
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.LiteralValue
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange
import prog8.compiler.target.c64.Petscii
import java.awt.EventQueue
import java.io.CharConversionException
import java.util.*
@@ -32,31 +33,31 @@ class StatusFlags {
var negative: Boolean = false
var irqd: Boolean = false
private fun setFlags(value: LiteralValue?) {
private fun setFlags(value: NumericLiteralValue?) {
if (value != null) {
when (value.type) {
DataType.UBYTE -> {
val v = value.bytevalue!!.toInt()
val v = value.number.toInt()
negative = v > 127
zero = v == 0
}
DataType.BYTE -> {
val v = value.bytevalue!!.toInt()
val v = value.number.toInt()
negative = v < 0
zero = v == 0
}
DataType.UWORD -> {
val v = value.wordvalue!!
val v = value.number.toInt()
negative = v > 32767
zero = v == 0
}
DataType.WORD -> {
val v = value.wordvalue!!
val v = value.number.toInt()
negative = v < 0
zero = v == 0
}
DataType.FLOAT -> {
val flt = value.floatvalue!!
val flt = value.number.toDouble()
negative = flt < 0.0
zero = flt == 0.0
}
@@ -312,7 +313,7 @@ class AstVm(val program: Program) {
}
private fun executeStatement(sub: INameScope, stmt: IStatement) {
private fun executeStatement(sub: INameScope, stmt: Statement) {
instructionCounter++
if (instructionCounter % 200 == 0)
Thread.sleep(1)
@@ -387,7 +388,7 @@ class AstVm(val program: Program) {
runtimeVariables.set(identScope, ident.name, value)
}
VarDeclType.MEMORY -> {
val addr=ident.value!!.constValue(program)!!.asIntegerValue!!
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
@@ -413,9 +414,9 @@ class AstVm(val program: Program) {
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())
when {
stmt.operator == "++" -> value=value.inc()
stmt.operator == "--" -> value=value.dec()
value = when {
stmt.operator == "++" -> value.inc()
stmt.operator == "--" -> value.dec()
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
arrayvalue.array[index] = value.numericValue()
@@ -518,7 +519,7 @@ class AstVm(val program: Program) {
break
} else {
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) {
executeAnonymousScope(choice.statements)
break
@@ -543,7 +544,7 @@ class AstVm(val program: Program) {
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 {
target.identifier != null -> {
val decl = contextStmt.definingScope().lookup(target.identifier.nameInSource, contextStmt) as? VarDecl
@@ -613,7 +614,7 @@ class AstVm(val program: Program) {
}
}
else {
val address = (vardecl.value as LiteralValue).asIntegerValue!!
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) {
@@ -640,7 +641,7 @@ class AstVm(val program: Program) {
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>): RuntimeValue? {
var result: RuntimeValue? = null
@@ -834,34 +835,34 @@ class AstVm(val program: Program) {
}
}
"max" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(args[0].type, numbers.max())
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.max())
}
"min" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(args[0].type, numbers.min())
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.min())
}
"avg" -> {
val numbers = args.map { it.numericValue().toDouble() }
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.FLOAT, numbers.average())
}
"sum" -> {
val sum = args.map { it.numericValue().toDouble() }.sum()
val sum = args.single().array!!.map { it.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)
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.map { it.numericValue().toDouble() }
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
}
"all" -> {
val numbers = args.map { it.numericValue().toDouble() }
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
}
"swap" ->
-18
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))
}
}
+8 -5
View File
@@ -1,6 +1,6 @@
package prog8.vm.astvm
import prog8.ast.*
import prog8.ast.Program
import prog8.ast.base.ArrayElementTypes
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
@@ -24,14 +24,17 @@ class EvalContext(val program: Program, val mem: Memory, val statusflags: Status
val performBuiltinFunction: BuiltinfunctionCaller,
val executeSubroutine: SubroutineCaller)
fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
val constval = expr.constValue(ctx.program)
if(constval!=null)
return RuntimeValue.from(constval, ctx.program.heap)
return RuntimeValue.fromLv(constval)
when(expr) {
is LiteralValue -> {
return RuntimeValue.from(expr, ctx.program.heap)
is NumericLiteralValue -> {
return RuntimeValue.fromLv(expr)
}
is ReferenceLiteralValue -> {
return RuntimeValue.fromLv(expr, ctx.program.heap)
}
is PrefixExpression -> {
return when(expr.operator) {
+1 -1
View File
@@ -18,7 +18,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0
private var cursorY: Int=0
val keyboardBuffer: Deque<Char> = LinkedList<Char>()
val keyboardBuffer: Deque<Char> = LinkedList()
init {
val size = Dimension(image.width * SCALING, image.height * SCALING)
+21 -11
View File
@@ -1,9 +1,14 @@
package prog8.vm.astvm
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.LiteralValue
import prog8.ast.Program
import prog8.ast.base.DataType
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.statements.Statement
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
@@ -20,11 +25,11 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
val globalpos = Position("<<global>>", 0, 0, 0)
val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.A.name, null,
LiteralValue.optimalInteger(0, globalpos), isArray = false, hiddenButDoNotRemove = true, position = globalpos)
NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.X.name, null,
LiteralValue.optimalInteger(255, globalpos), isArray = false, hiddenButDoNotRemove = true, position = globalpos)
NumericLiteralValue.optimalInteger(255, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.Y.name, null,
LiteralValue.optimalInteger(0, globalpos), isArray = false, hiddenButDoNotRemove = true, position = globalpos)
NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
vdA.linkParents(program.namespace)
vdX.linkParents(program.namespace)
vdY.linkParents(program.namespace)
@@ -35,19 +40,24 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
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) {
// we can assume the value in the vardecl already has been converted into a constant LiteralValue here.
VarDeclType.VAR -> {
if(decl.datatype!=DataType.STRUCT) {
val value = RuntimeValue.from(decl.value as LiteralValue, heap)
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)
}
}
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 -> {
// consts should have been const-folded away
@@ -57,7 +67,7 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
return super.visit(decl)
}
// override fun accept(assignment: Assignment): IStatement {
// override fun accept(assignment: Assignment): Statement {
// if(assignment is VariableInitializationAssignment) {
// println("INIT VAR $assignment")
// }
+12 -9
View File
@@ -4,10 +4,13 @@ import prog8.ast.antlr.unescape
import prog8.ast.base.*
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference
import prog8.vm.RuntimeValue
import prog8.compiler.HeapValues
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.util.*
import java.util.regex.Pattern
@@ -226,18 +229,18 @@ class Program (val name: String,
if(valueStr[0] !='"' && ':' !in valueStr)
throw VmExecutionException("missing value type character")
val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).toShort(16))
DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).toShort(16))
DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).toInt(16))
DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).toInt(16))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).toDouble())
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).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
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).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).substringBefore(' ').toDouble())// TODO process ZP and struct info?
in StringDatatypes -> {
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")
else if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid string value, should be a heap reference")
else {
val heapId = valueStr.substring(5).toInt()
val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
RuntimeValue(type, heapId = heapId)
}
}
@@ -245,7 +248,7 @@ class Program (val name: String,
if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid array value, should be a heap reference")
else {
val heapId = valueStr.substring(5).toInt()
val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
RuntimeValue(type, heapId = heapId)
}
}
+4 -8
View File
@@ -1,19 +1,15 @@
package prog8.vm.stackvm
import prog8.ast.base.DataType
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.ast.base.*
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.Petscii
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.PrintStream
import java.util.*
+91 -78
View File
@@ -3,24 +3,32 @@ package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.expressions.LiteralValue
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
}
private fun sameValueAndType(rv1: ReferenceLiteralValue, rv2: ReferenceLiteralValue): Boolean {
return rv1.type==rv2.type && rv1==rv2
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestParserLiteralValue {
class TestParserNumericLiteralValue {
private val dummyPos = Position("test", 0, 0, 0)
@Test
fun testIdentity() {
val v = LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos)
val v = NumericLiteralValue(DataType.UWORD, 12345, dummyPos)
assertEquals(v, v)
assertFalse(v != v)
assertTrue(v <= v)
@@ -28,104 +36,109 @@ class TestParserLiteralValue {
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
fun testEqualsAndNotEquals() {
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos))
assertEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos))
assertEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos))
assertEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos))
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos))
assertEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos))
assertEquals(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos))
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos))
assertEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos))
assertEquals(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertEquals(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, 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)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 100, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos), LiteralValue(DataType.UBYTE, 254, position = dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12345.0, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos), LiteralValue(DataType.UBYTE, 100, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 22239.0, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 22239, position = dummyPos)))
assertTrue(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos)))
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 100, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 254, dummyPos), NumericLiteralValue(DataType.UBYTE, 254, dummyPos)))
assertTrue(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12345, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12345.0, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos), NumericLiteralValue(DataType.UBYTE, 100, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 22239.0, dummyPos), NumericLiteralValue(DataType.UWORD, 22239, 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(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos))
assertNotEquals(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos))
assertNotEquals(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos))
assertNotEquals(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos))
assertNotEquals(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, 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(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 101, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UBYTE, 100, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 101.0, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 245, position = dummyPos), LiteralValue(DataType.UBYTE, 246, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 12346, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.UWORD, wordvalue = 12345, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 12346.0, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UBYTE, 9, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.UWORD, wordvalue = 9, position = dummyPos)))
assertFalse(sameValueAndType(LiteralValue(DataType.FLOAT, floatvalue = 9.99, position = dummyPos), LiteralValue(DataType.FLOAT, floatvalue = 9.0, position = dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UBYTE, 101, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.UWORD, 101, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UBYTE, 100, dummyPos), NumericLiteralValue(DataType.FLOAT, 101.0, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 245, dummyPos), NumericLiteralValue(DataType.UBYTE, 246, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.UWORD, 12346, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.UWORD, 12345, dummyPos), NumericLiteralValue(DataType.FLOAT, 12346.0, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UBYTE, 9, dummyPos)))
assertFalse(sameValueAndType(NumericLiteralValue(DataType.FLOAT, 9.99, dummyPos), NumericLiteralValue(DataType.UWORD, 9, 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)
val lvOneR = LiteralValue(DataType.UBYTE, 1, position = dummyPos)
val lvTwoR = LiteralValue(DataType.UBYTE, 2, position = dummyPos)
val lvThreeR = LiteralValue(DataType.UBYTE, 3, position = dummyPos)
val lvFour= LiteralValue(DataType.UBYTE, 4, position = dummyPos)
val lv1 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos)
val lv2 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos)
val lv3 = LiteralValue(DataType.ARRAY_UB, arrayvalue = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos)
}
@Test
fun testEqualsRef() {
assertTrue(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos)))
assertFalse(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "bye", position = dummyPos)))
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, 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)
assertNotEquals(lv1, lv3)
}
@Test
fun testGreaterThan(){
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) > LiteralValue(DataType.UBYTE, 99, position = dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos))
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 253, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 254, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) > LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) > LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) > NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) > NumericLiteralValue(DataType.UWORD, 254, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) >= LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) >= LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos))
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) >= NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) >= NumericLiteralValue(DataType.UWORD, 255, dummyPos))
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) >= NumericLiteralValue(DataType.FLOAT, 100.1, dummyPos))
}
@Test
fun testLessThan() {
assertTrue(LiteralValue(DataType.UBYTE, 100, position = dummyPos) < LiteralValue(DataType.UBYTE, 101, position = dummyPos))
assertTrue(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 255, position = dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.1, position = dummyPos))
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 101, dummyPos))
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 255, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
assertTrue(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
assertTrue(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertTrue(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 254, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) < LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) < LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos))
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) < NumericLiteralValue(DataType.UBYTE, 100, dummyPos))
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) < NumericLiteralValue(DataType.UWORD, 254, 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(LiteralValue(DataType.UWORD, wordvalue = 254, position = dummyPos) <= LiteralValue(DataType.UWORD, wordvalue = 253, position = dummyPos))
assertFalse(LiteralValue(DataType.FLOAT, floatvalue = 100.0, position = dummyPos) <= LiteralValue(DataType.FLOAT, floatvalue = 99.9, position = dummyPos))
assertFalse(NumericLiteralValue(DataType.UBYTE, 100, dummyPos) <= NumericLiteralValue(DataType.UBYTE, 99, dummyPos))
assertFalse(NumericLiteralValue(DataType.UWORD, 254, dummyPos) <= NumericLiteralValue(DataType.UWORD, 253, dummyPos))
assertFalse(NumericLiteralValue(DataType.FLOAT, 100.0, dummyPos) <= NumericLiteralValue(DataType.FLOAT, 99.9, dummyPos))
}
}
+1 -1
View File
@@ -8,10 +8,10 @@ import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.IterableDatatypes
import prog8.ast.base.WordDatatypes
import prog8.vm.RuntimeValue
import prog8.compiler.HeapValues
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.Opcode
import prog8.vm.RuntimeValue
import prog8.vm.stackvm.*
import kotlin.test.*
+9 -14
View File
@@ -7,14 +7,15 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.LiteralValue
import prog8.vm.RuntimeValue
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.compiler.*
import prog8.compiler.target.c64.MachineDefinition.Mflpt5
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.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import java.io.CharConversionException
import kotlin.test.*
@@ -340,8 +341,8 @@ class TestPetscii {
@Test
fun testLiteralValueComparisons() {
val ten = LiteralValue(DataType.UWORD, wordvalue = 10, position = Position("", 0, 0, 0))
val nine = LiteralValue(DataType.UBYTE, bytevalue = 9, position = Position("", 0, 0, 0))
val ten = NumericLiteralValue(DataType.UWORD, 10, Position("", 0, 0, 0))
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position("", 0, 0, 0))
assertEquals(ten, ten)
assertNotEquals(ten, nine)
assertFalse(ten != ten)
@@ -357,17 +358,11 @@ class TestPetscii {
assertTrue(ten <= ten)
assertFalse(ten < ten)
val abc = LiteralValue(DataType.STR, strvalue = "abc", position = Position("", 0, 0, 0))
val abd = LiteralValue(DataType.STR, strvalue = "abd", position = Position("", 0, 0, 0))
val abc = ReferenceLiteralValue(DataType.STR, str = "abc", position = Position("", 0, 0, 0))
val abd = ReferenceLiteralValue(DataType.STR, str = "abd", position = Position("", 0, 0, 0))
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
assertTrue(abc < abd)
assertTrue(abc <= abd)
assertFalse(abd <= abc)
assertTrue(abd >= abc)
assertTrue(abd > abc)
assertFalse(abc > abd)
}
@Test
+37 -3
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
Code example
------------
Code examples
-------------
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
:align: center
: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
------------------------------
+20 -11
View File
@@ -237,6 +237,7 @@ Arrays
^^^^^^
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[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]
@@ -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,
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::
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),
@@ -285,9 +293,9 @@ 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 useful
if you want to pass pointers around
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::
@@ -298,7 +306,7 @@ and then create a variable with it::
ubyte blue
}
Color rgb = [255,122,0]
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
@@ -354,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
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
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
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::
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
@@ -367,12 +382,6 @@ are generated)
this behavior may change in a future version so that subsequent runs always
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
-----
+7 -2
View File
@@ -215,7 +215,7 @@ Variable declarations
^^^^^^^^^^^^^^^^^^^^^
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
when selecting variables to be put into zeropage.
The syntax is::
@@ -231,10 +231,11 @@ Various examples::
str name = "my name is Irmen"
uword address = &counter
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
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
Color rgb = [1,255,0] ; a struct variable
Color rgb = {1,255,0} ; a struct variable with initial values
Data types
@@ -374,6 +375,10 @@ and other structs can not be part of a struct. Vice versa, a struct can not occu
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
---------
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.
-2
View File
@@ -3,8 +3,6 @@
%zeropage basicsafe
; 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 {
+14 -13
View File
@@ -53,20 +53,21 @@
~ irq {
sub irq() {
c64.EXTCOL--
; float up & wobble horizontally
for ubyte @zp i in 0 to 14 step 2 {
c64.SPXY[i+1]--
ubyte @zp r = rnd()
if r>200
c64.SPXY[i]++
else if r<40
c64.SPXY[i]--
sub irq() {
c64.EXTCOL--
; float up & wobble horizontally
for ubyte @zp i in 0 to 14 step 2 {
c64.SPXY[i+1]--
ubyte @zp r = rnd()
if r>200
c64.SPXY[i]++
else if r<40
c64.SPXY[i]--
}
c64.EXTCOL++
}
c64.EXTCOL++
}
}
+31
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')
}
}
+60 -29
View File
@@ -1,44 +1,75 @@
%import c64utils
%import c64flt
%zeropage basicsafe
%option enable_floats
~ main {
struct Color {
uword red
ubyte green
ubyte blue
}
str naam = "irmen"
word[] array = [1,2,3,4]
uword uw = $ab12
Color rgb = [255,128,0]
Color rgb2 = [111,222,33]
ubyte @zp zpvar=99
sub start() {
uword fake_address
word w1
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
fake_address = &naam
c64scr.print_uwhex(1, fake_address)
c64scr.print(", ")
ubyte[] blaat = 10 to 20
fake_address = &array
c64scr.print_uwhex(1, fake_address)
c64scr.print(", ")
for ubyte c in 'a' to 'z' {
c64.CHROUT(blaat[c])
}
fake_address = &rgb
c64scr.print_uwhex(1, fake_address)
c64scr.print("\n")
word q
q=w26
q+=w27
q+=w28
q+=w29
q+=w30
q+=w10
q+=w11
q+=w12
q+=w13
q+=w14
q+=w15
q+=w16
q+=w17
q+=w18
q+=w19
q+=w20
q+=w21
q+=w22
q+=w23
q+=w24
q+=w25
q+=w26
q+=w27
; @todo only works once reference types are actually references:
;str name2 = naam ; @todo name2 points to same str as naam
;str name2 = fake_address ; @todo fake_address hopefully points to a str
;Color colz = fake_address ; @todo fake_address hopefully points to a Color
return
}
}
+12 -2
View File
@@ -73,7 +73,9 @@ block: '~' identifier integerliteral? statement_block EOL ;
statement :
directive
| varinitializer
| structvarinitializer
| vardecl
| structvardecl
| constdecl
| memoryvardecl
| structdecl
@@ -110,10 +112,14 @@ directive :
directivearg : stringliteral | identifier | integerliteral ;
vardecl: (datatype | structname=identifier) ZEROPAGE? (arrayindex | ARRAYSIG) ? varname=identifier ;
vardecl: datatype ZEROPAGE? (arrayindex | ARRAYSIG) ? varname=identifier ;
structvardecl: structname=identifier varname=identifier ;
varinitializer : vardecl '=' expression ;
structvarinitializer : structvardecl '=' expression ;
constdecl: 'const' varinitializer ;
memoryvardecl: ADDRESS_OF varinitializer;
@@ -208,7 +214,9 @@ wordsuffix : '.w' ;
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 ;
@@ -216,6 +224,7 @@ charliteral : SINGLECHAR ;
floatliteral : FLOAT_NUMBER ;
literalvalue :
integerliteral
| booleanliteral
@@ -223,6 +232,7 @@ literalvalue :
| stringliteral
| charliteral
| floatliteral
| structliteral
;
inlineasm : '%asm' INLINEASMBLOCK;