From 4b7d656a2f849b55dbf6a6949b1aeb56c8b8260a Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 14 Aug 2018 16:29:08 +0200 Subject: [PATCH] range initializers --- docs/source/programming.rst | 6 ++- docs/source/syntaxreference.rst | 17 +++++- il65/antlr/il65.g4 | 2 +- il65/examples/test.ill | 17 +++++- il65/src/il65/Main.kt | 6 ++- il65/src/il65/ast/AST.kt | 37 +++++++++++-- il65/src/il65/ast/AstChecker.kt | 53 +++++++++++++++++-- .../il65/optimizing/ExpressionOptimizer.kt | 37 ++++++++++++- .../il65/optimizing/StatementsOptimizer.kt | 47 ++++++++++++++++ 9 files changed, 205 insertions(+), 17 deletions(-) create mode 100644 il65/src/il65/optimizing/StatementsOptimizer.kt diff --git a/docs/source/programming.rst b/docs/source/programming.rst index b4b339ff3..c0c5ba22f 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -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`` diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 0e480d25c..ebc6aed46 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -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* ( `` to `` ) +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:: diff --git a/il65/antlr/il65.g4 b/il65/antlr/il65.g4 index 8ef4fd8f6..86f726b36 100644 --- a/il65/antlr/il65.g4 +++ b/il65/antlr/il65.g4 @@ -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 diff --git a/il65/examples/test.ill b/il65/examples/test.ill index b005fc049..13391f9be 100644 --- a/il65/examples/test.ill +++ b/il65/examples/test.ill @@ -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 diff --git a/il65/src/il65/Main.kt b/il65/src/il65/Main.kt index dc1dd4ba2..d58fb6f1e 100644 --- a/il65/src/il65/Main.kt +++ b/il65/src/il65/Main.kt @@ -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) { 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... diff --git a/il65/src/il65/ast/AST.kt b/il65/src/il65/ast/AST.kt index 13192fc60..9603ab923 100644 --- a/il65/src/il65/ast/AST.kt +++ b/il65/src/il65/ast/AST.kt @@ -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 { + 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) : 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) } diff --git a/il65/src/il65/ast/AstChecker.kt b/il65/src/il65/ast/AstChecker.kt index e09741e01..88403721e 100644 --- a/il65/src/il65/ast/AstChecker.kt +++ b/il65/src/il65/ast/AstChecker.kt @@ -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)) diff --git a/il65/src/il65/optimizing/ExpressionOptimizer.kt b/il65/src/il65/optimizing/ExpressionOptimizer.kt index 3f9cd1e61..7165b0316 100644 --- a/il65/src/il65/optimizing/ExpressionOptimizer.kt +++ b/il65/src/il65/optimizing/ExpressionOptimizer.kt @@ -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 + } } diff --git a/il65/src/il65/optimizing/StatementsOptimizer.kt b/il65/src/il65/optimizing/StatementsOptimizer.kt new file mode 100644 index 000000000..bcafcac93 --- /dev/null +++ b/il65/src/il65/optimizing/StatementsOptimizer.kt @@ -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 + 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 + } +} \ No newline at end of file