From 6b89bb7be581feb033ecc34aa6d8528417f40af7 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 29 Sep 2018 17:33:59 +0200 Subject: [PATCH] strings and arrays are now stored in a 'heap' instead of in the value itself, to reflect how the target platform will store them --- compiler/examples/numbergame-c64.p8 | 76 ++ compiler/examples/numbergame.p8 | 124 ++- compiler/examples/stackvmtest.txt | 39 - compiler/examples/test.p8 | 48 +- compiler/src/prog8/CompilerMain.kt | 13 +- compiler/src/prog8/ast/AST.kt | 150 ++-- compiler/src/prog8/ast/AstChecker.kt | 107 +-- compiler/src/prog8/compiler/Compiler.kt | 149 ++-- .../src/prog8/functions/BuiltinFunctions.kt | 220 +++-- .../prog8/optimizing/ConstExprEvaluator.kt | 9 +- .../src/prog8/optimizing/ConstantFolding.kt | 63 +- compiler/src/prog8/optimizing/Extensions.kt | 19 +- .../prog8/optimizing/SimplifyExpressions.kt | 15 +- .../prog8/optimizing/StatementOptimizer.kt | 10 +- compiler/src/prog8/parser/prog8Lexer.java | 11 +- compiler/src/prog8/parser/prog8Parser.java | 14 +- compiler/src/prog8/stackvm/Program.kt | 328 ++++++++ compiler/src/prog8/stackvm/StackVm.kt | 792 ++---------------- compiler/src/prog8/stackvm/Value.kt | 373 +++++++++ compiler/test/StackVMOpcodeTests.kt | 223 ++--- compiler/test/ValueOperationsTests.kt | 17 +- 21 files changed, 1502 insertions(+), 1298 deletions(-) create mode 100644 compiler/examples/numbergame-c64.p8 delete mode 100644 compiler/examples/stackvmtest.txt create mode 100644 compiler/src/prog8/stackvm/Program.kt create mode 100644 compiler/src/prog8/stackvm/Value.kt diff --git a/compiler/examples/numbergame-c64.p8 b/compiler/examples/numbergame-c64.p8 new file mode 100644 index 000000000..a1eb6bbb4 --- /dev/null +++ b/compiler/examples/numbergame-c64.p8 @@ -0,0 +1,76 @@ +%output prg +%import c64lib +%import mathlib + +~ main { + sub start() -> () { + str name = "?" * 80 + str guess = "?" * 80 + byte secretnumber = 0 + byte attempts_left = 10 + memory word freadstr_arg = $22 ; argument for FREADSTR + + c64.init_system() + c64.VMCSB |= 2 ; activate lowercase charset + + ; greeting + c64scr.print_string("Enter your name: ") + Y = c64scr.input_chars(name) + c64.CHROUT("\n") + c64.CHROUT("\n") + c64scr.print_string("Hello, ") + c64scr.print_string(name) + c64.CHROUT(".") + c64.CHROUT("\n") + + ; create a secret random number from 1-100 + c64.RNDA(0) ; fac = rnd(0) + c64.MUL10() ; fac *= 10 + c64.MUL10() ; .. and now *100 + c64.FADDH() ; add 0.5.. + c64.FADDH() ; and again, so +1 total + AY = c64flt.GETADRAY() + secretnumber = A + ;A=math.randbyte() + ;A+=c64.RASTER + ;A-=c64.TIME_LO + ;X,secretnumber=math.divmod_bytes(A, 99) + + c64scr.print_string("I am thinking of a number from 1 to 100!You'll have to guess it!\n") + +ask_guess: + c64scr.print_string("\nYou have ") + c64scr.print_byte_decimal(attempts_left) + c64scr.print_string(" guess") + if(attempts_left>0) c64scr.print_string("es") + + c64scr.print_string(" left.\nWhat is your next guess? ") + Y = c64scr.input_chars(guess) + c64.CHROUT("\n") + freadstr_arg = guess + c64.FREADSTR(A) + AY = c64flt.GETADRAY() + if(A==secretnumber) { + c64scr.print_string("\nThat's my number, impressive!\n") + goto goodbye + } + c64scr.print_string("That is too ") + if(A > secretnumber) + c64scr.print_string("low!\n") + else + c64scr.print_string("high!\n") + + attempts_left-- + if(attempts_left>0) goto ask_guess + ; more efficient: if_nz goto ask_guess + + ; game over. + c64scr.print_string("\nToo bad! It was: ") + c64scr.print_byte_decimal(secretnumber) + c64.CHROUT("\n") + +goodbye: + c64scr.print_string("\nThanks for playing. Bye!\n") + return + } +} diff --git a/compiler/examples/numbergame.p8 b/compiler/examples/numbergame.p8 index a1eb6bbb4..e989e8b5a 100644 --- a/compiler/examples/numbergame.p8 +++ b/compiler/examples/numbergame.p8 @@ -1,76 +1,72 @@ %output prg -%import c64lib -%import mathlib ~ main { sub start() -> () { - str name = "?" * 80 - str guess = "?" * 80 + str name = "?" * 20 + str guess = "?" * 20 byte secretnumber = 0 byte attempts_left = 10 - memory word freadstr_arg = $22 ; argument for FREADSTR - - c64.init_system() - c64.VMCSB |= 2 ; activate lowercase charset ; greeting - c64scr.print_string("Enter your name: ") - Y = c64scr.input_chars(name) - c64.CHROUT("\n") - c64.CHROUT("\n") - c64scr.print_string("Hello, ") - c64scr.print_string(name) - c64.CHROUT(".") - c64.CHROUT("\n") + _vm_write_str("Enter your name: ") + ; _vm_input_str(name) + _vm_write_char($8d) + _vm_write_char($8d) + _vm_write_str("Hello, ") + _vm_write_str(name) + _vm_write_char($2e) + _vm_write_char($8d) - ; create a secret random number from 1-100 - c64.RNDA(0) ; fac = rnd(0) - c64.MUL10() ; fac *= 10 - c64.MUL10() ; .. and now *100 - c64.FADDH() ; add 0.5.. - c64.FADDH() ; and again, so +1 total - AY = c64flt.GETADRAY() - secretnumber = A - ;A=math.randbyte() - ;A+=c64.RASTER - ;A-=c64.TIME_LO - ;X,secretnumber=math.divmod_bytes(A, 99) - - c64scr.print_string("I am thinking of a number from 1 to 100!You'll have to guess it!\n") - -ask_guess: - c64scr.print_string("\nYou have ") - c64scr.print_byte_decimal(attempts_left) - c64scr.print_string(" guess") - if(attempts_left>0) c64scr.print_string("es") - - c64scr.print_string(" left.\nWhat is your next guess? ") - Y = c64scr.input_chars(guess) - c64.CHROUT("\n") - freadstr_arg = guess - c64.FREADSTR(A) - AY = c64flt.GETADRAY() - if(A==secretnumber) { - c64scr.print_string("\nThat's my number, impressive!\n") - goto goodbye - } - c64scr.print_string("That is too ") - if(A > secretnumber) - c64scr.print_string("low!\n") - else - c64scr.print_string("high!\n") - - attempts_left-- - if(attempts_left>0) goto ask_guess - ; more efficient: if_nz goto ask_guess - - ; game over. - c64scr.print_string("\nToo bad! It was: ") - c64scr.print_byte_decimal(secretnumber) - c64.CHROUT("\n") - -goodbye: - c64scr.print_string("\nThanks for playing. Bye!\n") return + +; ; create a secret random number from 1-100 +; c64.RNDA(0) ; fac = rnd(0) +; c64.MUL10() ; fac *= 10 +; c64.MUL10() ; .. and now *100 +; c64.FADDH() ; add 0.5.. +; c64.FADDH() ; and again, so +1 total +; AY = c64flt.GETADRAY() +; secretnumber = A +; ;A=math.randbyte() +; ;A+=c64.RASTER +; ;A-=c64.TIME_LO +; ;X,secretnumber=math.divmod_bytes(A, 99) +; +; c64scr.print_string("I am thinking of a number from 1 to 100!You'll have to guess it!\n") +; +;ask_guess: +; c64scr.print_string("\nYou have ") +; c64scr.print_byte_decimal(attempts_left) +; c64scr.print_string(" guess") +; if(attempts_left>0) c64scr.print_string("es") +; +; c64scr.print_string(" left.\nWhat is your next guess? ") +; Y = c64scr.input_chars(guess) +; c64.CHROUT("\n") +; freadstr_arg = guess +; c64.FREADSTR(A) +; AY = c64flt.GETADRAY() +; if(A==secretnumber) { +; c64scr.print_string("\nThat's my number, impressive!\n") +; goto goodbye +; } +; c64scr.print_string("That is too ") +; if(A > secretnumber) +; c64scr.print_string("low!\n") +; else +; c64scr.print_string("high!\n") +; +; attempts_left-- +; if(attempts_left>0) goto ask_guess +; ; more efficient: if_nz goto ask_guess +; +; ; game over. +; c64scr.print_string("\nToo bad! It was: ") +; c64scr.print_byte_decimal(secretnumber) +; c64.CHROUT("\n") +; +;goodbye: +; c64scr.print_string("\nThanks for playing. Bye!\n") +; return } } diff --git a/compiler/examples/stackvmtest.txt b/compiler/examples/stackvmtest.txt deleted file mode 100644 index 4a63961c8..000000000 --- a/compiler/examples/stackvmtest.txt +++ /dev/null @@ -1,39 +0,0 @@ -; source code for a stackvm program -; init memory bytes/words/strings -%memory -0400 01 02 03 04 05 06 07 08 09 22 33 44 55 66 -0500 1111 2222 3333 4444 -1000 "Hello world!\n" -%end_memory -; init global var table with bytes/words/floats/strings -%variables -main.var1 str "This is main.var1" -main.var2 byte aa -main.var3 word ea44 -main.var4 float 3.1415927 -main.textcolor byte 0 -input.prompt str "Enter a number: " -input.result word 0 -%end_variables -; instructions and labels -%instructions - nop - syscall WRITE_MEMSTR w:1000 -loop: - inc_var main.textcolor - syscall RANDOM - syscall RANDOM - push_var main.textcolor - syscall GFX_PIXEL -; syscall WRITE_VAR "input.prompt" -; push b:10 -; syscall INPUT_STR -; pop_var input.result -; syscall WRITE_VAR "input.result" -; push b:8d -; syscall WRITE_CHAR - jump loop -%end_instructions - - - diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index 2f600bc10..5a8db23a7 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -2,17 +2,45 @@ ~ main { sub start() -> () { + str msg1 = "abc" + str msg2 = "abc" + str msg3 = "abc123" + str_p msg4 = "abc" + byte[5] array1 = 0 + byte[5] array2 = 222 + byte[6] array3 = [1,2,3,4,5,66] + word[5] array4 = 333 + word[5] array5 = [1,2,3,4,59999] + word[5] array6 = [1,2,3,4,5] + word[5] array7 = [1,2,3,4,999+22/33] + byte[2,3] matrix1 = [1,2, 3,4, 5,6] + byte[2,3] matrix2 = [1,2, 3,4, 5,6] + byte[2,3] matrix3 = [11,22, 33,44, 55,66] + str message = "Calculating Mandelbrot Fractal..." + _vm_gfx_text(5, 5, 7, message) + _vm_gfx_text(5, 5, 7, "Calculating Mandelbrot Fractal...") - A = calcIt(12345, 99) - ;A = 99/5 + lsb(12345) - _vm_write_num(A) - _vm_write_char($8d) - return + A= len("abcdef") + + A= len([4,5,99/X]) + A= max([4,5,99]) + A= min([4,5,99]) + A= avg([4,5,99]) + A= sum([4,5,99]) + A= any([4,5,99]) + A= all([4,5,99]) + + A= len(msg3) + + float xx + + A= len(array3) + A= max(array3) + A= min(array3) + xx= avg(array3) + A= sum(array3) + A= any(array3) + A= all(array3) } - -sub calcIt(length: XY, control: A) -> (Y) { - - return control/5 +lsb(length) -} } diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index bbc4f53fc..ce31d1d1f 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -55,27 +55,28 @@ fun main(args: Array) { moduleAst.checkIdentifiers() println("Optimizing...") - moduleAst.constantFold(namespace) - moduleAst.checkValid(namespace, compilerOptions) // check if tree is valid + val heap = HeapValues() + moduleAst.constantFold(namespace, heap) + moduleAst.checkValid(namespace, compilerOptions, heap) // check if tree is valid val allScopedSymbolDefinitions = moduleAst.checkIdentifiers() while(true) { // keep optimizing expressions and statements until no more steps remain - val optsDone1 = moduleAst.simplifyExpressions(namespace) - val optsDone2 = moduleAst.optimizeStatements(namespace) + val optsDone1 = moduleAst.simplifyExpressions(namespace, heap) + val optsDone2 = moduleAst.optimizeStatements(namespace, heap) if(optsDone1 + optsDone2 == 0) break } StatementReorderer().process(moduleAst) // reorder statements to please the compiler later namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime - moduleAst.checkValid(namespace, compilerOptions) // check if final tree is valid + moduleAst.checkValid(namespace, compilerOptions, heap) // check if final tree is valid moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls // namespace.debugPrint() // compile the syntax tree into stackvmProg form, and optimize that val compiler = Compiler(compilerOptions) - val intermediate = compiler.compile(moduleAst) + val intermediate = compiler.compile(moduleAst, heap) intermediate.optimize() val stackVmFilename = intermediate.name + "_stackvm.txt" diff --git a/compiler/src/prog8/ast/AST.kt b/compiler/src/prog8/ast/AST.kt index 61e046932..7301e0c2e 100644 --- a/compiler/src/prog8/ast/AST.kt +++ b/compiler/src/prog8/ast/AST.kt @@ -2,6 +2,7 @@ package prog8.ast import org.antlr.v4.runtime.ParserRuleContext import org.antlr.v4.runtime.tree.TerminalNode +import prog8.compiler.HeapValues import prog8.compiler.target.c64.Petscii import prog8.functions.* import prog8.parser.prog8Parser @@ -677,11 +678,11 @@ data class AssignTarget(val register: Register?, val identifier: IdentifierRefer interface IExpression: Node { - fun isIterable(namespace: INameScope): Boolean - fun constValue(namespace: INameScope): LiteralValue? + fun isIterable(namespace: INameScope, heap: HeapValues): Boolean + fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? fun process(processor: IAstProcessor): IExpression fun referencesIdentifier(name: String): Boolean - fun resultingDatatype(namespace: INameScope): DataType? + fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? } @@ -695,11 +696,11 @@ class PrefixExpression(val operator: String, var expression: IExpression, overri expression.linkParents(this) } - override fun constValue(namespace: INameScope): LiteralValue? = null + override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String) = expression.referencesIdentifier(name) - override fun resultingDatatype(namespace: INameScope): DataType? = expression.resultingDatatype(namespace) - override fun isIterable(namespace: INameScope) = false + override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? = expression.resultingDatatype(namespace, heap) + override fun isIterable(namespace: INameScope, heap: HeapValues) = false } @@ -713,13 +714,13 @@ 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(namespace: INameScope): LiteralValue? = null - override fun isIterable(namespace: INameScope) = false + override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null + override fun isIterable(namespace: INameScope, heap: HeapValues) = false override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name) - override fun resultingDatatype(namespace: INameScope): DataType? { - val leftDt = left.resultingDatatype(namespace) - val rightDt = right.resultingDatatype(namespace) + override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? { + val leftDt = left.resultingDatatype(namespace, heap) + val rightDt = right.resultingDatatype(namespace, heap) return when(operator) { "+", "-", "*", "**", "%" -> if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt) "//" -> if(leftDt==null || rightDt==null) null else integerDivisionOpDt(leftDt, rightDt) @@ -731,7 +732,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I "<=", ">=", "==", "!=" -> DataType.BYTE "/" -> { - val rightNum = right.constValue(namespace)?.asNumericValue?.toDouble() + val rightNum = right.constValue(namespace, heap)?.asNumericValue?.toDouble() if(rightNum!=null) { when(leftDt) { DataType.BYTE -> @@ -807,8 +808,10 @@ class LiteralValue(val type: DataType, val floatvalue: Double? = null, val strvalue: String? = null, val arrayvalue: Array? = null, + val heapId: Int? =null, override val position: Position) : IExpression { override lateinit var parent: Node + override fun referencesIdentifier(name: String) = arrayvalue?.any { it.referencesIdentifier(name) } ?: false val isString = type==DataType.STR || type==DataType.STR_P || type==DataType.STR_S || type==DataType.STR_PS @@ -858,13 +861,13 @@ class LiteralValue(val type: DataType, DataType.BYTE -> if(bytevalue==null) throw FatalAstException("literal value missing bytevalue") DataType.WORD -> if(wordvalue==null) throw FatalAstException("literal value missing wordvalue") DataType.FLOAT -> if(floatvalue==null) throw FatalAstException("literal value missing floatvalue") - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { - if(strvalue==null) throw FatalAstException("literal value missing strvalue") - } - DataType.ARRAY, DataType.ARRAY_W -> if(arrayvalue==null) throw FatalAstException("literal value missing arrayvalue") + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> + if(strvalue==null && heapId==null) throw FatalAstException("literal value missing strvalue/heapId") + DataType.ARRAY, DataType.ARRAY_W -> + if(arrayvalue==null && heapId==null) throw FatalAstException("literal value missing arrayvalue/heapId") DataType.MATRIX -> TODO("matrix literalvalue? for now, arrays are good enough for this") } - if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null) + if(bytevalue==null && wordvalue==null && floatvalue==null && arrayvalue==null && strvalue==null && heapId==null) throw FatalAstException("literal value without actual value") } @@ -893,7 +896,7 @@ class LiteralValue(val type: DataType, arrayvalue?.forEach {it.linkParents(this)} } - override fun constValue(namespace: INameScope): LiteralValue? = this + override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = this override fun process(processor: IAstProcessor) = processor.process(this) override fun toString(): String { @@ -901,16 +904,25 @@ class LiteralValue(val type: DataType, DataType.BYTE -> "byte:$bytevalue" DataType.WORD -> "word:$wordvalue" DataType.FLOAT -> "float:$floatvalue" - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS-> "str:$strvalue" - DataType.ARRAY, DataType.ARRAY_W -> "array:$arrayvalue" - DataType.MATRIX -> "matrix:$arrayvalue" + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS-> { + if(heapId!=null) "str:#$heapId" + else "str:$strvalue" + } + DataType.ARRAY, DataType.ARRAY_W -> { + if(heapId!=null) "array:#$heapId" + else "array:$arrayvalue" + } + DataType.MATRIX -> { + if(heapId!=null) "matrix:#$heapId" + else "matrix:$arrayvalue" + } } return "LiteralValue($vstr)" } - override fun resultingDatatype(namespace: INameScope) = type + override fun resultingDatatype(namespace: INameScope, heap: HeapValues) = type - override fun isIterable(namespace: INameScope): Boolean = IterableDatatypes.contains(type) + override fun isIterable(namespace: INameScope, heap: HeapValues): Boolean = IterableDatatypes.contains(type) override fun hashCode(): Int { val bh = bytevalue?.hashCode() ?: 0x10001234 @@ -954,13 +966,13 @@ class RangeExpr(var from: IExpression, step.linkParents(this) } - override fun constValue(namespace: INameScope): LiteralValue? = null - override fun isIterable(namespace: INameScope) = true + override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null + override fun isIterable(namespace: INameScope, heap: HeapValues) = true override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String): Boolean = from.referencesIdentifier(name) || to.referencesIdentifier(name) - override fun resultingDatatype(namespace: INameScope): DataType? { - val fromDt=from.resultingDatatype(namespace) - val toDt=to.resultingDatatype(namespace) + override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? { + val fromDt=from.resultingDatatype(namespace, heap) + val toDt=to.resultingDatatype(namespace, heap) return when { fromDt==null || toDt==null -> null fromDt==DataType.WORD || toDt==DataType.WORD -> DataType.WORD @@ -1023,15 +1035,15 @@ class RegisterExpr(val register: Register, override val position: Position) : IE this.parent = parent } - override fun constValue(namespace: INameScope): LiteralValue? = null + override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null override fun process(processor: IAstProcessor) = this override fun referencesIdentifier(name: String): Boolean = false - override fun isIterable(namespace: INameScope) = false + override fun isIterable(namespace: INameScope, heap: HeapValues) = false override fun toString(): String { return "RegisterExpr(register=$register, pos=$position)" } - override fun resultingDatatype(namespace: INameScope): DataType? { + override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? { return when(register){ Register.A, Register.X, Register.Y -> DataType.BYTE Register.AX, Register.AY, Register.XY -> DataType.WORD @@ -1053,7 +1065,7 @@ data class IdentifierReference(val nameInSource: List, override val posi this.parent = parent } - override fun constValue(namespace: INameScope): LiteralValue? { + override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? { val node = namespace.lookup(nameInSource, this) ?: throw UndefinedSymbolError(this) val vardecl = node as? VarDecl @@ -1062,7 +1074,7 @@ data class IdentifierReference(val nameInSource: List, override val posi } else if(vardecl.type!=VarDeclType.CONST) { return null } - return vardecl.value?.constValue(namespace) + return vardecl.value?.constValue(namespace, heap) } override fun toString(): String { @@ -1072,7 +1084,7 @@ data class IdentifierReference(val nameInSource: List, override val posi override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String): Boolean = nameInSource.last() == name // @todo is this correct all the time? - override fun resultingDatatype(namespace: INameScope): DataType? { + override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? { val targetStmt = targetStatement(namespace) if(targetStmt is VarDecl) { return targetStmt.datatype @@ -1081,7 +1093,7 @@ data class IdentifierReference(val nameInSource: List, override val posi } } - override fun isIterable(namespace: INameScope): Boolean = IterableDatatypes.contains(resultingDatatype(namespace)) + override fun isIterable(namespace: INameScope, heap: HeapValues): Boolean = IterableDatatypes.contains(resultingDatatype(namespace, heap)) } @@ -1131,45 +1143,45 @@ class FunctionCall(override var target: IdentifierReference, arglist.forEach { it.linkParents(this) } } - override fun constValue(namespace: INameScope) = constValue(namespace, true) + override fun constValue(namespace: INameScope, heap: HeapValues) = constValue(namespace, heap, true) - private fun constValue(namespace: INameScope, withDatatypeCheck: Boolean): LiteralValue? { + private fun constValue(namespace: INameScope, heap: HeapValues, withDatatypeCheck: Boolean): LiteralValue? { // if the function is a built-in function and the args are consts, should try to const-evaluate! if(target.nameInSource.size>1) return null try { val resultValue = when (target.nameInSource[0]) { - "sin" -> builtinSin(arglist, position, namespace) - "cos" -> builtinCos(arglist, position, namespace) - "abs" -> builtinAbs(arglist, position, namespace) - "acos" -> builtinAcos(arglist, position, namespace) - "asin" -> builtinAsin(arglist, position, namespace) - "tan" -> builtinTan(arglist, position, namespace) - "atan" -> builtinAtan(arglist, position, namespace) - "ln" -> builtinLn(arglist, position, namespace) - "log2" -> builtinLog2(arglist, position, namespace) - "log10" -> builtinLog10(arglist, position, namespace) - "sqrt" -> builtinSqrt(arglist, position, namespace) - "max" -> builtinMax(arglist, position, namespace) - "min" -> builtinMin(arglist, position, namespace) - "round" -> builtinRound(arglist, position, namespace) - "rad" -> builtinRad(arglist, position, namespace) - "deg" -> builtinDeg(arglist, position, namespace) - "sum" -> builtinSum(arglist, position, namespace) - "avg" -> builtinAvg(arglist, position, namespace) - "len" -> builtinLen(arglist, position, namespace) - "lsb" -> builtinLsb(arglist, position, namespace) - "msb" -> builtinMsb(arglist, position, namespace) - "flt" -> builtinFlt(arglist, position, namespace) - "any" -> builtinAny(arglist, position, namespace) - "all" -> builtinAll(arglist, position, namespace) - "floor" -> builtinFloor(arglist, position, namespace) - "ceil" -> builtinCeil(arglist, position, namespace) + "sin" -> builtinSin(arglist, position, namespace, heap) + "cos" -> builtinCos(arglist, position, namespace, heap) + "abs" -> builtinAbs(arglist, position, namespace, heap) + "acos" -> builtinAcos(arglist, position, namespace, heap) + "asin" -> builtinAsin(arglist, position, namespace, heap) + "tan" -> builtinTan(arglist, position, namespace, heap) + "atan" -> builtinAtan(arglist, position, namespace, heap) + "ln" -> builtinLn(arglist, position, namespace, heap) + "log2" -> builtinLog2(arglist, position, namespace, heap) + "log10" -> builtinLog10(arglist, position, namespace, heap) + "sqrt" -> builtinSqrt(arglist, position, namespace, heap) + "max" -> builtinMax(arglist, position, namespace, heap) + "min" -> builtinMin(arglist, position, namespace, heap) + "round" -> builtinRound(arglist, position, namespace, heap) + "rad" -> builtinRad(arglist, position, namespace, heap) + "deg" -> builtinDeg(arglist, position, namespace, heap) + "sum" -> builtinSum(arglist, position, namespace, heap) + "avg" -> builtinAvg(arglist, position, namespace, heap) + "len" -> builtinLen(arglist, position, namespace, heap) + "lsb" -> builtinLsb(arglist, position, namespace, heap) + "msb" -> builtinMsb(arglist, position, namespace, heap) + "flt" -> builtinFlt(arglist, position, namespace, heap) + "any" -> builtinAny(arglist, position, namespace, heap) + "all" -> builtinAll(arglist, position, namespace, heap) + "floor" -> builtinFloor(arglist, position, namespace, heap) + "ceil" -> builtinCeil(arglist, position, namespace, heap) "lsl", "lsr", "rol", "rol2", "ror", "ror2", "set_carry", "clear_carry", "set_irqd", "clear_irqd" -> throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used in expressions because it doesn't return a value", position) else -> null } if(withDatatypeCheck) { - val resultDt = this.resultingDatatype(namespace) + val resultDt = this.resultingDatatype(namespace, heap) if(resultValue==null || resultDt == resultValue.type) return resultValue throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position") @@ -1190,10 +1202,10 @@ class FunctionCall(override var target: IdentifierReference, override fun process(processor: IAstProcessor) = processor.process(this) override fun referencesIdentifier(name: String): Boolean = target.referencesIdentifier(name) || arglist.any{it.referencesIdentifier(name)} - override fun resultingDatatype(namespace: INameScope): DataType? { - val constVal = constValue(namespace, false) + override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? { + val constVal = constValue(namespace, heap,false) if(constVal!=null) - return constVal.resultingDatatype(namespace) + return constVal.resultingDatatype(namespace, heap) val stmt = target.targetStatement(namespace) ?: return null when (stmt) { is BuiltinFunctionStatementPlaceholder -> { @@ -1201,7 +1213,7 @@ class FunctionCall(override var target: IdentifierReference, target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") { return null // these have no return value } - return builtinFunctionReturnType(target.nameInSource[0], this.arglist, namespace) + return builtinFunctionReturnType(target.nameInSource[0], this.arglist, namespace, heap) } is Subroutine -> { if(stmt.returnvalues.isEmpty()) { @@ -1225,7 +1237,7 @@ class FunctionCall(override var target: IdentifierReference, TODO("datatype of functioncall to $stmt") } - override fun isIterable(namespace: INameScope) : Boolean { + override fun isIterable(namespace: INameScope, heap: HeapValues) : Boolean { TODO("isIterable of function call result") } } diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index 7f96e5b98..70a462fc0 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -1,6 +1,7 @@ package prog8.ast import prog8.compiler.CompilationOptions +import prog8.compiler.HeapValues import prog8.functions.BuiltinFunctionNames import prog8.parser.ParsingFailedError @@ -8,8 +9,8 @@ import prog8.parser.ParsingFailedError * General checks on the Ast */ -fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions) { - val checker = AstChecker(globalNamespace, compilerOptions) +fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions, heap: HeapValues) { + val checker = AstChecker(globalNamespace, compilerOptions, heap) this.process(checker) printErrors(checker.result(), name) } @@ -38,7 +39,9 @@ fun printWarning(msg: String, position: Position, detailInfo: String?=null) { } -class AstChecker(private val namespace: INameScope, private val compilerOptions: CompilationOptions) : IAstProcessor { +class AstChecker(private val namespace: INameScope, + private val compilerOptions: CompilationOptions, + private val heap: HeapValues) : IAstProcessor { private val checkResult: MutableList = mutableListOf() fun result(): List { @@ -87,10 +90,10 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: override fun process(forLoop: ForLoop): IStatement { if(forLoop.body.isEmpty()) printWarning("for loop body is empty", forLoop.position) - if(!forLoop.iterable.isIterable(namespace)) { + if(!forLoop.iterable.isIterable(namespace, heap)) { checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position)) } else { - val iterableDt = forLoop.iterable.resultingDatatype(namespace) + val iterableDt = forLoop.iterable.resultingDatatype(namespace, heap) if (forLoop.loopRegister != null) { printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position) // loop register @@ -281,11 +284,11 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: } val targetDatatype = assignment.target.determineDatatype(namespace, assignment) - val constVal = assignment.value.constValue(namespace) + val constVal = assignment.value.constValue(namespace, heap) if(constVal!=null) { - checkValueTypeAndRange(targetDatatype, null, constVal) + checkValueTypeAndRange(targetDatatype, null, constVal, heap) } else { - val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace) + val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap) if(sourceDatatype==null) { if(assignment.value is FunctionCall) checkResult.add(ExpressionError("function call doesn't return a value to use in assignment", assignment.value.position)) @@ -340,7 +343,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: } when { decl.value is RangeExpr -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as RangeExpr) - decl.value is LiteralValue -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as LiteralValue) + decl.value is LiteralValue -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as LiteralValue, heap) else -> { err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!::class.simpleName}") return super.process(decl) @@ -433,14 +436,14 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: if(!compilerOptions.floats && literalValue.type==DataType.FLOAT) { checkResult.add(SyntaxError("floating point value used, but floating point is not enabled via options", literalValue.position)) } - checkValueTypeAndRange(literalValue.type, null, literalValue) + checkValueTypeAndRange(literalValue.type, null, literalValue, heap) return super.process(literalValue) } override fun process(expr: BinaryExpression): IExpression { when(expr.operator){ "/", "//", "%" -> { - val numeric = expr.right.constValue(namespace)?.asNumericValue?.toDouble() + val numeric = expr.right.constValue(namespace, heap)?.asNumericValue?.toDouble() if(numeric==0.0) checkResult.add(ExpressionError("division by zero", expr.right.position)) } @@ -453,9 +456,9 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: checkResult.add(SyntaxError(msg, range.position)) } super.process(range) - val from = range.from.constValue(namespace) - val to = range.to.constValue(namespace) - val stepLv = range.step.constValue(namespace) ?: LiteralValue(DataType.BYTE, 1, position = range.position) + val from = range.from.constValue(namespace, heap) + val to = range.to.constValue(namespace, heap) + val stepLv = range.step.constValue(namespace, heap) ?: LiteralValue(DataType.BYTE, 1, position = range.position) if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) { err("range step must be an integer != 0") return range @@ -555,8 +558,8 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, range: RangeExpr) : Boolean { - val from = range.from.constValue(namespace) - val to = range.to.constValue(namespace) + val from = range.from.constValue(namespace, heap) + val to = range.to.constValue(namespace, heap) if(from==null || to==null) { checkResult.add(SyntaxError("range from and to values must be constants", range.position)) return false @@ -596,7 +599,7 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: } } - private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, value: LiteralValue) : Boolean { + private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, value: LiteralValue, heap: HeapValues) : Boolean { fun err(msg: String) : Boolean { checkResult.add(ExpressionError(msg, value.position)) return false @@ -629,37 +632,21 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: return err("value '$number' out of range for unsigned word") } DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { - val str = value.strvalue - ?: return err("string value expected") + val str = value.strvalue ?: heap.get(value.heapId!!).str!! if (str.isEmpty() || str.length > 255) return err("string length must be 1 to 255") } DataType.ARRAY -> { - // value may be either a single byte, or a byte array + // value may be either a single byte, or a byte array (of all constant values) if(value.type==DataType.ARRAY) { + val array = heap.get(value.heapId!!).array!! if(arrayspec!=null) { - // arrayspec is not always known when checking - val constX = arrayspec.x.constValue(namespace) + val constX = arrayspec.x.constValue(namespace, heap) if(constX?.asIntegerValue==null) return err("array size specifier must be constant integer value") val expectedSize = constX.asIntegerValue - if (value.arrayvalue!!.size != expectedSize) - return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})") - } - for (av in value.arrayvalue!!) { - if(av is LiteralValue) { - val number = av.bytevalue - ?: return err("array must be all bytes") - if (number < 0 || number > 255) - return err("value '$number' in array is out of range for unsigned byte") - } else if(av is RegisterExpr) { - if(av.register!=Register.A && av.register!=Register.X && av.register!=Register.Y) - return err("register '$av' in byte array is not a single register") - } else { - val avDt = av.resultingDatatype(namespace) - if(avDt!=DataType.BYTE) - return err("array must be all bytes") - } + if (array.size != expectedSize) + return err("initializer array size mismatch (expecting $expectedSize, got ${array.size})") } } else if(value.type==DataType.ARRAY_W) { return err("initialization value must be an array of bytes") @@ -675,26 +662,15 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: DataType.ARRAY_W -> { // value may be either a single word, or a word array if(value.type==DataType.ARRAY || value.type==DataType.ARRAY_W) { + val array = heap.get(value.heapId!!).array!! if(arrayspec!=null) { // arrayspec is not always known when checking - val constX = arrayspec.x.constValue(namespace) + val constX = arrayspec.x.constValue(namespace, heap) if(constX?.asIntegerValue==null) return err("array size specifier must be constant integer value") val expectedSize = constX.asIntegerValue - if (value.arrayvalue!!.size != expectedSize) - return err("initializer array size mismatch (expecting $expectedSize, got ${value.arrayvalue.size})") - } - for (av in value.arrayvalue!!) { - if(av is LiteralValue) { - val number = av.asIntegerValue - ?: return err("array must be all integers") - if (number < 0 || number > 65535) - return err("value '$number' in array is out of range for unsigned word") - } else { - val avDt = av.resultingDatatype(namespace) - if(avDt!=DataType.BYTE && avDt!=DataType.WORD) - return err("array must be all integers") - } + if (array.size != expectedSize) + return err("initializer array size mismatch (expecting $expectedSize, got ${array.size})") } } else { val number = value.asIntegerValue ?: return if (value.floatvalue!=null) @@ -708,21 +684,14 @@ class AstChecker(private val namespace: INameScope, private val compilerOptions: DataType.MATRIX -> { // value can only be a single byte, or a byte array (which represents the matrix) if(value.type==DataType.ARRAY) { - for (av in value.arrayvalue!!) { - val number = (av as LiteralValue).bytevalue - ?: return err("array must be all bytes") - val constX = arrayspec!!.x.constValue(namespace) - val constY = arrayspec.y!!.constValue(namespace) - if(constX?.asIntegerValue==null || constY?.asIntegerValue==null) - return err("matrix size specifiers must be constant integer values") - - val expectedSize = constX.asIntegerValue * constY.asIntegerValue - if (value.arrayvalue.size != expectedSize) - return err("initializer matrix size mismatch (expecting $expectedSize, got ${value.arrayvalue.size} elements)") - - if (number < 0 || number > 255) - return err("value '$number' in byte array is out of range for unsigned byte") - } + val constX = arrayspec!!.x.constValue(namespace, heap) + val constY = arrayspec.y!!.constValue(namespace, heap) + if(constX?.asIntegerValue==null || constY?.asIntegerValue==null) + return err("matrix size specifiers must be constant integer values") + val matrix = heap.get(value.heapId!!).array!! + val expectedSize = constX.asIntegerValue * constY.asIntegerValue + if (matrix.size != expectedSize) + return err("initializer matrix size mismatch (expecting $expectedSize, got ${matrix.size} elements)") } else { val number = value.bytevalue ?: return err("unsigned byte value expected") diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index fcda3f016..416212539 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -24,8 +24,52 @@ fun Number.toHex(): String { } +class HeapValues { + class HeapValue(val type: DataType, val str: String?, val array: IntArray?) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as HeapValue + return type==other.type && str==other.str && Arrays.equals(array, other.array) + } -class StackVmProgram(val name: String) { + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + (str?.hashCode() ?: 0) + result = 31 * result + (array?.let { Arrays.hashCode(it) } ?: 0) + return result + } + } + + private val heap = mutableListOf() + + fun add(type: DataType, str: String): Int { + if (str.isEmpty() || str.length > 255) + throw IllegalArgumentException("string length must be 1-255") + + // strings are 'interned' and shared if they're the same + val value = HeapValue(type, str, null) + val existing = heap.indexOf(value) + if(existing>=0) + return existing + heap.add(value) + return heap.size-1 + } + + fun add(type: DataType, array: IntArray): Int { + // arrays are never shared + heap.add(HeapValue(type, null, array)) + return heap.size-1 + } + + fun get(heapId: Int): HeapValue = heap[heapId] + + fun allStrings() = heap.asSequence().withIndex().filter { it.value.str!=null }.toList() + fun allArrays() = heap.asSequence().withIndex().filter { it.value.array!=null }.toList() +} + + +class StackVmProgram(val name: String, val heap: HeapValues) { private val instructions = mutableListOf() private val variables = mutableMapOf>() private val memory = mutableMapOf>() @@ -44,9 +88,19 @@ class StackVmProgram(val name: String) { fun blockvar(scopedname: String, decl: VarDecl) { val value = when(decl.datatype) { - DataType.BYTE, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue) - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> Value(decl.datatype, null, (decl.value as LiteralValue).strvalue) - DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> TODO("array/matrix variable values") + DataType.BYTE, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!) + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { + val litval = (decl.value as LiteralValue) + if(litval.heapId==null) + throw CompilerException("string should already be in the heap") + Value(decl.datatype, litval.heapId) + } + DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> { + val litval = (decl.value as LiteralValue) + if(litval.heapId==null) + throw CompilerException("array/matrix should already be in the heap") + Value(decl.datatype, litval.heapId) + } } // We keep the block structure intact: vars are stored per block. This is needed eventually for the actual 6502 code generation later... @@ -57,7 +111,7 @@ class StackVmProgram(val name: String) { } fun writeAsText(out: PrintStream) { - Program(name, instructions, labels, variables, memory).print(out) + Program(name, instructions, labels, variables, memory, heap).print(out) } fun instr(opcode: Opcode, arg: Value? = null, callLabel: String? = null) { @@ -71,7 +125,7 @@ class StackVmProgram(val name: String) { } fun line(position: Position) { - instructions.add(Instruction(Opcode.LINE, Value(DataType.STR, null, "${position.line} ${position.file}"))) + instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}")) } } @@ -99,18 +153,18 @@ data class CompilationOptions(val output: OutputType, class Compiler(private val options: CompilationOptions) { - fun compile(module: Module) : StackVmProgram { + fun compile(module: Module, heap: HeapValues) : StackVmProgram { println("\nCreating stackVM code...") val namespace = module.definingScope() - val intermediate = StackVmProgram(module.name) + val intermediate = StackVmProgram(module.name, heap) - // create the pool of all variables used in all blocks and scopes + // create the heap of all variables used in all blocks and scopes val varGather = VarGatherer(intermediate) varGather.process(module) println(" ${intermediate.numVariables} allocated variables and constants") - val translator = StatementTranslator(intermediate, namespace) + val translator = StatementTranslator(intermediate, namespace, heap) translator.process(module) println(" ${translator.stmtUniqueSequenceNr} source statements, ${intermediate.numInstructions} resulting instructions") @@ -130,7 +184,9 @@ class Compiler(private val options: CompilationOptions) { } -private class StatementTranslator(private val stackvmProg: StackVmProgram, private val namespace: INameScope): IAstProcessor { +private class StatementTranslator(private val stackvmProg: StackVmProgram, + private val namespace: INameScope, + private val heap: HeapValues): IAstProcessor { var stmtUniqueSequenceNr = 0 private set @@ -290,8 +346,8 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva } private fun checkForFloatPrecisionProblem(left: IExpression, right: IExpression) { - val leftDt = left.resultingDatatype(namespace) - val rightDt = right.resultingDatatype(namespace) + val leftDt = left.resultingDatatype(namespace, heap) + val rightDt = right.resultingDatatype(namespace, heap) if (leftDt == DataType.BYTE || leftDt == DataType.WORD) { if(rightDt==DataType.FLOAT) printWarning("byte or word value implicitly converted to float. Suggestion: use explicit flt() conversion or revert to byte/word arithmetic", left.position) @@ -301,7 +357,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva private fun translate(expr: IExpression) { when(expr) { is RegisterExpr -> { - stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, expr.register.toString())) + stackvmProg.instr(Opcode.PUSH_VAR, callLabel = expr.register.toString()) } is PrefixExpression -> { translate(expr.expression) @@ -333,14 +389,14 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva is VarDecl -> { when(target.type) { VarDeclType.VAR -> - stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, target.scopedname)) + stackvmProg.instr(Opcode.PUSH_VAR, callLabel = target.scopedname) VarDeclType.CONST -> throw CompilerException("const ref should have been const-folded away") VarDeclType.MEMORY -> { when(target.datatype){ - DataType.BYTE -> stackvmProg.instr(Opcode.PUSH_MEM, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue)) - DataType.WORD -> stackvmProg.instr(Opcode.PUSH_MEM_W, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue)) - DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH_MEM_F, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue)) + DataType.BYTE -> stackvmProg.instr(Opcode.PUSH_MEM, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue!!)) + DataType.WORD -> stackvmProg.instr(Opcode.PUSH_MEM_W, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue!!)) + DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH_MEM_F, Value(DataType.WORD, (target.value as LiteralValue).asNumericValue!!)) else -> TODO("invalid datatype for memory variable expression: $target") } } @@ -354,17 +410,13 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva TODO("TRANSLATE range $expr") } else -> { - val lv = expr.constValue(namespace) ?: throw CompilerException("constant expression required, not $expr") + val lv = expr.constValue(namespace, heap) ?: throw CompilerException("constant expression required, not $expr") when(lv.type) { - DataType.BYTE -> stackvmProg.instr(Opcode.PUSH, Value(DataType.BYTE, lv.bytevalue)) - DataType.WORD -> stackvmProg.instr(Opcode.PUSH, Value(DataType.WORD, lv.wordvalue)) - DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH, Value(DataType.FLOAT, lv.floatvalue)) - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> stackvmProg.instr(Opcode.PUSH, Value(DataType.STR,null, lv.strvalue)) - DataType.ARRAY, DataType.ARRAY_W -> { - lv.arrayvalue?.forEach { translate(it) } - stackvmProg.instr(Opcode.ARRAY, Value(DataType.WORD, lv.arrayvalue!!.size)) - } - DataType.MATRIX -> TODO("matrix type") + DataType.BYTE -> stackvmProg.instr(Opcode.PUSH, Value(DataType.BYTE, lv.bytevalue!!)) + DataType.WORD -> stackvmProg.instr(Opcode.PUSH, Value(DataType.WORD, lv.wordvalue!!)) + DataType.FLOAT -> stackvmProg.instr(Opcode.PUSH, Value(DataType.FLOAT, lv.floatvalue!!)) + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS, + DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> stackvmProg.instr(Opcode.PUSH, Value(lv.type, lv.heapId!!)) } } } @@ -396,7 +448,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva "flt" -> { // 1 argument, type determines the exact opcode to use val arg = args.single() - when (arg.resultingDatatype(namespace)) { + when (arg.resultingDatatype(namespace, heap)) { DataType.BYTE -> stackvmProg.instr(Opcode.B2FLOAT) DataType.WORD -> stackvmProg.instr(Opcode.W2FLOAT) DataType.FLOAT -> stackvmProg.instr(Opcode.NOP) @@ -507,14 +559,14 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva stackvmProg.line(stmt.position) if(stmt.target.register!=null) { when(stmt.operator) { - "++" -> stackvmProg.instr(Opcode.INC_VAR, Value(DataType.STR, null, stmt.target.register.toString())) - "--" -> stackvmProg.instr(Opcode.DEC_VAR, Value(DataType.STR, null, stmt.target.register.toString())) + "++" -> stackvmProg.instr(Opcode.INC_VAR, callLabel = stmt.target.register.toString()) + "--" -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = stmt.target.register.toString()) } } else { val targetStatement = stmt.target.identifier!!.targetStatement(namespace) as VarDecl when(stmt.operator) { - "++" -> stackvmProg.instr(Opcode.INC_VAR, Value(DataType.STR, null, targetStatement.scopedname)) - "--" -> stackvmProg.instr(Opcode.DEC_VAR, Value(DataType.STR, null, targetStatement.scopedname)) + "++" -> stackvmProg.instr(Opcode.INC_VAR, callLabel = targetStatement.scopedname) + "--" -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = targetStatement.scopedname) } } } @@ -522,7 +574,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva private fun translate(stmt: Assignment) { stackvmProg.line(stmt.position) translate(stmt.value) - val valueDt = stmt.value.resultingDatatype(namespace) + val valueDt = stmt.value.resultingDatatype(namespace, heap) val targetDt = stmt.target.determineDatatype(namespace, stmt) if(valueDt!=targetDt) { // convert value to target datatype if possible @@ -552,11 +604,11 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva if(stmt.target.identifier!=null) { val target = stmt.target.identifier!!.targetStatement(namespace)!! when(target) { - is VarDecl -> stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, target.scopedname)) + is VarDecl -> stackvmProg.instr(Opcode.PUSH_VAR, callLabel = target.scopedname) else -> throw CompilerException("invalid assignment target type ${target::class}") } } else if(stmt.target.register!=null) { - stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stmt.target.register.toString())) + stackvmProg.instr(Opcode.PUSH_VAR, callLabel = stmt.target.register.toString()) } translateAugAssignOperator(stmt.aug_op) } @@ -565,11 +617,11 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva if(stmt.target.identifier!=null) { val target = stmt.target.identifier!!.targetStatement(namespace)!! when(target) { - is VarDecl -> stackvmProg.instr(Opcode.POP_VAR, Value(DataType.STR, null, target.scopedname)) + is VarDecl -> stackvmProg.instr(Opcode.POP_VAR, callLabel = target.scopedname) else -> throw CompilerException("invalid assignment target type ${target::class}") } } else if(stmt.target.register!=null) { - stackvmProg.instr(Opcode.POP_VAR, Value(DataType.STR, null, stmt.target.register.toString())) + stackvmProg.instr(Opcode.POP_VAR, callLabel = stmt.target.register.toString()) } } @@ -704,32 +756,31 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva continueStmtLabelStack.push(continueLabel) breakStmtLabelStack.push(breakLabel) - val varValue = Value(DataType.STR, null, varname) stackvmProg.instr(Opcode.PUSH, Value(varDt, range.first)) - stackvmProg.instr(Opcode.POP_VAR, varValue) + stackvmProg.instr(Opcode.POP_VAR, callLabel = varname) stackvmProg.label(loopLabel) translate(body) stackvmProg.label(continueLabel) when { - range.step==1 -> stackvmProg.instr(Opcode.INC_VAR, varValue) - range.step==-1 -> stackvmProg.instr(Opcode.DEC_VAR, varValue) + range.step==1 -> stackvmProg.instr(Opcode.INC_VAR, callLabel = varname) + range.step==-1 -> stackvmProg.instr(Opcode.DEC_VAR, callLabel = varname) range.step>1 -> { - stackvmProg.instr(Opcode.PUSH_VAR, varValue) + stackvmProg.instr(Opcode.PUSH_VAR, callLabel = varname) stackvmProg.instr(Opcode.PUSH, Value(varDt, range.step)) stackvmProg.instr(Opcode.ADD) - stackvmProg.instr(Opcode.POP_VAR, varValue) + stackvmProg.instr(Opcode.POP_VAR, callLabel = varname) } range.step<1 -> { - stackvmProg.instr(Opcode.PUSH_VAR, varValue) + stackvmProg.instr(Opcode.PUSH_VAR, callLabel = varname) stackvmProg.instr(Opcode.PUSH, Value(varDt, abs(range.step))) stackvmProg.instr(Opcode.SUB) - stackvmProg.instr(Opcode.POP_VAR, varValue) + stackvmProg.instr(Opcode.POP_VAR, callLabel = varname) } } // TODO: optimize edge cases if last value = 255 or 0 (for bytes) etc. to avoid PUSH / SUB opcodes and make use of the wrapping around of the value. stackvmProg.instr(Opcode.PUSH, Value(varDt, range.last+range.step)) - stackvmProg.instr(Opcode.PUSH_VAR, varValue) + stackvmProg.instr(Opcode.PUSH_VAR, callLabel = varname) stackvmProg.instr(Opcode.SUB) stackvmProg.instr(Opcode.BNZ, callLabel = loopLabel) @@ -838,9 +889,9 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram, priva postIncr.linkParents(range.parent) translate(postIncr) if(lvTarget.register!=null) - stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stringvalue=lvTarget.register.toString())) + stackvmProg.instr(Opcode.PUSH_VAR, callLabel =lvTarget.register.toString()) else - stackvmProg.instr(Opcode.PUSH_VAR, Value(DataType.STR, null, stringvalue=targetStatement!!.scopedname)) + stackvmProg.instr(Opcode.PUSH_VAR, callLabel =targetStatement!!.scopedname) val branch = BranchStatement( BranchCondition.NZ, listOf(Jump(null, null, loopLabel, range.position)), diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index a56dc43ea..a4e055237 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -1,6 +1,7 @@ package prog8.functions import prog8.ast.* +import prog8.compiler.HeapValues import kotlin.math.log2 @@ -19,9 +20,9 @@ val BuiltinFunctionsWithoutSideEffects = BuiltinFunctionNames - setOf( "_vm_write_str", "_vm_gfx_clearscr", "_vm_gfx_pixel", "_vm_gfx_text") -fun builtinFunctionReturnType(function: String, args: List, namespace: INameScope): DataType? { +fun builtinFunctionReturnType(function: String, args: List, namespace: INameScope, heap: HeapValues): DataType? { fun integerDatatypeFromArg(arg: IExpression): DataType { - val dt = arg.resultingDatatype(namespace) + val dt = arg.resultingDatatype(namespace, heap) return when(dt) { DataType.BYTE -> DataType.BYTE DataType.WORD -> DataType.WORD @@ -33,7 +34,7 @@ fun builtinFunctionReturnType(function: String, args: List, namespa fun datatypeFromListArg(arglist: IExpression): DataType { if(arglist is LiteralValue) { if(arglist.type==DataType.ARRAY || arglist.type==DataType.ARRAY_W || arglist.type==DataType.MATRIX) { - val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace)} + val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace, heap)} if(dt.any { it!=DataType.BYTE && it!=DataType.WORD && it!=DataType.FLOAT}) { throw FatalAstException("fuction $function only accepts array of numeric values") } @@ -51,7 +52,7 @@ fun builtinFunctionReturnType(function: String, args: List, namespa "lsb", "msb", "any", "all", "rnd" -> DataType.BYTE "rndw" -> DataType.WORD "rol", "rol2", "ror", "ror2", "lsl", "lsr", "set_carry", "clear_carry", "set_irqd", "clear_irqd" -> null // no return value so no datatype - "abs" -> args.single().resultingDatatype(namespace) + "abs" -> args.single().resultingDatatype(namespace, heap) "max", "min" -> datatypeFromListArg(args.single()) "round", "floor", "ceil" -> integerDatatypeFromArg(args.single()) "sum" -> { @@ -96,10 +97,10 @@ fun builtinFunctionReturnType(function: String, args: List, namespa class NotConstArgumentException: AstException("not a const argument to a built-in function") -private fun oneDoubleArg(args: List, position: Position, namespace:INameScope, function: (arg: Double)->Number): LiteralValue { +private fun oneDoubleArg(args: List, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue { if(args.size!=1) throw SyntaxError("built-in function requires one floating point argument", position) - val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException() + val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() if(constval.type!=DataType.FLOAT) throw SyntaxError("built-in function requires one floating point argument", position) @@ -107,10 +108,10 @@ private fun oneDoubleArg(args: List, position: Position, namespace: return numericLiteral(function(float), args[0].position) } -private fun oneDoubleArgOutputInt(args: List, position: Position, namespace:INameScope, function: (arg: Double)->Number): LiteralValue { +private fun oneDoubleArgOutputInt(args: List, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue { if(args.size!=1) throw SyntaxError("built-in function requires one floating point argument", position) - val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException() + val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val float: Double = when(constval.type) { DataType.BYTE, DataType.WORD, DataType.FLOAT -> constval.asNumericValue!!.toDouble() else -> throw SyntaxError("built-in function requires one floating point argument", position) @@ -118,10 +119,10 @@ private fun oneDoubleArgOutputInt(args: List, position: Position, n return numericLiteral(function(float).toInt(), args[0].position) } -private fun oneIntArgOutputInt(args: List, position: Position, namespace:INameScope, function: (arg: Int)->Number): LiteralValue { +private fun oneIntArgOutputInt(args: List, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Int)->Number): LiteralValue { if(args.size!=1) throw SyntaxError("built-in function requires one integer argument", position) - val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException() + val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() if(constval.type!=DataType.BYTE && constval.type!=DataType.WORD) throw SyntaxError("built-in function requires one integer argument", position) @@ -129,95 +130,117 @@ private fun oneIntArgOutputInt(args: List, position: Position, name return numericLiteral(function(integer).toInt(), args[0].position) } -private fun collectionArgOutputNumber(args: List, position: Position, namespace:INameScope, +private fun collectionArgOutputNumber(args: List, position: Position, + namespace:INameScope, heap: HeapValues, function: (arg: Collection)->Number): LiteralValue { if(args.size!=1) throw SyntaxError("builtin function requires one non-scalar argument", position) - val iterable = args[0].constValue(namespace) - if(iterable?.arrayvalue == null) - throw SyntaxError("builtin function requires one non-scalar argument", position) - val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue } - if(constants.contains(null)) - throw NotConstArgumentException() - val result = function(constants.map { it!!.toDouble() }).toDouble() + var iterable = args[0].constValue(namespace, heap) + if(iterable==null) { + if(args[0] !is IdentifierReference) + throw SyntaxError("function over weird argument ${args[0]}", position) + iterable = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap) + ?: throw SyntaxError("function over weird argument ${args[0]}", position) + } + + val result = if(iterable.arrayvalue != null) { + val constants = iterable.arrayvalue!!.map { it.constValue(namespace, heap)?.asNumericValue } + if(constants.contains(null)) + throw NotConstArgumentException() + function(constants.map { it!!.toDouble() }).toDouble() + } else { + val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array/matrix argument", position) + function(array.map { it.toDouble() }) + } return numericLiteral(result, args[0].position) } -private fun collectionArgOutputBoolean(args: List, position: Position, namespace:INameScope, +private fun collectionArgOutputBoolean(args: List, position: Position, + namespace:INameScope, heap: HeapValues, function: (arg: Collection)->Boolean): LiteralValue { if(args.size!=1) throw SyntaxError("builtin function requires one non-scalar argument", position) - val iterable = args[0].constValue(namespace) - if(iterable?.arrayvalue == null) - throw SyntaxError("builtin function requires one non-scalar argument", position) - val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue } - if(constants.contains(null)) - throw NotConstArgumentException() - val result = function(constants.map { it?.toDouble()!! }) + var iterable = args[0].constValue(namespace, heap) + if(iterable==null) { + if(args[0] !is IdentifierReference) + throw SyntaxError("function over weird argument ${args[0]}", position) + iterable = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap) + ?: throw SyntaxError("function over weird argument ${args[0]}", position) + } + + val result = if(iterable.arrayvalue != null) { + val constants = iterable.arrayvalue!!.map { it.constValue(namespace, heap)?.asNumericValue } + if(constants.contains(null)) + throw NotConstArgumentException() + function(constants.map { it!!.toDouble() }) + } else { + val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array/matrix argument", position) + function(array.map { it.toDouble() }) + } return LiteralValue.fromBoolean(result, position) } -fun builtinRound(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArgOutputInt(args, position, namespace, Math::round) +fun builtinRound(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArgOutputInt(args, position, namespace, heap, Math::round) -fun builtinFloor(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArgOutputInt(args, position, namespace, Math::floor) +fun builtinFloor(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArgOutputInt(args, position, namespace, heap, Math::floor) -fun builtinCeil(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArgOutputInt(args, position, namespace, Math::ceil) +fun builtinCeil(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArgOutputInt(args, position, namespace, heap, Math::ceil) -fun builtinSin(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::sin) +fun builtinSin(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::sin) -fun builtinCos(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::cos) +fun builtinCos(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::cos) -fun builtinAcos(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::acos) +fun builtinAcos(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::acos) -fun builtinAsin(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::asin) +fun builtinAsin(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::asin) -fun builtinTan(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::tan) +fun builtinTan(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::tan) -fun builtinAtan(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::atan) +fun builtinAtan(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::atan) -fun builtinLn(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::log) +fun builtinLn(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::log) -fun builtinLog2(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, ::log2) +fun builtinLog2(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, ::log2) -fun builtinLog10(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::log10) +fun builtinLog10(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::log10) -fun builtinSqrt(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::sqrt) +fun builtinSqrt(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::sqrt) -fun builtinRad(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::toRadians) +fun builtinRad(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::toRadians) -fun builtinDeg(args: List, position: Position, namespace:INameScope): LiteralValue - = oneDoubleArg(args, position, namespace, Math::toDegrees) +fun builtinDeg(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneDoubleArg(args, position, namespace, heap, Math::toDegrees) -fun builtinFlt(args: List, position: Position, namespace:INameScope): LiteralValue { +fun builtinFlt(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { // 1 numeric arg, convert to float if(args.size!=1) throw SyntaxError("flt requires one numeric argument", position) - val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException() + val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val number = constval.asNumericValue ?: throw SyntaxError("flt requires one numeric argument", position) return LiteralValue(DataType.FLOAT, floatvalue = number.toDouble(), position = position) } -fun builtinAbs(args: List, position: Position, namespace:INameScope): LiteralValue { +fun builtinAbs(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { // 1 arg, type = float or int, result type= same as argument type if(args.size!=1) throw SyntaxError("abs requires one numeric argument", position) - val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException() + val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val number = constval.asNumericValue return when (number) { is Int, is Byte, is Short -> numericLiteral(Math.abs(number.toInt()), args[0].position) @@ -227,50 +250,73 @@ fun builtinAbs(args: List, position: Position, namespace:INameScope } -fun builtinLsb(args: List, position: Position, namespace:INameScope): LiteralValue - = oneIntArgOutputInt(args, position, namespace) { x: Int -> x and 255 } +fun builtinLsb(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneIntArgOutputInt(args, position, namespace, heap) { x: Int -> x and 255 } -fun builtinMsb(args: List, position: Position, namespace:INameScope): LiteralValue - = oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 8 and 255} +fun builtinMsb(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = oneIntArgOutputInt(args, position, namespace, heap) { x: Int -> x ushr 8 and 255} -fun builtinMin(args: List, position: Position, namespace:INameScope): LiteralValue - = collectionArgOutputNumber(args, position, namespace) { it.min()!! } +fun builtinMin(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = collectionArgOutputNumber(args, position, namespace, heap) { it.min()!! } -fun builtinMax(args: List, position: Position, namespace:INameScope): LiteralValue - = collectionArgOutputNumber(args, position, namespace) { it.max()!! } +fun builtinMax(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = collectionArgOutputNumber(args, position, namespace, heap) { it.max()!! } -fun builtinSum(args: List, position: Position, namespace:INameScope): LiteralValue - = collectionArgOutputNumber(args, position, namespace) { it.sum() } +fun builtinSum(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = collectionArgOutputNumber(args, position, namespace, heap) { it.sum() } -fun builtinAvg(args: List, position: Position, namespace:INameScope): LiteralValue { +fun builtinAvg(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { if(args.size!=1) - throw SyntaxError("avg requires one non-scalar argument", position) - val iterable = args[0].constValue(namespace) - if(iterable?.arrayvalue == null) - throw SyntaxError("avg requires one non-scalar argument", position) - val constants = iterable.arrayvalue.map { it.constValue(namespace)?.asNumericValue } - if(constants.contains(null)) - throw NotConstArgumentException() - val result = (constants.map { it!!.toDouble() }).average() + throw SyntaxError("avg requires array/matrix argument", position) + var iterable = args[0].constValue(namespace, heap) + if(iterable==null) { + if(args[0] !is IdentifierReference) + throw SyntaxError("avg over weird argument ${args[0]}", position) + iterable = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap) + ?: throw SyntaxError("avg over weird argument ${args[0]}", position) + } + + val result = if(iterable.arrayvalue!=null) { + val constants = iterable.arrayvalue!!.map { it.constValue(namespace, heap)?.asNumericValue } + if (constants.contains(null)) + throw NotConstArgumentException() + (constants.map { it!!.toDouble() }).average() + } + else { + val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("avg requires array/matrix argument", position) + array.average() + } return numericLiteral(result, args[0].position) } -fun builtinLen(args: List, position: Position, namespace:INameScope): LiteralValue { +fun builtinLen(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { if(args.size!=1) throw SyntaxError("len requires one argument", position) - val argument = args[0].constValue(namespace) ?: throw NotConstArgumentException() + var argument = args[0].constValue(namespace, heap) + if(argument==null) { + if(args[0] !is IdentifierReference) + throw SyntaxError("len over weird argument ${args[0]}", position) + argument = ((args[0] as IdentifierReference).targetStatement(namespace) as? VarDecl)?.value?.constValue(namespace, heap) + ?: throw SyntaxError("len over weird argument ${args[0]}", position) + } return when(argument.type) { - DataType.ARRAY, DataType.ARRAY_W -> numericLiteral(argument.arrayvalue!!.size, args[0].position) - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> numericLiteral(argument.strvalue!!.length, args[0].position) + DataType.ARRAY, DataType.ARRAY_W -> { + val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).array!!.size + numericLiteral(arraySize, args[0].position) + } + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { + val str = argument.strvalue ?: heap.get(argument.heapId!!).str!! + numericLiteral(str.length, args[0].position) + } else -> throw SyntaxError("len of weird argument ${args[0]}", position) } } -fun builtinAny(args: List, position: Position, namespace:INameScope): LiteralValue - = collectionArgOutputBoolean(args, position, namespace) { it.any { v -> v != 0.0} } +fun builtinAny(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = collectionArgOutputBoolean(args, position, namespace, heap) { it.any { v -> v != 0.0} } -fun builtinAll(args: List, position: Position, namespace:INameScope): LiteralValue - = collectionArgOutputBoolean(args, position, namespace) { it.all { v -> v != 0.0} } +fun builtinAll(args: List, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue + = collectionArgOutputBoolean(args, position, namespace, heap) { it.all { v -> v != 0.0} } private fun numericLiteral(value: Number, position: Position): LiteralValue { diff --git a/compiler/src/prog8/optimizing/ConstExprEvaluator.kt b/compiler/src/prog8/optimizing/ConstExprEvaluator.kt index cde1842ad..2e729ae32 100644 --- a/compiler/src/prog8/optimizing/ConstExprEvaluator.kt +++ b/compiler/src/prog8/optimizing/ConstExprEvaluator.kt @@ -184,7 +184,7 @@ class ConstExprEvaluator { } private fun multiply(left: LiteralValue, right: LiteralValue): LiteralValue { - val error = "cannot multiply $left and $right" + 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) @@ -200,13 +200,6 @@ class ConstExprEvaluator { right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.floatvalue * right.floatvalue, position = left.position) else -> throw ExpressionError(error, left.position) } - left.strvalue!=null -> when { - right.asIntegerValue!=null -> { - if(left.strvalue.length * right.asIntegerValue > 255) throw ExpressionError("string too long", left.position) - LiteralValue(DataType.STR, strvalue = left.strvalue.repeat(right.asIntegerValue), position = left.position) - } - else -> throw ExpressionError(error, left.position) - } else -> throw ExpressionError(error, left.position) } } diff --git a/compiler/src/prog8/optimizing/ConstantFolding.kt b/compiler/src/prog8/optimizing/ConstantFolding.kt index 62af95b84..9a109e70c 100644 --- a/compiler/src/prog8/optimizing/ConstantFolding.kt +++ b/compiler/src/prog8/optimizing/ConstantFolding.kt @@ -1,10 +1,11 @@ package prog8.optimizing import prog8.ast.* +import prog8.compiler.HeapValues import prog8.compiler.target.c64.Petscii -class ConstantFolding(private val namespace: INameScope) : IAstProcessor { +class ConstantFolding(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor { var optimizationsDone: Int = 0 var errors : MutableList = mutableListOf() @@ -94,8 +95,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor { */ override fun process(identifier: IdentifierReference): IExpression { return try { - val cval = identifier.constValue(namespace) ?: return identifier - val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, cval.strvalue, cval.arrayvalue, identifier.position) + val cval = identifier.constValue(namespace, heap) ?: return identifier + val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, cval.strvalue, cval.arrayvalue, position=identifier.position) copy.parent = identifier.parent return copy } catch (ax: AstException) { @@ -107,7 +108,7 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor { override fun process(functionCall: FunctionCall): IExpression { return try { super.process(functionCall) - functionCall.constValue(namespace) ?: functionCall + functionCall.constValue(namespace, heap) ?: functionCall } catch (ax: AstException) { addError(ax) functionCall @@ -183,8 +184,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor { override fun process(expr: BinaryExpression): IExpression { return try { super.process(expr) - val leftconst = expr.left.constValue(namespace) - val rightconst = expr.right.constValue(namespace) + val leftconst = expr.left.constValue(namespace, heap) + val rightconst = expr.right.constValue(namespace, heap) val subExpr: BinaryExpression? = when { leftconst!=null -> expr.right as? BinaryExpression @@ -192,8 +193,8 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor { else -> null } if(subExpr!=null) { - val subleftconst = subExpr.left.constValue(namespace) - val subrightconst = subExpr.right.constValue(namespace) + val subleftconst = subExpr.left.constValue(namespace, heap) + val subrightconst = subExpr.right.constValue(namespace, heap) if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) // try reordering. return groupTwoConstsTogether(expr, subExpr, @@ -286,12 +287,23 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor { } override fun process(literalValue: LiteralValue): LiteralValue { - if(literalValue.arrayvalue!=null) { + if(literalValue.strvalue!=null) { + // intern the string; move it into the heap + if(literalValue.strvalue.length !in 1..255) + addError(ExpressionError("string literal length must be between 1 and 255", literalValue.position)) + else { + val heapId = heap.add(literalValue.type, literalValue.strvalue) // TODO: we don't know the actual string type yet, STR != STR_P etc... + val newValue = LiteralValue(literalValue.type, heapId = heapId, position = literalValue.position) + return super.process(newValue) + } + } else if(literalValue.arrayvalue!=null) { val newArray = literalValue.arrayvalue.map { it.process(this) }.toTypedArray() // determine if the values are all bytes or that we need a word array instead var arrayDt = DataType.ARRAY + var allElementsAreConstant = true for (expr in newArray) { - val valueDt = expr.resultingDatatype(namespace) + allElementsAreConstant = allElementsAreConstant and (expr is LiteralValue) + val valueDt = expr.resultingDatatype(namespace, heap) if(valueDt==DataType.BYTE) continue else { @@ -299,6 +311,37 @@ class ConstantFolding(private val namespace: INameScope) : IAstProcessor { break } } + + // if the values are all constants, the array is moved to the heap + if(allElementsAreConstant) { + val array = newArray.map { + val litval = it as? LiteralValue + if(litval==null) { + addError(ExpressionError("array elements must all be constants", literalValue.position)) + return super.process(literalValue) + } + if(litval.bytevalue==null && litval.wordvalue==null) { + if(arrayDt==DataType.ARRAY) + addError(ExpressionError("byte array elements must all be integers 0..255", literalValue.position)) + else + addError(ExpressionError("word array elements must all be integers 0..65535", literalValue.position)) + return super.process(literalValue) + } + val integer = litval.asIntegerValue!! + if(arrayDt==DataType.ARRAY && integer !in 0..255) { + addError(ExpressionError("byte array elements must all be integers 0..255", literalValue.position)) + return super.process(literalValue) + } else if(arrayDt==DataType.ARRAY_W && integer !in 0..65535) { + addError(ExpressionError("word array elements must all be integers 0..65535", literalValue.position)) + return super.process(literalValue) + } + integer + }.toIntArray() + val heapId = heap.add(arrayDt, array) + val newValue = LiteralValue(arrayDt, heapId=heapId, position = literalValue.position) + return super.process(newValue) + } + val newValue = LiteralValue(arrayDt, arrayvalue = newArray, position = literalValue.position) return super.process(newValue) } diff --git a/compiler/src/prog8/optimizing/Extensions.kt b/compiler/src/prog8/optimizing/Extensions.kt index f46db0f5d..e8e216d98 100644 --- a/compiler/src/prog8/optimizing/Extensions.kt +++ b/compiler/src/prog8/optimizing/Extensions.kt @@ -3,17 +3,12 @@ package prog8.optimizing import prog8.ast.AstException import prog8.ast.INameScope import prog8.ast.Module +import prog8.compiler.HeapValues import prog8.parser.ParsingFailedError -/** - * TODO: array, matrix, string and float constants should be put into a constant-pool, - * so that they're only stored once instead of replicated everywhere. - * Note that initial constant folding of them is fine: it's needed to be able to - * optimize the expressions. But as a final step, they should be consolidated again - */ -fun Module.constantFold(globalNamespace: INameScope) { - val optimizer = ConstantFolding(globalNamespace) +fun Module.constantFold(globalNamespace: INameScope, heap: HeapValues) { + val optimizer = ConstantFolding(globalNamespace, heap) try { this.process(optimizer) } catch (ax: AstException) { @@ -35,8 +30,8 @@ fun Module.constantFold(globalNamespace: INameScope) { } -fun Module.optimizeStatements(globalNamespace: INameScope): Int { - val optimizer = StatementOptimizer(globalNamespace) +fun Module.optimizeStatements(globalNamespace: INameScope, heap: HeapValues): Int { + val optimizer = StatementOptimizer(globalNamespace, heap) this.process(optimizer) if(optimizer.optimizationsDone > 0) println("[${this.name}] Debug: ${optimizer.optimizationsDone} statement optimizations performed") @@ -44,8 +39,8 @@ fun Module.optimizeStatements(globalNamespace: INameScope): Int { return optimizer.optimizationsDone } -fun Module.simplifyExpressions(namespace: INameScope) : Int { - val optimizer = SimplifyExpressions(namespace) +fun Module.simplifyExpressions(namespace: INameScope, heap: HeapValues) : Int { + val optimizer = SimplifyExpressions(namespace, heap) this.process(optimizer) if(optimizer.optimizationsDone > 0) println("[${this.name}] Debug: ${optimizer.optimizationsDone} expression optimizations performed") diff --git a/compiler/src/prog8/optimizing/SimplifyExpressions.kt b/compiler/src/prog8/optimizing/SimplifyExpressions.kt index 7d50d010b..50d7b2a1d 100644 --- a/compiler/src/prog8/optimizing/SimplifyExpressions.kt +++ b/compiler/src/prog8/optimizing/SimplifyExpressions.kt @@ -1,6 +1,7 @@ package prog8.optimizing import prog8.ast.* +import prog8.compiler.HeapValues import kotlin.math.abs /* @@ -18,7 +19,7 @@ import kotlin.math.abs */ -class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor { +class SimplifyExpressions(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor { var optimizationsDone: Int = 0 override fun process(assignment: Assignment): IStatement { @@ -30,8 +31,8 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor { override fun process(expr: BinaryExpression): IExpression { super.process(expr) - val leftVal = expr.left.constValue(namespace) - val rightVal = expr.right.constValue(namespace) + val leftVal = expr.left.constValue(namespace, heap) + val rightVal = expr.right.constValue(namespace, heap) val constTrue = LiteralValue.fromBoolean(true, expr.position) val constFalse = LiteralValue.fromBoolean(false, expr.position) @@ -121,9 +122,9 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor { expr.left = expr.right expr.right = tmp optimizationsDone++ - return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(namespace), leftVal) + return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(namespace, heap), leftVal) } - return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(namespace)) + return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(namespace, heap)) } private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression { @@ -284,13 +285,13 @@ class SimplifyExpressions(private val namespace: INameScope) : IAstProcessor { } } - if (expr.left.resultingDatatype(namespace) == DataType.BYTE) { + if (expr.left.resultingDatatype(namespace, heap) == DataType.BYTE) { if(abs(rightConst.asNumericValue!!.toDouble()) >= 256.0) { optimizationsDone++ return LiteralValue(DataType.BYTE, 0, position = expr.position) } } - else if (expr.left.resultingDatatype(namespace) == DataType.WORD) { + else if (expr.left.resultingDatatype(namespace, heap) == DataType.WORD) { if(abs(rightConst.asNumericValue!!.toDouble()) >= 65536.0) { optimizationsDone++ return LiteralValue(DataType.BYTE, 0, position = expr.position) diff --git a/compiler/src/prog8/optimizing/StatementOptimizer.kt b/compiler/src/prog8/optimizing/StatementOptimizer.kt index 40580389f..256981241 100644 --- a/compiler/src/prog8/optimizing/StatementOptimizer.kt +++ b/compiler/src/prog8/optimizing/StatementOptimizer.kt @@ -1,6 +1,7 @@ package prog8.optimizing import prog8.ast.* +import prog8.compiler.HeapValues import prog8.functions.BuiltinFunctionNames import prog8.functions.BuiltinFunctionsWithoutSideEffects @@ -9,6 +10,7 @@ import prog8.functions.BuiltinFunctionsWithoutSideEffects todo remove unused blocks todo remove unused variables todo remove unused subroutines + todo remove unused strings and arrays from the heap todo remove if statements with empty statement blocks todo replace if statements with only else block todo statement optimization: create augmented assignment from assignment that only refers to its lvalue (A=A+10, A=4*A, ...) @@ -23,7 +25,7 @@ import prog8.functions.BuiltinFunctionsWithoutSideEffects todo inline subroutines that are "sufficiently small" */ -class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcessor { +class StatementOptimizer(private val globalNamespace: INameScope, private val heap: HeapValues) : IAstProcessor { var optimizationsDone: Int = 0 private set @@ -43,7 +45,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso override fun process(ifStatement: IfStatement): IStatement { super.process(ifStatement) - val constvalue = ifStatement.condition.constValue(globalNamespace) + val constvalue = ifStatement.condition.constValue(globalNamespace, heap) if(constvalue!=null) { return if(constvalue.asBooleanValue){ // always true -> keep only if-part @@ -75,7 +77,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso override fun process(whileLoop: WhileLoop): IStatement { super.process(whileLoop) - val constvalue = whileLoop.condition.constValue(globalNamespace) + val constvalue = whileLoop.condition.constValue(globalNamespace, heap) if(constvalue!=null) { return if(constvalue.asBooleanValue){ // always true @@ -92,7 +94,7 @@ class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcesso override fun process(repeatLoop: RepeatLoop): IStatement { super.process(repeatLoop) - val constvalue = repeatLoop.untilCondition.constValue(globalNamespace) + val constvalue = repeatLoop.untilCondition.constValue(globalNamespace, heap) if(constvalue!=null) { return if(constvalue.asBooleanValue){ // always true -> keep only the statement block diff --git a/compiler/src/prog8/parser/prog8Lexer.java b/compiler/src/prog8/parser/prog8Lexer.java index 1e22cce95..c1f8b76ff 100644 --- a/compiler/src/prog8/parser/prog8Lexer.java +++ b/compiler/src/prog8/parser/prog8Lexer.java @@ -1,13 +1,12 @@ // Generated from /home/irmen/Projects/prog8/compiler/antlr/prog8.g4 by ANTLR 4.7 package prog8.parser; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.CharStream; -import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.TokenStream; + import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.atn.*; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.LexerATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; import org.antlr.v4.runtime.dfa.DFA; -import org.antlr.v4.runtime.misc.*; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class prog8Lexer extends Lexer { diff --git a/compiler/src/prog8/parser/prog8Parser.java b/compiler/src/prog8/parser/prog8Parser.java index 3d82f60da..4fc26a200 100644 --- a/compiler/src/prog8/parser/prog8Parser.java +++ b/compiler/src/prog8/parser/prog8Parser.java @@ -1,13 +1,15 @@ // Generated from /home/irmen/Projects/prog8/compiler/antlr/prog8.g4 by ANTLR 4.7 package prog8.parser; -import org.antlr.v4.runtime.atn.*; -import org.antlr.v4.runtime.dfa.DFA; + import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.misc.*; -import org.antlr.v4.runtime.tree.*; +import org.antlr.v4.runtime.atn.ATN; +import org.antlr.v4.runtime.atn.ATNDeserializer; +import org.antlr.v4.runtime.atn.ParserATNSimulator; +import org.antlr.v4.runtime.atn.PredictionContextCache; +import org.antlr.v4.runtime.dfa.DFA; +import org.antlr.v4.runtime.tree.TerminalNode; + import java.util.List; -import java.util.Iterator; -import java.util.ArrayList; @SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"}) public class prog8Parser extends Parser { diff --git a/compiler/src/prog8/stackvm/Program.kt b/compiler/src/prog8/stackvm/Program.kt new file mode 100644 index 000000000..39612579c --- /dev/null +++ b/compiler/src/prog8/stackvm/Program.kt @@ -0,0 +1,328 @@ +package prog8.stackvm + +import prog8.ast.DataType +import prog8.compiler.HeapValues +import java.io.File +import java.io.PrintStream +import java.util.* +import java.util.regex.Pattern + +class Program (val name: String, + prog: MutableList, + val labels: Map, + val variables: Map>, + val memory: Map>, + val heap: HeapValues) +{ + companion object { + fun load(filename: String): Program { + val lines = File(filename).readLines().withIndex().iterator() + val memory = mutableMapOf>() + val vars = mutableMapOf>() + var instructions = mutableListOf() + var labels = mapOf() + val heap = HeapValues() + while(lines.hasNext()) { + val (lineNr, line) = lines.next() + if(line.startsWith(';') || line.isEmpty()) + continue + else if(line=="%memory") + loadMemory(lines, memory) + else if(line=="%heap") + loadHeap(lines, heap) + else if(line=="%variables") + loadVars(lines, vars) + else if(line=="%instructions") { + val (insResult, labelResult) = loadInstructions(lines, heap) + instructions = insResult + labels = labelResult + } + else throw VmExecutionException("syntax error at line ${lineNr + 1}") + } + return Program(filename, instructions, labels, vars, memory, heap) + } + + private fun loadHeap(lines: Iterator>, heap: HeapValues) { + val splitpattern = Pattern.compile("\\s+") + val heapvalues = mutableListOf>() + while(true) { + val (lineNr, line) = lines.next() + if (line == "%end_heap") + break + val parts = line.split(splitpattern, limit=3) + val value = Triple(parts[0].toInt(), DataType.valueOf(parts[1].toUpperCase()), parts[2]) + heapvalues.add(value) + } + heapvalues.sortedBy { it.first }.forEach { + when(it.second) { + DataType.STR, + DataType.STR_P, + DataType.STR_S, + DataType.STR_PS -> heap.add(it.second, unescape(it.third.substring(1, it.third.length-1))) + DataType.ARRAY, + DataType.ARRAY_W, + DataType.MATRIX -> { + val numbers = it.third.substring(1, it.third.length-1).split(',') + val intarray = numbers.map{it.trim().toInt()}.toIntArray() + heap.add(it.second, intarray) + } + else -> throw VmExecutionException("invalid heap value type $it.second") + } + } + } + + private fun loadInstructions(lines: Iterator>, heap: HeapValues): Pair, Map> { + val instructions = mutableListOf() + val labels = mutableMapOf() + val splitpattern = Pattern.compile("\\s+") + val nextInstructionLabels = Stack() // more than one label can occur on the same line + + while(true) { + val (lineNr, line) = lines.next() + if(line=="%end_instructions") + return Pair(instructions, labels) + if(!line.startsWith(' ') && line.endsWith(':')) { + nextInstructionLabels.push(line.substring(0, line.length-1)) + } else if(line.startsWith(' ')) { + val parts = line.trimStart().split(splitpattern, limit = 2) + val opcodeStr = parts[0].toUpperCase() + val opcode= Opcode.valueOf(if(opcodeStr.startsWith('_')) opcodeStr.substring(1) else opcodeStr) + val args = if(parts.size==2) parts[1] else null + val instruction = when(opcode) { + Opcode.LINE -> Instruction(opcode, null, callLabel = args) + Opcode.JUMP, Opcode.CALL, Opcode.BNEG, Opcode.BPOS, + Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC -> { + if(args!!.startsWith('$')) { + Instruction(opcode, Value(DataType.WORD, args.substring(1).toInt(16))) + } else { + Instruction(opcode, callLabel = args) + } + } + Opcode.INC_VAR, Opcode.DEC_VAR, + Opcode.SHR_VAR, Opcode.SHL_VAR, Opcode.ROL_VAR, Opcode.ROR_VAR, + Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR -> { + val withoutQuotes = + if(args!!.startsWith('"') && args.endsWith('"')) + args.substring(1, args.length-1) else args + + Instruction(opcode, callLabel = withoutQuotes) + } + Opcode.SYSCALL -> { + val call = Syscall.valueOf(args!!) + Instruction(opcode, Value(DataType.BYTE, call.callNr)) + } + else -> { + Instruction(opcode, getArgValue(args, heap)) + } + } + instructions.add(instruction) + while(nextInstructionLabels.isNotEmpty()) { + val label = nextInstructionLabels.pop() + labels[label] = instruction + } + } else throw VmExecutionException("syntax error at line ${lineNr + 1}") + } + } + + private fun getArgValue(args: String?, heap: HeapValues): Value? { + if(args==null) + return null + if(args[0]=='"' && args[args.length-1]=='"') { + throw VmExecutionException("encountered a string arg value, but all strings should already have been moved into the heap") + } + val (type, valueStr) = args.split(':') + return when(type) { + "b" -> Value(DataType.BYTE, valueStr.toShort(16)) + "w" -> Value(DataType.WORD, valueStr.toInt(16)) + "f" -> Value(DataType.FLOAT, valueStr.toDouble()) + "heap" -> { + val heapId = valueStr.toInt() + Value(heap.get(heapId).type, heapId) + } + else -> throw VmExecutionException("invalid datatype $type") + } + } + + private fun loadVars(lines: Iterator>, + vars: MutableMap>): Map> { + val splitpattern = Pattern.compile("\\s+") + while(true) { + val (lineNr, line) = lines.next() + if(line=="%end_variables") + return vars + val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3) + if(valueStr[0] !='"' && !valueStr.contains(':')) + throw VmExecutionException("missing value type character") + val type = DataType.valueOf(typeStr.toUpperCase()) + val value = when(type) { + DataType.BYTE -> Value(DataType.BYTE, valueStr.substring(2).toShort(16)) + DataType.WORD -> Value(DataType.WORD, valueStr.substring(2).toInt(16)) + DataType.FLOAT -> Value(DataType.FLOAT, valueStr.substring(2).toDouble()) + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { + 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() + Value(type, heapId) + } + } + DataType.ARRAY, + DataType.ARRAY_W, + DataType.MATRIX -> { + if(!valueStr.startsWith("heap:")) + throw VmExecutionException("invalid array/matrix value, should be a heap reference") + else { + val heapId = valueStr.substring(5).toInt() + Value(type, heapId) + } + } + } + val blockname = name.substringBefore('.') + val blockvars = vars[blockname] ?: mutableMapOf() + vars[blockname] = blockvars + blockvars[name] = value + } + } + + private fun unescape(st: String): String { + val result = mutableListOf() + val iter = st.iterator() + while(iter.hasNext()) { + val c = iter.nextChar() + if(c=='\\') { + val ec = iter.nextChar() + result.add(when(ec) { + '\\' -> '\\' + 'b' -> '\b' + 'n' -> '\n' + 'r' -> '\r' + 't' -> '\t' + 'u' -> { + "${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar() + } + else -> throw VmExecutionException("invalid escape char: $ec") + }) + } else { + result.add(c) + } + } + return result.joinToString("") + } + + private fun loadMemory(lines: Iterator>, memory: MutableMap>): Map> { + while(true) { + val (lineNr, line) = lines.next() + if(line=="%end_memory") + return memory + val address = line.substringBefore(' ').toInt(16) + val rest = line.substringAfter(' ').trim() + if(rest.startsWith('"')) { + TODO("memory init with char/string") + } else { + val valueStrings = rest.split(' ') + val values = mutableListOf() + valueStrings.forEach { + when(it.length) { + 2 -> values.add(Value(DataType.BYTE, it.toShort(16))) + 4 -> values.add(Value(DataType.WORD, it.toInt(16))) + else -> throw VmExecutionException("invalid value at line $lineNr+1") + } + } + memory[address] = values + } + } + } + } + + val program: List + + init { + prog.add(Instruction(Opcode.TERMINATE)) + prog.add(Instruction(Opcode.NOP)) + program = prog + connect() + } + + private fun connect() { + val it1 = program.iterator() + val it2 = program.iterator() + it2.next() + + while(it1.hasNext() && it2.hasNext()) { + val instr = it1.next() + val nextInstr = it2.next() + when(instr.opcode) { + Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction + Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic + Opcode.JUMP -> { + if(instr.callLabel==null) { + throw VmExecutionException("stackVm doesn't support JUMP to memory address") + } else { + // jump to label + val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") + instr.next = target + } + } + Opcode.BCC, Opcode.BCS, Opcode.BZ, Opcode.BNZ, Opcode.BNEG, Opcode.BPOS -> { + if(instr.callLabel==null) { + throw VmExecutionException("stackVm doesn't support branch to memory address") + } else { + // branch to label + val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") + instr.next = jumpInstr + instr.nextAlt = nextInstr + } + } + Opcode.CALL -> { + if(instr.callLabel==null) { + throw VmExecutionException("stackVm doesn't support CALL to memory address") + } else { + // call label + val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") + instr.next = jumpInstr + instr.nextAlt = nextInstr // instruction to return to + } + } + else -> instr.next = nextInstr + } + } + } + + fun print(out: PrintStream, embeddedLabels: Boolean=true) { + out.println("; stackVM program code for '$name'") + out.println("%memory") + if(memory.isNotEmpty()) { + TODO("print out initial memory load") + } + out.println("%end_memory") + out.println("%heap") + heap.allStrings().forEach { + out.println("${it.index} ${it.value.type.toString().toLowerCase()} \"${it.value.str}\"") + } + heap.allArrays().forEach { + out.println("${it.index} ${it.value.type.toString().toLowerCase()} ${it.value.array!!.toList()}") + } + out.println("%end_heap") + out.println("%variables") + // just flatten all block vars into one global list for now... + for(variable in variables.flatMap { e->e.value.entries}) { + val valuestr = variable.value.toString() + out.println("${variable.key} ${variable.value.type.toString().toLowerCase()} $valuestr") + } + out.println("%end_variables") + out.println("%instructions") + val labels = this.labels.entries.associateBy({it.value}) {it.key} + for(instr in this.program) { + if(!embeddedLabels) { + val label = labels[instr] + if (label != null) + out.println("$label:") + } else { + out.println(instr) + } + } + out.println("%end_instructions") + } +} \ No newline at end of file diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index 0737b502a..4054e0ce1 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -1,11 +1,11 @@ package prog8.stackvm import prog8.ast.DataType +import prog8.compiler.HeapValues import prog8.compiler.target.c64.Petscii import java.io.File import java.io.PrintStream import java.util.* -import java.util.regex.Pattern import kotlin.math.* enum class Opcode { @@ -17,7 +17,6 @@ enum class Opcode { PUSH_MEM_F, // push float value from memory to stack PUSH_VAR, // push a variable DUP, // push topmost value once again - ARRAY, // create arrayvalue of number of integer values on the stack, given in argument // popping values off the (evaluation) stack, possibly storing them in another location DISCARD, // discard top value @@ -165,373 +164,6 @@ enum class Syscall(val callNr: Short) { } -class Value(val type: DataType, numericvalue: Number?, val stringvalue: String?=null, val arrayvalue: IntArray?=null) { - private var byteval: Short? = null - private var wordval: Int? = null - private var floatval: Double? = null - val asBooleanValue: Boolean - - init { - when(type) { - DataType.BYTE -> { - byteval = (numericvalue!!.toInt() and 255).toShort() // byte wrap around 0..255 - asBooleanValue = byteval != (0.toShort()) - } - DataType.WORD -> { - wordval = numericvalue!!.toInt() and 65535 // word wrap around 0..65535 - asBooleanValue = wordval != 0 - } - DataType.FLOAT -> { - floatval = numericvalue!!.toDouble() - asBooleanValue = floatval != 0.0 - } - DataType.ARRAY, DataType.ARRAY_W -> { - asBooleanValue = arrayvalue!!.isNotEmpty() - } - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { - if(stringvalue==null) throw VmExecutionException("expect stringvalue for STR type") - asBooleanValue = stringvalue.isNotEmpty() - } - else -> throw VmExecutionException("invalid datatype $type") - } - } - - override fun toString(): String { - return when(type) { - DataType.BYTE -> "b:%02x".format(byteval) - DataType.WORD -> "w:%04x".format(wordval) - DataType.FLOAT -> "f:$floatval" - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> "\"$stringvalue\"" - DataType.ARRAY -> TODO("tostring array") - DataType.ARRAY_W -> TODO("tostring word array") - DataType.MATRIX -> TODO("tostring matrix") - } - } - - fun numericValue(): Number { - return when(type) { - DataType.BYTE -> byteval!! - DataType.WORD-> wordval!! - DataType.FLOAT -> floatval!! - else -> throw VmExecutionException("invalid datatype for numeric value: $type") - } - } - - fun integerValue(): Int { - return when(type) { - DataType.BYTE -> byteval!!.toInt() - DataType.WORD-> wordval!! - DataType.FLOAT -> throw VmExecutionException("float to integer loss of precision") - else -> throw VmExecutionException("invalid datatype for integer value: $type") - } - } - - override fun hashCode(): Int { - val bh = byteval?.hashCode() ?: 0x10001234 - val wh = wordval?.hashCode() ?: 0x01002345 - val fh = floatval?.hashCode() ?: 0x00103456 - val ah = arrayvalue?.hashCode() ?: 0x11119876 - return bh xor wh xor fh xor ah xor type.hashCode() - } - - override fun equals(other: Any?): Boolean { - if(other==null || other !is Value) - return false - return compareTo(other)==0 // note: datatype doesn't matter - } - - operator fun compareTo(other: Value): Int { - return when(type) { - DataType.BYTE, DataType.WORD, DataType.FLOAT -> { - when(other.type) { - DataType.BYTE, DataType.WORD, DataType.FLOAT -> { - numericValue().toDouble().compareTo(other.numericValue().toDouble()) - } - else -> throw VmExecutionException("comparison can only be done between two numeric values") - } - } - else -> throw VmExecutionException("comparison can only be done between two numeric values") - } - } - - private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): Value { - if(result.toDouble() < 0 ) { - return when(leftDt) { - DataType.BYTE -> { - // BYTE can become WORD if right operand is WORD, or when value is too large for byte - when(rightDt) { - DataType.BYTE -> Value(DataType.BYTE, result.toInt() and 255) - DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535) - DataType.FLOAT -> throw VmExecutionException("floating point loss of precision") - else -> throw VmExecutionException("$op on non-numeric result type") - } - } - DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535) - DataType.FLOAT -> Value(DataType.FLOAT, result) - else -> throw VmExecutionException("$op on non-numeric type") - } - } - - return when(leftDt) { - DataType.BYTE -> { - // BYTE can become WORD if right operand is WORD, or when value is too large for byte - if(result.toDouble() >= 256) - return Value(DataType.WORD, result) - when(rightDt) { - DataType.BYTE -> Value(DataType.BYTE, result) - DataType.WORD -> Value(DataType.WORD, result) - DataType.FLOAT -> throw VmExecutionException("floating point loss of precision") - else -> throw VmExecutionException("$op on non-numeric result type") - } - } - DataType.WORD -> Value(DataType.WORD, result) - DataType.FLOAT -> Value(DataType.FLOAT, result) - else -> throw VmExecutionException("$op on non-numeric type") - } - } - - fun add(other: Value): Value { - if(other.type == DataType.FLOAT && (type!=DataType.FLOAT)) - throw VmExecutionException("floating point loss of precision on type $type") - val v1 = numericValue() - val v2 = other.numericValue() - val result = v1.toDouble() + v2.toDouble() - return arithResult(type, result, other.type, "add") - } - - fun sub(other: Value): Value { - if(other.type == DataType.FLOAT && (type!=DataType.FLOAT)) - throw VmExecutionException("floating point loss of precision on type $type") - val v1 = numericValue() - val v2 = other.numericValue() - val result = v1.toDouble() - v2.toDouble() - return arithResult(type, result, other.type, "sub") - } - - fun mul(other: Value): Value { - if(other.type == DataType.FLOAT && (type!=DataType.FLOAT)) - throw VmExecutionException("floating point loss of precision on type $type") - val v1 = numericValue() - val v2 = other.numericValue() - val result = v1.toDouble() * v2.toDouble() - return arithResult(type, result, other.type, "mul") - } - - fun div(other: Value): Value { - if(other.type == DataType.FLOAT && (type!=DataType.FLOAT)) - throw VmExecutionException("floating point loss of precision on type $type") - val v1 = numericValue() - val v2 = other.numericValue() - if(v2.toDouble()==0.0) { - if (type == DataType.BYTE) - return Value(DataType.BYTE, 255) - else if(type == DataType.WORD) - return Value(DataType.WORD, 65535) - } - val result = v1.toDouble() / v2.toDouble() - // NOTE: integer division returns integer result! - return when(type) { - DataType.BYTE -> Value(DataType.BYTE, result) - DataType.WORD -> Value(DataType.WORD, result) - DataType.FLOAT -> Value(DataType.FLOAT, result) - else -> throw VmExecutionException("div on non-numeric type") - } - } - - fun floordiv(other: Value): Value { - if(other.type == DataType.FLOAT && (type!=DataType.FLOAT)) - throw VmExecutionException("floating point loss of precision on type $type") - val v1 = numericValue() - val v2 = other.numericValue() - val result = floor(v1.toDouble() / v2.toDouble()) - // NOTE: integer division returns integer result! - return when(type) { - DataType.BYTE -> Value(DataType.BYTE, result) - DataType.WORD -> Value(DataType.WORD, result) - DataType.FLOAT -> Value(DataType.FLOAT, result) - else -> throw VmExecutionException("div on non-numeric type") - } - } - - fun remainder(other: Value): Value? { - val v1 = numericValue() - val v2 = other.numericValue() - val result = v1.toDouble() % v2.toDouble() - return arithResult(type, result, other.type, "remainder") - } - - fun pow(other: Value): Value { - val v1 = numericValue() - val v2 = other.numericValue() - val result = v1.toDouble().pow(v2.toDouble()) - return arithResult(type, result, other.type,"pow") - } - - fun shl(): Value { - val v = integerValue() - return Value(type, v shl 1) - } - - fun shr(): Value { - val v = integerValue() - return Value(type, v ushr 1) - } - - fun rol(carry: Boolean): Pair { - // 9 or 17 bit rotate left (with carry)) - return when(type) { - DataType.BYTE -> { - val v = byteval!!.toInt() - val newCarry = (v and 0x80) != 0 - val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0) - Pair(Value(DataType.BYTE, newval), newCarry) - } - DataType.WORD -> { - val v = wordval!! - val newCarry = (v and 0x8000) != 0 - val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0) - Pair(Value(DataType.WORD, newval), newCarry) - } - else -> throw VmExecutionException("rol can only work on byte/word") - } - } - - fun ror(carry: Boolean): Pair { - // 9 or 17 bit rotate right (with carry) - return when(type) { - DataType.BYTE -> { - val v = byteval!!.toInt() - val newCarry = v and 1 != 0 - val newval = (v ushr 1) or (if(carry) 0x80 else 0) - Pair(Value(DataType.BYTE, newval), newCarry) - } - DataType.WORD -> { - val v = wordval!! - val newCarry = v and 1 != 0 - val newval = (v ushr 1) or (if(carry) 0x8000 else 0) - Pair(Value(DataType.WORD, newval), newCarry) - } - else -> throw VmExecutionException("ror2 can only work on byte/word") - } - } - - fun rol2(): Value { - // 8 or 16 bit rotate left - return when(type) { - DataType.BYTE -> { - val v = byteval!!.toInt() - val carry = (v and 0x80) ushr 7 - val newval = (v and 0x7f shl 1) or carry - Value(DataType.BYTE, newval) - } - DataType.WORD -> { - val v = wordval!! - val carry = (v and 0x8000) ushr 15 - val newval = (v and 0x7fff shl 1) or carry - Value(DataType.WORD, newval) - } - else -> throw VmExecutionException("rol2 can only work on byte/word") - } - } - - fun ror2(): Value { - // 8 or 16 bit rotate right - return when(type) { - DataType.BYTE -> { - val v = byteval!!.toInt() - val carry = v and 1 shl 7 - val newval = (v ushr 1) or carry - Value(DataType.BYTE, newval) - } - DataType.WORD -> { - val v = wordval!! - val carry = v and 1 shl 15 - val newval = (v ushr 1) or carry - Value(DataType.WORD, newval) - } - else -> throw VmExecutionException("ror2 can only work on byte/word") - } - } - - fun neg(): Value { - return when(type) { - DataType.BYTE -> Value(DataType.BYTE, -(byteval!!)) - DataType.WORD -> Value(DataType.WORD, -(wordval!!)) - DataType.FLOAT -> Value(DataType.FLOAT, -(floatval)!!) - else -> throw VmExecutionException("neg can only work on byte/word/float") - } - } - - fun bitand(other: Value): Value { - val v1 = integerValue() - val v2 = other.integerValue() - val result = v1 and v2 - return Value(type, result) - } - - fun bitor(other: Value): Value { - val v1 = integerValue() - val v2 = other.integerValue() - val result = v1 or v2 - return Value(type, result) - } - - fun bitxor(other: Value): Value { - val v1 = integerValue() - val v2 = other.integerValue() - val result = v1 xor v2 - return Value(type, result) - } - - fun and(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue && other.asBooleanValue) 1 else 0) - fun or(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue || other.asBooleanValue) 1 else 0) - fun xor(other: Value) = Value(DataType.BYTE, if(this.asBooleanValue xor other.asBooleanValue) 1 else 0) - fun not() = Value(DataType.BYTE, if(this.asBooleanValue) 0 else 1) - - fun inv(): Value { - return when(type) { - DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv() and 255) - DataType.WORD -> Value(DataType.WORD, wordval!!.inv() and 65535) - else -> throw VmExecutionException("inv can only work on byte/word") - } - } - - fun inc(): Value { - return when(type) { - DataType.BYTE -> Value(DataType.BYTE, (byteval!! + 1) and 255) - DataType.WORD -> Value(DataType.WORD, (wordval!! + 1) and 65535) - DataType.FLOAT -> Value(DataType.FLOAT, floatval!! + 1) - else -> throw VmExecutionException("inc can only work on byte/word/float") - } - } - - fun dec(): Value { - return when(type) { - DataType.BYTE -> Value(DataType.BYTE, (byteval!! - 1) and 255) - DataType.WORD -> Value(DataType.WORD, (wordval!! - 1) and 65535) - DataType.FLOAT -> Value(DataType.FLOAT, floatval!! - 1) - else -> throw VmExecutionException("dec can only work on byte/word/float") - } - } - - fun lsb(): Value { - return when(type) { - DataType.BYTE -> Value(DataType.BYTE, byteval!!) - DataType.WORD -> Value(DataType.BYTE, wordval!! and 255) - else -> throw VmExecutionException("not can only work on byte/word") - } - } - - fun msb(): Value { - return when(type) { - DataType.BYTE -> Value(DataType.BYTE, 0) - DataType.WORD -> Value(DataType.BYTE, wordval!! ushr 8 and 255) - else -> throw VmExecutionException("not can only work on byte/word") - } - } -} - - open class Instruction(val opcode: Opcode, val arg: Value? = null, val callLabel: String? = null) @@ -547,14 +179,14 @@ open class Instruction(val opcode: Opcode, Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR) val result = when { - opcode==Opcode.LINE -> "_line ${arg!!.stringvalue}" + opcode==Opcode.LINE -> "_line $callLabel" opcode==Opcode.SYSCALL -> { val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() } "syscall $syscall" } opcodesWithVarArgument.contains(opcode) -> { // opcodes that manipulate a variable - "${opcode.toString().toLowerCase()} ${argStr.substring(1, argStr.length-1)}" + "${opcode.toString().toLowerCase()} $callLabel" } callLabel==null -> "${opcode.toString().toLowerCase()} $argStr" else -> "${opcode.toString().toLowerCase()} $callLabel $argStr" @@ -589,269 +221,6 @@ class MyStack : Stack() { } } -class Program (val name: String, - prog: MutableList, - val labels: Map, - val variables: Map>, - val memory: Map>) -{ - companion object { - fun load(filename: String): Program { - val lines = File(filename).readLines().withIndex().iterator() - var memory = mapOf>() - var vars = mapOf>() - var instructions = mutableListOf() - var labels = mapOf() - while(lines.hasNext()) { - val (lineNr, line) = lines.next() - if(line.startsWith(';') || line.isEmpty()) - continue - else if(line=="%memory") - memory = loadMemory(lines) - else if(line=="%variables") - vars = loadVars(lines) - else if(line=="%instructions") { - val (insResult, labelResult) = loadInstructions(lines) - instructions = insResult - labels = labelResult - } - else throw VmExecutionException("syntax error at line ${lineNr+1}") - } - return Program(filename, instructions, labels, vars, memory) - } - - private fun loadInstructions(lines: Iterator>): Pair, Map> { - val instructions = mutableListOf() - val labels = mutableMapOf() - val splitpattern = Pattern.compile("\\s+") - val nextInstructionLabels = Stack() // more than one label can occur on the same line - - while(true) { - val (lineNr, line) = lines.next() - if(line=="%end_instructions") - return Pair(instructions, labels) - if(!line.startsWith(' ') && line.endsWith(':')) { - nextInstructionLabels.push(line.substring(0, line.length-1)) - } else if(line.startsWith(' ')) { - val parts = line.trimStart().split(splitpattern, limit = 2) - val opcodeStr = parts[0].toUpperCase() - val opcode=Opcode.valueOf(if(opcodeStr.startsWith('_')) opcodeStr.substring(1) else opcodeStr) - val args = if(parts.size==2) parts[1] else null - val instruction = when(opcode) { - Opcode.LINE -> Instruction(opcode, Value(DataType.STR, null, stringvalue = args)) - Opcode.JUMP, Opcode.CALL, Opcode.BNEG, Opcode.BPOS, - Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC -> { - if(args!!.startsWith('$')) { - Instruction(opcode, Value(DataType.WORD, args.substring(1).toInt(16))) - } else { - Instruction(opcode, callLabel = args) - } - } - Opcode.INC_VAR, Opcode.DEC_VAR, - Opcode.SHR_VAR, Opcode.SHL_VAR, Opcode.ROL_VAR, Opcode.ROR_VAR, - Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR -> { - val withoutQuotes = - if(args!!.startsWith('"') && args.endsWith('"')) - args.substring(1, args.length-1) else args - Instruction(opcode, Value(DataType.STR, null, withoutQuotes)) - } - Opcode.SYSCALL -> { - val call = Syscall.valueOf(args!!) - Instruction(opcode, Value(DataType.BYTE, call.callNr)) - } - else -> { - // println("INSTR $opcode at $lineNr args=$args") - Instruction(opcode, getArgValue(args)) - } - } - instructions.add(instruction) - while(nextInstructionLabels.isNotEmpty()) { - val label = nextInstructionLabels.pop() - labels[label] = instruction - } - } else throw VmExecutionException("syntax error at line ${lineNr+1}") - } - } - - private fun getArgValue(args: String?): Value? { - if(args==null) - return null - if(args[0]=='"' && args[args.length-1]=='"') { - // it's a string. - return Value(DataType.STR, null, unescape(args.substring(1, args.length-1))) - } - val (type, valueStr) = args.split(':') - return when(type) { - "b" -> Value(DataType.BYTE, valueStr.toShort(16)) - "w" -> Value(DataType.WORD, valueStr.toInt(16)) - "f" -> Value(DataType.FLOAT, valueStr.toDouble()) - else -> throw VmExecutionException("invalid datatype $type") - } - } - - private fun loadVars(lines: Iterator>): Map> { - val vars = mutableMapOf>() - val splitpattern = Pattern.compile("\\s+") - while(true) { - val (lineNr, line) = lines.next() - if(line=="%end_variables") - return vars - val (name, type, valueStr) = line.split(splitpattern, limit = 3) - if(valueStr[0] !='"' && valueStr[1]!=':') - throw VmExecutionException("missing value type character") - val value = when(type) { - "byte" -> Value(DataType.BYTE, valueStr.substring(2).toShort(16)) - "word" -> Value(DataType.WORD, valueStr.substring(2).toInt(16)) - "float" -> Value(DataType.FLOAT, valueStr.substring(2).toDouble()) - "str", "str_p", "str_s", "str_ps" -> { - if(valueStr.startsWith('"') && valueStr.endsWith('"')) - Value(DataType.STR, null, unescape(valueStr.substring(1, valueStr.length-1))) - else - throw VmExecutionException("str should be enclosed in quotes at line ${lineNr+1}") - } - else -> throw VmExecutionException("invalid datatype at line ${lineNr+1}") - } - val blockname = name.substringBefore('.') - val blockvars = vars[blockname] ?: mutableMapOf() - vars[blockname] = blockvars - blockvars[name] = value - } - } - - private fun unescape(st: String): String { - val result = mutableListOf() - val iter = st.iterator() - while(iter.hasNext()) { - val c = iter.nextChar() - if(c=='\\') { - val ec = iter.nextChar() - result.add(when(ec) { - '\\' -> '\\' - 'b' -> '\b' - 'n' -> '\n' - 'r' -> '\r' - 't' -> '\t' - 'u' -> { - "${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar() - } - else -> throw VmExecutionException("invalid escape char: $ec") - }) - } else { - result.add(c) - } - } - return result.joinToString("") - } - - private fun loadMemory(lines: Iterator>): Map> { - val memory = mutableMapOf>() - while(true) { - val (lineNr, line) = lines.next() - if(line=="%end_memory") - return memory - val address = line.substringBefore(' ').toInt(16) - val rest = line.substringAfter(' ').trim() - if(rest.startsWith('"')) { - memory[address] = listOf(Value(DataType.STR, null, unescape(rest.substring(1, rest.length - 1)))) - } else { - val valueStrings = rest.split(' ') - val values = mutableListOf() - valueStrings.forEach { - when(it.length) { - 2 -> values.add(Value(DataType.BYTE, it.toShort(16))) - 4 -> values.add(Value(DataType.WORD, it.toInt(16))) - else -> throw VmExecutionException("invalid value at line $lineNr+1") - } - } - memory[address] = values - } - } - } - } - - val program: List - - init { - prog.add(Instruction(Opcode.TERMINATE)) - prog.add(Instruction(Opcode.NOP)) - program = prog - connect() - } - - private fun connect() { - val it1 = program.iterator() - val it2 = program.iterator() - it2.next() - - while(it1.hasNext() && it2.hasNext()) { - val instr = it1.next() - val nextInstr = it2.next() - when(instr.opcode) { - Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction - Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic - Opcode.JUMP -> { - if(instr.callLabel==null) { - throw VmExecutionException("stackVm doesn't support JUMP to memory address") - } else { - // jump to label - val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") - instr.next = target - } - } - Opcode.BCC, Opcode.BCS, Opcode.BZ, Opcode.BNZ, Opcode.BNEG, Opcode.BPOS -> { - if(instr.callLabel==null) { - throw VmExecutionException("stackVm doesn't support branch to memory address") - } else { - // branch to label - val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") - instr.next = jumpInstr - instr.nextAlt = nextInstr - } - } - Opcode.CALL -> { - if(instr.callLabel==null) { - throw VmExecutionException("stackVm doesn't support CALL to memory address") - } else { - // call label - val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}") - instr.next = jumpInstr - instr.nextAlt = nextInstr // instruction to return to - } - } - else -> instr.next = nextInstr - } - } - } - - fun print(out: PrintStream, embeddedLabels: Boolean=true) { - out.println("; stackVM program code for '$name'") - out.println("%memory") - if(memory.isNotEmpty()) { - TODO("print out initial memory load") - } - out.println("%end_memory") - out.println("%variables") - // just flatten all block vars into one global list for now... - for(variable in variables.flatMap { e->e.value.entries}) { - val valuestr = variable.value.toString() - out.println("${variable.key} ${variable.value.type.toString().toLowerCase()} $valuestr") - } - out.println("%end_variables") - out.println("%instructions") - val labels = this.labels.entries.associateBy({it.value}) {it.key} - for(instr in this.program) { - if(!embeddedLabels) { - val label = labels[instr] - if (label != null) - out.println("$label:") - } else { - out.println(instr) - } - } - out.println("%end_instructions") - } -} - class StackVm(private var traceOutputFile: String?) { val mem = Memory() @@ -866,6 +235,7 @@ class StackVm(private var traceOutputFile: String?) { var callstack = MyStack() private set private var program = listOf() + private var heap = HeapValues() private var traceOutput = if(traceOutputFile!=null) PrintStream(File(traceOutputFile), "utf-8") else null private var canvas: BitmapScreenPanel? = null private val rnd = Random() @@ -877,7 +247,9 @@ class StackVm(private var traceOutputFile: String?) { fun load(program: Program, canvas: BitmapScreenPanel?) { this.program = program.program + this.heap = program.heap this.canvas = canvas + variables.clear() for(variable in program.variables.flatMap { e->e.value.entries }) variables[variable.key] = variable.value @@ -955,8 +327,9 @@ class StackVm(private var traceOutputFile: String?) { address += 5 } DataType.STR -> { - mem.setString(address, value.stringvalue!!) - address += value.stringvalue.length+1 + TODO("mem init with string") + //mem.setString(address, value.stringvalue!!) + //address += value.stringvalue.length+1 } else -> throw VmExecutionException("invalid mem datatype ${value.type}") } @@ -982,22 +355,6 @@ class StackVm(private var traceOutputFile: String?) { evalstack.push(Value(DataType.FLOAT, mem.getFloat(address))) } Opcode.DUP -> evalstack.push(evalstack.peek()) - Opcode.ARRAY -> { - val amount = ins.arg!!.integerValue() - if(amount<=0) - throw VmExecutionException("array size must be > 0") - val array = mutableListOf() - var arrayDt = DataType.ARRAY - for (i in 1..amount) { - val value = evalstack.pop() - if(value.type!=DataType.BYTE && value.type!=DataType.WORD) - throw VmExecutionException("array requires values to be all byte/word") - if(value.type==DataType.WORD) - arrayDt = DataType.ARRAY_W - array.add(0, value.integerValue()) - } - evalstack.push(Value(arrayDt, null, arrayvalue = array.toIntArray())) - } Opcode.DISCARD -> evalstack.pop() Opcode.SWAP -> { val (top, second) = evalstack.pop2() @@ -1120,14 +477,21 @@ class StackVm(private var traceOutputFile: String?) { val value = evalstack.pop() when(value.type){ DataType.BYTE, DataType.WORD, DataType.FLOAT -> print(value.numericValue()) - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> print(value.stringvalue) - DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> print(value.arrayvalue) + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { + TODO("print stringvalue") + } + DataType.ARRAY, DataType.ARRAY_W -> { + TODO("print array value") + } + DataType.MATRIX -> { + TODO("print matrix value") + } } } Syscall.INPUT_STR -> { val maxlen = evalstack.pop().integerValue() val input = readLine()?.substring(0, maxlen) ?: "" - evalstack.push(Value(DataType.STR, null, input)) + TODO("input_str opcode should put the string in a given heap location (overwriting old string)") } Syscall.GFX_PIXEL -> { // plot pixel at (x, y, color) from stack @@ -1140,23 +504,25 @@ class StackVm(private var traceOutputFile: String?) { canvas?.clearScreen(color.integerValue()) } Syscall.GFX_TEXT -> { - val text = evalstack.pop() + val textPtr = evalstack.pop() val color = evalstack.pop() val (y, x) = evalstack.pop2() - canvas?.writeText(x.integerValue(), y.integerValue(), text.stringvalue!!, color.integerValue()) + val text = heap.get(textPtr.heapId) + canvas?.writeText(x.integerValue(), y.integerValue(), text.str!!, color.integerValue()) } Syscall.FUNC_RND -> evalstack.push(Value(DataType.BYTE, rnd.nextInt() and 255)) Syscall.FUNC_RNDW -> evalstack.push(Value(DataType.WORD, rnd.nextInt() and 65535)) Syscall.FUNC_RNDF -> evalstack.push(Value(DataType.FLOAT, rnd.nextDouble())) Syscall.FUNC_LEN -> { val value = evalstack.pop() - when(value.type) { - DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> - evalstack.push(Value(DataType.WORD, value.stringvalue!!.length)) - DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> - evalstack.push(Value(DataType.WORD, value.arrayvalue!!.size)) - else -> throw VmExecutionException("cannot get length of $value") - } + TODO("func_len") +// when(value.type) { +// DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> +// evalstack.push(Value(DataType.WORD, value.stringvalue!!.length)) +// DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> +// evalstack.push(Value(DataType.WORD, value.arrayvalue!!.size)) +// else -> throw VmExecutionException("cannot get length of $value") +// } } Syscall.FUNC_SIN -> evalstack.push(Value(DataType.FLOAT, sin(evalstack.pop().numericValue().toDouble()))) Syscall.FUNC_COS -> evalstack.push(Value(DataType.FLOAT, cos(evalstack.pop().numericValue().toDouble()))) @@ -1205,40 +571,46 @@ class StackVm(private var traceOutputFile: String?) { evalstack.push(result) } Syscall.FUNC_MAX -> { - val array = evalstack.pop() + val iterable = evalstack.pop() val dt = - when { - array.type==DataType.ARRAY -> DataType.BYTE - array.type==DataType.ARRAY_W -> DataType.WORD - else -> throw VmExecutionException("invalid array datatype $array") + when(iterable.type) { + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS, + DataType.ARRAY, DataType.MATRIX -> DataType.BYTE + DataType.ARRAY_W -> DataType.WORD + else -> throw VmExecutionException("uniterable value $iterable") } - evalstack.push(Value(dt, array.arrayvalue!!.max())) + TODO("func_max on array/matrix/string") } Syscall.FUNC_MIN -> { - val array = evalstack.pop() + val iterable = evalstack.pop() val dt = - when { - array.type==DataType.ARRAY -> DataType.BYTE - array.type==DataType.ARRAY_W -> DataType.WORD - else -> throw VmExecutionException("invalid array datatype $array") + when(iterable.type) { + DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS, + DataType.ARRAY, DataType.MATRIX -> DataType.BYTE + DataType.ARRAY_W -> DataType.WORD + else -> throw VmExecutionException("uniterable value $iterable") } - evalstack.push(Value(dt, array.arrayvalue!!.min())) + TODO("func_min on array/matrix/string") } Syscall.FUNC_AVG -> { - val array = evalstack.pop() - evalstack.push(Value(DataType.FLOAT, array.arrayvalue!!.average())) + val iterable = evalstack.pop() + TODO("func_avg") +// evalstack.push(Value(DataType.FLOAT, array.arrayvalue!!.average())) } Syscall.FUNC_SUM -> { - val array = evalstack.pop() - evalstack.push(Value(DataType.WORD, array.arrayvalue!!.sum())) + val iterable = evalstack.pop() + TODO("func_sum") +// evalstack.push(Value(DataType.WORD, array.arrayvalue!!.sum())) } Syscall.FUNC_ANY -> { - val array = evalstack.pop() - evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.any{ v -> v != 0}) 1 else 0)) + val iterable = evalstack.pop() + TODO("func_any") +// evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.any{ v -> v != 0}) 1 else 0)) } Syscall.FUNC_ALL -> { - val array = evalstack.pop() - evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.all{ v -> v != 0}) 1 else 0)) + val iterable = evalstack.pop() + TODO("func_all") +// evalstack.push(Value(DataType.BYTE, if(array.arrayvalue!!.all{ v -> v != 0}) 1 else 0)) } else -> throw VmExecutionException("unimplemented syscall $syscall") } @@ -1370,61 +742,51 @@ class StackVm(private var traceOutputFile: String?) { return callstack.pop() } Opcode.PUSH_VAR -> { - val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") + val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}") evalstack.push(variable) } Opcode.POP_VAR -> { val value = evalstack.pop() - val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") + val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}") if(variable.type!=value.type) - throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type} for var $varname") - variables[varname] = value + throw VmExecutionException("value datatype ${value.type} is not the same as variable datatype ${variable.type} for var ${ins.callLabel}") + variables[ins.callLabel!!] = value } Opcode.SHL_VAR -> { - val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") - variables[varname] = variable.shl() + val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}") + variables[ins.callLabel!!] = variable.shl() } Opcode.SHR_VAR -> { - val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") - variables[varname] = variable.shr() + val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}") + variables[ins.callLabel!!] = variable.shr() } Opcode.ROL_VAR -> { - val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") + val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}") val (newValue, newCarry) = variable.rol(P_carry) - variables[varname] = newValue + variables[ins.callLabel!!] = newValue P_carry = newCarry } Opcode.ROR_VAR -> { - val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") + val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}") val (newValue, newCarry) = variable.ror(P_carry) - variables[varname] = newValue + variables[ins.callLabel!!] = newValue P_carry = newCarry } Opcode.ROL2_VAR -> { - val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") - variables[varname] = variable.rol2() + val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}") + variables[ins.callLabel!!] = variable.rol2() } Opcode.ROR2_VAR -> { - val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") - variables[varname] = variable.ror2() + val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}") + variables[ins.callLabel!!] = variable.ror2() } Opcode.INC_VAR -> { - val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") - variables[varname] = variable.inc() + val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}") + variables[ins.callLabel!!] = variable.inc() } Opcode.DEC_VAR -> { - val varname = ins.arg!!.stringvalue ?: throw VmExecutionException("${ins.opcode} expects string argument (the variable name)") - val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname") - variables[varname] = variable.dec() + val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}") + variables[ins.callLabel!!] = variable.dec() } Opcode.LSB -> { val v = evalstack.pop() @@ -1507,7 +869,7 @@ class StackVm(private var traceOutputFile: String?) { } } Opcode.LINE -> { - sourceLine = ins.arg!!.stringvalue!! + sourceLine = ins.callLabel!! } else -> throw VmExecutionException("unimplemented opcode: ${ins.opcode}") } diff --git a/compiler/src/prog8/stackvm/Value.kt b/compiler/src/prog8/stackvm/Value.kt new file mode 100644 index 000000000..c17db5ee8 --- /dev/null +++ b/compiler/src/prog8/stackvm/Value.kt @@ -0,0 +1,373 @@ +package prog8.stackvm + +import prog8.ast.DataType +import kotlin.math.floor +import kotlin.math.pow + +class Value(val type: DataType, numericvalueOrHeapId: Number) { + private var byteval: Short? = null + private var wordval: Int? = null + private var floatval: Double? = null + var heapId: Int = 0 + private set + val asBooleanValue: Boolean + + init { + when(type) { + DataType.BYTE -> { + byteval = (numericvalueOrHeapId.toInt() and 255).toShort() // byte wrap around 0..255 + asBooleanValue = byteval != (0.toShort()) + } + DataType.WORD -> { + wordval = numericvalueOrHeapId.toInt() and 65535 // word wrap around 0..65535 + asBooleanValue = wordval != 0 + } + DataType.FLOAT -> { + floatval = numericvalueOrHeapId.toDouble() + asBooleanValue = floatval != 0.0 + } + else -> { + if(numericvalueOrHeapId !is Int) + throw VmExecutionException("for non-numeric types, the value should be an integer heapId") + heapId = numericvalueOrHeapId + asBooleanValue=true + } + } + } + + override fun toString(): String { + return when(type) { + DataType.BYTE -> "b:%02x".format(byteval) + DataType.WORD -> "w:%04x".format(wordval) + DataType.FLOAT -> "f:$floatval" + else -> "heap:$heapId" + } + } + + fun numericValue(): Number { + return when(type) { + DataType.BYTE -> byteval!! + DataType.WORD -> wordval!! + DataType.FLOAT -> floatval!! + else -> throw VmExecutionException("invalid datatype for numeric value: $type") + } + } + + fun integerValue(): Int { + return when(type) { + DataType.BYTE -> byteval!!.toInt() + DataType.WORD -> wordval!! + DataType.FLOAT -> throw VmExecutionException("float to integer loss of precision") + else -> throw VmExecutionException("invalid datatype for integer value: $type") + } + } + + override fun hashCode(): Int { + val bh = byteval?.hashCode() ?: 0x10001234 + val wh = wordval?.hashCode() ?: 0x01002345 + val fh = floatval?.hashCode() ?: 0x00103456 + return bh xor wh xor fh xor heapId.hashCode() xor type.hashCode() + } + + override fun equals(other: Any?): Boolean { + if(other==null || other !is Value) + return false + if(type==other.type) { + return when (type) { + DataType.STR, DataType.STR_S, DataType.STR_P, DataType.STR_PS, DataType.ARRAY, DataType.ARRAY_W, DataType.MATRIX -> heapId==other.heapId + DataType.BYTE, DataType.WORD, DataType.FLOAT -> compareTo(other)==0 + } + } + return compareTo(other)==0 // note: datatype doesn't matter + } + + operator fun compareTo(other: Value): Int { + return when(type) { + DataType.BYTE, DataType.WORD, DataType.FLOAT -> { + when(other.type) { + DataType.BYTE, DataType.WORD, DataType.FLOAT -> { + numericValue().toDouble().compareTo(other.numericValue().toDouble()) + } + else -> throw VmExecutionException("comparison can only be done between two numeric values") + } + } + else -> throw VmExecutionException("comparison can only be done between two numeric values") + } + } + + private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): Value { + if(result.toDouble() < 0 ) { + return when(leftDt) { + DataType.BYTE -> { + // BYTE can become WORD if right operand is WORD, or when value is too large for byte + when(rightDt) { + DataType.BYTE -> Value(DataType.BYTE, result.toInt() and 255) + DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535) + DataType.FLOAT -> throw VmExecutionException("floating point loss of precision") + else -> throw VmExecutionException("$op on non-numeric result type") + } + } + DataType.WORD -> Value(DataType.WORD, result.toInt() and 65535) + DataType.FLOAT -> Value(DataType.FLOAT, result) + else -> throw VmExecutionException("$op on non-numeric type") + } + } + + return when(leftDt) { + DataType.BYTE -> { + // BYTE can become WORD if right operand is WORD, or when value is too large for byte + if(result.toDouble() >= 256) + return Value(DataType.WORD, result) + when(rightDt) { + DataType.BYTE -> Value(DataType.BYTE, result) + DataType.WORD -> Value(DataType.WORD, result) + DataType.FLOAT -> throw VmExecutionException("floating point loss of precision") + else -> throw VmExecutionException("$op on non-numeric result type") + } + } + DataType.WORD -> Value(DataType.WORD, result) + DataType.FLOAT -> Value(DataType.FLOAT, result) + else -> throw VmExecutionException("$op on non-numeric type") + } + } + + fun add(other: Value): Value { + if(other.type == DataType.FLOAT && (type!= DataType.FLOAT)) + throw VmExecutionException("floating point loss of precision on type $type") + val v1 = numericValue() + val v2 = other.numericValue() + val result = v1.toDouble() + v2.toDouble() + return arithResult(type, result, other.type, "add") + } + + fun sub(other: Value): Value { + if(other.type == DataType.FLOAT && (type!= DataType.FLOAT)) + throw VmExecutionException("floating point loss of precision on type $type") + val v1 = numericValue() + val v2 = other.numericValue() + val result = v1.toDouble() - v2.toDouble() + return arithResult(type, result, other.type, "sub") + } + + fun mul(other: Value): Value { + if(other.type == DataType.FLOAT && (type!= DataType.FLOAT)) + throw VmExecutionException("floating point loss of precision on type $type") + val v1 = numericValue() + val v2 = other.numericValue() + val result = v1.toDouble() * v2.toDouble() + return arithResult(type, result, other.type, "mul") + } + + fun div(other: Value): Value { + if(other.type == DataType.FLOAT && (type!= DataType.FLOAT)) + throw VmExecutionException("floating point loss of precision on type $type") + val v1 = numericValue() + val v2 = other.numericValue() + if(v2.toDouble()==0.0) { + if (type == DataType.BYTE) + return Value(DataType.BYTE, 255) + else if(type == DataType.WORD) + return Value(DataType.WORD, 65535) + } + val result = v1.toDouble() / v2.toDouble() + // NOTE: integer division returns integer result! + return when(type) { + DataType.BYTE -> Value(DataType.BYTE, result) + DataType.WORD -> Value(DataType.WORD, result) + DataType.FLOAT -> Value(DataType.FLOAT, result) + else -> throw VmExecutionException("div on non-numeric type") + } + } + + fun floordiv(other: Value): Value { + if(other.type == DataType.FLOAT && (type!= DataType.FLOAT)) + throw VmExecutionException("floating point loss of precision on type $type") + val v1 = numericValue() + val v2 = other.numericValue() + val result = floor(v1.toDouble() / v2.toDouble()) + // NOTE: integer division returns integer result! + return when(type) { + DataType.BYTE -> Value(DataType.BYTE, result) + DataType.WORD -> Value(DataType.WORD, result) + DataType.FLOAT -> Value(DataType.FLOAT, result) + else -> throw VmExecutionException("div on non-numeric type") + } + } + + fun remainder(other: Value): Value? { + val v1 = numericValue() + val v2 = other.numericValue() + val result = v1.toDouble() % v2.toDouble() + return arithResult(type, result, other.type, "remainder") + } + + fun pow(other: Value): Value { + val v1 = numericValue() + val v2 = other.numericValue() + val result = v1.toDouble().pow(v2.toDouble()) + return arithResult(type, result, other.type,"pow") + } + + fun shl(): Value { + val v = integerValue() + return Value(type, v shl 1) + } + + fun shr(): Value { + val v = integerValue() + return Value(type, v ushr 1) + } + + fun rol(carry: Boolean): Pair { + // 9 or 17 bit rotate left (with carry)) + return when(type) { + DataType.BYTE -> { + val v = byteval!!.toInt() + val newCarry = (v and 0x80) != 0 + val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0) + Pair(Value(DataType.BYTE, newval), newCarry) + } + DataType.WORD -> { + val v = wordval!! + val newCarry = (v and 0x8000) != 0 + val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0) + Pair(Value(DataType.WORD, newval), newCarry) + } + else -> throw VmExecutionException("rol can only work on byte/word") + } + } + + fun ror(carry: Boolean): Pair { + // 9 or 17 bit rotate right (with carry) + return when(type) { + DataType.BYTE -> { + val v = byteval!!.toInt() + val newCarry = v and 1 != 0 + val newval = (v ushr 1) or (if(carry) 0x80 else 0) + Pair(Value(DataType.BYTE, newval), newCarry) + } + DataType.WORD -> { + val v = wordval!! + val newCarry = v and 1 != 0 + val newval = (v ushr 1) or (if(carry) 0x8000 else 0) + Pair(Value(DataType.WORD, newval), newCarry) + } + else -> throw VmExecutionException("ror2 can only work on byte/word") + } + } + + fun rol2(): Value { + // 8 or 16 bit rotate left + return when(type) { + DataType.BYTE -> { + val v = byteval!!.toInt() + val carry = (v and 0x80) ushr 7 + val newval = (v and 0x7f shl 1) or carry + Value(DataType.BYTE, newval) + } + DataType.WORD -> { + val v = wordval!! + val carry = (v and 0x8000) ushr 15 + val newval = (v and 0x7fff shl 1) or carry + Value(DataType.WORD, newval) + } + else -> throw VmExecutionException("rol2 can only work on byte/word") + } + } + + fun ror2(): Value { + // 8 or 16 bit rotate right + return when(type) { + DataType.BYTE -> { + val v = byteval!!.toInt() + val carry = v and 1 shl 7 + val newval = (v ushr 1) or carry + Value(DataType.BYTE, newval) + } + DataType.WORD -> { + val v = wordval!! + val carry = v and 1 shl 15 + val newval = (v ushr 1) or carry + Value(DataType.WORD, newval) + } + else -> throw VmExecutionException("ror2 can only work on byte/word") + } + } + + fun neg(): Value { + return when(type) { + DataType.BYTE -> Value(DataType.BYTE, -(byteval!!)) + DataType.WORD -> Value(DataType.WORD, -(wordval!!)) + DataType.FLOAT -> Value(DataType.FLOAT, -(floatval)!!) + else -> throw VmExecutionException("neg can only work on byte/word/float") + } + } + + fun bitand(other: Value): Value { + val v1 = integerValue() + val v2 = other.integerValue() + val result = v1 and v2 + return Value(type, result) + } + + fun bitor(other: Value): Value { + val v1 = integerValue() + val v2 = other.integerValue() + val result = v1 or v2 + return Value(type, result) + } + + fun bitxor(other: Value): Value { + val v1 = integerValue() + val v2 = other.integerValue() + val result = v1 xor v2 + return Value(type, result) + } + + fun and(other: Value) = Value(DataType.BYTE, if (this.asBooleanValue && other.asBooleanValue) 1 else 0) + fun or(other: Value) = Value(DataType.BYTE, if (this.asBooleanValue || other.asBooleanValue) 1 else 0) + fun xor(other: Value) = Value(DataType.BYTE, if (this.asBooleanValue xor other.asBooleanValue) 1 else 0) + fun not() = Value(DataType.BYTE, if (this.asBooleanValue) 0 else 1) + + fun inv(): Value { + return when(type) { + DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt().inv() and 255) + DataType.WORD -> Value(DataType.WORD, wordval!!.inv() and 65535) + else -> throw VmExecutionException("inv can only work on byte/word") + } + } + + fun inc(): Value { + return when(type) { + DataType.BYTE -> Value(DataType.BYTE, (byteval!! + 1) and 255) + DataType.WORD -> Value(DataType.WORD, (wordval!! + 1) and 65535) + DataType.FLOAT -> Value(DataType.FLOAT, floatval!! + 1) + else -> throw VmExecutionException("inc can only work on byte/word/float") + } + } + + fun dec(): Value { + return when(type) { + DataType.BYTE -> Value(DataType.BYTE, (byteval!! - 1) and 255) + DataType.WORD -> Value(DataType.WORD, (wordval!! - 1) and 65535) + DataType.FLOAT -> Value(DataType.FLOAT, floatval!! - 1) + else -> throw VmExecutionException("dec can only work on byte/word/float") + } + } + + fun lsb(): Value { + return when(type) { + DataType.BYTE -> Value(DataType.BYTE, byteval!!) + DataType.WORD -> Value(DataType.BYTE, wordval!! and 255) + else -> throw VmExecutionException("not can only work on byte/word") + } + } + + fun msb(): Value { + return when(type) { + DataType.BYTE -> Value(DataType.BYTE, 0) + DataType.WORD -> Value(DataType.BYTE, wordval!! ushr 8 and 255) + else -> throw VmExecutionException("not can only work on byte/word") + } + } +} \ No newline at end of file diff --git a/compiler/test/StackVMOpcodeTests.kt b/compiler/test/StackVMOpcodeTests.kt index b5020dcd5..3ff4148e0 100644 --- a/compiler/test/StackVMOpcodeTests.kt +++ b/compiler/test/StackVMOpcodeTests.kt @@ -2,10 +2,10 @@ package prog8tests import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.empty -import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import prog8.ast.DataType +import prog8.compiler.HeapValues import prog8.stackvm.* import kotlin.test.* @@ -49,11 +49,13 @@ class TestStackVmOpcodes { if(vars!=null) { for (blockvar in vars) { val blockname = blockvar.key.substringBefore('.') - val variables = blockvars.getValue(blockname) + val variables = blockvars[blockname] ?: mutableMapOf() + blockvars[blockname] = variables variables[blockvar.key] = blockvar.value } } - return Program("test", ins, labels ?: mapOf(), blockvars, mem ?: mapOf()) + val heap = HeapValues() + return Program("test", ins, labels ?: mapOf(), blockvars, mem ?: mapOf(), heap) } @Test @@ -84,7 +86,7 @@ class TestStackVmOpcodes { @Test fun testLine() { - val ins = mutableListOf(Instruction(Opcode.LINE, Value(DataType.STR, null, "line 99"))) + val ins = mutableListOf(Instruction(Opcode.LINE, callLabel = "line 99")) vm.load(makeProg(ins), null) assertEquals("", vm.sourceLine) vm.step(1) @@ -149,7 +151,7 @@ class TestStackVmOpcodes { @Test fun testPushVar() { - val ins = mutableListOf(Instruction(Opcode.PUSH_VAR, Value(DataType.STR, null, "varname"))) + val ins = mutableListOf(Instruction(Opcode.PUSH_VAR, callLabel = "varname")) vm.load(makeProg(ins, mapOf("varname" to Value(DataType.FLOAT, 42.999))), null) assertEquals(7, vm.variables.size) assertTrue(vm.variables.containsKey("varname")) @@ -206,47 +208,6 @@ class TestStackVmOpcodes { assertEquals(Value(DataType.FLOAT, 42.999), vm.evalstack.pop()) } - @Test - fun testArray() { - val ins = mutableListOf( - Instruction(Opcode.PUSH, Value(DataType.WORD, 111)), - Instruction(Opcode.PUSH, Value(DataType.WORD, 222)), - Instruction(Opcode.PUSH, Value(DataType.WORD, 333)), - Instruction(Opcode.ARRAY, Value(DataType.WORD, 2))) - vm.load(makeProg(ins), null) - assertThat(vm.evalstack, empty()) - vm.step(4) - assertEquals(2, vm.evalstack.size) - var array = vm.evalstack.pop() - assertEquals(DataType.ARRAY_W, array.type) - assertThat(array.arrayvalue, equalTo(intArrayOf(222, 333))) - assertEquals(Value(DataType.WORD, 111), vm.evalstack.pop()) - - val ins2 = mutableListOf( - Instruction(Opcode.PUSH, Value(DataType.BYTE, 11)), - Instruction(Opcode.PUSH, Value(DataType.BYTE, 22)), - Instruction(Opcode.PUSH, Value(DataType.BYTE, 33)), - Instruction(Opcode.ARRAY, Value(DataType.WORD, 2))) - vm.load(makeProg(ins2), null) - assertThat(vm.evalstack, empty()) - vm.step(4) - assertEquals(2, vm.evalstack.size) - array = vm.evalstack.pop() - assertEquals(DataType.ARRAY, array.type) - assertThat(array.arrayvalue, equalTo(intArrayOf(22, 33))) - assertEquals(Value(DataType.BYTE, 11), vm.evalstack.pop()) - - val ins3 = mutableListOf( - Instruction(Opcode.PUSH, Value(DataType.BYTE, 11)), - Instruction(Opcode.PUSH, Value(DataType.WORD, 222)), - Instruction(Opcode.PUSH, Value(DataType.FLOAT, 333.33)), - Instruction(Opcode.ARRAY, Value(DataType.WORD, 2))) - vm.load(makeProg(ins3), null) - assertFailsWith { - vm.step(4) - } - } - @Test fun testPopMem() { val ins = mutableListOf( @@ -275,9 +236,9 @@ class TestStackVmOpcodes { Instruction(Opcode.PUSH, Value(DataType.FLOAT, 42.25)), Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)), Instruction(Opcode.PUSH, Value(DataType.BYTE, 123)), - Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var1")), - Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var2")), - Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var3"))) + Instruction(Opcode.POP_VAR, callLabel = "var1"), + Instruction(Opcode.POP_VAR, callLabel = "var2"), + Instruction(Opcode.POP_VAR, callLabel = "var3")) val vars = mapOf( "var1" to Value(DataType.BYTE, 0), "var2" to Value(DataType.WORD, 0), @@ -292,7 +253,7 @@ class TestStackVmOpcodes { val ins2 = mutableListOf( Instruction(Opcode.PUSH, Value(DataType.WORD, 0x42ea)), - Instruction(Opcode.POP_VAR, Value(DataType.STR, null, "var1"))) + Instruction(Opcode.POP_VAR, callLabel = "var1")) val vars2 = mapOf( "var1" to Value(DataType.BYTE, 0) ) @@ -450,12 +411,12 @@ class TestStackVmOpcodes { @Test fun testAnd() { val values = listOf( - Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), + Value(DataType.ARRAY, 111), Value(DataType.BYTE, 0), Value(DataType.WORD, 0), - Value(DataType.STR, null, ""), - Value(DataType.STR, null, "hello"), - Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), + Value(DataType.STR, 222), + Value(DataType.STR, 333), + Value(DataType.ARRAY, 444), Value(DataType.FLOAT, 300.33), Value(DataType.WORD, 5000), Value(DataType.BYTE, 200), @@ -466,7 +427,7 @@ class TestStackVmOpcodes { Value(DataType.BYTE, 1), Value(DataType.BYTE, 1), Value(DataType.BYTE, 1), - Value(DataType.BYTE, 0), + Value(DataType.BYTE, 1), Value(DataType.BYTE, 0), Value(DataType.BYTE, 0), Value(DataType.BYTE, 0)) @@ -478,19 +439,19 @@ class TestStackVmOpcodes { @Test fun testOr() { val values = listOf( - Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.BYTE, 0), Value(DataType.WORD, 0), - Value(DataType.STR, null, ""), - Value(DataType.STR, null, "hello"), - Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), - Value(DataType.FLOAT, 300.33), - Value(DataType.WORD, 5000), + Value(DataType.STR, 222), + Value(DataType.STR, 333), + Value(DataType.ARRAY, 444), + Value(DataType.FLOAT, 0), + Value(DataType.WORD, 1), + Value(DataType.WORD, 0), Value(DataType.BYTE, 0), Value(DataType.BYTE, 0)) val expected = listOf( Value(DataType.BYTE, 0), - Value(DataType.BYTE, 1), + Value(DataType.BYTE, 0), Value(DataType.BYTE, 1), Value(DataType.BYTE, 1), Value(DataType.BYTE, 1), @@ -506,12 +467,12 @@ class TestStackVmOpcodes { @Test fun testXor() { val values = listOf( - Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), + Value(DataType.ARRAY, 111), Value(DataType.BYTE, 1), Value(DataType.WORD, 0), - Value(DataType.STR, null, ""), - Value(DataType.STR, null, "hello"), - Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), + Value(DataType.STR, 222), + Value(DataType.STR, 333), + Value(DataType.ARRAY, 444), Value(DataType.FLOAT, 300.33), Value(DataType.WORD, 5000), Value(DataType.BYTE, 0), @@ -522,10 +483,10 @@ class TestStackVmOpcodes { Value(DataType.BYTE, 1), Value(DataType.BYTE, 0), Value(DataType.BYTE, 1), - Value(DataType.BYTE, 1), - Value(DataType.BYTE, 1), Value(DataType.BYTE, 0), - Value(DataType.BYTE, 1)) + Value(DataType.BYTE, 0), + Value(DataType.BYTE, 1), + Value(DataType.BYTE, 0)) val operator = Opcode.XOR testBinaryOperator(values, operator, expected) @@ -534,8 +495,8 @@ class TestStackVmOpcodes { @Test fun testNot() { val values = listOf( - Value(DataType.STR, null, ""), - Value(DataType.STR, null, "hello"), + Value(DataType.STR, 111), + Value(DataType.STR, 222), Value(DataType.FLOAT, 0.0), Value(DataType.FLOAT, 300.33), Value(DataType.WORD, 0), @@ -550,7 +511,7 @@ class TestStackVmOpcodes { Value(DataType.BYTE, 0), Value(DataType.BYTE, 1), Value(DataType.BYTE, 0), - Value(DataType.BYTE, 1) + Value(DataType.BYTE, 0) ) val operator = Opcode.NOT @@ -785,10 +746,10 @@ class TestStackVmOpcodes { @Test fun testIncVar() { val ins = mutableListOf( - Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var1")), - Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var2")), - Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var1")), - Instruction(Opcode.INC_VAR, Value(DataType.STR, null, "var2"))) + Instruction(Opcode.INC_VAR, callLabel ="var1"), + Instruction(Opcode.INC_VAR, callLabel ="var2"), + Instruction(Opcode.INC_VAR, callLabel ="var1"), + Instruction(Opcode.INC_VAR, callLabel ="var2")) val vars = mapOf("var1" to Value(DataType.WORD, 65534), "var2" to Value(DataType.BYTE, 254)) vm.load(makeProg(ins, vars = vars), null) @@ -803,10 +764,10 @@ class TestStackVmOpcodes { @Test fun testDecVar() { val ins = mutableListOf( - Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var1")), - Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var2")), - Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var1")), - Instruction(Opcode.DEC_VAR, Value(DataType.STR, null, "var2"))) + Instruction(Opcode.DEC_VAR, callLabel = "var1"), + Instruction(Opcode.DEC_VAR, callLabel = "var2"), + Instruction(Opcode.DEC_VAR, callLabel = "var1"), + Instruction(Opcode.DEC_VAR, callLabel = "var2")) val vars = mapOf("var1" to Value(DataType.WORD,1), "var2" to Value(DataType.BYTE, 1)) vm.load(makeProg(ins, vars = vars), null) @@ -887,11 +848,11 @@ class TestStackVmOpcodes { testComparisonOperator(values, expected, Opcode.LESS) val valuesInvalid = listOf( - Value(DataType.STR, null, stringvalue = "hello"), - Value(DataType.STR, null, stringvalue = "hello") + Value(DataType.STR, 333), + Value(DataType.STR, 333) ) assertFailsWith { - testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESS) // can't compare strings + testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESS) // can't order strings } } @@ -917,11 +878,11 @@ class TestStackVmOpcodes { testComparisonOperator(values, expected, Opcode.LESSEQ) val valuesInvalid = listOf( - Value(DataType.STR, null, stringvalue = "hello"), - Value(DataType.STR, null, stringvalue = "hello") + Value(DataType.STR, 333), + Value(DataType.STR, 333) ) assertFailsWith { - testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESSEQ) // can't compare strings + testComparisonOperator(valuesInvalid, listOf(0), Opcode.LESSEQ) // can't order strings } } @@ -947,11 +908,11 @@ class TestStackVmOpcodes { testComparisonOperator(values, expected, Opcode.GREATER) val valuesInvalid = listOf( - Value(DataType.STR, null, stringvalue = "hello"), - Value(DataType.STR, null, stringvalue = "hello") + Value(DataType.STR, 333), + Value(DataType.STR, 333) ) assertFailsWith { - testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATER) // can't compare strings + testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATER) // can't order strings } } @@ -977,11 +938,11 @@ class TestStackVmOpcodes { testComparisonOperator(values, expected, Opcode.GREATEREQ) val valuesInvalid = listOf( - Value(DataType.STR, null, stringvalue = "hello"), - Value(DataType.STR, null, stringvalue = "hello") + Value(DataType.STR, 333), + Value(DataType.STR, 333) ) assertFailsWith { - testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATEREQ) // can't compare strings + testComparisonOperator(valuesInvalid, listOf(0), Opcode.GREATEREQ) // can't order strings } } @@ -1007,12 +968,12 @@ class TestStackVmOpcodes { testComparisonOperator(values, expected, Opcode.EQUAL) val valuesInvalid = listOf( - Value(DataType.STR, null, stringvalue = "hello"), - Value(DataType.STR, null, stringvalue = "hello") + Value(DataType.STR, 111), + Value(DataType.STR, 222), // 0 + Value(DataType.STR, 333), + Value(DataType.STR, 333) // 1 ) - assertFailsWith { - testComparisonOperator(valuesInvalid, listOf(0), Opcode.EQUAL) // can't compare strings - } + testComparisonOperator(valuesInvalid, listOf(0, 1), Opcode.EQUAL) } @Test @@ -1037,12 +998,12 @@ class TestStackVmOpcodes { testComparisonOperator(values, expected, Opcode.NOTEQUAL) val valuesInvalid = listOf( - Value(DataType.STR, null, stringvalue = "hello"), - Value(DataType.STR, null, stringvalue = "hello") + Value(DataType.STR, 111), + Value(DataType.STR, 222), // 1 + Value(DataType.STR, 333), + Value(DataType.STR, 333) // 0 ) - assertFailsWith { - testComparisonOperator(valuesInvalid, listOf(0), Opcode.NOTEQUAL) // can't compare strings - } + testComparisonOperator(valuesInvalid, listOf(1, 0), Opcode.NOTEQUAL) } @Test @@ -1052,9 +1013,9 @@ class TestStackVmOpcodes { Instruction(Opcode.BCC, callLabel = "label"), Instruction(Opcode.CLC), Instruction(Opcode.BCC, callLabel = "label"), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.TERMINATE), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + Instruction(Opcode.LINE, callLabel = "string2")) val labels = mapOf("label" to ins.last()) // points to the second LINE instruction vm.load(makeProg(ins, labels=labels), null) vm.step(2) @@ -1072,9 +1033,9 @@ class TestStackVmOpcodes { Instruction(Opcode.BCS, callLabel = "label"), Instruction(Opcode.SEC), Instruction(Opcode.BCS, callLabel = "label"), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.TERMINATE), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + Instruction(Opcode.LINE, callLabel = "string2")) val labels = mapOf("label" to ins.last()) // points to the second LINE instruction vm.load(makeProg(ins, labels=labels), null) assertFalse(vm.P_carry) @@ -1093,9 +1054,9 @@ class TestStackVmOpcodes { Instruction(Opcode.BZ, callLabel = "label"), Instruction(Opcode.PUSH, Value(DataType.WORD, 0)), Instruction(Opcode.BZ, callLabel = "label"), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.TERMINATE), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + Instruction(Opcode.LINE, callLabel = "string2")) val labels = mapOf("label" to ins.last()) // points to the second LINE instruction vm.load(makeProg(ins, labels=labels), null) vm.step(2) @@ -1113,9 +1074,9 @@ class TestStackVmOpcodes { Instruction(Opcode.BNZ, callLabel = "label"), Instruction(Opcode.PUSH, Value(DataType.WORD, 1)), Instruction(Opcode.BNZ, callLabel = "label"), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.TERMINATE), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + Instruction(Opcode.LINE, callLabel = "string2")) val labels = mapOf("label" to ins.last()) // points to the second LINE instruction vm.load(makeProg(ins, labels=labels), null) vm.step(2) @@ -1135,9 +1096,9 @@ class TestStackVmOpcodes { Instruction(Opcode.BNEG, callLabel = "label"), Instruction(Opcode.PUSH, Value(DataType.FLOAT, -99)), Instruction(Opcode.BNEG, callLabel = "label"), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.TERMINATE), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + Instruction(Opcode.LINE, callLabel = "string2")) val labels = mapOf("label" to ins.last()) // points to the second LINE instruction vm.load(makeProg(ins, labels=labels), null) vm.step(2) @@ -1157,9 +1118,9 @@ class TestStackVmOpcodes { Instruction(Opcode.BPOS, callLabel = "label"), Instruction(Opcode.PUSH, Value(DataType.WORD, 0)), Instruction(Opcode.BPOS, callLabel = "label"), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.TERMINATE), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + Instruction(Opcode.LINE, callLabel = "string2")) val labels = mapOf("label" to ins.last()) // points to the second LINE instruction vm.load(makeProg(ins, labels=labels), null) vm.step(2) @@ -1174,9 +1135,9 @@ class TestStackVmOpcodes { fun testJump() { val ins = mutableListOf( Instruction(Opcode.JUMP, callLabel = "label"), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")), + Instruction(Opcode.LINE, callLabel = "string1"), Instruction(Opcode.TERMINATE), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string2"))) + Instruction(Opcode.LINE, callLabel = "string2")) val labels = mapOf("label" to ins.last()) // points to the second LINE instruction vm.load(makeProg(ins, labels=labels), null) vm.step(2) @@ -1191,7 +1152,7 @@ class TestStackVmOpcodes { val ins = mutableListOf( Instruction(Opcode.RETURN), Instruction(Opcode.TERMINATE), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "string1")) + Instruction(Opcode.LINE, callLabel = "string1") ) vm.load(makeProg(ins), null) assertFailsWith { @@ -1211,9 +1172,9 @@ class TestStackVmOpcodes { // @todo this only tests call with zero parameters for now. val ins = mutableListOf( Instruction(Opcode.CALL, callLabel = "label"), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "returned")), + Instruction(Opcode.LINE, callLabel = "returned"), Instruction(Opcode.TERMINATE), - Instruction(Opcode.LINE, Value(DataType.STR, null, stringvalue = "called")), + Instruction(Opcode.LINE, callLabel = "called"), Instruction(Opcode.RETURN) ) val labels = mapOf("label" to ins[3]) // points to the LINE instruction @@ -1560,16 +1521,16 @@ class TestStackVmOpcodes { } } - private fun testBinaryOperator(values: List, operator: Opcode, expected: List) { - assertEquals(values.size, expected.size+1) + private fun testBinaryOperator(valuesToPush: List, operator: Opcode, expected: List) { + assertEquals(valuesToPush.size, expected.size+1) val ins = mutableListOf() - for (value in values) + for (value in valuesToPush) ins.add(Instruction(Opcode.PUSH, value)) - for (i in 1 until values.size) + for (i in 1 until valuesToPush.size) ins.add(Instruction(operator)) vm.load(makeProg(ins), null) - vm.step(values.size) - assertEquals(values.size, vm.evalstack.size) + vm.step(valuesToPush.size) + assertEquals(valuesToPush.size, vm.evalstack.size) for (expectedVal in expected) { vm.step(1) assertEquals(expectedVal, vm.evalstack.peek()) @@ -1579,18 +1540,18 @@ class TestStackVmOpcodes { } } - private fun testUnaryOperator(values: List, operator: Opcode, expected: List) { - assertEquals(values.size, expected.size) + private fun testUnaryOperator(valuesToPush: List, operator: Opcode, expected: List) { + assertEquals(valuesToPush.size, expected.size) val ins = mutableListOf() - for (value in values) + for (value in valuesToPush) ins.add(Instruction(Opcode.PUSH, value)) - for (i in 1..values.size) { + for (i in 1..valuesToPush.size) { ins.add(Instruction(operator)) ins.add(Instruction(Opcode.DISCARD)) } vm.load(makeProg(ins), null) - vm.step(values.size) - assertEquals(values.size, vm.evalstack.size) + vm.step(valuesToPush.size) + assertEquals(valuesToPush.size, vm.evalstack.size) for (expectedVal in expected) { vm.step(1) assertEquals(expectedVal, vm.evalstack.peek()) diff --git a/compiler/test/ValueOperationsTests.kt b/compiler/test/ValueOperationsTests.kt index aff5cd86d..58a93b15d 100644 --- a/compiler/test/ValueOperationsTests.kt +++ b/compiler/test/ValueOperationsTests.kt @@ -77,13 +77,18 @@ class TestStackVmValue { assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.BYTE, 9))) assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.WORD, 9))) assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.0))) + } - assertFailsWith { - assertTrue(sameValueAndType(Value(DataType.STR, null, "hello"), Value(DataType.STR, null, "hello"))) - } - assertFailsWith { - assertTrue(sameValueAndType(Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)), Value(DataType.ARRAY, null, arrayvalue = intArrayOf(1,2,3)))) - } + @Test + fun testEqualsAndNotEqualsHeapTypes() + { + assertTrue(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 999))) + assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR_P, 999))) + assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 222))) + + assertTrue(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.ARRAY_W, 999))) + assertFalse(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.MATRIX, 999))) + assertFalse(sameValueAndType(Value(DataType.ARRAY_W, 999), Value(DataType.ARRAY_W, 222))) } @Test