mirror of
https://github.com/irmen/prog8.git
synced 2024-11-22 15:33:02 +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.
|
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``
|
||||||
|
@ -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::
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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...
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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