mirror of
https://github.com/irmen/prog8.git
synced 2024-11-26 11:49:22 +00:00
range initializers
This commit is contained in:
parent
397fdc61cd
commit
4b7d656a2f
@ -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``
|
||||
|
@ -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::
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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...
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
47
il65/src/il65/optimizing/StatementsOptimizer.kt
Normal file
47
il65/src/il65/optimizing/StatementsOptimizer.kt
Normal 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
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user