range initializers

This commit is contained in:
Irmen de Jong 2018-08-14 16:29:08 +02:00
parent 397fdc61cd
commit 4b7d656a2f
9 changed files with 205 additions and 17 deletions

View File

@ -152,7 +152,7 @@ Variables and values
-------------------- --------------------
Variables are named values that can change during the execution of the program. Variables are named values that can change during the execution of the program.
When declaring a variable it is possible to specify the initial value it should get. When declaring a variable it is required to specify the initial value it should get.
Values will usually be part of an expression or assignment statement:: Values will usually be part of an expression or assignment statement::
12345 ; integer number 12345 ; integer number
@ -163,6 +163,10 @@ Values will usually be part of an expression or assignment statement::
byte counter = 42 ; variable of size 8 bits, with initial value 42 byte counter = 42 ; variable of size 8 bits, with initial value 42
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]
Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.) Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.)
cannot be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte`` cannot be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``

View File

@ -191,6 +191,21 @@ The data that the code works on is stored in variables. Variable names have to b
Values in the source code are written using *value literals*. In the table of the supported Values in the source code are written using *value literals*. In the table of the supported
data types below you can see how they should be written. data types below you can see how they should be written.
Range expression
----------------
A special value is the *range expression* ( ``<startvalue> to <endvalue>`` )
which represents a range of numbers or characters,
from the starting value to (and including) the ending value.
If used in the place of a literal value, it expands into the actual array of values::
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 as well?
Variable declarations Variable declarations
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
@ -469,7 +484,7 @@ Notice that this is a valid way to end a subroutine (you can either ``return`` f
to another piece of code that eventually returns). to another piece of code that eventually returns).
conditional execution Conditional execution
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
With the 'if' / 'else' statement you can execute code depending on the value of a condition:: With the 'if' / 'else' statement you can execute code depending on the value of a condition::

View File

@ -124,7 +124,7 @@ expression :
| left = expression bop = 'or' right = expression | left = expression bop = 'or' right = expression
| left = expression bop = 'xor' right = expression | left = expression bop = 'xor' right = expression
| prefix = 'not' expression | prefix = 'not' expression
| rangefrom = expression 'to' rangeto = expression | rangefrom = expression 'to' rangeto = expression // create separate rule once for-loops are here?
| literalvalue | literalvalue
| register | register
| identifier | identifier

View File

@ -1,4 +1,10 @@
~ main $c003 { ~ main $c003 {
word[100] ascending = 100 to 199
word[100] ascending2 = "a" to "z"
str ascending3 = "a" to "z"
str ascending5 = "z" to "z"
const byte cc = 4 + (2==9)
byte cc2 = 4 - (2==10)
memory byte derp = max($ffdd) memory byte derp = max($ffdd)
memory byte derpA = abs(-2.5-0.5) memory byte derpA = abs(-2.5-0.5)
memory byte derpB = max(1, 2.2, 4.4, 100) memory byte derpB = max(1, 2.2, 4.4, 100)
@ -19,9 +25,17 @@
const byte equal = 4==4 const byte equal = 4==4
const byte equal2 = (4+hopla)>0 const byte equal2 = (4+hopla)>0
%breakpoint
byte equalQQ = 4==4
const byte equalQQ2 = (4+hopla)>0
XY = hopla*2+hopla1 XY = hopla*2+hopla1
A = "derp" * %000100 A = "derp" * %000100
byte equalWW = 4==4
const byte equalWW2 = (4+hopla)>0
if (1==1) goto hopla if (1==1) goto hopla
if (1==2) return 44 if (1==2) return 44
@ -32,13 +46,14 @@
if (5==5) { if (5==5) {
A=99 A=99
%breakpoint
} }
if(6==6) { if(6==6) {
A=99 A=99
} else X=33 } else X=33
if(6==6) { if(6>36) {
A=99 A=99
} else { } else {
X=33 X=33

View File

@ -1,7 +1,8 @@
package il65 package il65
import il65.ast.* import il65.ast.*
import il65.optimizing.optimize import il65.optimizing.optimizeExpressions
import il65.optimizing.optimizeStatements
import il65.parser.il65Lexer import il65.parser.il65Lexer
import il65.parser.il65Parser import il65.parser.il65Parser
import org.antlr.v4.runtime.CharStreams import org.antlr.v4.runtime.CharStreams
@ -128,7 +129,8 @@ fun main(args: Array<String>) {
val globalNamespace = moduleAst.namespace() val globalNamespace = moduleAst.namespace()
// globalNamespace.debugPrint() // globalNamespace.debugPrint()
moduleAst.optimize(globalNamespace) moduleAst.optimizeExpressions(globalNamespace)
moduleAst.optimizeStatements(globalNamespace)
moduleAst.checkValid(globalNamespace) // check if final tree is valid moduleAst.checkValid(globalNamespace) // check if final tree is valid
// todo compile to asm... // todo compile to asm...

View File

@ -100,6 +100,12 @@ interface IAstProcessor {
ifStatement.elsepart = ifStatement.elsepart?.map { it.process(this) } ifStatement.elsepart = ifStatement.elsepart?.map { it.process(this) }
return ifStatement return ifStatement
} }
fun process(range: RangeExpr): IExpression {
range.from = range.from.process(this)
range.to = range.to.process(this)
return range
}
} }
@ -185,6 +191,25 @@ interface INameScope {
} }
/**
* Inserted into the Ast in place of modified nodes (not inserted directly as a parser result)
* It can hold zero or more replacement statements that have to be inserted at that point.
*/
data class AnonymousStatementList(override var parent: Node?, var statements: List<IStatement>) : IStatement {
override var position: Position? = null
override fun linkParents(parent: Node) {
this.parent = parent
statements.forEach { it.linkParents(this) }
}
override fun process(processor: IAstProcessor): IStatement {
statements = statements.map { it.process(processor) }
return this
}
}
data class Module(override val name: String, data class Module(override val name: String,
override var statements: List<IStatement>) : Node, INameScope { override var statements: List<IStatement>) : Node, INameScope {
override var position: Position? = null override var position: Position? = null
@ -433,6 +458,12 @@ data class LiteralValue(val intvalue: Int? = null,
} }
} }
fun asBoolean(): Boolean =
(floatvalue!=null && floatvalue != 0.0) ||
(intvalue!=null && intvalue != 0) ||
(strvalue!=null && strvalue.isNotEmpty()) ||
(arrayvalue != null && arrayvalue.isNotEmpty())
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
arrayvalue?.forEach {it.linkParents(this)} arrayvalue?.forEach {it.linkParents(this)}
@ -454,11 +485,7 @@ data class RangeExpr(var from: IExpression, var to: IExpression) : IExpression {
} }
override fun constValue(namespace: INameScope): LiteralValue? = null override fun constValue(namespace: INameScope): LiteralValue? = null
override fun process(processor: IAstProcessor): IExpression { override fun process(processor: IAstProcessor) = processor.process(this)
from = from.process(processor)
to = to.process(processor)
return this
}
} }

View File

@ -92,6 +92,11 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
fun err(msg: String) { fun err(msg: String) {
checkResult.add(SyntaxError(msg, subroutine.position)) checkResult.add(SyntaxError(msg, subroutine.position))
} }
// subroutines may only be defined directly inside a block
if(subroutine.parent !is Block)
err("subroutines can only be defined in a block (not in other scopes)")
val uniqueNames = subroutine.parameters.map { it.name }.toSet() val uniqueNames = subroutine.parameters.map { it.name }.toSet()
if(uniqueNames.size!=subroutine.parameters.size) if(uniqueNames.size!=subroutine.parameters.size)
err("parameter names should be unique") err("parameter names should be unique")
@ -183,6 +188,19 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
return super.process(decl) return super.process(decl)
} }
/**
* check if condition
*/
override fun process(ifStatement: IfStatement): IStatement {
val constvalue = ifStatement.condition.constValue(globalNamespace)
if(constvalue!=null) {
val msg = if (constvalue.asBoolean()) "condition is always true" else "condition is always false"
println("${ifStatement.position} Warning: $msg")
}
return super.process(ifStatement)
}
/** /**
* check the arguments of the directive * check the arguments of the directive
*/ */
@ -222,17 +240,17 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
err("invalid import directive, cannot import itself") err("invalid import directive, cannot import itself")
} }
"%breakpoint" -> { "%breakpoint" -> {
if(directive.parent !is Block) err("this directive may only occur in a block") if(directive.parent is Module) err("this directive may only occur in a block")
if(directive.args.isNotEmpty()) if(directive.args.isNotEmpty())
err("invalid breakpoint directive, expected no arguments") err("invalid breakpoint directive, expected no arguments")
} }
"%asminclude" -> { "%asminclude" -> {
if(directive.parent !is Block) err("this directive may only occur in a block") if(directive.parent is Module) err("this directive may only occur in a block")
if(directive.args.size!=2 || directive.args[0].str==null || directive.args[1].name==null) if(directive.args.size!=2 || directive.args[0].str==null || directive.args[1].name==null)
err("invalid asminclude directive, expected arguments: \"filename\", scopelabel") err("invalid asminclude directive, expected arguments: \"filename\", scopelabel")
} }
"%asmbinary" -> { "%asmbinary" -> {
if(directive.parent !is Block) err("this directive may only occur in a block") if(directive.parent is Module) err("this directive may only occur in a block")
val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]" val errormsg = "invalid asmbinary directive, expected arguments: \"filename\" [, offset [, length ] ]"
if(directive.args.isEmpty()) err(errormsg) if(directive.args.isEmpty()) err(errormsg)
if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg) if(directive.args.isNotEmpty() && directive.args[0].str==null) err(errormsg)
@ -261,7 +279,7 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
else { else {
val expected = decl.arraySizeX(globalNamespace) val expected = decl.arraySizeX(globalNamespace)
if (value.arrayvalue.size != expected) if (value.arrayvalue.size != expected)
checkResult.add(SyntaxError("initializer array size mismatch (expecting $expected)", decl.position)) checkResult.add(SyntaxError("initializer array size mismatch (expecting $expected, got ${value.arrayvalue.size})", decl.position))
else { else {
value.arrayvalue.forEach { value.arrayvalue.forEach {
checkValueRange(decl.datatype, it.constValue(globalNamespace)!!, it.position) checkValueRange(decl.datatype, it.constValue(globalNamespace)!!, it.position)
@ -277,7 +295,7 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
else { else {
val expected = decl.arraySizeX(globalNamespace)!! * decl.arraySizeY(globalNamespace)!! val expected = decl.arraySizeX(globalNamespace)!! * decl.arraySizeY(globalNamespace)!!
if (value.arrayvalue.size != expected) if (value.arrayvalue.size != expected)
checkResult.add(SyntaxError("initializer array size mismatch (expecting $expected)", decl.position)) checkResult.add(SyntaxError("initializer array size mismatch (expecting $expected, got ${value.arrayvalue.size})", decl.position))
else { else {
value.arrayvalue.forEach { value.arrayvalue.forEach {
checkValueRange(decl.datatype, it.constValue(globalNamespace)!!, it.position) checkValueRange(decl.datatype, it.constValue(globalNamespace)!!, it.position)
@ -287,6 +305,31 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
} }
} }
override fun process(range: RangeExpr): IExpression {
fun err(msg: String) {
checkResult.add(SyntaxError(msg, range.position))
}
super.process(range)
val from = range.from.constValue(globalNamespace)
val to = range.to.constValue(globalNamespace)
if(from!=null && to != null) {
when {
from.intvalue!=null && to.intvalue!=null -> {
if(from.intvalue > to.intvalue)
err("range from is larger than to value")
}
from.strvalue!=null && to.strvalue!=null -> {
if(from.strvalue.length!=1 || to.strvalue.length!=1)
err("range from and to must be a single character")
if(from.strvalue[0] > to.strvalue[0])
err("range from is larger than to value")
}
else -> err("range expression must be over integers or over characters")
}
}
return range
}
private fun checkValueRange(datatype: DataType, value: LiteralValue, position: Position?) { private fun checkValueRange(datatype: DataType, value: LiteralValue, position: Position?) {
fun err(msg: String) { fun err(msg: String) {
checkResult.add(SyntaxError(msg, position)) checkResult.add(SyntaxError(msg, position))

View File

@ -4,7 +4,7 @@ import il65.ast.*
import kotlin.math.pow import kotlin.math.pow
fun Module.optimize(globalNamespace: INameScope) { fun Module.optimizeExpressions(globalNamespace: INameScope) {
val optimizer = ExpressionOptimizer(globalNamespace) val optimizer = ExpressionOptimizer(globalNamespace)
this.process(optimizer) this.process(optimizer)
if(optimizer.optimizationsDone==0) if(optimizer.optimizationsDone==0)
@ -108,6 +108,41 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
else -> expr else -> expr
} }
} }
override fun process(range: RangeExpr): IExpression {
super.process(range)
val from = range.from.constValue(globalNamespace)
val to = range.to.constValue(globalNamespace)
if(from!=null && to != null) {
when {
from.intvalue!=null && to.intvalue!=null -> {
// int range
val rangevalue = from.intvalue.rangeTo(to.intvalue)
if(rangevalue.last-rangevalue.first > 65535) {
throw AstException("amount of values in range exceeds 65535, at ${range.position}")
}
return LiteralValue(arrayvalue = rangevalue.map {
val v = LiteralValue(intvalue=it)
v.position=range.position
v.parent=range.parent
v
})
}
from.strvalue!=null && to.strvalue!=null -> {
// char range
val rangevalue = from.strvalue[0].rangeTo(to.strvalue[0])
if(rangevalue.last-rangevalue.first > 65535) {
throw AstException("amount of characters in range exceeds 65535, at ${range.position}")
}
val newval = LiteralValue(strvalue = rangevalue.toList().joinToString(""))
newval.position = range.position
newval.parent = range.parent
return newval
}
}
}
return range
}
} }

View File

@ -0,0 +1,47 @@
package il65.optimizing
import il65.ast.*
import kotlin.math.pow
fun Module.optimizeStatements(globalNamespace: INameScope) {
val optimizer = StatementOptimizer(globalNamespace)
this.process(optimizer)
if(optimizer.optimizationsDone==0)
println("[${this.name}] 0 optimizations performed")
while(optimizer.optimizationsDone>0) {
println("[${this.name}] ${optimizer.optimizationsDone} optimizations performed")
optimizer.reset()
this.process(optimizer)
}
this.linkParents() // re-link in final configuration
}
class StatementOptimizer(private val globalNamespace: INameScope) : IAstProcessor {
var optimizationsDone: Int = 0
private set
fun reset() {
optimizationsDone = 0
}
override fun process(ifStatement: IfStatement): IStatement {
super.process(ifStatement)
val constvalue = ifStatement.condition.constValue(globalNamespace)
if(constvalue!=null) {
val statements: List<IStatement>
return if(constvalue.asBoolean()) {
// always true -> keep only if-part
println("${ifStatement.position} Warning: condition is always true")
AnonymousStatementList(ifStatement.parent, ifStatement.statements)
} else {
// always false -> keep only else-part
println("${ifStatement.position} Warning: condition is always false")
AnonymousStatementList(ifStatement.parent, ifStatement.elsepart)
}
}
return ifStatement
}
}