mirror of
https://github.com/irmen/prog8.git
synced 2025-02-04 02:30:19 +00:00
add "X in [1,2,3]" expression (efficient containment check)
This commit is contained in:
parent
7a9e5afb93
commit
de6ce4a46e
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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<Number>) {
|
||||
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 -> {
|
||||
|
@ -22,7 +22,7 @@ class BinExprSplitter(private val program: Program, private val options: Compila
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
|
||||
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
|
||||
|
@ -23,6 +23,13 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
noModifications
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
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<IAstModification> {
|
||||
// Try to turn a unary prefix expression into a single constant value.
|
||||
// Compile-time constant sub expressions will be evaluated on the spot.
|
||||
|
@ -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)
|
||||
|
@ -344,6 +344,25 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
|
||||
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
|
||||
|
@ -72,10 +72,10 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
|
||||
fun getTempVarName(dt: InferredTypes.InferredType): List<String> {
|
||||
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")
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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<IAstModification> {
|
||||
// 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<IAstModification> {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
@ -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<IAstModification> {
|
||||
if(expr.operator=="in") {
|
||||
println("IN-TEST: $expr\n in: $parent")
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -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<IAstModification> {
|
||||
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
|
||||
|
@ -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<IAstModification> {
|
||||
// replace trivial containment checks with just false or a single comparison
|
||||
fun replaceWithEquals(value: NumericLiteralValue): Iterable<IAstModification> {
|
||||
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<IAstModification> {
|
||||
errors.warn("condition is always false", containment.position)
|
||||
return listOf(IAstModification.ReplaceNode(containment, NumericLiteralValue.fromBoolean(false, containment.position), parent))
|
||||
}
|
||||
|
||||
fun checkArray(array: Array<Expression>): Iterable<IAstModification> {
|
||||
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<IAstModification> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
})
|
||||
|
@ -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)") {
|
||||
|
@ -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} ")
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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<String>): 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)
|
||||
|
@ -87,6 +87,7 @@ abstract class AstWalker {
|
||||
open fun before(block: Block, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(branch: Branch, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = noModifications
|
||||
@ -128,6 +129,7 @@ abstract class AstWalker {
|
||||
open fun after(block: Block, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(branch: Branch, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(builtinFunctionPlaceholder: BuiltinFunctionPlaceholder, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = noModifications
|
||||
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = 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) }
|
||||
|
@ -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) }
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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:**
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
106
examples/test.p8
106
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")
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user