implemented array indexing

This commit is contained in:
Irmen de Jong 2018-10-01 22:23:16 +02:00
parent 8f26fdef61
commit 0cdae48ce7
11 changed files with 1052 additions and 839 deletions

View File

@ -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? ')'
;

View File

@ -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
}

View File

@ -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() }

View File

@ -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)

View File

@ -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_"))

View File

@ -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

View File

@ -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

View File

@ -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}")
}

View File

@ -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.)

View File

@ -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