From b2876b0a03fde639a62b811def5955f6c509e6a9 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 28 Dec 2021 15:39:46 +0100 Subject: [PATCH 1/4] add a suggestion to use when statement if it seems appropriate --- .../codegen/target/cpu6502/codegen/AsmGen.kt | 4 ++-- .../compiler/BeforeAsmGenerationAstChanger.kt | 4 ++-- .../compiler/astprocessing/VariousCleanups.kt | 13 +++++++++++++ .../test/TestCompilerOnImportsAndIncludes.kt | 3 --- docs/source/todo.rst | 19 ++++++++++--------- examples/test.p8 | 5 +++++ 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt index fcf751679..cbe5b1d52 100644 --- a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt +++ b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/AsmGen.kt @@ -786,8 +786,8 @@ class AsmGen(private val program: Program, is When -> translate(stmt) is AnonymousScope -> translate(stmt) is BuiltinFunctionPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore") - is UntilLoop -> throw AssemblyError("do..until should have been desugared to jumps") - is WhileLoop -> throw AssemblyError("while should have been desugared to jumps") + is UntilLoop -> throw AssemblyError("do..until should have been converted to jumps") + is WhileLoop -> throw AssemblyError("while should have been converted to jumps") is Block -> throw AssemblyError("block should have been handled elsewhere") is Break -> throw AssemblyError("break should have been replaced by goto") else -> throw AssemblyError("missing asm translation for $stmt") diff --git a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt index da3bcaf39..f9939ca9e 100644 --- a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt +++ b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt @@ -36,11 +36,11 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o } override fun before(whileLoop: WhileLoop, parent: Node): Iterable { - throw FatalAstException("while should have been desugared to jumps") + throw FatalAstException("while should have been converted to jumps") } override fun before(untilLoop: UntilLoop, parent: Node): Iterable { - throw FatalAstException("do..until should have been desugared to jumps") + throw FatalAstException("do..until should have been converted to jumps") } override fun before(block: Block, parent: Node): Iterable { diff --git a/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt b/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt index c482a026a..07e99f49d 100644 --- a/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt +++ b/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt @@ -91,6 +91,19 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter) return noModifications } + override fun before(expr: BinaryExpression, parent: Node): Iterable { + if(expr.operator == "or") { + val leftBinExpr = expr.left as? BinaryExpression + val rightBinExpr = expr.right as? BinaryExpression + if(leftBinExpr!=null && leftBinExpr.operator=="==" && rightBinExpr!=null && rightBinExpr.operator=="==") { + if(leftBinExpr.right is NumericLiteralValue && rightBinExpr.right is NumericLiteralValue) { + errors.warn("consider using when statement to test for multiple values", expr.position) + } + } + } + return noModifications + } + override fun after(expr: BinaryExpression, parent: Node): Iterable { if(expr.operator in ComparisonOperators) { val leftConstVal = expr.left.constValue(program) diff --git a/compiler/test/TestCompilerOnImportsAndIncludes.kt b/compiler/test/TestCompilerOnImportsAndIncludes.kt index cbc2f7ea8..a02bee74f 100644 --- a/compiler/test/TestCompilerOnImportsAndIncludes.kt +++ b/compiler/test/TestCompilerOnImportsAndIncludes.kt @@ -11,9 +11,6 @@ import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.Label import prog8.codegen.target.Cx16Target import prog8tests.helpers.* -import prog8tests.helpers.assertFailure -import prog8tests.helpers.assertSuccess -import prog8tests.helpers.compileFile import kotlin.io.path.name diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 6c79256a4..772790af2 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,7 +3,10 @@ TODO For next compiler release (7.6) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... +add "if X in [1,2,3] {...}" syntax , as an alternative to when X { 1,2,3-> {...} } +if the array is not a literal, do a normal containment test instead in an array or string or range +change "consider using when statement..." to "consider using if X in [..] or when statement..." + Blocked by an official Commander-x16 v39 release ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -13,12 +16,14 @@ Blocked by an official Commander-x16 v39 release Future ^^^^^^ -- rethink the whole "isAugmentable" business. Because the way this is determined, should always also be exactly mirrorred in the AugmentableAssignmentAsmGen or you'll get a crash at code gen time. -- simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation +- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``v_`` + then we can get rid of the instruction lists in the machinedefinitions as well? - fix the asm-labels problem (github issue #62) +- simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation - get rid of all TODO's in the code - improve testability further, add more tests - use more of Result<> and Either<> to handle errors/ nulls better +- rethink the whole "isAugmentable" business. Because the way this is determined, should always also be exactly mirrorred in the AugmentableAssignmentAsmGen or you'll get a crash at code gen time. - can we get rid of pieces of asmgen.AssignmentAsmGen by just reusing the AugmentableAssignment ? generated code should not suffer - add a switch to not create the globals-initialization logic, but instead create a smaller program (that can only run once though) - c64: make the graphics.BITMAP_ADDRESS configurable (VIC banking) @@ -27,7 +32,7 @@ Future - add a flood fill routine to gfx2? - add a diskio.f_seek() routine for the Cx16 that uses its seek dos api? - make it possible for diskio to read and write from more than one file at the same time (= use multiple io channels)? -- make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``v_`` +- fix problems in c128 target - [problematic due to 64tass:] add a compiler option to not remove unused subroutines. this allows for building library programs. But this won't work with 64tass's .proc ... Perhaps replace all uses of .proc/.pend by .block/.bend will fix that? (but we lose the optimizing aspect of the assembler where it strips out unused code. @@ -36,13 +41,9 @@ Future More code optimization ideas ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +- automatically convert if statements that test for multiple values (if X==1 or X==2..) to if X in [1,2,..] statements - byte typed expressions should be evaluated in the accumulator where possible, without (temp)var for instance value = otherbyte >> 1 --> lda otherbite ; lsr a; sta value -- rewrite multiple choice if into when: - if X==1 or X==2 or X==3 { truepart } else { falsepart } - -> when X { 1,2,3->truepart else->falsepart } - same with assignment if the lhs is simple var or memaddr - - rewrite expression tree evaluation such that it doesn't use an eval stack but flatten the tree into linear code that uses a fixed number of predetermined value 'variables' - this removes the need for the BinExprSplitter? (which is problematic and very limited now) - introduce byte-index operator to avoid index multiplications in loops over arrays? see github issue #4 diff --git a/examples/test.p8 b/examples/test.p8 index 230fae544..9b9d512b0 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -9,6 +9,11 @@ main { if xx<100 { foobar() } + + if xx==9 or xx==10 or xx==11 or xx==12 or xx==13 { + txt.print("9 10 11\n") + } + txt.print("\nthe end\n") } From 7a9e5afb93762b5d64f89099cbf2c684ded3e165 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 28 Dec 2021 17:21:41 +0100 Subject: [PATCH 2/4] fix: for loop over array literal no longer crashes the compiler --- .../compiler/astprocessing/AstExtensions.kt | 3 +- .../astprocessing/LiteralsToAutoVars.kt | 13 ++-- compiler/test/TestCompilerOnRanges.kt | 64 +++++++++++++++++ docs/source/todo.rst | 1 + examples/test.p8 | 70 ++++++++++++++++++- 5 files changed, 140 insertions(+), 11 deletions(-) diff --git a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt index 0f2414962..c634096ab 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt @@ -91,7 +91,8 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, program: Program, transforms.applyModifications() val lit2decl = LiteralsToAutoVars(this) lit2decl.visit(this) - lit2decl.applyModifications() + if(errors.noErrors()) + lit2decl.applyModifications() } } diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt index 1eb039a42..4a74b8991 100644 --- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt +++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt @@ -1,6 +1,5 @@ package prog8.compiler.astprocessing -import prog8.ast.IStatementContainer import prog8.ast.Node import prog8.ast.Program import prog8.ast.base.DataType @@ -28,26 +27,24 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { override fun after(array: ArrayLiteralValue, parent: Node): Iterable { val vardecl = array.parent as? VarDecl if(vardecl!=null) { - // adjust the datatype of the array (to an educated guess) + // adjust the datatype of the array (to an educated guess from the vardecl type) val arrayDt = array.type if(arrayDt isnot vardecl.datatype) { val cast = array.cast(vardecl.datatype) - if (cast != null && cast !== array) + if(cast!=null && cast !== array) return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl)) } } else { val arrayDt = array.guessDatatype(program) if(arrayDt.isKnown) { - // this array literal is part of an expression, turn it into an identifier reference + // turn the array literal it into an identifier reference val litval2 = array.cast(arrayDt.getOr(DataType.UNDEFINED)) if(litval2!=null) { - if(array.parent !is IStatementContainer) - return noModifications val vardecl2 = VarDecl.createAuto(litval2) val identifier = IdentifierReference(listOf(vardecl2.name), vardecl2.position) return listOf( - IAstModification.ReplaceNode(array, identifier, parent), - IAstModification.InsertFirst(vardecl2, array.parent as IStatementContainer) + IAstModification.ReplaceNode(array, identifier, parent), + IAstModification.InsertFirst(vardecl2, array.definingScope) ) } } diff --git a/compiler/test/TestCompilerOnRanges.kt b/compiler/test/TestCompilerOnRanges.kt index a114ca1bc..132b77c94 100644 --- a/compiler/test/TestCompilerOnRanges.kt +++ b/compiler/test/TestCompilerOnRanges.kt @@ -301,4 +301,68 @@ class TestCompilerOnRanges: FunSpec({ forloop.iterable shouldBe instanceOf() (forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY) } + + + test("for statement on all possible iterable expressions") { + compileText(C64Target, false, """ + main { + sub start() { + ubyte xx + str name = "irmen" + ubyte[] values = [1,2,3,4,5,6,7] + + for xx in name { + xx++ + } + + for xx in values { + xx++ + } + + for xx in 10 to 20 step 3 { + xx++ + } + + for xx in "abcdef" { + xx++ + } + + for xx in [2,4,6,8] { + xx++ + } + } + }""").assertSuccess() + } + + // TODO enable this after this if-syntax is implemented + xtest("if containment check on all possible iterable expressions") { + compileText(C64Target, false, """ + main { + sub start() { + ubyte xx + str name = "irmen" + ubyte[] values = [1,2,3,4,5,6,7] + + if xx in name { + xx++ + } + + if xx in values { + xx++ + } + + if xx in 10 to 20 step 3 { + xx++ + } + + if xx in "abcdef" { + xx++ + } + + if xx in [2,4,6,8] { + xx++ + } + } + }""").assertSuccess() + } }) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 772790af2..be79136f8 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -6,6 +6,7 @@ For next compiler release (7.6) add "if X in [1,2,3] {...}" syntax , as an alternative to when X { 1,2,3-> {...} } if the array is not a literal, do a normal containment test instead in an array or string or range change "consider using when statement..." to "consider using if X in [..] or when statement..." +also add to the docs! Blocked by an official Commander-x16 v39 release diff --git a/examples/test.p8 b/examples/test.p8 index 9b9d512b0..317d9c925 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -5,10 +5,76 @@ main { sub start() { ubyte @shared xx + str name = "irmen" + ubyte[] values = [1,2,3,4,5,6,7] - if xx<100 { - foobar() + for xx in name { + txt.chrout(xx) + txt.spc() } + txt.nl() + + for xx in values { + txt.print_ub(xx) + txt.spc() + } + txt.nl() + + for xx in 10 to 20 step 3 { + txt.print_ub(xx) + txt.spc() + } + txt.nl() + + for xx in "abcdef" { + txt.print_ub(xx) + txt.spc() + } + txt.nl() + + for xx in [2,4,6,8] { + txt.print_ub(xx) + txt.spc() + } + txt.nl() + + +; if xx in 100 { ; TODO error +; xx++ +; } +; +; if xx in 'a' { ; TODO error +; xx++ +; } +; +; if xx in "abc" { ; TODO containment test via when +; xx++ +; } +; +; if xx in [1,2,3,4,5] { ; TODO containment test via when +; xx++ +; } +; +; if xx in [1,2,3,4,5,6,7,8,9,10] { ; TODO containment test via loop? +; xx++ +; } +; +; if xx in name { ; TODO containment test via loop +; xx++ +; } +; +; if xx in values { ; TODO containment test via loop +; xx++ +; } +; +; if xx in 10 to 20 step 2 { ; TODO +; +; } + + ; TODO const optimizing of the containment tests + ; TODO also with (u)word and floats + + if xx==9 or xx==10 or xx==11 or xx==12 or xx==13 { txt.print("9 10 11\n") From de6ce4a46eed5e84afe0b6fa9df20bd32ecda99f Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 29 Dec 2021 16:21:37 +0100 Subject: [PATCH 3/4] add "X in [1,2,3]" expression (efficient containment check) --- .../cpu6502/codegen/ExpressionsAsmGen.kt | 2 + .../target/cpu6502/codegen/ForLoopsAsmGen.kt | 1 - .../codegen/assignment/AssignmentAsmGen.kt | 122 ++++++++++++++++++ .../src/prog8/optimizer/BinExprSplitter.kt | 2 +- .../optimizer/ConstantFoldingOptimizer.kt | 7 + .../optimizer/ConstantIdentifierReplacer.kt | 2 - .../prog8/optimizer/ExpressionSimplifier.kt | 19 +++ .../src/prog8/optimizer/Extensions.kt | 8 +- .../src/prog8/optimizer/StatementOptimizer.kt | 5 +- compiler/res/prog8lib/prog8_lib.asm | 42 ++++++ .../compiler/BeforeAsmGenerationAstChanger.kt | 10 +- compiler/src/prog8/compiler/Compiler.kt | 2 +- .../compiler/astprocessing/AstChecker.kt | 12 ++ .../compiler/astprocessing/AstExtensions.kt | 4 +- .../compiler/astprocessing/AstPreprocessor.kt | 25 ++-- .../compiler/astprocessing/CodeDesugarer.kt | 14 +- .../astprocessing/LiteralsToAutoVars.kt | 6 +- .../compiler/astprocessing/VariousCleanups.kt | 76 ++++++++++- compiler/test/TestCompilerOnRanges.kt | 75 ++++++++++- compiler/test/TestOptimization.kt | 2 +- .../src/prog8/ast/AstToSourceTextConverter.kt | 6 + compilerAst/src/prog8/ast/Program.kt | 3 +- .../prog8/ast/expressions/AstExpressions.kt | 122 ++++++++++++++++++ compilerAst/src/prog8/ast/walk/AstWalker.kt | 9 ++ compilerAst/src/prog8/ast/walk/IAstVisitor.kt | 5 + .../prog8/compilerinterface/AstExtensions.kt | 38 ------ docs/source/programming.rst | 13 ++ docs/source/syntaxreference.rst | 19 ++- docs/source/todo.rst | 7 +- examples/test.p8 | 106 +++++++-------- parser/antlr/Prog8ANTLR.g4 | 1 + 31 files changed, 615 insertions(+), 150 deletions(-) diff --git a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ExpressionsAsmGen.kt b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ExpressionsAsmGen.kt index 172e59009..d85e21f7d 100644 --- a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ExpressionsAsmGen.kt +++ b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ExpressionsAsmGen.kt @@ -37,9 +37,11 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge is NumericLiteralValue -> translateExpression(expression) is IdentifierReference -> translateExpression(expression) is FunctionCallExpr -> translateFunctionCallResultOntoStack(expression) + is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported") is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable") is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding") + else -> TODO("missing expression asmgen for $expression") } } diff --git a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ForLoopsAsmGen.kt b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ForLoopsAsmGen.kt index ea357b49a..fbc693ce2 100644 --- a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ForLoopsAsmGen.kt +++ b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/ForLoopsAsmGen.kt @@ -9,7 +9,6 @@ import prog8.ast.expressions.RangeExpr import prog8.ast.statements.ForLoop import prog8.ast.toHex import prog8.codegen.target.AssemblyError -import prog8.compilerinterface.toConstantIntegerRange import kotlin.math.absoluteValue internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) { diff --git a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/assignment/AssignmentAsmGen.kt b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/assignment/AssignmentAsmGen.kt index 9aa840c7e..7c8f7e799 100644 --- a/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/assignment/AssignmentAsmGen.kt +++ b/codeGeneration/src/prog8/codegen/target/cpu6502/codegen/assignment/AssignmentAsmGen.kt @@ -271,6 +271,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen else -> throw AssemblyError("invalid prefix operator") } } + is ContainmentCheck -> { + containmentCheckIntoA(value) + assignRegisterByte(assign.target, CpuRegister.A) + } else -> { // Everything else just evaluate via the stack. // (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here, @@ -294,6 +298,124 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen } } + private fun containmentCheckIntoA(containment: ContainmentCheck) { + val elementDt = containment.element.inferType(program) + val range = containment.iterable as? RangeExpr + if(range!=null) { + val constRange = range.toConstantIntegerRange() + if(constRange!=null) + return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), constRange.toList()) + TODO("non-const range containment check ${containment.position}") + } + val variable = (containment.iterable as? IdentifierReference)?.targetVarDecl(program) + if(variable!=null) { + if(elementDt istype DataType.FLOAT) + throw AssemblyError("containment check of floats not supported") + if(variable.autogeneratedDontRemove) { + when(variable.datatype) { + DataType.STR -> { + require(elementDt.isBytes) + val stringVal = variable.value as StringLiteralValue + val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding) + return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() }) + } + DataType.ARRAY_F -> { + // require(elementDt istype DataType.FLOAT) + throw AssemblyError("containment check of floats not supported") + } + in ArrayDatatypes -> { + require(elementDt.isInteger) + val arrayVal = variable.value as ArrayLiteralValue + val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() } + return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values) + } + else -> throw AssemblyError("invalid dt") + } + } + val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference) + when(variable.datatype) { + DataType.STR -> { + assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname) + assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE) + val stringVal = variable.value as StringLiteralValue + asmgen.out(" ldy #${stringVal.value.length}") + asmgen.out(" jsr prog8_lib.containment_bytearray") + return + } + DataType.ARRAY_F -> throw AssemblyError("containment check of floats not supported") + DataType.ARRAY_B, DataType.ARRAY_UB -> { + val arrayVal = variable.value as ArrayLiteralValue + assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname) + assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE) + asmgen.out(" ldy #${arrayVal.value.size}") + asmgen.out(" jsr prog8_lib.containment_bytearray") + return + } + DataType.ARRAY_W, DataType.ARRAY_UW -> { + val arrayVal = variable.value as ArrayLiteralValue + assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine) + assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname) + asmgen.out(" ldy #${arrayVal.value.size}") + asmgen.out(" jsr prog8_lib.containment_wordarray") + return + } + else -> throw AssemblyError("invalid dt") + } + } + val stringVal = containment.iterable as? StringLiteralValue + if(stringVal!=null) { + require(elementDt.isBytes) + val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding) + return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() }) + } + val arrayVal = containment.iterable as? ArrayLiteralValue + if(arrayVal!=null) { + require(elementDt.isInteger) + val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() } + return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values) + } + + throw AssemblyError("invalid containment iterable type") + } + + private fun containmentCheckIntoA(element: Expression, dt: DataType, values: List) { + if(values.size<2) + throw AssemblyError("containment check against 0 or 1 values should have been optimized away") + + // TODO don't generate a huge cmp-list when we go over a certain number of values + val containsLabel = asmgen.makeLabel("contains") + when(dt) { + in ByteDatatypes -> { + asmgen.assignExpressionToRegister(element, RegisterOrPair.A, dt==DataType.BYTE) + for (value in values) { + asmgen.out(" cmp #$value | beq +") + } + asmgen.out(""" + lda #0 + beq ++ ++ lda #1 ++""") + } + in WordDatatypes -> { + asmgen.assignExpressionToRegister(element, RegisterOrPair.AY, dt==DataType.WORD) + for (value in values) { + asmgen.out(""" + cmp #<$value + bne + + cpy #>$value + beq $containsLabel ++""") + } + asmgen.out(""" + lda #0 + beq + +$containsLabel lda #1 ++""") + } + else -> throw AssemblyError("invalid dt") + } + } + private fun assignStatusFlagByte(target: AsmAssignTarget, statusflag: Statusflag) { when(statusflag) { Statusflag.Pc -> { diff --git a/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt b/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt index 1f8549e50..84d9a2f27 100644 --- a/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt +++ b/codeOptimizers/src/prog8/optimizer/BinExprSplitter.kt @@ -22,7 +22,7 @@ class BinExprSplitter(private val program: Program, private val options: Compila override fun after(assignment: Assignment, parent: Node): Iterable { - if(assignment.value.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions) + if(assignment.value.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions) return noModifications val binExpr = assignment.value as? BinaryExpression diff --git a/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt b/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt index 78ecb09dc..aa5104eb3 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantFoldingOptimizer.kt @@ -23,6 +23,13 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() { noModifications } + override fun after(containment: ContainmentCheck, parent: Node): Iterable { + val result = containment.constValue(program) + if(result!=null) + return listOf(IAstModification.ReplaceNode(containment, result, parent)) + return noModifications + } + override fun after(expr: PrefixExpression, parent: Node): Iterable { // Try to turn a unary prefix expression into a single constant value. // Compile-time constant sub expressions will be evaluated on the spot. diff --git a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt index fcb1e14b7..c48fcff72 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt @@ -9,8 +9,6 @@ import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification import prog8.compilerinterface.ICompilationTarget import prog8.compilerinterface.IErrorReporter -import prog8.compilerinterface.size -import prog8.compilerinterface.toConstantIntegerRange // Fix up the literal value's type to match that of the vardecl // (also check range literal operands types before they get expanded into arrays for instance) diff --git a/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt b/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt index bbec36e9c..915f8d8e4 100644 --- a/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt +++ b/codeOptimizers/src/prog8/optimizer/ExpressionSimplifier.kt @@ -344,6 +344,25 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() { return noModifications } + override fun after(containment: ContainmentCheck, parent: Node): Iterable { + val range = containment.iterable as? RangeExpr + if(range!=null && range.step.constValue(program)?.number==1.0) { + val from = range.from.constValue(program) + val to = range.to.constValue(program) + val value = containment.element + if(from!=null && to!=null && value.isSimple) { + if(to.number-from.number>6.0) { + // replace containment test with X>=from and X<=to + val left = BinaryExpression(value, ">=", from, containment.position) + val right = BinaryExpression(value.copy(), "<=", to, containment.position) + val comparison = BinaryExpression(left, "and", right, containment.position) + return listOf(IAstModification.ReplaceNode(containment, comparison, parent)) + } + } + } + return noModifications + } + private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? { return when { subBinExpr.left isSameAs x -> subBinExpr.right diff --git a/codeOptimizers/src/prog8/optimizer/Extensions.kt b/codeOptimizers/src/prog8/optimizer/Extensions.kt index b63c695a0..08a2b46e2 100644 --- a/codeOptimizers/src/prog8/optimizer/Extensions.kt +++ b/codeOptimizers/src/prog8/optimizer/Extensions.kt @@ -72,10 +72,10 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom fun getTempVarName(dt: InferredTypes.InferredType): List { return when { // TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable... - dt.istype(DataType.UBYTE) -> listOf("cx16", "r9L") - dt.istype(DataType.BYTE) -> listOf("cx16", "r9sL") - dt.istype(DataType.UWORD) -> listOf("cx16", "r9") - dt.istype(DataType.WORD) -> listOf("cx16", "r9s") + dt istype DataType.UBYTE -> listOf("cx16", "r9L") + dt istype DataType.BYTE -> listOf("cx16", "r9sL") + dt istype DataType.UWORD -> listOf("cx16", "r9") + dt istype DataType.WORD -> listOf("cx16", "r9s") dt.isPassByReference -> listOf("cx16", "r9") else -> throw FatalAstException("invalid dt $dt") } diff --git a/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt b/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt index d14d753fe..cab6a9e1d 100644 --- a/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt +++ b/codeOptimizers/src/prog8/optimizer/StatementOptimizer.kt @@ -8,7 +8,6 @@ import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification import prog8.compilerinterface.ICompilationTarget import prog8.compilerinterface.IErrorReporter -import prog8.compilerinterface.size import kotlin.math.floor @@ -154,11 +153,11 @@ class StatementOptimizer(private val program: Program, if(constvalue!=null) { return if(constvalue.asBooleanValue){ // always true -> keep only if-part - errors.warn("condition is always true", ifElse.position) + errors.warn("condition is always true", ifElse.condition.position) listOf(IAstModification.ReplaceNode(ifElse, ifElse.truepart, parent)) } else { // always false -> keep only else-part - errors.warn("condition is always false", ifElse.position) + errors.warn("condition is always false", ifElse.condition.position) listOf(IAstModification.ReplaceNode(ifElse, ifElse.elsepart, parent)) } } diff --git a/compiler/res/prog8lib/prog8_lib.asm b/compiler/res/prog8lib/prog8_lib.asm index 5c4e78c20..dffc8dd6e 100644 --- a/compiler/res/prog8lib/prog8_lib.asm +++ b/compiler/res/prog8lib/prog8_lib.asm @@ -1083,3 +1083,45 @@ strlen .proc bne - + rts .pend + + +containment_bytearray .proc + ; -- check if a value exists in a byte array. + ; parameters: P8ZP_SCRATCH_W1: address of the byte array, A = byte to check, Y = length of array (>=1). + ; returns boolean 0/1 in A. + dey +- cmp (P8ZP_SCRATCH_W1),y + beq + + dey + cpy #255 + bne - + lda #0 + rts ++ lda #1 + rts + .pend + +containment_wordarray .proc + ; -- check if a value exists in a word array. + ; parameters: P8ZP_SCRATCH_W1: value to check, P8ZP_SCRATCH_W2: address of the word array, Y = length of array (>=1). + ; returns boolean 0/1 in A. + dey + tya + asl a + tay +- lda P8ZP_SCRATCH_W1 + cmp (P8ZP_SCRATCH_W2),y + bne + + lda P8ZP_SCRATCH_W1+1 + iny + cmp (P8ZP_SCRATCH_W2),y + beq _found ++ dey + dey + cpy #254 + bne - + lda #0 + rts +_found lda #1 + rts + .pend diff --git a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt index f9939ca9e..1f33e5fea 100644 --- a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt +++ b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt @@ -67,7 +67,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o && !assignment.target.isIOAddress(options.compTarget.machine)) { val binExpr = assignment.value as? BinaryExpression - if(binExpr!=null && binExpr.inferType(program).istype(DataType.FLOAT) && !options.optimizeFloatExpressions) + if(binExpr!=null && binExpr.inferType(program) istype DataType.FLOAT && !options.optimizeFloatExpressions) return noModifications if (binExpr != null && binExpr.operator !in ComparisonOperators) { @@ -270,10 +270,10 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o } if(separateRightExpr) { val name = when { - rightDt.istype(DataType.UBYTE) -> listOf("prog8_lib","retval_interm_ub") - rightDt.istype(DataType.UWORD) -> listOf("prog8_lib","retval_interm_uw") - rightDt.istype(DataType.BYTE) -> listOf("prog8_lib","retval_interm_b2") - rightDt.istype(DataType.WORD) -> listOf("prog8_lib","retval_interm_w2") + rightDt istype DataType.UBYTE -> listOf("prog8_lib","retval_interm_ub") + rightDt istype DataType.UWORD -> listOf("prog8_lib","retval_interm_uw") + rightDt istype DataType.BYTE -> listOf("prog8_lib","retval_interm_b2") + rightDt istype DataType.WORD -> listOf("prog8_lib","retval_interm_w2") else -> throw AssemblyError("invalid dt") } rightOperandReplacement = IdentifierReference(name, expr.position) diff --git a/compiler/src/prog8/compiler/Compiler.kt b/compiler/src/prog8/compiler/Compiler.kt index 07331e607..f16a77079 100644 --- a/compiler/src/prog8/compiler/Compiler.kt +++ b/compiler/src/prog8/compiler/Compiler.kt @@ -255,7 +255,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { // perform initial syntax checks and processings println("Processing for target ${compilerOptions.compTarget.name}...") - program.preprocessAst(program) + program.preprocessAst(program, errors) program.checkIdentifiers(errors, program, compilerOptions) errors.report() // TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 56b01b7f0..05736a482 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -874,6 +874,7 @@ internal class AstChecker(private val program: Program, if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes) errors.err("bitwise operator can only be used on integer operands", expr.right.position) } + "in" -> throw FatalAstException("in expression should have been replaced by containmentcheck") } if(leftDt !in NumericDatatypes && leftDt != DataType.STR) @@ -1206,6 +1207,17 @@ internal class AstChecker(private val program: Program, super.visit(whenChoice) } + override fun visit(containment: ContainmentCheck) { + if(!containment.iterable.inferType(program).isIterable) + errors.err("value set for containment check must be an iterable type", containment.iterable.position) + if(containment.parent is BinaryExpression) + errors.err("containment check is currently not supported in complex expressions", containment.position) + + // TODO check that iterable contains the same types as the element that is searched + + super.visit(containment) + } + private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? { when (val targetStatement = target.targetStatement(program)) { is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement diff --git a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt index c634096ab..3e3755164 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstExtensions.kt @@ -72,8 +72,8 @@ internal fun Program.verifyFunctionArgTypes() { fixer.visit(this) } -internal fun Program.preprocessAst(program: Program) { - val transforms = AstPreprocessor(program) +internal fun Program.preprocessAst(program: Program, errors: IErrorReporter) { + val transforms = AstPreprocessor(program, errors) transforms.visit(this) var mods = transforms.applyModifications() while(mods>0) diff --git a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt index 10512a06c..38b50b816 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt @@ -2,21 +2,15 @@ package prog8.compiler.astprocessing import prog8.ast.Node import prog8.ast.Program -import prog8.ast.base.NumericDatatypes -import prog8.ast.base.SyntaxError -import prog8.ast.base.VarDeclType -import prog8.ast.expressions.IdentifierReference -import prog8.ast.expressions.NumericLiteralValue -import prog8.ast.expressions.RangeExpr -import prog8.ast.statements.AnonymousScope -import prog8.ast.statements.AssignTarget -import prog8.ast.statements.Assignment -import prog8.ast.statements.VarDecl +import prog8.ast.base.* +import prog8.ast.expressions.* +import prog8.ast.statements.* import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification +import prog8.compilerinterface.IErrorReporter -class AstPreprocessor(val program: Program) : AstWalker() { +class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWalker() { override fun after(range: RangeExpr, parent: Node): Iterable { // has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes @@ -82,4 +76,13 @@ class AstPreprocessor(val program: Program) : AstWalker() { } return noModifications } + + override fun after(expr: BinaryExpression, parent: Node): Iterable { + // this has to be done here becuse otherwise the string / range literal values will have been replaced by variables + if(expr.operator=="in") { + val containment = ContainmentCheck(expr.left, expr.right, expr.position) + return listOf(IAstModification.ReplaceNode(expr, containment, parent)) + } + return noModifications + } } diff --git a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt index e067848b9..24ee31655 100644 --- a/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt +++ b/compiler/src/prog8/compiler/astprocessing/CodeDesugarer.kt @@ -6,10 +6,7 @@ import prog8.ast.Node import prog8.ast.Program import prog8.ast.base.ParentSentinel import prog8.ast.base.Position -import prog8.ast.expressions.DirectMemoryRead -import prog8.ast.expressions.FunctionCallExpr -import prog8.ast.expressions.IdentifierReference -import prog8.ast.expressions.PrefixExpression +import prog8.ast.expressions.* import prog8.ast.statements.* import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstModification @@ -25,6 +22,8 @@ internal class CodeDesugarer(val program: Program, private val errors: IErrorRep // // List of modifications: // - replace 'break' statements by a goto + generated after label. + // - replace while and do-until loops by just jumps. + // - replace peek() and poke() by direct memory accesses. private var generatedLabelSequenceNumber: Int = 0 @@ -135,4 +134,11 @@ _after: } return noModifications } + + override fun after(expr: BinaryExpression, parent: Node): Iterable { + if(expr.operator=="in") { + println("IN-TEST: $expr\n in: $parent") + } + return noModifications + } } diff --git a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt index 4a74b8991..5aec1f574 100644 --- a/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt +++ b/compiler/src/prog8/compiler/astprocessing/LiteralsToAutoVars.kt @@ -4,6 +4,7 @@ import prog8.ast.Node import prog8.ast.Program import prog8.ast.base.DataType import prog8.ast.expressions.ArrayLiteralValue +import prog8.ast.expressions.ContainmentCheck import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.StringLiteralValue import prog8.ast.statements.VarDecl @@ -15,7 +16,7 @@ import prog8.ast.walk.IAstModification internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { override fun after(string: StringLiteralValue, parent: Node): Iterable { - if(string.parent !is VarDecl && string.parent !is WhenChoice) { + if(string.parent !is VarDecl && string.parent !is WhenChoice && string.parent !is ContainmentCheck) { // replace the literal string by an identifier reference to the interned string val scopedName = program.internString(string) val identifier = IdentifierReference(scopedName, string.position) @@ -35,6 +36,9 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl)) } } else { + if(array.parent is ContainmentCheck) + return noModifications + val arrayDt = array.guessDatatype(program) if(arrayDt.isKnown) { // turn the array literal it into an identifier reference diff --git a/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt b/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt index 07e99f49d..eefc53c8e 100644 --- a/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt +++ b/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt @@ -3,6 +3,8 @@ package prog8.compiler.astprocessing import prog8.ast.IStatementContainer import prog8.ast.Node import prog8.ast.Program +import prog8.ast.base.ArrayDatatypes +import prog8.ast.base.DataType import prog8.ast.base.FatalAstException import prog8.ast.expressions.* import prog8.ast.statements.* @@ -97,7 +99,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter) val rightBinExpr = expr.right as? BinaryExpression if(leftBinExpr!=null && leftBinExpr.operator=="==" && rightBinExpr!=null && rightBinExpr.operator=="==") { if(leftBinExpr.right is NumericLiteralValue && rightBinExpr.right is NumericLiteralValue) { - errors.warn("consider using when statement to test for multiple values", expr.position) + errors.warn("consider using 'in' or 'when' to test for multiple values", expr.position) } } } @@ -124,4 +126,76 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter) } return noModifications } + + override fun after(containment: ContainmentCheck, parent: Node): Iterable { + // replace trivial containment checks with just false or a single comparison + fun replaceWithEquals(value: NumericLiteralValue): Iterable { + errors.warn("containment could be written as just a single comparison", containment.position) + val equals = BinaryExpression(containment.element, "==", value, containment.position) + return listOf(IAstModification.ReplaceNode(containment, equals, parent)) + } + + fun replaceWithFalse(): Iterable { + errors.warn("condition is always false", containment.position) + return listOf(IAstModification.ReplaceNode(containment, NumericLiteralValue.fromBoolean(false, containment.position), parent)) + } + + fun checkArray(array: Array): Iterable { + if(array.isEmpty()) + return replaceWithFalse() + if(array.size==1) { + val constVal = array[0].constValue(program) + if(constVal!=null) + return replaceWithEquals(constVal) + } + return noModifications + } + + fun checkString(stringVal: StringLiteralValue): Iterable { + if(stringVal.value.isEmpty()) + return replaceWithFalse() + if(stringVal.value.length==1) { + val string = program.encoding.encodeString(stringVal.value, stringVal.altEncoding) + return replaceWithEquals(NumericLiteralValue(DataType.UBYTE, string[0].toDouble(), stringVal.position)) + } + return noModifications + } + + when(containment.iterable) { + is ArrayLiteralValue -> { + val array = (containment.iterable as ArrayLiteralValue).value + return checkArray(array) + } + is IdentifierReference -> { + val variable = (containment.iterable as IdentifierReference).targetVarDecl(program)!! + when(variable.datatype) { + DataType.STR -> { + val stringVal = (variable.value as StringLiteralValue) + return checkString(stringVal) + } + in ArrayDatatypes -> { + val array = (variable.value as ArrayLiteralValue).value + return checkArray(array) + } + else -> {} + } + } + is RangeExpr -> { + val constValues = (containment.iterable as RangeExpr).toConstantIntegerRange() + if(constValues!=null) { + if (constValues.isEmpty()) + return replaceWithFalse() + if (constValues.count()==1) + return replaceWithEquals(NumericLiteralValue.optimalNumeric(constValues.first, containment.position)) + } + } + is StringLiteralValue -> { + val stringVal = containment.iterable as StringLiteralValue + return checkString(stringVal) + } + else -> {} + } + return noModifications + } } + diff --git a/compiler/test/TestCompilerOnRanges.kt b/compiler/test/TestCompilerOnRanges.kt index 132b77c94..5cfdc7f27 100644 --- a/compiler/test/TestCompilerOnRanges.kt +++ b/compiler/test/TestCompilerOnRanges.kt @@ -12,8 +12,6 @@ import prog8.ast.statements.ForLoop import prog8.ast.statements.VarDecl import prog8.codegen.target.C64Target import prog8.codegen.target.Cx16Target -import prog8.compilerinterface.size -import prog8.compilerinterface.toConstantIntegerRange import prog8tests.helpers.* import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.assertFailure @@ -308,8 +306,10 @@ class TestCompilerOnRanges: FunSpec({ main { sub start() { ubyte xx + uword ww str name = "irmen" ubyte[] values = [1,2,3,4,5,6,7] + uword[] wvalues = [1000,2000,3000] for xx in name { xx++ @@ -330,19 +330,48 @@ class TestCompilerOnRanges: FunSpec({ for xx in [2,4,6,8] { xx++ } + + for ww in [9999,8888,7777] { + xx++ + } + + for ww in wvalues { + xx++ + } } - }""").assertSuccess() + }""", writeAssembly = true).assertSuccess() } - // TODO enable this after this if-syntax is implemented - xtest("if containment check on all possible iterable expressions") { + test("if containment check on all possible iterable expressions") { compileText(C64Target, false, """ main { sub start() { ubyte xx + uword ww str name = "irmen" ubyte[] values = [1,2,3,4,5,6,7] + uword[] wvalues = [1000,2000,3000] + if 'm' in name { + xx++ + } + + if 5 in values { + xx++ + } + + if 16 in 10 to 20 step 3 { + xx++ + } + + if 'b' in "abcdef" { + xx++ + } + + if 8 in [2,4,6,8] { + xx++ + } + if xx in name { xx++ } @@ -362,7 +391,41 @@ class TestCompilerOnRanges: FunSpec({ if xx in [2,4,6,8] { xx++ } + + if ww in [9999,8888,7777] { + xx++ + } + + if ww in wvalues { + xx++ + } } - }""").assertSuccess() + }""", writeAssembly = true).assertSuccess() + } + + test("containment check in expressions") { + compileText(C64Target, false, """ + main { + sub start() { + ubyte xx + uword ww + str name = "irmen" + ubyte[] values = [1,2,3,4,5,6,7] + uword[] wvalues = [1000,2000,3000] + + xx = 'm' in name + xx = 5 in values + xx = 16 in 10 to 20 step 3 + xx = 'b' in "abcdef" + xx = 8 in [2,4,6,8] + xx = xx in name + xx = xx in values + xx = xx in 10 to 20 step 3 + xx = xx in "abcdef" + xx = xx in [2,4,6,8] + xx = ww in [9000,8000,7000] + xx = ww in wvalues + } + }""", writeAssembly = true).assertSuccess() } }) diff --git a/compiler/test/TestOptimization.kt b/compiler/test/TestOptimization.kt index 4b3d45ca4..063809042 100644 --- a/compiler/test/TestOptimization.kt +++ b/compiler/test/TestOptimization.kt @@ -278,7 +278,7 @@ class TestOptimization: FunSpec({ wwAssign.target.identifier?.nameInSource shouldBe listOf("ww") expr.type shouldBe DataType.UWORD - expr.expression.inferType(result.program).istype(DataType.UBYTE) shouldBe true + expr.expression.inferType(result.program) istype DataType.UBYTE shouldBe true } test("intermediate assignment steps have correct types for codegen phase (BeforeAsmGenerationAstChanger)") { diff --git a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt index 34de0da9b..f01cdad73 100644 --- a/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt +++ b/compilerAst/src/prog8/ast/AstToSourceTextConverter.kt @@ -48,6 +48,12 @@ class AstToSourceTextConverter(val output: (text: String) -> Unit, val program: outputln("}\n") } + override fun visit(containment: ContainmentCheck) { + containment.element.accept(this) + output(" in ") + containment.iterable.accept(this) + } + override fun visit(expr: PrefixExpression) { if(expr.operator.any { it.isLetter() }) output(" ${expr.operator} ") diff --git a/compilerAst/src/prog8/ast/Program.kt b/compilerAst/src/prog8/ast/Program.kt index ffd06837e..9f72e8810 100644 --- a/compilerAst/src/prog8/ast/Program.kt +++ b/compilerAst/src/prog8/ast/Program.kt @@ -4,6 +4,7 @@ import prog8.ast.base.DataType import prog8.ast.base.FatalAstException import prog8.ast.base.Position import prog8.ast.base.VarDeclType +import prog8.ast.expressions.ContainmentCheck import prog8.ast.expressions.StringLiteralValue import prog8.ast.statements.Block import prog8.ast.statements.Subroutine @@ -82,7 +83,7 @@ class Program(val name: String, // Move a string literal into the internal, deduplicated, string pool // replace it with a variable declaration that points to the entry in the pool. - if(string.parent is VarDecl) { + if(string.parent is VarDecl || string.parent is ContainmentCheck) { // deduplication can only be performed safely for known-const strings (=string literals OUTSIDE OF A VARDECL)! throw FatalAstException("cannot intern a string literal that's part of a vardecl") } diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index 64e3e9671..9fc48e13d 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -7,6 +7,7 @@ import prog8.ast.statements.* import prog8.ast.walk.AstWalker import prog8.ast.walk.IAstVisitor import java.util.* +import kotlin.math.abs import kotlin.math.round val AssociativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "==", "!=") @@ -219,6 +220,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex "<=", ">=", "==", "!=" -> InferredTypes.knownFor(DataType.UBYTE) "<<", ">>" -> leftDt + "in" -> InferredTypes.knownFor(DataType.UBYTE) else -> throw FatalAstException("resulting datatype check for invalid operator $operator") } } @@ -801,6 +803,42 @@ class RangeExpr(var from: Expression, return "RangeExpr(from $from, to $to, step $step, pos=$position)" } + fun toConstantIntegerRange(): IntProgression? { + + fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression { + return when { + fromVal <= toVal -> when { + stepVal <= 0 -> IntRange.EMPTY + stepVal == 1 -> fromVal..toVal + else -> fromVal..toVal step stepVal + } + else -> when { + stepVal >= 0 -> IntRange.EMPTY + stepVal == -1 -> fromVal downTo toVal + else -> fromVal downTo toVal step abs(stepVal) + } + } + } + + val fromLv = from as? NumericLiteralValue + val toLv = to as? NumericLiteralValue + val stepLv = step as? NumericLiteralValue + if(fromLv==null || toLv==null || stepLv==null) + return null + val fromVal = fromLv.number.toInt() + val toVal = toLv.number.toInt() + val stepVal = stepLv.number.toInt() + return makeRange(fromVal, toVal, stepVal) + } + + + fun size(): Int? { + val fromLv = (from as? NumericLiteralValue) + val toLv = (to as? NumericLiteralValue) + if(fromLv==null || toLv==null) + return null + return toConstantIntegerRange()?.count() + } } @@ -951,6 +989,90 @@ class FunctionCallExpr(override var target: IdentifierReference, } +class ContainmentCheck(var element: Expression, + var iterable: Expression, + override val position: Position): Expression() { + + override lateinit var parent: Node + + override fun linkParents(parent: Node) { + this.parent = parent + element.parent = this + iterable.linkParents(this) + } + + override val isSimple: Boolean = false + override fun copy() = ContainmentCheck(element.copy(), iterable.copy(), position) + override fun constValue(program: Program): NumericLiteralValue? { + val elementConst = element.constValue(program) + if(elementConst!=null) { + when(iterable){ + is ArrayLiteralValue -> { + val exists = (iterable as ArrayLiteralValue).value.any { it.constValue(program)==elementConst } + return NumericLiteralValue.fromBoolean(exists, position) + } + is RangeExpr -> { + val intRange = (iterable as RangeExpr).toConstantIntegerRange() + if(intRange!=null && elementConst.type in IntegerDatatypes) { + val exists = elementConst.number.toInt() in intRange + return NumericLiteralValue.fromBoolean(exists, position) + } + } + is StringLiteralValue -> { + if(elementConst.type in ByteDatatypes) { + val stringval = iterable as StringLiteralValue + val exists = program.encoding.encodeString(stringval.value, stringval.altEncoding).contains(elementConst.number.toInt().toUByte() ) + return NumericLiteralValue.fromBoolean(exists, position) + } + } + else -> {} + } + } + + when(iterable){ + is ArrayLiteralValue -> { + val array= iterable as ArrayLiteralValue + if(array.value.isEmpty()) + return NumericLiteralValue.fromBoolean(false, position) + } + is RangeExpr -> { + val size = (iterable as RangeExpr).size() + if(size!=null && size==0) + return NumericLiteralValue.fromBoolean(false, position) + } + is StringLiteralValue -> { + if((iterable as StringLiteralValue).value.isEmpty()) + return NumericLiteralValue.fromBoolean(false, position) + } + else -> {} + } + + return null + } + + override fun accept(visitor: IAstVisitor) = visitor.visit(this) + override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent) + override fun referencesIdentifier(nameInSource: List): Boolean { + if(element is IdentifierReference) + return element.referencesIdentifier(nameInSource) + return iterable?.referencesIdentifier(nameInSource) ?: false + } + + override fun inferType(program: Program) = InferredTypes.knownFor(DataType.UBYTE) + + override fun replaceChildNode(node: Node, replacement: Node) { + if(replacement !is Expression) + throw FatalAstException("invalid replace") + if(node===element) + element=replacement + else if(node===iterable) + iterable=replacement + else + throw FatalAstException("invalid replace") + } +} + + fun invertCondition(cond: Expression): BinaryExpression? { if(cond is BinaryExpression) { val invertedOperator = invertedComparisonOperator(cond.operator) diff --git a/compilerAst/src/prog8/ast/walk/AstWalker.kt b/compilerAst/src/prog8/ast/walk/AstWalker.kt index e98223f91..aeacf1160 100644 --- a/compilerAst/src/prog8/ast/walk/AstWalker.kt +++ b/compilerAst/src/prog8/ast/walk/AstWalker.kt @@ -87,6 +87,7 @@ abstract class AstWalker { open fun before(block: Block, parent: Node): Iterable = noModifications open fun before(branch: Branch, parent: Node): Iterable = noModifications open fun before(breakStmt: Break, parent: Node): Iterable = noModifications + open fun before(containment: ContainmentCheck, parent: Node): Iterable = noModifications open fun before(decl: VarDecl, parent: Node): Iterable = noModifications open fun before(directive: Directive, parent: Node): Iterable = noModifications open fun before(expr: BinaryExpression, parent: Node): Iterable = noModifications @@ -128,6 +129,7 @@ abstract class AstWalker { open fun after(block: Block, parent: Node): Iterable = noModifications open fun after(branch: Branch, parent: Node): Iterable = noModifications open fun after(breakStmt: Break, parent: Node): Iterable = noModifications + open fun after(containment: ContainmentCheck, parent: Node): Iterable = noModifications open fun after(builtinFunctionPlaceholder: BuiltinFunctionPlaceholder, parent: Node): Iterable = noModifications open fun after(decl: VarDecl, parent: Node): Iterable = noModifications open fun after(directive: Directive, parent: Node): Iterable = noModifications @@ -228,6 +230,13 @@ abstract class AstWalker { track(after(directive, parent), directive, parent) } + fun visit(containment: ContainmentCheck, parent: Node) { + track(before(containment, parent), containment, parent) + containment.element.accept(this, containment) + containment.iterable?.accept(this, containment) + track(after(containment, parent), containment, parent) + } + fun visit(block: Block, parent: Node) { track(before(block, parent), block, parent) block.statements.forEach { it.accept(this, block) } diff --git a/compilerAst/src/prog8/ast/walk/IAstVisitor.kt b/compilerAst/src/prog8/ast/walk/IAstVisitor.kt index 4bf1fcc60..8ea30bfec 100644 --- a/compilerAst/src/prog8/ast/walk/IAstVisitor.kt +++ b/compilerAst/src/prog8/ast/walk/IAstVisitor.kt @@ -26,6 +26,11 @@ interface IAstVisitor { fun visit(directive: Directive) { } + fun visit(containment: ContainmentCheck) { + containment.element.accept(this) + containment.iterable?.accept(this) + } + fun visit(block: Block) { block.statements.forEach { it.accept(this) } } diff --git a/compilerInterfaces/src/prog8/compilerinterface/AstExtensions.kt b/compilerInterfaces/src/prog8/compilerinterface/AstExtensions.kt index b64f46f80..ef1aa297b 100644 --- a/compilerInterfaces/src/prog8/compilerinterface/AstExtensions.kt +++ b/compilerInterfaces/src/prog8/compilerinterface/AstExtensions.kt @@ -4,9 +4,7 @@ import prog8.ast.base.FatalAstException import prog8.ast.base.VarDeclType import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.NumericLiteralValue -import prog8.ast.expressions.RangeExpr import prog8.ast.statements.AssignTarget -import kotlin.math.abs fun AssignTarget.isIOAddress(machine: IMachineDefinition): Boolean { val memAddr = memoryAddress @@ -49,39 +47,3 @@ fun AssignTarget.isIOAddress(machine: IMachineDefinition): Boolean { else -> return false } } - -fun RangeExpr.toConstantIntegerRange(): IntProgression? { - - fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression { - return when { - fromVal <= toVal -> when { - stepVal <= 0 -> IntRange.EMPTY - stepVal == 1 -> fromVal..toVal - else -> fromVal..toVal step stepVal - } - else -> when { - stepVal >= 0 -> IntRange.EMPTY - stepVal == -1 -> fromVal downTo toVal - else -> fromVal downTo toVal step abs(stepVal) - } - } - } - - val fromLv = from as? NumericLiteralValue - val toLv = to as? NumericLiteralValue - val stepLv = step as? NumericLiteralValue - if(fromLv==null || toLv==null || stepLv==null) - return null - val fromVal = fromLv.number.toInt() - val toVal = toLv.number.toInt() - val stepVal = stepLv.number.toInt() - return makeRange(fromVal, toVal, stepVal) -} - -fun RangeExpr.size(): Int? { - val fromLv = (from as? NumericLiteralValue) - val toLv = (to as? NumericLiteralValue) - if(fromLv==null || toLv==null) - return null - return toConstantIntegerRange()?.count() -} diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 86c6e600c..cf1185ae1 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -283,6 +283,8 @@ It's possible to assign a new array to another array, this will overwrite all el array with those in the value array. The number and types of elements have to match. For large arrays this is a slow operation because every element is copied over. It should probably be avoided. +Using the ``in`` operator you can easily check if a value is present in an array, +example: ``if choice in [1,2,3,4] {....}`` **Arrays at a specific memory location:** Using the memory-mapped syntax it is possible to define an array to be located at a specific memory location. @@ -332,6 +334,11 @@ as newlines, quote characters themselves, and so on. The ones used most often ar ``\\``, ``\"``, ``\n``, ``\r``. For a detailed description of all of them and what they mean, read the syntax reference on strings. +Using the ``in`` operator you can easily check if a characater is present in a string, +example: ``if '@' in email_address {....}`` (however this gives no clue about the location +in the string where the character is present, if you need that, use the ``string.find()`` +library function instead) + .. hint:: Strings/arrays and uwords (=memory address) can often be interchanged. An array of strings is actually an array of uwords where every element is the memory @@ -542,6 +549,9 @@ The when-*value* can be any expression but the choice values have to evaluate to compile-time constant integers (bytes or words). They also have to be the same datatype as the when-value, otherwise no efficient comparison can be done. +.. note:: + Instead of chaining several value equality checks together using ``or`` (ex.: ``if x==1 or xx==5 or xx==9``), + consider using a ``when`` statement or ``in`` containment check instead. These are more efficient. Assignments ----------- @@ -587,6 +597,9 @@ Expressions can contain procedure and function calls. There are various built-in functions such as sin(), cos(), min(), max() that can be used in expressions (see :ref:`builtinfunctions`). You can also reference idendifiers defined elsewhere in your code. +Read the :ref:`syntaxreference` chapter for all details on the available operators and kinds of expressions you can write. + + .. attention:: **Floating points used in expressions:** diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index ef3a0fe6f..c410189fb 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -497,6 +497,24 @@ range creation: ``to`` ; i loops 0, 1, 2, ... 127 } +containment check: ``in`` + Tests if a value is present in a list of values, which can be a string or an array. + The result is a simple boolean ``true`` or ``false``. + Consider using this instead of chaining multiple value tests with ``or``, because the + containment check is more efficient. + Examples:: + + ubyte cc + if cc in [' ', '@', 0] { + txt.print("cc is one of the values") + } + + str email_address = "?????????" + if '@' in email_address { + txt.print("email address seems ok") + } + + address of: ``&`` This is a prefix operator that can be applied to a string or array variable or literal value. It results in the memory address (UWORD) of that string or array in memory: ``uword a = &stringvar`` @@ -799,4 +817,3 @@ case you have to use { } to enclose them:: } else -> txt.print("don't know") } - diff --git a/docs/source/todo.rst b/docs/source/todo.rst index be79136f8..8ffbca86a 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,11 +3,7 @@ TODO For next compiler release (7.6) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -add "if X in [1,2,3] {...}" syntax , as an alternative to when X { 1,2,3-> {...} } -if the array is not a literal, do a normal containment test instead in an array or string or range -change "consider using when statement..." to "consider using if X in [..] or when statement..." -also add to the docs! - +... Blocked by an official Commander-x16 v39 release ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,6 +16,7 @@ Future - make it possible to use cpu opcodes such as 'nop' as variable names by prefixing all asm vars with something such as ``v_`` then we can get rid of the instruction lists in the machinedefinitions as well? - fix the asm-labels problem (github issue #62) +- make (an option) to let 64tass produce a listing file as well as output. - simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation - get rid of all TODO's in the code - improve testability further, add more tests diff --git a/examples/test.p8 b/examples/test.p8 index 317d9c925..e77c46cce 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -6,75 +6,57 @@ main { sub start() { ubyte @shared xx str name = "irmen" - ubyte[] values = [1,2,3,4,5,6,7] + ubyte[] values = [1,2,3,4,5] - for xx in name { - txt.chrout(xx) - txt.spc() + if 1 in values { + txt.print("1 ok\n") + } else { + txt.print("1 err\n") } - txt.nl() - - for xx in values { - txt.print_ub(xx) - txt.spc() + if 5 in values { + txt.print("7 ok\n") + } else { + txt.print("7 err\n") } - txt.nl() - - for xx in 10 to 20 step 3 { - txt.print_ub(xx) - txt.spc() + if not(8 in values) { + txt.print("8 ok\n") + } else { + txt.print("8 err\n") } - txt.nl() - for xx in "abcdef" { - txt.print_ub(xx) - txt.spc() + xx = 1 + if xx in values { + txt.print("xx1 ok\n") + } else { + txt.print("xx1 err\n") } - txt.nl() - - for xx in [2,4,6,8] { - txt.print_ub(xx) - txt.spc() + if xx in [1,3,5] { + txt.print("xx1b ok\n") + } else { + txt.print("xx1b err\n") + } + xx=5 + if xx in values { + txt.print("xx7 ok\n") + } else { + txt.print("xx7 err\n") + } + if xx in [1,3,5] { + txt.print("xx7b ok\n") + } else { + txt.print("xx7b err\n") + } + xx=8 + if not(xx in values) { + txt.print("xx8 ok\n") + } else { + txt.print("xx8 err\n") + } + if not(xx in [1,3,5]) { + txt.print("xx8b ok\n") + } else { + txt.print("xx8b err\n") } - txt.nl() - - -; if xx in 100 { ; TODO error -; xx++ -; } -; -; if xx in 'a' { ; TODO error -; xx++ -; } -; -; if xx in "abc" { ; TODO containment test via when -; xx++ -; } -; -; if xx in [1,2,3,4,5] { ; TODO containment test via when -; xx++ -; } -; -; if xx in [1,2,3,4,5,6,7,8,9,10] { ; TODO containment test via loop? -; xx++ -; } -; -; if xx in name { ; TODO containment test via loop -; xx++ -; } -; -; if xx in values { ; TODO containment test via loop -; xx++ -; } -; -; if xx in 10 to 20 step 2 { ; TODO -; -; } - - ; TODO const optimizing of the containment tests - ; TODO also with (u)word and floats - - if xx==9 or xx==10 or xx==11 or xx==12 or xx==13 { txt.print("9 10 11\n") diff --git a/parser/antlr/Prog8ANTLR.g4 b/parser/antlr/Prog8ANTLR.g4 index dd15d4261..3d123afdb 100644 --- a/parser/antlr/Prog8ANTLR.g4 +++ b/parser/antlr/Prog8ANTLR.g4 @@ -167,6 +167,7 @@ expression : | left = expression EOL? bop = '|' EOL? right = expression | left = expression EOL? bop = ('==' | '!=') EOL? right = expression | rangefrom = expression rto = ('to'|'downto') rangeto = expression ('step' rangestep = expression)? // can't create separate rule due to mutual left-recursion + | left = expression EOL? bop = 'in' EOL? right = expression | prefix = 'not' expression | left = expression EOL? bop = 'and' EOL? right = expression | left = expression EOL? bop = 'or' EOL? right = expression From 4be3d63c0e28d60b5d8d37af0a03df97e6ae96d7 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Wed, 29 Dec 2021 18:00:25 +0100 Subject: [PATCH 4/4] slight optimization of if-in --- .../compiler/BeforeAsmGenerationAstChanger.kt | 4 +- docs/source/todo.rst | 4 +- examples/test.p8 | 121 +----------------- 3 files changed, 6 insertions(+), 123 deletions(-) diff --git a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt index 1f33e5fea..7007462a9 100644 --- a/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt +++ b/compiler/src/prog8/compiler/BeforeAsmGenerationAstChanger.kt @@ -249,8 +249,8 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o var rightAssignment: Assignment? = null var rightOperandReplacement: Expression? = null - val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall - val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall + val separateLeftExpr = !expr.left.isSimple && expr.left !is IFunctionCall && expr.left !is ContainmentCheck + val separateRightExpr = !expr.right.isSimple && expr.right !is IFunctionCall && expr.right !is ContainmentCheck val leftDt = expr.left.inferType(program) val rightDt = expr.right.inferType(program) diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 8ffbca86a..a735c6c0d 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -18,6 +18,7 @@ Future - fix the asm-labels problem (github issue #62) - make (an option) to let 64tass produce a listing file as well as output. - simplifyConditionalExpression() should not split expression if it still results in stack-based evaluation +- simplifyConditionalExpression() sometimes introduces needless assignment to r9 tempvar - get rid of all TODO's in the code - improve testability further, add more tests - use more of Result<> and Either<> to handle errors/ nulls better @@ -39,7 +40,8 @@ Future More code optimization ideas ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -- automatically convert if statements that test for multiple values (if X==1 or X==2..) to if X in [1,2,..] statements +- automatically convert if statements that test for multiple values (if X==1 or X==2..) to if X in [1,2,..] statements, instead of just a warning +- - byte typed expressions should be evaluated in the accumulator where possible, without (temp)var for instance value = otherbyte >> 1 --> lda otherbite ; lsr a; sta value - rewrite expression tree evaluation such that it doesn't use an eval stack but flatten the tree into linear code that uses a fixed number of predetermined value 'variables' diff --git a/examples/test.p8 b/examples/test.p8 index e77c46cce..13f789996 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,127 +1,8 @@ %import textio %zeropage basicsafe - main { sub start() { - ubyte @shared xx - str name = "irmen" - ubyte[] values = [1,2,3,4,5] - - if 1 in values { - txt.print("1 ok\n") - } else { - txt.print("1 err\n") - } - if 5 in values { - txt.print("7 ok\n") - } else { - txt.print("7 err\n") - } - if not(8 in values) { - txt.print("8 ok\n") - } else { - txt.print("8 err\n") - } - - xx = 1 - if xx in values { - txt.print("xx1 ok\n") - } else { - txt.print("xx1 err\n") - } - if xx in [1,3,5] { - txt.print("xx1b ok\n") - } else { - txt.print("xx1b err\n") - } - xx=5 - if xx in values { - txt.print("xx7 ok\n") - } else { - txt.print("xx7 err\n") - } - if xx in [1,3,5] { - txt.print("xx7b ok\n") - } else { - txt.print("xx7b err\n") - } - xx=8 - if not(xx in values) { - txt.print("xx8 ok\n") - } else { - txt.print("xx8 err\n") - } - if not(xx in [1,3,5]) { - txt.print("xx8b ok\n") - } else { - txt.print("xx8b err\n") - } - - if xx==9 or xx==10 or xx==11 or xx==12 or xx==13 { - txt.print("9 10 11\n") - } - - txt.print("\nthe end\n") + txt.nl() } - - sub foobar() { - txt.print("foobar\n") - } - } -; -; -;main { -; ubyte @shared foo=99 -; sub thing(uword rr) { -; ubyte @shared xx = rr[1] ; should still work as var initializer that will be rewritten -; ubyte @shared yy -; yy = rr[2] -; uword @shared other -; ubyte @shared zz = other[3] -; } -; sub start() { -; -; txt.print("should print: 10 40 80 20\n") -; -; ubyte @shared xx -; -; if xx >0 -; goto $c000 -; else -; xx++ -;labeltje: -; -; repeat { -; xx++ -; if xx==10 -; break -; } -; txt.print_ub(xx) -; txt.nl() -; -; while xx<50 { -; xx++ -; if xx==40 -; break -; } -; txt.print_ub(xx) -; txt.nl() -; -; do { -; xx++ -; if xx==80 -; break -; } until xx>100 -; txt.print_ub(xx) -; txt.nl() -; -; for xx in 0 to 25 { -; if xx==20 -; break -; } -; txt.print_ub(xx) -; txt.nl() -; } -;}