diff --git a/codeGenVirtual/src/prog8/codegen/virtual/ExpressionGen.kt b/codeGenVirtual/src/prog8/codegen/virtual/ExpressionGen.kt index 49b6cfe8a..575b60150 100644 --- a/codeGenVirtual/src/prog8/codegen/virtual/ExpressionGen.kt +++ b/codeGenVirtual/src/prog8/codegen/virtual/ExpressionGen.kt @@ -3,10 +3,7 @@ package prog8.codegen.virtual import prog8.code.StStaticVariable import prog8.code.StSub import prog8.code.ast.* -import prog8.code.core.AssemblyError -import prog8.code.core.DataType -import prog8.code.core.PassByValueDatatypes -import prog8.code.core.SignedDatatypes +import prog8.code.core.* import prog8.vm.Opcode import prog8.vm.VmDataType @@ -187,13 +184,7 @@ internal class ExpressionGen(private val codeGen: CodeGen) { code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=regMask) } "not" -> { - val label = codeGen.createLabelName() - code += VmCodeInstruction(Opcode.BZ, vmDt, reg1=resultRegister, symbol = label) - code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, value=1) - code += VmCodeLabel(label) - val regMask = codeGen.vmRegisters.nextFree() - code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=regMask, value=1) - code += VmCodeInstruction(Opcode.XOR, vmDt, reg1=resultRegister, reg2=regMask) + code += VmCodeInstruction(Opcode.NOT, vmDt, reg1=resultRegister) } else -> throw AssemblyError("weird prefix operator") } @@ -330,15 +321,28 @@ internal class ExpressionGen(private val codeGen: CodeGen) { } code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister) } else { - val rightResultReg = codeGen.vmRegisters.nextFree() - code += translateExpression(binExpr.left, resultRegister, -1) - code += translateExpression(binExpr.right, rightResultReg, -1) - val ins = if (signed) { - if (greaterEquals) Opcode.SGES else Opcode.SGTS + if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) { + val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY) + comparisonCall.children.add(binExpr.left) + comparisonCall.children.add(binExpr.right) + code += translate(comparisonCall, resultRegister, -1) + val zeroRegister = codeGen.vmRegisters.nextFree() + code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0) + code += if(greaterEquals) + VmCodeInstruction(Opcode.SGES, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister) + else + VmCodeInstruction(Opcode.SGTS, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister) } else { - if (greaterEquals) Opcode.SGE else Opcode.SGT + val rightResultReg = codeGen.vmRegisters.nextFree() + code += translateExpression(binExpr.left, resultRegister, -1) + code += translateExpression(binExpr.right, rightResultReg, -1) + val ins = if (signed) { + if (greaterEquals) Opcode.SGES else Opcode.SGTS + } else { + if (greaterEquals) Opcode.SGE else Opcode.SGT + } + code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg) } - code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg) } return code } @@ -366,15 +370,28 @@ internal class ExpressionGen(private val codeGen: CodeGen) { } code += VmCodeInstruction(ins, VmDataType.BYTE, reg1 = resultRegister, reg2 = zeroRegister) } else { - val rightResultReg = codeGen.vmRegisters.nextFree() - code += translateExpression(binExpr.left, resultRegister, -1) - code += translateExpression(binExpr.right, rightResultReg, -1) - val ins = if (signed) { - if (lessEquals) Opcode.SLES else Opcode.SLTS + if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) { + val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY) + comparisonCall.children.add(binExpr.left) + comparisonCall.children.add(binExpr.right) + code += translate(comparisonCall, resultRegister, -1) + val zeroRegister = codeGen.vmRegisters.nextFree() + code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=zeroRegister, value=0) + code += if(lessEquals) + VmCodeInstruction(Opcode.SLES, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister) + else + VmCodeInstruction(Opcode.SLTS, VmDataType.BYTE, reg1=resultRegister, reg2=zeroRegister) } else { - if (lessEquals) Opcode.SLE else Opcode.SLT + val rightResultReg = codeGen.vmRegisters.nextFree() + code += translateExpression(binExpr.left, resultRegister, -1) + code += translateExpression(binExpr.right, rightResultReg, -1) + val ins = if (signed) { + if (lessEquals) Opcode.SLES else Opcode.SLTS + } else { + if (lessEquals) Opcode.SLE else Opcode.SLT + } + code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg) } - code += VmCodeInstruction(ins, vmDt, reg1 = resultRegister, reg2 = rightResultReg) } return code } @@ -386,22 +403,37 @@ internal class ExpressionGen(private val codeGen: CodeGen) { val rightFpReg = codeGen.vmRegisters.nextFreeFloat() code += translateExpression(binExpr.left, -1, leftFpReg) code += translateExpression(binExpr.right, -1, rightFpReg) - code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg) - if(!notEquals) { + if (notEquals) { + code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=resultRegister, fpReg1 = leftFpReg, fpReg2 = rightFpReg) + } else { val label = codeGen.createLabelName() - code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=resultRegister, symbol = label) + val valueReg = codeGen.vmRegisters.nextFree() code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=1) + code += VmCodeInstruction(Opcode.FCOMP, VmDataType.FLOAT, reg1=valueReg, fpReg1 = leftFpReg, fpReg2 = rightFpReg) + code += VmCodeInstruction(Opcode.BZ, VmDataType.BYTE, reg1=valueReg, symbol = label) + code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=resultRegister, value=0) code += VmCodeLabel(label) - val regMask = codeGen.vmRegisters.nextFree() - code += VmCodeInstruction(Opcode.LOAD, VmDataType.BYTE, reg1=regMask, value=1) - code += VmCodeInstruction(Opcode.XOR, VmDataType.BYTE, reg1=resultRegister, reg2=regMask) } } else { - val rightResultReg = codeGen.vmRegisters.nextFree() - code += translateExpression(binExpr.left, resultRegister, -1) - code += translateExpression(binExpr.right, rightResultReg, -1) - val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ - code += VmCodeInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg) + if(binExpr.left.type==DataType.STR && binExpr.right.type==DataType.STR) { + val comparisonCall = PtFunctionCall(listOf("prog8_lib", "string_compare"), false, DataType.BYTE, Position.DUMMY) + comparisonCall.children.add(binExpr.left) + comparisonCall.children.add(binExpr.right) + code += translate(comparisonCall, resultRegister, -1) + if(notEquals) { + val maskReg = codeGen.vmRegisters.nextFree() + code += VmCodeInstruction(Opcode.LOAD, vmDt, reg1=maskReg, value=1) + code += VmCodeInstruction(Opcode.AND, vmDt, reg1=resultRegister, reg2=maskReg) + } else { + code += VmCodeInstruction(Opcode.NOT, vmDt, reg1=resultRegister) + } + } else { + val rightResultReg = codeGen.vmRegisters.nextFree() + code += translateExpression(binExpr.left, resultRegister, -1) + code += translateExpression(binExpr.right, rightResultReg, -1) + val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ + code += VmCodeInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg) + } } return code } diff --git a/compiler/res/prog8lib/virtual/prog8_lib.p8 b/compiler/res/prog8lib/virtual/prog8_lib.p8 index 62e966e92..d457a25cd 100644 --- a/compiler/res/prog8lib/virtual/prog8_lib.p8 +++ b/compiler/res/prog8lib/virtual/prog8_lib.p8 @@ -37,4 +37,19 @@ prog8_lib { } return false } + + sub string_compare(str st1, str st2) -> byte { + ; Compares two strings for sorting. + ; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2. + ; Note that you can also directly compare strings and string values with eachother using + ; comparison operators ==, < etcetera (it will use strcmp for you under water automatically). + %asm {{ + loadm.w r0, {prog8_lib.string_compare.st1} + loadm.w r1, {prog8_lib.string_compare.st2} + syscall 29 + return + }} + } + } + diff --git a/compiler/res/prog8lib/virtual/string.p8 b/compiler/res/prog8lib/virtual/string.p8 new file mode 100644 index 000000000..06aa15243 --- /dev/null +++ b/compiler/res/prog8lib/virtual/string.p8 @@ -0,0 +1,119 @@ +; 0-terminated string manipulation routines. For the Virtual Machine target. +; +; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 + + +string { + sub length(str st) -> ubyte { + ; Returns the number of bytes in the string. + ; This value is determined during runtime and counts upto the first terminating 0 byte in the string, + ; regardless of the size of the string during compilation time. Don’t confuse this with len and sizeof! + ubyte count = 0 + while st[count] + count++ + return count + } + + sub left(str source, ubyte slen, str target) { + ; Copies the left side of the source string of the given length to target string. + ; It is assumed the target string buffer is large enough to contain the result. + ; Also, you have to make sure yourself that length is smaller or equal to the length of the source string. + ; Modifies in-place, doesn’t return a value (so can’t be used in an expression). + target[slen] = 0 + ubyte ix + for ix in 0 to slen-1 { + target[ix] = source[ix] + } + } + + sub right(str source, ubyte slen, str target) { + ; Copies the right side of the source string of the given length to target string. + ; It is assumed the target string buffer is large enough to contain the result. + ; Also, you have to make sure yourself that length is smaller or equal to the length of the source string. + ; Modifies in-place, doesn’t return a value (so can’t be used in an expression). + ubyte offset = length(source)-slen + ubyte ix + for ix in 0 to slen-1 { + target[ix] = source[ix+offset] + } + target[ix]=0 + } + + sub slice(str source, ubyte start, ubyte slen, str target) { + ; Copies a segment from the source string, starting at the given index, + ; and of the given length to target string. + ; It is assumed the target string buffer is large enough to contain the result. + ; Also, you have to make sure yourself that start and length are within bounds of the strings. + ; Modifies in-place, doesn’t return a value (so can’t be used in an expression). + ubyte ix + for ix in 0 to slen-1 { + target[ix] = source[ix+start] + } + target[ix]=0 + } + + sub find(str st, ubyte character) -> ubyte { + ; Locates the first position of the given character in the string, + ; returns Carry set if found + index in A, or Carry clear if not found. + ubyte ix + for ix in 0 to length(st)-1 { + if st[ix]==character { + sys.set_carry() + return ix + } + } + sys.clear_carry() + return 0 + } + + sub copy(str source, str target) -> ubyte { + ; Copy a string to another, overwriting that one. + ; Returns the length of the string that was copied. + ; Often you don’t have to call this explicitly and can just write string1 = string2 + ; but this function is useful if you’re dealing with addresses for instance. + ubyte ix + repeat { + ubyte char=source[ix] + target[ix]=char + if not char + return ix + ix++ + } + } + + sub compare(str st1, str st2) -> byte { + ; Compares two strings for sorting. + ; Returns -1 (255), 0 or 1 depending on wether string1 sorts before, equal or after string2. + ; Note that you can also directly compare strings and string values with eachother using + ; comparison operators ==, < etcetera (it will use strcmp for you under water automatically). + return prog8_lib.string_compare(st1, st2) + } + + sub lower(str st) -> ubyte { + ; Lowercases the petscii string in-place. Returns length of the string. + ; (for efficiency, non-letter characters > 128 will also not be left intact, + ; but regular text doesn't usually contain those characters anyway.) + ubyte ix + repeat { + ubyte char=st[ix] + if not char + return ix + if char >= 'A' and char <= 'Z' + st[ix] = char | %00100000 + ix++ + } + } + + sub upper(str st) -> ubyte { + ; Uppercases the petscii string in-place. Returns length of the string. + ubyte ix + repeat { + ubyte char=st[ix] + if not char + return ix + if char >= 97 and char <= 122 + st[ix] = char & %11011111 + ix++ + } + } +} diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 25bd80bfd..a883daf92 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -388,8 +388,8 @@ private fun createAssemblyAndAssemble(program: Program, // to help clean up the code that still depends on them. // removeAllVardeclsFromAst(program) -// println("*********** AST RIGHT BEFORE ASM GENERATION *************") -// printProgram(program) + println("*********** AST RIGHT BEFORE ASM GENERATION *************") + printProgram(program) val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly() errors.report() diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 062ea05da..1f00f1797 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -889,7 +889,7 @@ internal class AstChecker(private val program: Program, if(rightDt!in NumericDatatypes && rightDt != DataType.STR) errors.err("right operand is not numeric or str", expr.right.position) if(leftDt!=rightDt) { - if(leftDt==DataType.STR && rightDt in IntegerDatatypes) { + if(leftDt==DataType.STR && rightDt in IntegerDatatypes && expr.operator=="*") { // only exception allowed: str * constvalue if(expr.right.constValue(program)==null) errors.err("can only use string repeat with a constant number value", expr.left.position) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 37445a207..8a5f6dfdd 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,9 +3,10 @@ TODO For next release ^^^^^^^^^^^^^^^^ -- vm: get rid of all the conditional set instructions -- vm: add more instructions operating directly on memory instead of only registers? +- c64 target: after exit, switching charset case is still disabled. Don't disable this by default in startup? - vm: check array type in PtAssignTarget +- vm: animals example game breaks after adding first new animal... +- vm: add more instructions operating directly on memory instead of only registers? (translate assignment self-assigns) - in-place modifiying functions (rol, ror, ..) don't accept a memory address but require a memory-read expression. that is weird. - complete the Inliner - add McCarthy evaluation to shortcircuit and/or expressions. First do ifs by splitting them up? Then do expressions that compute a value? diff --git a/examples/test.p8 b/examples/test.p8 index de1b86f65..0a41ba65f 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,5 +1,6 @@ %import textio %import math +%import string %import floats %zeropage dontuse @@ -25,37 +26,44 @@ main { ubyte @shared value = inline_candidate() - ubyte lowb = 4 - ubyte highb = $ea + str name = "irmen123ABC" + str other = "zrmen123ABC" - if lowb+4 - lowb++ - if math.sin8u(lowb) - lowb++ - if lowb - lowb++ + txt.print_ub(string.upper(name)) + txt.print(name) + txt.nl() + txt.print_ub(string.lower(name)) + txt.print(name) + txt.nl() - if lowb==0 - lowb++ + uword otheraddr = &other + txt.print_ub(name!=other) + txt.spc() + txt.print_ub(name==other) + txt.nl() + txt.print_ub(name>other) + txt.spc() + txt.print_ub(name>=other) + txt.nl() + txt.print_ub(nameother) + txt.spc() + txt.print_ub(name>=other) + txt.nl() + txt.print_ub(name1 , ~0 -> 0) lsrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits + set Carry to shifted bit asrn reg1, reg2 - reg1 = multi-shift reg1 right by reg2 bits (signed) + set Carry to shifted bit lsln reg1, reg2 - reg1 = multi-shift reg1 left by reg2 bits + set Carry to shifted bit @@ -225,6 +226,7 @@ enum class Opcode { AND, OR, XOR, + NOT, ASRN, LSRN, LSLN, @@ -273,7 +275,9 @@ val OpcodesWithAddress = setOf( Opcode.STOREM, Opcode.STOREX, Opcode.STOREZM, - Opcode.STOREZX + Opcode.STOREZX, + Opcode.INCM, + Opcode.DECM ) @@ -474,6 +478,7 @@ val instructionFormats = mutableMapOf( Opcode.AND to InstructionFormat.from("BW,r1,r2"), Opcode.OR to InstructionFormat.from("BW,r1,r2"), Opcode.XOR to InstructionFormat.from("BW,r1,r2"), + Opcode.NOT to InstructionFormat.from("BW,r1"), Opcode.ASRN to InstructionFormat.from("BW,r1,r2"), Opcode.LSRN to InstructionFormat.from("BW,r1,r2"), Opcode.LSLN to InstructionFormat.from("BW,r1,r2"), diff --git a/virtualmachine/src/prog8/vm/SysCalls.kt b/virtualmachine/src/prog8/vm/SysCalls.kt index f6c1abd84..ecf73ab62 100644 --- a/virtualmachine/src/prog8/vm/SysCalls.kt +++ b/virtualmachine/src/prog8/vm/SysCalls.kt @@ -34,6 +34,7 @@ SYSCALLS: 26 = reverse_bytes array 27 = reverse_words array 28 = reverse_floats array +29 = compare strings */ enum class Syscall { @@ -66,6 +67,7 @@ enum class Syscall { REVERSE_BYTES, REVERSE_WORDS, REVERSE_FLOATS, + COMPARE_STRINGS } object SysCalls { @@ -255,6 +257,14 @@ object SysCalls { val string = vm.memory.getString(stringAddr.toInt()) vm.registers.setSW(0, string.toShort()) } + Syscall.COMPARE_STRINGS -> { + val firstAddr = vm.registers.getUW(0) + val secondAddr = vm.registers.getUW(1) + val first = vm.memory.getString(firstAddr.toInt()) + val second = vm.memory.getString(secondAddr.toInt()) + val comparison = first.compareTo(second) + vm.registers.setSB(0, comparison.toByte()) + } else -> TODO("syscall ${call.name}") } } diff --git a/virtualmachine/src/prog8/vm/VirtualMachine.kt b/virtualmachine/src/prog8/vm/VirtualMachine.kt index 53c6ab231..3f2c2d797 100644 --- a/virtualmachine/src/prog8/vm/VirtualMachine.kt +++ b/virtualmachine/src/prog8/vm/VirtualMachine.kt @@ -150,6 +150,7 @@ class VirtualMachine(val memory: Memory, program: List) { Opcode.AND -> InsAND(ins) Opcode.OR -> InsOR(ins) Opcode.XOR -> InsXOR(ins) + Opcode.NOT -> InsNOT(ins) Opcode.ASRN -> InsASRM(ins) Opcode.LSRN -> InsLSRM(ins) Opcode.LSLN -> InsLSLM(ins) @@ -852,6 +853,15 @@ class VirtualMachine(val memory: Memory, program: List) { pc++ } + private fun InsNOT(i: Instruction) { + when(i.type!!) { + VmDataType.BYTE -> registers.setUB(i.reg1!!, if(registers.getUB(i.reg1)==0.toUByte()) 1u else 0u) + VmDataType.WORD -> registers.setUW(i.reg1!!, if(registers.getUW(i.reg1)==0.toUShort()) 1u else 0u) + VmDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i") + } + pc++ + } + private fun InsASRM(i: Instruction) { val (left: Int, right: Int) = getLogicalOperandsS(i) statusCarry = (left and 1)!=0