diff --git a/compiler/examples/test.p8 b/compiler/examples/test.p8 index 8d5610d54..bf1d1f902 100644 --- a/compiler/examples/test.p8 +++ b/compiler/examples/test.p8 @@ -14,6 +14,7 @@ sub start() { memory uword memaddr = $c000 uword[2] wordarray byte b1 + memory byte mb1 = $c991 str stringvar = "??????????\n\n\nnext line\r\r\rnext line after carriagereturn" @@ -34,29 +35,38 @@ sub start() { secretnumber = '@' secretnumber = '\r' - testword = stringvar ; @todo fix str address assignment - testword = "stringstring" ; @todo fix str address assignment - freadstr_arg = stringvar ; @todo fix str address assignment - freadstr_arg = "stringstring" ; @todo fix str address assignment - secretnumber = "stringstring2222" ; @todo fix str address assignment + testword = stringvar + testword = wordarray + freadstr_arg = stringvar + freadstr_arg = wordarray + wordarray[1] = stringvar + wordarray[1] = wordarray + wordarray[b1] = stringvar + wordarray[b1] = wordarray + wordarray[mb1] = stringvar + wordarray[mb1] = wordarray + ; testword = "stringstring" ; @todo asmgen for this + ; freadstr_arg = "stringstring" ; @todo asmgen for this + ; freadstr_arg = "stringstring2222" ; @todo asmgen for this + ; wordarray[1] = "stringstring" ; @todo asmgen for this - address =c64.MEMBOT(1, 40000.w) ; ok! - address =c64.MEMBOT(1, address) ; ok! - address =c64.MEMBOT(1, memaddr) ; ok! - - A, Y =c64.GETADR() ; ok! - Y, A =c64.GETADR() ; ok! - address = c64flt.GETADRAY() ; ok! - memaddr = c64flt.GETADRAY() ; ok! - wordarray[1] = c64flt.GETADRAY() ; ok! - v1, v2 =c64.GETADR() ; ok! - address =c64.IOBASE() ; ok! - A = c64.CHRIN() ; ok ! - X = c64.CHRIN() ; ok ! - Y = c64.CHRIN() ; ok! - v1 = c64.CHRIN() ; ok ! +; address =c64.MEMBOT(1, 40000.w) ; ok! +; address =c64.MEMBOT(1, address) ; ok! +; address =c64.MEMBOT(1, memaddr) ; ok! +; +; A, Y =c64.GETADR() ; ok! +; Y, A =c64.GETADR() ; ok! +; address = c64flt.GETADRAY() ; ok! +; memaddr = c64flt.GETADRAY() ; ok! +; wordarray[1] = c64flt.GETADRAY() ; ok! +; v1, v2 =c64.GETADR() ; ok! +; address =c64.IOBASE() ; ok! +; A = c64.CHRIN() ; ok ! +; X = c64.CHRIN() ; ok ! +; Y = c64.CHRIN() ; ok! +; v1 = c64.CHRIN() ; ok ! return } diff --git a/compiler/src/prog8/ast/AstChecker.kt b/compiler/src/prog8/ast/AstChecker.kt index a5229775b..87c9aa59e 100644 --- a/compiler/src/prog8/ast/AstChecker.kt +++ b/compiler/src/prog8/ast/AstChecker.kt @@ -338,26 +338,36 @@ class AstChecker(private val namespace: INameScope, if(target.identifier!=null) { val targetName = target.identifier.nameInSource val targetSymbol = namespace.lookup(targetName, assignment) - when { - targetSymbol == null -> { + when (targetSymbol) { + null -> { checkResult.add(ExpressionError("undefined symbol: ${targetName.joinToString(".")}", assignment.position)) return assignment } - targetSymbol !is VarDecl -> { + !is VarDecl -> { checkResult.add(SyntaxError("assignment LHS must be register or variable", assignment.position)) return assignment } - targetSymbol.type == VarDeclType.CONST -> { - checkResult.add(ExpressionError("cannot assign new value to a constant", assignment.position)) - return assignment + else -> { + if(targetSymbol.type == VarDeclType.CONST) { + checkResult.add(ExpressionError("cannot assign new value to a constant", assignment.position)) + return assignment + } + if(assignment.value.resultingDatatype(namespace, heap) in ArrayDatatypes) { + if(targetSymbol.datatype==DataType.UWORD) + return assignment // array can be assigned to UWORD (it's address should be taken as the value then) + } } - else -> {} } } - // it is not possible to assign a new arrayspec to something. - if(assignment.value.resultingDatatype(namespace, heap) in ArrayDatatypes) - checkResult.add(SyntaxError("it's not possible to assign an arrayspec literal value to something, use it as a variable decl initializer instead", assignment.position)) + // it is only possible to assign an array to something that is an UWORD or UWORD array (in which case the address of the array value is used as the value) + if(assignment.value.resultingDatatype(namespace, heap) in ArrayDatatypes) { + // the UWORD case has been handled above already, check for UWORD array + val arrayVar = target.arrayindexed?.identifier?.targetStatement(namespace) + if(arrayVar is VarDecl && arrayVar.datatype==DataType.ARRAY_UW) + return assignment + checkResult.add(SyntaxError("it's not possible to assign an array to something other than an UWORD, use it as a variable decl initializer instead", assignment.position)) + } if(assignment.aug_op!=null) { // check augmented assignment: @@ -844,10 +854,12 @@ class AstChecker(private val namespace: INameScope, return err("value '$number' out of range for byte") } DataType.UWORD -> { + if(value.isString || value.isArray) // string or array are assignable to uword; their memory address is used. + return true val number = value.asIntegerValue ?: return if (value.floatvalue!=null) err("unsigned word value expected instead of float; possible loss of precision") else - err("unsigned word value expected") + err("unsigned word value or address expected") if (number < 0 || number > 65535) return err("value '$number' out of range for unsigned word") } @@ -950,7 +962,7 @@ class AstChecker(private val namespace: INameScope, DataType.BYTE -> sourceDatatype==DataType.BYTE DataType.UBYTE -> sourceDatatype==DataType.UBYTE DataType.WORD -> sourceDatatype==DataType.BYTE || sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.WORD - DataType.UWORD -> sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.UWORD + DataType.UWORD -> sourceDatatype in setOf(DataType.UBYTE, DataType.UWORD, DataType.STR, DataType.STR_S) || sourceDatatype in ArrayDatatypes DataType.FLOAT -> sourceDatatype in NumericDatatypes DataType.STR -> sourceDatatype==DataType.STR DataType.STR_S -> sourceDatatype==DataType.STR_S diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 554e59753..f8ce72528 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -1180,13 +1180,44 @@ private class StatementTranslator(private val prog: IntermediateProgram, when(targetDt) { DataType.UBYTE, DataType.BYTE -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt") - DataType.UWORD, DataType.WORD -> { + DataType.WORD -> { when (valueDt) { DataType.UBYTE -> prog.instr(Opcode.UB2UWORD) DataType.BYTE -> prog.instr(Opcode.B2WORD) else -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt") } } + DataType.UWORD -> { + when (valueDt) { + DataType.UBYTE -> prog.instr(Opcode.UB2UWORD) + DataType.BYTE -> prog.instr(Opcode.B2WORD) + DataType.STR, DataType.STR_S -> { + when(stmt.value) { + is LiteralValue -> { + val lv = stmt.value as LiteralValue + prog.removeLastInstruction() + prog.instr(Opcode.PUSH_ADDR_STR, Value(lv.type, lv.heapId!!)) + } + is IdentifierReference -> { + val vardecl = (stmt.value as IdentifierReference).targetStatement(namespace) as VarDecl + prog.removeLastInstruction() + prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname) + } + else -> throw CompilerException("can only take address of a literal string value or a string/array variable") + } + } + DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_W, DataType.ARRAY_UW, DataType.ARRAY_F -> { + if (stmt.value is IdentifierReference) { + val vardecl = (stmt.value as IdentifierReference).targetStatement(namespace) as VarDecl + prog.removeLastInstruction() + prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname) + } + else + throw CompilerException("can only take address of a literal string value or a string/array variable") + } + else -> throw CompilerException("incompatible data types valueDt=$valueDt targetDt=$targetDt at $stmt") + } + } DataType.FLOAT -> { when (valueDt) { DataType.UBYTE -> prog.instr(Opcode.UB2FLOAT) diff --git a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt index 74d5c457e..782c72635 100644 --- a/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt +++ b/compiler/src/prog8/compiler/intermediate/IntermediateProgram.kt @@ -295,6 +295,10 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap currentBlock.instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}")) } + fun removeLastInstruction() { + currentBlock.instructions.removeAt(currentBlock.instructions.lastIndex) + } + fun symbolDef(name: String, value: Int) { currentBlock.integerConstants[name] = value } diff --git a/compiler/src/prog8/compiler/intermediate/Opcode.kt b/compiler/src/prog8/compiler/intermediate/Opcode.kt index 678067d39..81d6eee80 100644 --- a/compiler/src/prog8/compiler/intermediate/Opcode.kt +++ b/compiler/src/prog8/compiler/intermediate/Opcode.kt @@ -17,6 +17,8 @@ enum class Opcode { PUSH_REGAX_WORD, // push registers A/X as a 16-bit word PUSH_REGAY_WORD, // push registers A/Y as a 16-bit word PUSH_REGXY_WORD, // push registers X/Y as a 16-bit word + PUSH_ADDR_STR, // push the address of the string value (literal) + PUSH_ADDR_HEAPVAR, // push the address of the variable that's on the heap (string or array) // popping values off the (evaluation) stack, possibly storing them in another location DISCARD_BYTE, // discard top byte value @@ -227,7 +229,7 @@ val opcodesWithVarArgument = setOf( Opcode.ROL_VAR_BYTE, Opcode.ROL_VAR_WORD, Opcode.ROR_VAR_BYTE, Opcode.ROR_VAR_WORD, Opcode.ROL2_VAR_BYTE, Opcode.ROL2_VAR_WORD, Opcode.ROR2_VAR_BYTE, Opcode.ROR2_VAR_WORD, Opcode.POP_VAR_BYTE, Opcode.POP_VAR_WORD, Opcode.POP_VAR_FLOAT, - Opcode.PUSH_VAR_BYTE, Opcode.PUSH_VAR_WORD, Opcode.PUSH_VAR_FLOAT, + Opcode.PUSH_VAR_BYTE, Opcode.PUSH_VAR_WORD, Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_ADDR_HEAPVAR, Opcode.READ_INDEXED_VAR_BYTE, Opcode.READ_INDEXED_VAR_WORD, Opcode.READ_INDEXED_VAR_FLOAT, Opcode.WRITE_INDEXED_VAR_BYTE, Opcode.WRITE_INDEXED_VAR_WORD, Opcode.WRITE_INDEXED_VAR_FLOAT ) diff --git a/compiler/src/prog8/compiler/target/c64/AsmGen.kt b/compiler/src/prog8/compiler/target/c64/AsmGen.kt index 93d328604..798bed2f7 100644 --- a/compiler/src/prog8/compiler/target/c64/AsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/AsmGen.kt @@ -1298,6 +1298,15 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, sty ${segment[1].callLabel}+1 """ }, + // var = address-of other var + AsmPattern(listOf(Opcode.PUSH_ADDR_HEAPVAR, Opcode.POP_VAR_WORD)) { segment -> + """ + lda #<${segment[0].callLabel} + ldy #>${segment[0].callLabel} + sta ${segment[1].callLabel} + sty ${segment[1].callLabel}+1 + """ + }, // var = mem ubyte AsmPattern(listOf(Opcode.PUSH_MEM_UB, Opcode.UB2UWORD, Opcode.POP_VAR_WORD)) { segment -> " lda ${hexVal(segment[0])} | sta ${segment[2].callLabel} | lda #0 | sta ${segment[2].callLabel}+1" @@ -1393,6 +1402,10 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.POP_MEM_WORD)) { segment -> " lda ${segment[0].callLabel} || sta ${hexVal(segment[1])} | lda ${segment[0].callLabel}+1 | sta ${hexValPlusOne(segment[1])}" }, + // mem uword = address-of var + AsmPattern(listOf(Opcode.PUSH_ADDR_HEAPVAR, Opcode.POP_MEM_WORD)) { segment -> + " lda #<${segment[0].callLabel} || sta ${hexVal(segment[1])} | lda #>${segment[0].callLabel} | sta ${hexValPlusOne(segment[1])}" + }, // mem (u)word = mem (u)word AsmPattern( listOf(Opcode.PUSH_MEM_UW, Opcode.POP_MEM_WORD), @@ -1607,6 +1620,20 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, sta ${segment[2].callLabel}+1,y """ }, + // uwordarray[index mem] = address-of var + AsmPattern( + listOf(Opcode.PUSH_ADDR_HEAPVAR, Opcode.PUSH_MEM_B, Opcode.WRITE_INDEXED_VAR_WORD), + listOf(Opcode.PUSH_ADDR_HEAPVAR, Opcode.PUSH_MEM_UB, Opcode.WRITE_INDEXED_VAR_WORD)) { segment -> + """ + lda ${hexVal(segment[1])} + asl a + tay + lda #<${segment[0].callLabel} + sta ${segment[2].callLabel},y + lda #>${segment[0].callLabel} + sta ${segment[2].callLabel}+1,y + """ + }, // uwordarray[index] = ubytevar AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.UB2UWORD, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_WORD)) { segment -> val index = segment[2].arg!!.integerValue()*2 @@ -1898,6 +1925,11 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, val index = segment[1].arg!!.integerValue()*2 " lda ${segment[0].callLabel} | sta ${segment[2].callLabel}+$index | lda ${segment[0].callLabel}+1 | sta ${segment[2].callLabel}+${index+1}" }, + // uwordarray[index] = address-of var + AsmPattern(listOf(Opcode.PUSH_ADDR_HEAPVAR, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_WORD)) { segment -> + val index = segment[1].arg!!.integerValue()*2 + " lda #<${segment[0].callLabel} | sta ${segment[2].callLabel}+$index | lda #>${segment[0].callLabel} | sta ${segment[2].callLabel}+${index+1}" + }, // uwordarray[index] = mem uword AsmPattern(listOf(Opcode.PUSH_MEM_UW, Opcode.PUSH_BYTE, Opcode.WRITE_INDEXED_VAR_WORD)) { segment -> val index = segment[1].arg!!.integerValue()*2 @@ -1967,6 +1999,16 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, } " $loadIndexY | lda ${segment[0].callLabel} | sta ${segment[2].callLabel},y | lda ${segment[0].callLabel}+1 | sta ${segment[2].callLabel}+1,y" }, + // uwordarray[indexvar] = address-of var + AsmPattern(listOf(Opcode.PUSH_ADDR_HEAPVAR, Opcode.PUSH_VAR_BYTE, Opcode.WRITE_INDEXED_VAR_WORD)) { segment -> + val loadIndexY = when(segment[1].callLabel) { + "A" -> " asl a | tay" + "X" -> " txa | asl a | tay" + "Y" -> " tya | asl a | tay" + else -> " lda ${segment[1].callLabel} | asl a | tay" + } + " $loadIndexY | lda #<${segment[0].callLabel} | sta ${segment[2].callLabel},y | lda #>${segment[0].callLabel} | sta ${segment[2].callLabel}+1,y" + }, // uwordarray[indexvar] = mem ubyte AsmPattern(listOf(Opcode.PUSH_MEM_UB, Opcode.UB2UWORD, Opcode.PUSH_VAR_BYTE, Opcode.WRITE_INDEXED_VAR_WORD)) { segment -> val loadIndexY = when(segment[2].callLabel) { @@ -2505,15 +2547,15 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, }, // AX register pair = word var AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.POP_REGAX_WORD)) { segment -> - " lda #<${segment[0].callLabel} | ldx #>${segment[0].callLabel}" + " lda ${segment[0].callLabel} | ldx ${segment[0].callLabel}+1" }, // AY register pair = word var AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.POP_REGAY_WORD)) { segment -> - " lda #<${segment[0].callLabel} | ldy #>${segment[0].callLabel}" + " lda ${segment[0].callLabel} | ldy ${segment[0].callLabel}+1" }, // XY register pair = word var AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.POP_REGXY_WORD)) { segment -> - " ldx #<${segment[0].callLabel} | ldy #>${segment[0].callLabel}" + " ldx ${segment[0].callLabel} | ldy ${segment[0].callLabel}+1" }, // AX register pair = mem word AsmPattern( diff --git a/compiler/src/prog8/stackvm/StackVm.kt b/compiler/src/prog8/stackvm/StackVm.kt index 2c3d66381..8698cf331 100644 --- a/compiler/src/prog8/stackvm/StackVm.kt +++ b/compiler/src/prog8/stackvm/StackVm.kt @@ -1323,6 +1323,19 @@ class StackVm(private var traceOutputFile: String?) { P_irqd = evalstack.pop().asBooleanValue } Opcode.INLINE_ASSEMBLY -> throw VmExecutionException("stackVm doesn't support executing inline assembly code") + Opcode.PUSH_ADDR_HEAPVAR -> { + val heapId = variables[ins.callLabel]!!.heapId + if(heapId<=0) + throw VmExecutionException("expected variable on heap") + evalstack.push(Value(DataType.UWORD, heapId)) // push the "address" of the string or array variable + } + Opcode.PUSH_ADDR_STR -> { + val heapId = ins.arg!!.heapId + if(heapId<=0) + throw VmExecutionException("expected string to be on heap") + evalstack.push(Value(DataType.UWORD, heapId)) // push the "address" of the string + } + //else -> throw VmExecutionException("unimplemented opcode: ${ins.opcode}") } diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 27713f898..eb19b07b8 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -207,8 +207,6 @@ Array types are also supported. They can be made of bytes, words and floats:: Right now, the array should be small enough to be indexable by a single byte index. This means byte arrays should be <= 256 elements, word arrays <= 128 elements, and float arrays <= 51 elements. This limit may or may not be lifted in a future version. - Matrixes can be indexed in each dimension only by a byte as well, this also means - their maximum size is 65536 elements (bytes). Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.) @@ -300,7 +298,7 @@ When declaring values with an initial value, this value will be set into the var the program reaches the declaration again. This can be in loops, multiple subroutine calls, or even multiple invocations of the entire program. -This only works for simple types, *and not for string variables, arrays and matrices*. +This only works for simple types, *and not for string variables and arrays*. It is assumed these are left unchanged by the program. If you do modify them in-place, you should take care yourself that they work as expected when the program is restarted.