check assignment targets

This commit is contained in:
Irmen de Jong 2018-09-06 01:02:36 +02:00
parent f23808eaae
commit 1a60119fde
3 changed files with 95 additions and 38 deletions

View File

@ -80,7 +80,6 @@
equalQQ = foo(33)
equalQQ = main.foo(33)
XY = hopla*2+hopla1
A = "derp" * %000100
byte equalWW = 4==4
const byte equalWW2 = (4+hopla)>0
@ -123,6 +122,14 @@
main.foo(1,2,3)
sub start () -> () {
word dinges = 0
dinges=round(blerp1)
A=round(blerp1)
return
}
mega:
X += 1
cool:

View File

@ -17,6 +17,8 @@ enum class DataType {
STR_P,
STR_S,
STR_PS
// TODO arrays (of byte, word) and matrix (of byte) should have their own datatype as well?
}
enum class Register {
@ -25,12 +27,7 @@ enum class Register {
Y,
AX,
AY,
XY,
PC,
PI,
PZ,
PN,
PV
XY
}
enum class Statusflag {
@ -168,6 +165,12 @@ interface IAstProcessor {
fun process(literalValue: LiteralValue): LiteralValue {
return literalValue
}
fun process(assignment: Assignment): IStatement {
assignment.target = assignment.target.process(this)
assignment.value = assignment.value.process(this)
return assignment
}
}
@ -362,7 +365,7 @@ private class GlobalNamespace(override val name: String,
override var statements: MutableList<IStatement>,
override val position: Position?) : INameScope {
private val scopedNamesUsed: MutableSet<String> = mutableSetOf("main") // main is always used
private val scopedNamesUsed: MutableSet<String> = mutableSetOf("main", "main.start") // main and main.start are always used
override fun usedNames(): Set<String> = scopedNamesUsed
@ -515,7 +518,7 @@ class VarDecl(val type: VarDeclType,
override fun process(processor: IAstProcessor) = processor.process(this)
val isScalar = arrayspec==null
val isScalar = arrayspec==null // TODO replace with actual array/matrix datatype itself?
val isArray = arrayspec!=null && arrayspec.y==null
val isMatrix = arrayspec?.y != null
val scopedname: List<String> by lazy { makeScopedName(name) }
@ -543,11 +546,7 @@ class Assignment(var target: AssignTarget, val aug_op : String?, var value: IExp
value.linkParents(this)
}
override fun process(processor: IAstProcessor): IStatement {
target = target.process(processor)
value = value.process(processor)
return this
}
override fun process(processor: IAstProcessor) = processor.process(this)
}
data class AssignTarget(val register: Register?, val identifier: IdentifierReference?) : Node {
@ -560,6 +559,18 @@ data class AssignTarget(val register: Register?, val identifier: IdentifierRefer
}
fun process(processor: IAstProcessor) = this
fun determineDatatype(namespace: INameScope, stmt: IStatement): DataType {
if(register!=null)
return when(register){
Register.A, Register.X, Register.Y -> DataType.BYTE
Register.AX, Register.AY, Register.XY -> DataType.WORD
}
val symbol = namespace.lookup(identifier!!.nameInSource, stmt)
if(symbol is VarDecl) return symbol.datatype
throw FatalAstException("cannot determine datatype of assignment target $this")
}
}
@ -597,9 +608,8 @@ class BinaryExpression(var left: IExpression, val operator: String, var right: I
right.linkParents(this)
}
override fun constValue(namespace: INameScope): LiteralValue? {
throw FatalAstException("binary expression should have been optimized away into a single value, before const value was requested (this error is often caused by another) pos=$position")
}
// 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 process(processor: IAstProcessor) = processor.process(this)
override fun referencesIdentifier(name: String) = left.referencesIdentifier(name) || right.referencesIdentifier(name)
@ -813,7 +823,7 @@ class FunctionCallStatement(override var target: IdentifierReference, override v
override fun process(processor: IAstProcessor) = processor.process(this)
override fun toString(): String {
return "FunctionCall(target=$target, targetStmt=$targetStatement, pos=$position)"
return "FunctionCall(target=$target, pos=$position)"
}
}

View File

@ -41,6 +41,13 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C
entry.value.mapTo(checkResult) { SyntaxError("directive can just occur once", it.position) }
}
}
// there must be a 'main' block with a 'start' subroutine for the program entry point.
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" } as? Block?
val startSub = mainBlock?.subScopes()?.get("start")
if(startSub==null) {
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", module.position))
}
}
override fun process(jump: Jump): IStatement {
@ -107,6 +114,31 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C
return subroutine
}
/**
* Assignment target must be register, or a variable name
* for constant-value assignments, check the datatype as well
*/
override fun process(assignment: Assignment): IStatement {
if(assignment.target.identifier!=null) {
val targetSymbol = globalNamespace.lookup(assignment.target.identifier!!.nameInSource, assignment)
if(targetSymbol !is VarDecl) {
checkResult.add(SyntaxError("assignment LHS must be register or variable", assignment.position))
return super.process(assignment)
} else if(targetSymbol.type==VarDeclType.CONST) {
checkResult.add(SyntaxError("cannot assign new value to a constant", assignment.position))
return super.process(assignment)
}
}
if(assignment.value is LiteralValue) {
val targetDatatype = assignment.target.determineDatatype(globalNamespace, assignment)
if(checkValueType(targetDatatype, assignment.value as LiteralValue, assignment.position)) {
checkValueRange(targetDatatype, assignment.value as LiteralValue, assignment.position)
}
}
return super.process(assignment)
}
/**
* Check the variable declarations (values within range etc)
*/
@ -399,28 +431,36 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C
return false
}
when {
vardecl.isScalar -> when (vardecl.datatype) {
DataType.FLOAT -> {
if (value.floatvalue == null)
return err("floating point value expected")
}
DataType.BYTE -> {
if (value.intvalue == null)
return err("byte integer value expected")
}
DataType.WORD -> {
if (value.intvalue == null)
return err("word integer value expected")
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
if (value.strvalue == null)
return err("string value expected")
}
}
vardecl.isArray -> if(value.arrayvalue==null)
return err("array value expected")
vardecl.isScalar -> checkValueType(vardecl.datatype, value, position)
vardecl.isArray -> if(value.arrayvalue==null) return err("array value expected")
vardecl.isMatrix -> TODO()
}
return true
}
private fun checkValueType(targetType: DataType, value: LiteralValue, position: Position?) : Boolean {
fun err(msg: String) : Boolean {
checkResult.add(SyntaxError(msg, position))
return false
}
when(targetType) {
DataType.FLOAT -> {
if (value.floatvalue == null)
return err("floating point value expected")
}
DataType.BYTE -> {
if (value.intvalue == null)
return err("byte integer value expected")
}
DataType.WORD -> {
if (value.intvalue == null)
return err("word integer value expected")
}
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
if (value.strvalue == null)
return err("string value expected")
}
}
return true
}
}