diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt index e606cf9b0..594c1d174 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/BuiltinFunctionsAsmGen.kt @@ -44,6 +44,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, "abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope) "any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope) "sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope) + "boolean" -> funcBoolean(fcall, func, resultToStack, resultRegister, sscope) "rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope) "sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope) "rol" -> funcRol(fcall) @@ -697,6 +698,28 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, } } + private fun funcBoolean(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) { + when (val dt = fcall.args.single().inferType(program).getOr(DataType.UNDEFINED)) { + in ByteDatatypes -> { + assignAsmGen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.A, dt==DataType.BYTE) + } + in WordDatatypes -> { + assignAsmGen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY, dt==DataType.WORD) + asmgen.out(" sty P8ZP_SCRATCH_B1 | ora P8ZP_SCRATCH_B1") + } + DataType.FLOAT -> { + assignAsmGen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.FAC1, true) + asmgen.out(" jsr floats.SIGN") + } + else -> throw AssemblyError("weird type") + } + + if(resultToStack) + asmgen.out(" sta P8ESTACK_LO,x | dex") + else if(resultRegister!=null) + assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister, false, scope, program, asmgen), CpuRegister.A) + } + private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) { when(func.name) { "rnd" -> { diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt index 1af8850fe..2009691d6 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/assignment/AssignmentAsmGen.kt @@ -321,61 +321,6 @@ internal class AssignmentAsmGen(private val program: Program, if(!expr.inferType(program).isInteger) return false -/* -TODO re-add these optimizations? after we improved the unneeded addition of !=0 expressions - if(expr.operator=="and") { - val dt = expr.left.inferType(program).getOrElse { throw AssemblyError("weird dt") } - if (dt in ByteDatatypes) { - assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE || dt==DataType.WORD) - asmgen.out(" pha") - assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", dt, expr.definingSubroutine) - asmgen.out(" pla | and P8ZP_SCRATCH_B1") - if(assign.target.datatype in ByteDatatypes) - assignRegisterByte(assign.target, CpuRegister.A) - else { - asmgen.out(" ldy #0") - assignRegisterpairWord(assign.target, RegisterOrPair.AY) - } - return true - } - else throw AssemblyError("weird dt for and, expected byte $expr @${expr.position}") - } - else if(expr.operator=="or") { - val dt = expr.left.inferType(program).getOrElse { throw AssemblyError("weird dt") } - if (dt in ByteDatatypes) { - assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE || dt==DataType.WORD) - asmgen.out(" pha") - assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", dt, expr.definingSubroutine) - asmgen.out(" pla | ora P8ZP_SCRATCH_B1") - if(assign.target.datatype in ByteDatatypes) - assignRegisterByte(assign.target, CpuRegister.A) - else { - asmgen.out(" ldy #0") - assignRegisterpairWord(assign.target, RegisterOrPair.AY) - } - return true - } - else throw AssemblyError("weird dt for or, expected byte $expr @${expr.position}") - } - else if(expr.operator=="xor") { - val dt = expr.left.inferType(program).getOrElse { throw AssemblyError("weird dt") } - if (dt in ByteDatatypes) { - assignExpressionToRegister(expr.left, RegisterOrPair.A, dt==DataType.BYTE || dt==DataType.WORD) - asmgen.out(" pha") - assignExpressionToVariable(expr.right, "P8ZP_SCRATCH_B1", dt, expr.definingSubroutine) - asmgen.out(" pla | eor P8ZP_SCRATCH_B1") - if(assign.target.datatype in ByteDatatypes) - assignRegisterByte(assign.target, CpuRegister.A) - else { - asmgen.out(" ldy #0") - assignRegisterpairWord(assign.target, RegisterOrPair.AY) - } - return true - } - else throw AssemblyError("weird dt for xor, expected byte $expr @${expr.position}") - } -*/ - if(expr.operator!="+" && expr.operator!="-") return false diff --git a/codeGenVirtual/src/prog8/codegen/virtual/BuiltinFuncGen.kt b/codeGenVirtual/src/prog8/codegen/virtual/BuiltinFuncGen.kt index 6241cffb4..e174a3938 100644 --- a/codeGenVirtual/src/prog8/codegen/virtual/BuiltinFuncGen.kt +++ b/codeGenVirtual/src/prog8/codegen/virtual/BuiltinFuncGen.kt @@ -32,6 +32,7 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen: "callrom" -> throw AssemblyError("callrom() is for cx16 target only") "msb" -> funcMsb(call, resultRegister) "lsb" -> funcLsb(call, resultRegister) + "boolean" -> funcBoolean(call, resultRegister) "memory" -> funcMemory(call, resultRegister) "peek" -> funcPeek(call, resultRegister) "peekw" -> funcPeekW(call, resultRegister) @@ -354,6 +355,30 @@ internal class BuiltinFuncGen(private val codeGen: CodeGen, private val exprGen: return code } + private fun funcBoolean(call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk { + val code = VmCodeChunk() + val vmDt = codeGen.vmType(call.args[0].type) + when (vmDt) { + VmDataType.FLOAT -> { + val fpValueReg = codeGen.vmRegisters.nextFreeFloat() + val fpSignReg = codeGen.vmRegisters.nextFreeFloat() + code += exprGen.translateExpression(call.args.single(), -1, fpValueReg) + code += VmCodeInstruction(Opcode.SGN, VmDataType.FLOAT, fpReg1 = fpSignReg, fpReg2 = fpValueReg) + code += VmCodeInstruction(Opcode.FTOSB, VmDataType.FLOAT, reg1 = resultRegister, fpReg1 = fpSignReg) + } + VmDataType.WORD -> { + val msbReg = codeGen.vmRegisters.nextFree() + code += exprGen.translateExpression(call.args.single(), resultRegister, -1) + code += VmCodeInstruction(Opcode.MSIG, VmDataType.BYTE, reg1 = msbReg, reg2=resultRegister) + code += VmCodeInstruction(Opcode.OR, VmDataType.BYTE, reg1=resultRegister, reg2=msbReg) + } + else -> { + code += exprGen.translateExpression(call.args.single(), resultRegister, -1) + } + } + return code + } + private fun funcRolRor(opcode: Opcode, call: PtBuiltinFunctionCall, resultRegister: Int): VmCodeChunk { val vmDt = codeGen.vmType(call.args[0].type) val code = VmCodeChunk() diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 7536e550d..137adb6f2 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -864,13 +864,9 @@ internal class AstChecker(private val program: Program, } } "and", "or", "xor" -> { - // only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1) + // only integer numeric operands accepted if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes) errors.err("logical operator can only be used on boolean operands", expr.right.position) - val constLeft = expr.left.constValue(program) - val constRight = expr.right.constValue(program) - if(constLeft!=null && constLeft.number.toInt() !in 0..1 || constRight!=null && constRight.number.toInt() !in 0..1) - errors.err("const literal argument of logical operator must be boolean (0 or 1)", expr.position) } "&", "|", "^" -> { // only integer numeric operands accepted diff --git a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt index 89bffa02c..4a1e98092 100644 --- a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt +++ b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt @@ -248,13 +248,12 @@ internal class StatementReorderer(val program: Program, } } else if(expr.operator in LogicalOperators) { - // make sure that logical expressions like "var and other-logical-expression - // is rewritten as "var!=0 and other-logical-expression", to avoid bitwise boolean and - // generating the wrong results later - - // TODO use bool(expr) instead of expr!=0 rewriting - fun wrapped(expr: Expression): Expression = - BinaryExpression(expr, "!=", NumericLiteral(DataType.UBYTE, 0.0, expr.position), expr.position) + fun wrapped(expr: Expression): Expression { + return if(expr.inferType(program).isBytes) + expr + else + FunctionCallExpression(IdentifierReference(listOf("boolean"), expr.position), mutableListOf(expr), expr.position) + } fun isLogicalExpr(expr: Expression?): Boolean { if(expr is BinaryExpression && expr.operator in (LogicalOperators + ComparisonOperators)) diff --git a/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt b/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt index 31babc35f..57842a093 100644 --- a/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt +++ b/compiler/src/prog8/compiler/astprocessing/VariousCleanups.kt @@ -233,5 +233,14 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter, override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable { return tryReplaceCallWithGosub(functionCallStatement, parent, program, options) } + + override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable { + if(functionCallExpr.target.nameInSource==listOf("boolean")) { + if(functionCallExpr.args[0].inferType(program).isBytes) { + return listOf(IAstModification.ReplaceNode(functionCallExpr, functionCallExpr.args[0], parent)) + } + } + return noModifications + } } diff --git a/compilerAst/src/prog8/compiler/BuiltinFunctions.kt b/compilerAst/src/prog8/compiler/BuiltinFunctions.kt index 4426c8c47..71d102616 100644 --- a/compilerAst/src/prog8/compiler/BuiltinFunctions.kt +++ b/compilerAst/src/prog8/compiler/BuiltinFunctions.kt @@ -106,6 +106,7 @@ private val functionSignatures: List = listOf( FSignature("lsb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x and 255).toDouble() } }, FSignature("msb" , true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x ushr 8 and 255).toDouble()} }, FSignature("mkword" , true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword), + FSignature("boolean" , true, listOf(FParam("value", NumericDatatypes)), DataType.UBYTE, ::builtinBoolean), FSignature("peek" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE), FSignature("peekw" , true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD), FSignature("poke" , false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null), @@ -250,6 +251,23 @@ private fun builtinMkword(args: List, position: Position, program: P return NumericLiteral(DataType.UWORD, result.toDouble(), position) } +private fun builtinBoolean(args: List, position: Position, program: Program): NumericLiteral { + if (args.size != 1) + throw SyntaxError("boolean requires one argument", position) + val constvalue = args[0].constValue(program) ?: throw NotConstArgumentException() + return if(constvalue.number==0.0) + NumericLiteral(DataType.UBYTE, 0.0, args[0].position) + else if(constvalue.type==DataType.UBYTE) + constvalue + else if(constvalue.type==DataType.FLOAT) + NumericLiteral(DataType.UBYTE, constvalue.number.sign, args[0].position) + else { + val value = constvalue.number.toInt() + val byteValue = ((value ushr 8) or value) and 255 + NumericLiteral(DataType.UBYTE, byteValue.toDouble(), args[0].position) + } +} + private fun builtinSgn(args: List, position: Position, program: Program): NumericLiteral { if (args.size != 1) throw SyntaxError("sgn requires one argument", position) diff --git a/docs/source/programming.rst b/docs/source/programming.rst index 2dc0353e8..97d6045c8 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -775,6 +775,10 @@ sort(array) Miscellaneous ^^^^^^^^^^^^^ +boolean(x) + Returns a byte value representing the boolean (truthy) value of x, where x can be any numeric type. + This means it returns 0 (false) when x equals 0, and a value other than 0 otherwise. + cmp(x,y) Compare the integer value x to integer value y. Doesn't return a value or boolean result, only sets the processor's status bits! You can use a conditional jumps (``if_cc`` etcetera) to act on this. diff --git a/docs/source/todo.rst b/docs/source/todo.rst index e79423b52..7262c0562 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,9 +3,6 @@ TODO For next release ^^^^^^^^^^^^^^^^ -- add a builtin function 'bool' that takes a numeric value and returns ubyte false if it was zero and true otherwise -- statementreorderer: after(expr: BinaryExpression): get rid of unneeded !=0 added to logical expressions - by using this bool() function instead for the operand if it is not a byte type - optimize logical expressions in attemptAssignOptimizedBinexpr() - add McCarthy evaluation to shortcircuit and/or expressions. First do ifs by splitting them up? Then do expressions that compute a value? ... diff --git a/examples/test.p8 b/examples/test.p8 index 13587287f..c24f06f91 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,5 +1,4 @@ %import textio -;%import test_stack %zeropage basicsafe ; NOTE: meant to test to virtual machine output target (use -target vitual) @@ -117,6 +116,7 @@ main { txt.print_ub(value) txt.nl() + ; a "pixelshader": ; sys.gfx_enable(0) ; enable lo res screen ; ubyte shifter