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.
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::
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[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.)
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
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
^^^^^^^^^^^^^^^^^^^^^
@ -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).
conditional execution
Conditional execution
^^^^^^^^^^^^^^^^^^^^^
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 = 'xor' right = expression
| prefix = 'not' expression
| rangefrom = expression 'to' rangeto = expression
| rangefrom = expression 'to' rangeto = expression // create separate rule once for-loops are here?
| literalvalue
| register
| identifier

View File

@ -1,4 +1,10 @@
~ 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 derpA = abs(-2.5-0.5)
memory byte derpB = max(1, 2.2, 4.4, 100)
@ -19,9 +25,17 @@
const byte equal = 4==4
const byte equal2 = (4+hopla)>0
%breakpoint
byte equalQQ = 4==4
const byte equalQQ2 = (4+hopla)>0
XY = hopla*2+hopla1
A = "derp" * %000100
byte equalWW = 4==4
const byte equalWW2 = (4+hopla)>0
if (1==1) goto hopla
if (1==2) return 44
@ -32,13 +46,14 @@
if (5==5) {
A=99
%breakpoint
}
if(6==6) {
A=99
} else X=33
if(6==6) {
if(6>36) {
A=99
} else {
X=33

View File

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

View File

@ -100,6 +100,12 @@ interface IAstProcessor {
ifStatement.elsepart = ifStatement.elsepart?.map { it.process(this) }
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,
override var statements: List<IStatement>) : Node, INameScope {
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) {
this.parent = parent
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 process(processor: IAstProcessor): IExpression {
from = from.process(processor)
to = to.process(processor)
return this
}
override fun process(processor: IAstProcessor) = processor.process(this)
}

View File

@ -92,6 +92,11 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
fun err(msg: String) {
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()
if(uniqueNames.size!=subroutine.parameters.size)
err("parameter names should be unique")
@ -183,6 +188,19 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
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
*/
@ -222,17 +240,17 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
err("invalid import directive, cannot import itself")
}
"%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())
err("invalid breakpoint directive, expected no arguments")
}
"%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)
err("invalid asminclude directive, expected arguments: \"filename\", scopelabel")
}
"%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 ] ]"
if(directive.args.isEmpty()) 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 {
val expected = decl.arraySizeX(globalNamespace)
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 {
value.arrayvalue.forEach {
checkValueRange(decl.datatype, it.constValue(globalNamespace)!!, it.position)
@ -277,7 +295,7 @@ class AstChecker(private val globalNamespace: INameScope) : IAstProcessor {
else {
val expected = decl.arraySizeX(globalNamespace)!! * decl.arraySizeY(globalNamespace)!!
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 {
value.arrayvalue.forEach {
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?) {
fun err(msg: String) {
checkResult.add(SyntaxError(msg, position))

View File

@ -4,7 +4,7 @@ import il65.ast.*
import kotlin.math.pow
fun Module.optimize(globalNamespace: INameScope) {
fun Module.optimizeExpressions(globalNamespace: INameScope) {
val optimizer = ExpressionOptimizer(globalNamespace)
this.process(optimizer)
if(optimizer.optimizationsDone==0)
@ -108,6 +108,41 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
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
}
}