mirror of
https://github.com/irmen/prog8.git
synced 2024-12-24 16:29:21 +00:00
implemented array indexing
This commit is contained in:
parent
8f26fdef61
commit
0cdae48ce7
@ -123,7 +123,6 @@ postincrdecr : assign_target operator = ('++' | '--') ;
|
||||
|
||||
expression :
|
||||
'(' expression ')'
|
||||
| expression arrayspec
|
||||
| functioncall
|
||||
| prefix = ('+'|'-'|'~') expression
|
||||
| left = expression bop = '**' right = expression
|
||||
@ -143,9 +142,15 @@ expression :
|
||||
| register
|
||||
| identifier
|
||||
| scoped_identifier
|
||||
| arrayindexed
|
||||
;
|
||||
|
||||
|
||||
arrayindexed :
|
||||
(identifier | scoped_identifier | register) arrayspec
|
||||
;
|
||||
|
||||
|
||||
functioncall :
|
||||
(identifier | scoped_identifier) '(' expression_list? ')'
|
||||
;
|
||||
|
@ -1,15 +1,21 @@
|
||||
|
||||
~ main {
|
||||
|
||||
; sub VECTOR (dir: Pc, userptr: XY) -> (X, A?, Y?) = $FF8D ; read/set I/O vector table
|
||||
|
||||
|
||||
asmsub VECTOR (dir: byte @ A, userptr: word @ XY) -> clobbers(A,X,Y) -> (byte @ X) = $ff8d
|
||||
; asmsub VECTOR (dir: byte @ Pc, userptr: word @ XY) -> byte @ X, clobbers @ A, clobbers @ Y = $ff8d
|
||||
|
||||
|
||||
|
||||
sub start() {
|
||||
|
||||
byte[100] array
|
||||
byte[4,5] mvar
|
||||
|
||||
A=AX[2]
|
||||
A=AY[2]
|
||||
A=XY[2]
|
||||
|
||||
A=array[98]
|
||||
A=array[99]
|
||||
A=mvar[13]
|
||||
A=mvar[2,3]
|
||||
A=mvar[Y,X]
|
||||
return
|
||||
|
||||
}
|
||||
|
@ -216,6 +216,12 @@ interface IAstProcessor {
|
||||
fun process(asmSubroutine: AsmSubroutine): IStatement {
|
||||
return asmSubroutine
|
||||
}
|
||||
|
||||
fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
|
||||
arrayIndexedExpression.identifier?.process(this)
|
||||
arrayIndexedExpression.array.process(this)
|
||||
return arrayIndexedExpression
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -805,6 +811,39 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
|
||||
}
|
||||
}
|
||||
|
||||
class ArrayIndexedExpression(val identifier: IdentifierReference?,
|
||||
val register: Register?,
|
||||
var array: ArraySpec,
|
||||
override val position: Position) : IExpression {
|
||||
override lateinit var parent: Node
|
||||
override fun linkParents(parent: Node) {
|
||||
this.parent = parent
|
||||
identifier?.linkParents(this)
|
||||
array.linkParents(this)
|
||||
}
|
||||
|
||||
override fun isIterable(namespace: INameScope, heap: HeapValues) = false
|
||||
override fun constValue(namespace: INameScope, heap: HeapValues): LiteralValue? = null
|
||||
override fun process(processor: IAstProcessor): IExpression = processor.process(this)
|
||||
override fun referencesIdentifier(name: String) = identifier?.referencesIdentifier(name) ?: false
|
||||
|
||||
override fun resultingDatatype(namespace: INameScope, heap: HeapValues): DataType? {
|
||||
if (register != null)
|
||||
return DataType.BYTE
|
||||
val target = identifier?.targetStatement(namespace)
|
||||
if (target is VarDecl) {
|
||||
return when (target.datatype) {
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.BYTE
|
||||
DataType.ARRAY, DataType.MATRIX -> DataType.BYTE
|
||||
DataType.ARRAY_W -> DataType.WORD
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
throw FatalAstException("cannot get indexed element on $target")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private data class NumericLiteral(val number: Number, val datatype: DataType)
|
||||
|
||||
|
||||
@ -1755,9 +1794,19 @@ private fun prog8Parser.ExpressionContext.toAst() : IExpression {
|
||||
if(childCount==3 && children[0].text=="(" && children[2].text==")")
|
||||
return expression(0).toAst() // expression within ( )
|
||||
|
||||
if(arrayindexed()!=null)
|
||||
return arrayindexed().toAst()
|
||||
|
||||
throw FatalAstException(text)
|
||||
}
|
||||
|
||||
private fun prog8Parser.ArrayindexedContext.toAst(): IExpression {
|
||||
return ArrayIndexedExpression(identifier()?.toAst() ?: scoped_identifier()?.toAst(),
|
||||
register()?.toAst(),
|
||||
arrayspec().toAst(),
|
||||
toPosition())
|
||||
}
|
||||
|
||||
|
||||
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
|
||||
|
||||
|
@ -290,7 +290,7 @@ class AstChecker(private val namespace: INameScope,
|
||||
if(assignment.value is FunctionCall)
|
||||
checkResult.add(ExpressionError("function call doesn't return a value to use in assignment", assignment.value.position))
|
||||
else
|
||||
checkResult.add(ExpressionError("assignment source ${assignment.value} is no value or has no proper datatype", assignment.value.position))
|
||||
checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position))
|
||||
}
|
||||
else {
|
||||
checkAssignmentCompatible(targetDatatype, sourceDatatype, assignment.value, assignment.position)
|
||||
@ -561,6 +561,34 @@ class AstChecker(private val namespace: INameScope,
|
||||
return super.process(postIncrDecr)
|
||||
}
|
||||
|
||||
override fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
|
||||
val reg=arrayIndexedExpression.register
|
||||
if(reg==null) {
|
||||
val target = arrayIndexedExpression.identifier!!.targetStatement(namespace)
|
||||
if(target is VarDecl) {
|
||||
if(target.datatype==DataType.BYTE || target.datatype==DataType.WORD || target.datatype==DataType.FLOAT)
|
||||
checkResult.add(SyntaxError("array indexing requires an iterable variable", arrayIndexedExpression.position))
|
||||
val arraysize = target.arrayspec?.size()
|
||||
if(arraysize!=null) {
|
||||
// check out of bounds
|
||||
if((arrayIndexedExpression.array.y as? LiteralValue)?.asIntegerValue != null) {
|
||||
throw FatalAstException("constant y dimension of index should have been const-folded with x into one value")
|
||||
}
|
||||
val index = (arrayIndexedExpression.array.x as? LiteralValue)?.asIntegerValue
|
||||
if(index!=null && (index<0 || index>=arraysize))
|
||||
checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.array.position))
|
||||
}
|
||||
} else
|
||||
checkResult.add(SyntaxError("array indexing requires a variable to act upon", arrayIndexedExpression.position))
|
||||
} else if(reg==Register.A || reg==Register.X || reg==Register.Y) {
|
||||
checkResult.add(SyntaxError("array indexing on registers requires register pair variable", arrayIndexedExpression.position))
|
||||
} else if(arrayIndexedExpression.array.y!=null) {
|
||||
checkResult.add(SyntaxError("array indexing on registers can only use one index dimension", arrayIndexedExpression.position))
|
||||
}
|
||||
|
||||
return super.process(arrayIndexedExpression)
|
||||
}
|
||||
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? {
|
||||
val targetStatement = target.targetStatement(namespace)
|
||||
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
|
@ -417,9 +417,7 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram,
|
||||
|
||||
private fun translate(expr: IExpression) {
|
||||
when(expr) {
|
||||
is RegisterExpr -> {
|
||||
stackvmProg.instr(Opcode.PUSH_VAR, callLabel = expr.register.toString())
|
||||
}
|
||||
is RegisterExpr -> stackvmProg.instr(Opcode.PUSH_VAR, callLabel = expr.register.toString())
|
||||
is PrefixExpression -> {
|
||||
translate(expr.expression)
|
||||
translatePrefixOperator(expr.operator)
|
||||
@ -444,29 +442,8 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram,
|
||||
}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val target = expr.targetStatement(namespace)
|
||||
when(target) {
|
||||
is VarDecl -> {
|
||||
when(target.type) {
|
||||
VarDeclType.VAR ->
|
||||
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!!))
|
||||
else -> TODO("invalid datatype for memory variable expression: $target")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else -> throw CompilerException("expression identifierref should be a vardef, not $target")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> translate(expr)
|
||||
is ArrayIndexedExpression -> translate(expr)
|
||||
is RangeExpr -> {
|
||||
TODO("TRANSLATE range $expr")
|
||||
}
|
||||
@ -491,6 +468,30 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(identifierRef: IdentifierReference) {
|
||||
val target = identifierRef.targetStatement(namespace)
|
||||
when (target) {
|
||||
is VarDecl -> {
|
||||
when (target.type) {
|
||||
VarDeclType.VAR ->
|
||||
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!!))
|
||||
else -> TODO("invalid datatype for memory variable expression: $target")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else -> throw CompilerException("expression identifierref should be a vardef, not $target")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(stmt: FunctionCallStatement) {
|
||||
stackvmProg.line(stmt.position)
|
||||
val targetStmt = stmt.target.targetStatement(namespace)!!
|
||||
@ -590,6 +591,33 @@ private class StatementTranslator(private val stackvmProg: StackVmProgram,
|
||||
stackvmProg.instr(opcode)
|
||||
}
|
||||
|
||||
private fun translate(arrayindexed: ArrayIndexedExpression) {
|
||||
val variable = arrayindexed.identifier?.targetStatement(namespace) as? VarDecl
|
||||
val variableName =
|
||||
if(arrayindexed.register!=null) {
|
||||
val reg=arrayindexed.register
|
||||
if(reg==Register.A || reg==Register.X || reg==Register.Y)
|
||||
throw CompilerException("requires register pair")
|
||||
if(arrayindexed.array.y!=null)
|
||||
throw CompilerException("when using an address, can only use one index dimension")
|
||||
reg.toString()
|
||||
} else {
|
||||
variable!!.scopedname
|
||||
}
|
||||
translate(arrayindexed.array.x)
|
||||
val y = arrayindexed.array.y
|
||||
if(y!=null) {
|
||||
// calc matrix index i=y*columns+x
|
||||
// (the const-folding will have removed this for us when both x and y are constants)
|
||||
translate(y)
|
||||
stackvmProg.instr(Opcode.PUSH, Value(DataType.BYTE, (variable!!.arrayspec!!.x as LiteralValue).asIntegerValue!!))
|
||||
stackvmProg.instr(Opcode.MUL)
|
||||
stackvmProg.instr(Opcode.ADD)
|
||||
}
|
||||
stackvmProg.instr(Opcode.PUSH_INDEXED_VAR, callLabel = variableName)
|
||||
}
|
||||
|
||||
|
||||
private fun createSyscall(funcname: String) {
|
||||
val function = (
|
||||
if (funcname.startsWith("_vm_"))
|
||||
|
@ -340,6 +340,23 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
}
|
||||
return super.process(literalValue)
|
||||
}
|
||||
|
||||
override fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
|
||||
if(arrayIndexedExpression.array.y!=null) {
|
||||
if(arrayIndexedExpression.array.size()!=null) {
|
||||
// both x and y are known
|
||||
// calculate the 2-dimension index i = y*columns + x
|
||||
if(arrayIndexedExpression.identifier!=null) {
|
||||
val x = (arrayIndexedExpression.array.x as LiteralValue).asIntegerValue!!
|
||||
val y = (arrayIndexedExpression.array.y as LiteralValue).asIntegerValue!!
|
||||
val variable = arrayIndexedExpression.identifier.targetStatement(namespace) as VarDecl
|
||||
val index = x + y*(variable.arrayspec!!.x as LiteralValue).asIntegerValue!!
|
||||
arrayIndexedExpression.array = ArraySpec(LiteralValue.optimalInteger(index, arrayIndexedExpression.array.position), null, arrayIndexedExpression.array.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.process(arrayIndexedExpression)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -101,7 +101,8 @@ class Program (val name: String,
|
||||
}
|
||||
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 -> {
|
||||
Opcode.ROL2_VAR, Opcode.ROR2_VAR, Opcode.POP_VAR, Opcode.PUSH_VAR,
|
||||
Opcode.PUSH_INDEXED_VAR -> {
|
||||
val withoutQuotes =
|
||||
if(args!!.startsWith('"') && args.endsWith('"'))
|
||||
args.substring(1, args.length-1) else args
|
||||
|
@ -93,6 +93,9 @@ enum class Opcode {
|
||||
EQUAL,
|
||||
NOTEQUAL,
|
||||
|
||||
// array access
|
||||
PUSH_INDEXED_VAR,
|
||||
|
||||
// branching
|
||||
JUMP,
|
||||
BCS,
|
||||
@ -905,6 +908,23 @@ class StackVm(private var traceOutputFile: String?) {
|
||||
Opcode.LINE -> {
|
||||
sourceLine = ins.callLabel!!
|
||||
}
|
||||
Opcode.PUSH_INDEXED_VAR -> {
|
||||
val index = evalstack.pop().integerValue()
|
||||
val variable = variables[ins.callLabel] ?: throw VmExecutionException("unknown variable: ${ins.callLabel}")
|
||||
if(variable.type==DataType.WORD) {
|
||||
// assume the variable is a pointer (address) and get the byte value from that memory location
|
||||
evalstack.push(Value(DataType.BYTE, mem.getByte(variable.integerValue())))
|
||||
} else {
|
||||
// get indexed element from the array
|
||||
val array = heap.get(variable.heapId)
|
||||
val result = array.array!![index]
|
||||
when(array.type) {
|
||||
DataType.ARRAY, DataType.MATRIX -> evalstack.push(Value(DataType.BYTE, result))
|
||||
DataType.ARRAY_W -> evalstack.push(Value(DataType.WORD, result))
|
||||
else -> throw VmExecutionException("not a proper array/matrix var")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> throw VmExecutionException("unimplemented opcode: ${ins.opcode}")
|
||||
}
|
||||
|
||||
|
@ -182,11 +182,15 @@ Values will usually be part of an expression or assignment statement::
|
||||
|
||||
Array and Matrix (2-dimensional array) types are also supported like this::
|
||||
|
||||
byte[4] array = [1, 2, 3, 4] ; initialize the array
|
||||
byte[99] array = 255 ; initialize array with all 255's [255, 255, 255, 255, ...]
|
||||
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
byte[2,3] matrix = 1 ; a matrix of 2*3=6 bytes all with value 1
|
||||
byte[2,3] matrix = [1,2,3,4,5,6] ; a 2*3 matrix with value |(1,2) (3,4) (5,6)|
|
||||
byte[4] array = [1, 2, 3, 4] ; initialize the array
|
||||
byte[99] array = 255 ; initialize array with all 255's [255, 255, 255, 255, ...]
|
||||
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
byte[2,3] matrix = 1 ; a matrix of 2*3=6 bytes all with value 1
|
||||
byte[2,3] matrix = [1,2,3,4,5,6] ; a 2*3 matrix with value |(1,2) (3,4) (5,6)|
|
||||
|
||||
value = array[3] ; the fourth value in the array (index is 0-based)
|
||||
value = matrix[4,2] ; the byte at the 5th column and 3rd row in the matrix
|
||||
char = string[4] ; the fifth character (=byte) in the string
|
||||
|
||||
|
||||
Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.)
|
||||
|
@ -315,8 +315,17 @@ If used in the place of a literal value, it expands into the actual array of val
|
||||
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
|
||||
|
||||
.. todo::
|
||||
this may be used later in the for-loop as well. Add 'step' to range expression?
|
||||
Array indexing
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Strings, arrays and matrixes form a sequence of values. You can access the individual values by
|
||||
indexing into the array.
|
||||
Syntax is familiar with brackets: ``arrayvar[x]`` or ``matrixvar[x, y]`` ::
|
||||
|
||||
array[2] ; the third byte in the array (index is 0-based)
|
||||
matrix[4,2] ; the byte at the 5th column and 3rd row in the matrix
|
||||
string[4] ; the fifth character (=byte) in the string
|
||||
|
||||
|
||||
|
||||
Operators
|
||||
@ -374,14 +383,6 @@ range creation: ``to``
|
||||
}
|
||||
|
||||
|
||||
.. todo::
|
||||
array indexing: ``[`` *index* ``]``
|
||||
When put after a sequence type (array, string or matrix) it means to point to the given element in that sequence::
|
||||
|
||||
array[2] ; the third byte in the array (index is 0-based)
|
||||
matrix[4,2] ; the byte at the 5th column and 3rd row in the matrix
|
||||
|
||||
|
||||
precedence grouping in expressions, or subroutine parameter list: ``(`` *expression* ``)``
|
||||
Parentheses are used to group parts of an expression to change the order of evaluation.
|
||||
(the subexpression inside the parentheses will be evaluated first):
|
||||
@ -427,14 +428,8 @@ The open curly brace must immediately follow the subroutine result specification
|
||||
and can have nothing following it. The close curly brace must be on its own line as well.
|
||||
|
||||
.. todo::
|
||||
Pre-defined subroutines that are available on specific memory addresses
|
||||
(in system ROM for instance) can be defined by assigning the routine's memory address to the sub,
|
||||
and not specifying a code block::
|
||||
|
||||
sub <identifier> ([proc_parameters]) -> [proc_results] = <address>
|
||||
|
||||
; example:
|
||||
sub CLOSE (logical: A) -> (A?, X?, Y?) = $FFC3
|
||||
asmsub with assigning memory address to refer to predefined ROM subroutines
|
||||
asmsub with a regular body to precisely control what registers are used to call the subroutine
|
||||
|
||||
|
||||
.. data:: parameters
|
||||
|
Loading…
Reference in New Issue
Block a user