diff --git a/compiler/src/prog8/ast/expressions/AstExpressions.kt b/compiler/src/prog8/ast/expressions/AstExpressions.kt index 0e33372c5..45f0c375b 100644 --- a/compiler/src/prog8/ast/expressions/AstExpressions.kt +++ b/compiler/src/prog8/ast/expressions/AstExpressions.kt @@ -97,14 +97,13 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex val leftDt = left.inferType(program) val rightDt = right.inferType(program) return when (operator) { - "+", "-", "*", "**", "%" -> if (leftDt == null || rightDt == null) null else { + "+", "-", "*", "**", "%", "/" -> if (leftDt == null || rightDt == null) null else { try { - arithmeticOpDt(leftDt, rightDt) + commonDatatype(leftDt, rightDt, null, null).first } catch (x: FatalAstException) { null } } - "/" -> if (leftDt == null || rightDt == null) null else divisionOpDt(leftDt, rightDt) "&" -> leftDt "|" -> leftDt "^" -> leftDt @@ -118,132 +117,61 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex } companion object { - fun divisionOpDt(leftDt: DataType, rightDt: DataType): DataType { + fun commonDatatype(leftDt: DataType, rightDt: DataType, + left: Expression?, right: Expression?): Pair { + // byte + byte -> byte + // byte + word -> word + // word + byte -> word + // word + word -> word + // a combination with a float will be float (but give a warning about this!) + return when (leftDt) { - DataType.UBYTE -> when (rightDt) { - DataType.UBYTE, DataType.UWORD -> DataType.UBYTE - DataType.BYTE, DataType.WORD -> DataType.WORD - DataType.FLOAT -> DataType.BYTE - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + DataType.UBYTE -> { + when (rightDt) { + DataType.UBYTE -> Pair(DataType.UBYTE, null) + DataType.BYTE -> Pair(DataType.BYTE, left) + DataType.UWORD -> Pair(DataType.UWORD, left) + DataType.WORD -> Pair(DataType.WORD, left) + DataType.FLOAT -> Pair(DataType.FLOAT, left) + else -> Pair(leftDt, null) // non-numeric datatype + } } - DataType.BYTE -> when (rightDt) { - in NumericDatatypes -> DataType.BYTE - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + DataType.BYTE -> { + when (rightDt) { + DataType.UBYTE -> Pair(DataType.BYTE, right) + DataType.BYTE -> Pair(DataType.BYTE, null) + DataType.UWORD -> Pair(DataType.WORD, left) + DataType.WORD -> Pair(DataType.WORD, left) + DataType.FLOAT -> Pair(DataType.FLOAT, left) + else -> Pair(leftDt, null) // non-numeric datatype + } } - DataType.UWORD -> when (rightDt) { - DataType.UBYTE, DataType.UWORD -> DataType.UWORD - DataType.BYTE, DataType.WORD -> DataType.WORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + DataType.UWORD -> { + when (rightDt) { + DataType.UBYTE -> Pair(DataType.UWORD, right) + DataType.BYTE -> Pair(DataType.WORD, right) + DataType.UWORD -> Pair(DataType.UWORD, null) + DataType.WORD -> Pair(DataType.WORD, left) + DataType.FLOAT -> Pair(DataType.FLOAT, left) + else -> Pair(leftDt, null) // non-numeric datatype + } } - DataType.WORD -> when (rightDt) { - in NumericDatatypes -> DataType.WORD - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + DataType.WORD -> { + when (rightDt) { + DataType.UBYTE -> Pair(DataType.WORD, right) + DataType.BYTE -> Pair(DataType.WORD, right) + DataType.UWORD -> Pair(DataType.WORD, right) + DataType.WORD -> Pair(DataType.WORD, null) + DataType.FLOAT -> Pair(DataType.FLOAT, left) + else -> Pair(leftDt, null) // non-numeric datatype + } } - DataType.FLOAT -> when (rightDt) { - in NumericDatatypes -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + DataType.FLOAT -> { + Pair(DataType.FLOAT, right) } - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") + else -> Pair(leftDt, null) // non-numeric datatype } } - - fun arithmeticOpDt(leftDt: DataType, rightDt: DataType): DataType { - return when (leftDt) { - DataType.UBYTE -> when (rightDt) { - DataType.UBYTE -> DataType.UBYTE - DataType.BYTE -> DataType.BYTE - DataType.UWORD -> DataType.UWORD - DataType.WORD -> DataType.WORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.BYTE -> when (rightDt) { - in ByteDatatypes -> DataType.BYTE - in WordDatatypes -> DataType.WORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.UWORD -> when (rightDt) { - DataType.UBYTE, DataType.UWORD -> DataType.UWORD - DataType.BYTE, DataType.WORD -> DataType.WORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.WORD -> when (rightDt) { - in IntegerDatatypes -> DataType.WORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - DataType.FLOAT -> when (rightDt) { - in NumericDatatypes -> DataType.FLOAT - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - else -> throw FatalAstException("arithmetic operation on incompatible datatypes: $leftDt and $rightDt") - } - } - } - - fun commonDatatype(leftDt: DataType, rightDt: DataType, - left: Expression, right: Expression): Pair { - // byte + byte -> byte - // byte + word -> word - // word + byte -> word - // word + word -> word - // a combination with a float will be float (but give a warning about this!) - - if(this.operator=="/") { - // division is a bit weird, don't cast the operands - val commondt = divisionOpDt(leftDt, rightDt) - return Pair(commondt, null) - } - - return when (leftDt) { - DataType.UBYTE -> { - when (rightDt) { - DataType.UBYTE -> Pair(DataType.UBYTE, null) - DataType.BYTE -> Pair(DataType.BYTE, left) - DataType.UWORD -> Pair(DataType.UWORD, left) - DataType.WORD -> Pair(DataType.WORD, left) - DataType.FLOAT -> Pair(DataType.FLOAT, left) - else -> Pair(leftDt, null) // non-numeric datatype - } - } - DataType.BYTE -> { - when (rightDt) { - DataType.UBYTE -> Pair(DataType.BYTE, right) - DataType.BYTE -> Pair(DataType.BYTE, null) - DataType.UWORD -> Pair(DataType.WORD, left) - DataType.WORD -> Pair(DataType.WORD, left) - DataType.FLOAT -> Pair(DataType.FLOAT, left) - else -> Pair(leftDt, null) // non-numeric datatype - } - } - DataType.UWORD -> { - when (rightDt) { - DataType.UBYTE -> Pair(DataType.UWORD, right) - DataType.BYTE -> Pair(DataType.UWORD, right) - DataType.UWORD -> Pair(DataType.UWORD, null) - DataType.WORD -> Pair(DataType.WORD, left) - DataType.FLOAT -> Pair(DataType.FLOAT, left) - else -> Pair(leftDt, null) // non-numeric datatype - } - } - DataType.WORD -> { - when (rightDt) { - DataType.UBYTE -> Pair(DataType.WORD, right) - DataType.BYTE -> Pair(DataType.WORD, right) - DataType.UWORD -> Pair(DataType.WORD, right) - DataType.WORD -> Pair(DataType.WORD, null) - DataType.FLOAT -> Pair(DataType.FLOAT, left) - else -> Pair(leftDt, null) // non-numeric datatype - } - } - DataType.FLOAT -> { - Pair(DataType.FLOAT, right) - } - else -> Pair(leftDt, null) // non-numeric datatype - } } } diff --git a/compiler/src/prog8/ast/processing/AstChecker.kt b/compiler/src/prog8/ast/processing/AstChecker.kt index 720c876f6..4069f3334 100644 --- a/compiler/src/prog8/ast/processing/AstChecker.kt +++ b/compiler/src/prog8/ast/processing/AstChecker.kt @@ -762,6 +762,8 @@ internal class AstChecker(private val program: Program, checkResult.add(ExpressionError("left operand is not numeric", expr.left.position)) if(rightDt!in NumericDatatypes) checkResult.add(ExpressionError("right operand is not numeric", expr.right.position)) + if(leftDt!=rightDt) + checkResult.add(ExpressionError("left and right operands aren't the same type", expr.left.position)) super.visit(expr) } diff --git a/compiler/src/prog8/ast/processing/StatementReorderer.kt b/compiler/src/prog8/ast/processing/StatementReorderer.kt index 20d5ea510..d9d506c6a 100644 --- a/compiler/src/prog8/ast/processing/StatementReorderer.kt +++ b/compiler/src/prog8/ast/processing/StatementReorderer.kt @@ -187,26 +187,29 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi } override fun visit(expr: BinaryExpression): Expression { - val leftDt = expr.left.inferType(program) - val rightDt = expr.right.inferType(program) + val expr2 = super.visit(expr) + if(expr2 !is BinaryExpression) + return expr2 + val leftDt = expr2.left.inferType(program) + val rightDt = expr2.right.inferType(program) if(leftDt!=null && rightDt!=null && leftDt!=rightDt) { // determine common datatype and add typecast as required to make left and right equal types - val (commonDt, toFix) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right) + val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt, rightDt, expr2.left, expr2.right) if(toFix!=null) { when { - toFix===expr.left -> { - expr.left = TypecastExpression(expr.left, commonDt, true, expr.left.position) - expr.left.linkParents(expr) + toFix===expr2.left -> { + expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position) + expr2.left.linkParents(expr2) } - toFix===expr.right -> { - expr.right = TypecastExpression(expr.right, commonDt, true, expr.right.position) - expr.right.linkParents(expr) + toFix===expr2.right -> { + expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position) + expr2.right.linkParents(expr2) } else -> throw FatalAstException("confused binary expression side") } } } - return super.visit(expr) + return expr2 } override fun visit(assignment: Assignment): Statement { diff --git a/compiler/src/prog8/compiler/target/c64/codegen2/AnonymousScopeVarsCleanup.kt b/compiler/src/prog8/compiler/target/c64/codegen2/AnonymousScopeVarsCleanup.kt index 10e9521fc..502dfc0ec 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen2/AnonymousScopeVarsCleanup.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen2/AnonymousScopeVarsCleanup.kt @@ -8,6 +8,7 @@ import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.statements.AnonymousScope import prog8.ast.statements.Statement import prog8.ast.statements.VarDecl +import java.util.* class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor { companion object { @@ -32,8 +33,12 @@ class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor { private val varsToMove: MutableMap> = mutableMapOf() + private val currentAnonScope: Stack = Stack() + override fun visit(scope: AnonymousScope): Statement { + currentAnonScope.push(scope) val scope2 = super.visit(scope) as AnonymousScope + currentAnonScope.pop() val vardecls = scope2.statements.filterIsInstance() varsToMove[scope2] = vardecls return scope2 @@ -43,26 +48,35 @@ class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor { override fun visit(decl: VarDecl): Statement { val decl2 = super.visit(decl) as VarDecl - val scope = decl2.definingScope() - if(scope is AnonymousScope) { - return decl2.withPrefixedName(nameprefix(scope)) - } - return decl2 + if(currentAnonScope.isEmpty()) + return decl2 + return decl2.withPrefixedName(nameprefix(currentAnonScope.peek())) } override fun visit(identifier: IdentifierReference): Expression { val ident = super.visit(identifier) if(ident !is IdentifierReference) return ident - - val scope = ident.definingScope() as? AnonymousScope ?: return ident + if(currentAnonScope.isEmpty()) + return ident val vardecl = ident.targetVarDecl(program.namespace) - return if(vardecl!=null && vardecl.definingScope() == ident.definingScope()) { + return if(vardecl!=null && vardecl.definingScope() === ident.definingScope()) { // prefix the variable name reference that is defined inside the anon scope - ident.withPrefixedName(nameprefix(scope)) + ident.withPrefixedName(nameprefix(currentAnonScope.peek())) } else { ident } } + /* +; @todo FIX Symbol lookup over anon scopes +; sub start() { +; for ubyte i in 0 to 10 { +; word rz = 4 +; if rz >= 1 { +; word persp = rz+1 +; } +; } +; } + */ } diff --git a/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt b/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt index 14aeab464..347fd428b 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen2/AsmGen2.kt @@ -1823,6 +1823,9 @@ $endLabel""") } } + // TODO: use optimized routines such as mul_10 + + private fun translateBinaryOperatorBytes(operator: String, types: DataType) { when(operator) { "**" -> throw AssemblyError("** operator requires floats") diff --git a/compiler/src/prog8/compiler/target/c64/codegen2/BuiltinFunctionsAsmGen.kt b/compiler/src/prog8/compiler/target/c64/codegen2/BuiltinFunctionsAsmGen.kt index 24da40efb..28a073f8f 100644 --- a/compiler/src/prog8/compiler/target/c64/codegen2/BuiltinFunctionsAsmGen.kt +++ b/compiler/src/prog8/compiler/target/c64/codegen2/BuiltinFunctionsAsmGen.kt @@ -2,6 +2,8 @@ package prog8.compiler.target.c64.codegen2 import prog8.ast.IFunctionCall import prog8.ast.Program +import prog8.ast.base.ByteDatatypes +import prog8.ast.base.DataType import prog8.ast.base.WordDatatypes import prog8.ast.expressions.Expression import prog8.ast.expressions.FunctionCall @@ -56,6 +58,24 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, translateFunctionArguments(fcall.arglist) asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x") } + "abs" -> { + translateFunctionArguments(fcall.arglist) + val dt = fcall.arglist.single().inferType(program)!! + when (dt) { + in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b") + in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w") + DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f") + else -> throw AssemblyError("weird type") + } + } + // TODO: any(f), all(f), max(f), min(f), sum(f) + "sin", "cos", "tan", "atan", + "ln", "log2", "sqrt", "rad", + "deg", "round", "floor", "ceil", + "rdnf" -> { + translateFunctionArguments(fcall.arglist) + asmgen.out(" jsr c64flt.func_$functionName") + } else -> { translateFunctionArguments(fcall.arglist) asmgen.out(" jsr prog8_lib.func_$functionName") diff --git a/compiler/src/prog8/functions/BuiltinFunctions.kt b/compiler/src/prog8/functions/BuiltinFunctions.kt index b3c343e3f..4435da5b6 100644 --- a/compiler/src/prog8/functions/BuiltinFunctions.kt +++ b/compiler/src/prog8/functions/BuiltinFunctions.kt @@ -145,12 +145,11 @@ fun builtinFunctionReturnType(function: String, args: List, program: return when (function) { "abs" -> { - when(val dt = args.single().inferType(program)) { - in ByteDatatypes -> DataType.UBYTE - in WordDatatypes -> DataType.UWORD - DataType.FLOAT -> DataType.FLOAT - else -> throw FatalAstException("weird datatype passed to abs $dt") - } + val dt = args.single().inferType(program) + if(dt in NumericDatatypes) + return dt + else + throw FatalAstException("weird datatype passed to abs $dt") } "max", "min" -> { when(val dt = datatypeFromIterableArg(args.single())) { diff --git a/compiler/src/prog8/vm/RuntimeValue.kt b/compiler/src/prog8/vm/RuntimeValue.kt index ea15f3169..ee9d054b3 100644 --- a/compiler/src/prog8/vm/RuntimeValue.kt +++ b/compiler/src/prog8/vm/RuntimeValue.kt @@ -107,8 +107,6 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?= asBoolean = floatval != 0.0 } else -> { - if(heapId==null) - throw IllegalArgumentException("for non-numeric types, a heapId should be given") byteval = null wordval = null floatval = null @@ -628,7 +626,7 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?= } -class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, 0) { +class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, array=range.toList().toTypedArray()) { override fun iterator(): Iterator { return range.iterator() } diff --git a/compiler/src/prog8/vm/astvm/AstVm.kt b/compiler/src/prog8/vm/astvm/AstVm.kt index 86e3e1a12..731c73162 100644 --- a/compiler/src/prog8/vm/astvm/AstVm.kt +++ b/compiler/src/prog8/vm/astvm/AstVm.kt @@ -675,23 +675,23 @@ class AstVm(val program: Program) { dialog.canvas.printText(args[0].wordval!!.toString(), true) } "c64scr.print_ubhex" -> { - val prefix = if (args[0].asBoolean) "$" else "" - val number = args[1].byteval!! + val number = args[0].byteval!! + val prefix = if (args[1].asBoolean) "$" else "" dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", true) } "c64scr.print_uwhex" -> { - val prefix = if (args[0].asBoolean) "$" else "" - val number = args[1].wordval!! + val number = args[0].wordval!! + val prefix = if (args[1].asBoolean) "$" else "" dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", true) } "c64scr.print_uwbin" -> { - val prefix = if (args[0].asBoolean) "%" else "" - val number = args[1].wordval!! + val number = args[0].wordval!! + val prefix = if (args[1].asBoolean) "%" else "" dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", true) } "c64scr.print_ubbin" -> { - val prefix = if (args[0].asBoolean) "%" else "" - val number = args[1].byteval!! + val number = args[0].byteval!! + val prefix = if (args[1].asBoolean) "%" else "" dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", true) } "c64scr.clear_screenchars" -> { diff --git a/docs/source/syntaxreference.rst b/docs/source/syntaxreference.rst index 7c7f8d972..9abebe9cd 100644 --- a/docs/source/syntaxreference.rst +++ b/docs/source/syntaxreference.rst @@ -387,7 +387,7 @@ arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%`` ``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations. ``/`` is division (will result in integer division when using on integer operands, and a floating point division when at least one of the operands is a float) ``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243. (it only works on floating point variables) - ``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space, %10 will be parsed as the binary number 2 + ``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space, %10 will be parsed as the binary number 2. Remainder is only supported on integer operands (not floats). bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>`` diff --git a/examples/test.p8 b/examples/test.p8 index 55b1d7396..851a5f84a 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -1,36 +1,47 @@ +%import c64lib +%import c64utils +%import c64flt %zeropage basicsafe main { sub start() { - c64.CHROUT('\n') - %asm {{ - stx $0410 - }} - c64.CHRIN() - %asm {{ - stx $0411 - }} - print_notes(80,35) - %asm {{ - stx $0412 - }} - return - } +; float fl = 123.4567 +; c64flt.print_f(round(fl)) +; c64.CHROUT('\n') +; c64flt.print_f(round(fl)) +; c64.CHROUT('\n') +; c64flt.print_f(round(fl)) +; c64.CHROUT('\n') +; c64flt.print_f(ceil(fl)) +; c64.CHROUT('\n') +; c64flt.print_f(ceil(fl)) +; c64.CHROUT('\n') +; c64flt.print_f(ceil(fl)) +; c64.CHROUT('\n') +; c64flt.print_f(floor(fl)) +; c64.CHROUT('\n') +; c64flt.print_f(floor(fl)) +; c64.CHROUT('\n') +; c64flt.print_f(floor(fl)) +; c64.CHROUT('\n') +; @($040a)=X +; return - sub print_notes(ubyte n1, ubyte n2) { - c64scr.print_ub(n1/2) - c64.CHROUT('\n') - c64scr.print_ub(n1/3) - c64.CHROUT('\n') - c64scr.print_ub(n1/4) - c64.CHROUT('\n') - c64.CHROUT('\n') - c64scr.print_ub(n2/2) - c64.CHROUT('\n') - c64scr.print_ub(n2/3) - c64.CHROUT('\n') - c64scr.print_ub(n2/4) + while true { + float clock_seconds = ((mkword(c64.TIME_LO, c64.TIME_MID) as float) + (c64.TIME_HI as float)*65536.0) / 60 + float hours = floor(clock_seconds / 3600) + clock_seconds -= hours*3600 + float minutes = floor(clock_seconds / 60) + clock_seconds = floor(clock_seconds - minutes * 60.0) + + c64scr.print("system time in ti$ is ") + c64flt.print_f(hours) + c64.CHROUT(':') + c64flt.print_f(minutes) + c64.CHROUT(':') + c64flt.print_f(clock_seconds) c64.CHROUT('\n') + } } }