diff --git a/codeCore/src/prog8/code/ast/AstExpressions.kt b/codeCore/src/prog8/code/ast/AstExpressions.kt index 1add583e3..283d61afb 100644 --- a/codeCore/src/prog8/code/ast/AstExpressions.kt +++ b/codeCore/src/prog8/code/ast/AstExpressions.kt @@ -294,6 +294,14 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre if (trunc != number) throw IllegalArgumentException("refused truncating of float to avoid loss of precision @$position") } + when(type) { + DataType.UBYTE -> require(number in 0.0..255.0) + DataType.BYTE -> require(number in -128.0..127.0) + DataType.UWORD -> require(number in 0.0..65535.0) + DataType.WORD -> require(number in -32728.0..32767.0) + DataType.LONG -> require(number in -2147483647.0..2147483647.0) + else -> {} + } } override fun hashCode(): Int = Objects.hash(type, number) @@ -318,7 +326,7 @@ class PtPrefix(val operator: String, type: DataType, position: Position): PtExpr get() = children.single() as PtExpression init { - require(operator in setOf("+", "-", "~", "^", "<<", "not")) { "invalid prefix operator: $operator" } + require(operator in setOf("+", "-", "~", "^", "<<", "not")) { "invalid prefix operator: $operator" } // TODO ^ and << are experimental } } diff --git a/codeCore/src/prog8/code/core/Operators.kt b/codeCore/src/prog8/code/core/Operators.kt index 2b1658c06..ef4e1df43 100644 --- a/codeCore/src/prog8/code/core/Operators.kt +++ b/codeCore/src/prog8/code/core/Operators.kt @@ -4,7 +4,7 @@ val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor") val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=") val LogicalOperators = setOf("and", "or", "xor", "not", "in") val BitwiseOperators = setOf("&", "|", "^", "~") -val PrefixOperators = setOf("+", "-", "~", "not") +val PrefixOperators = setOf("+", "-", "~", "^", "<<", "not") // TODO ^ and << are experimental fun invertedComparisonOperator(operator: String) = when (operator) { diff --git a/codeCore/src/prog8/code/target/encodings/PetsciiEncoding.kt b/codeCore/src/prog8/code/target/encodings/PetsciiEncoding.kt index ea978afc0..ac6380c34 100644 --- a/codeCore/src/prog8/code/target/encodings/PetsciiEncoding.kt +++ b/codeCore/src/prog8/code/target/encodings/PetsciiEncoding.kt @@ -1089,7 +1089,7 @@ object PetsciiEncoding { Ok(text.map { try { encodeChar(it, lowercase) - } catch (x: CharConversionException) { + } catch (_: CharConversionException) { encodeChar(it, !lowercase) } }) @@ -1135,7 +1135,7 @@ object PetsciiEncoding { Ok(text.map { try { encodeChar(it, lowercase) - } catch (x: CharConversionException) { + } catch (_: CharConversionException) { encodeChar(it, !lowercase) } }) diff --git a/compiler/src/prog8/CompilerMain.kt b/compiler/src/prog8/CompilerMain.kt index d725c4128..554fb5be4 100644 --- a/compiler/src/prog8/CompilerMain.kt +++ b/compiler/src/prog8/CompilerMain.kt @@ -275,7 +275,7 @@ private fun compileMain(args: Array): Boolean { return false else compilationResult = result - } catch (x: AstException) { + } catch (_: AstException) { return false } diff --git a/compiler/src/prog8/compiler/BuiltinFunctions.kt b/compiler/src/prog8/compiler/BuiltinFunctions.kt index 77f1cba73..da9c78ec8 100644 --- a/compiler/src/prog8/compiler/BuiltinFunctions.kt +++ b/compiler/src/prog8/compiler/BuiltinFunctions.kt @@ -23,7 +23,7 @@ internal val constEvaluatorsForBuiltinFuncs: Map "lsw" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x and 65535).toDouble() } }, "msb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 8 and 255).toDouble()} }, "msw" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 16 and 65535).toDouble()} }, - "bankof" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x ushr 16 and 255).toDouble()} }, + "bankof" to ::builtinBankof, "mkword" to ::builtinMkword, "clamp__ubyte" to ::builtinClampUByte, "clamp__byte" to ::builtinClampByte, @@ -153,6 +153,15 @@ private fun builtinLen(args: List, position: Position, program: Prog } } +private fun builtinBankof(args: List, position: Position, program: Program): NumericLiteral { + if (args.size != 1) + throw SyntaxError("bankof requires one argument", position) + val const = args[0].constValue(program)?.number?.toInt() ?: throw NotConstArgumentException() + if(const > 0xffffff) + throw SyntaxError("integer overflow, bank exceeds 255", position) + return NumericLiteral(DataType.UBYTE, (const ushr 16 and 255).toDouble(), position) +} + private fun builtinMkword(args: List, position: Position, program: Program): NumericLiteral { if (args.size != 2) throw SyntaxError("mkword requires msb and lsb arguments", position) diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index bf12426eb..82af2acbb 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -1142,21 +1142,33 @@ internal class AstChecker(private val program: Program, if(dt==DataType.UNDEFINED) return // any error should be reported elsewhere - if(expr.operator=="-") { - if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) { - errors.err("can only take negative of a signed number type", expr.position) + when (expr.operator) { + "-" -> { + if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) { + errors.err("can only take negative of a signed number type", expr.position) + } } - } - else if(expr.operator == "~") { - if(dt !in IntegerDatatypes) - errors.err("can only use bitwise invert on integer types", expr.position) - else if(dt==DataType.BOOL) - errors.err("bitwise invert is for integer types, use 'not' on booleans", expr.position) - } - else if(expr.operator == "not") { - if(dt!=DataType.BOOL) { - errors.err("logical not is for booleans", expr.position) + "~" -> { + if(dt !in IntegerDatatypes) + errors.err("can only use bitwise invert on integer types", expr.position) + else if(dt==DataType.BOOL) + errors.err("bitwise invert is for integer types, use 'not' on booleans", expr.position) } + "not" -> { + if(dt!=DataType.BOOL) { + errors.err("logical not is for booleans", expr.position) + } + } + "^" -> { // TODO ^ prefix operator is experimental + if(dt in IntegerDatatypes) { + val value = expr.expression.constValue(program)?.number?.toInt() + if(value!=null && value > 0xffffff) { + errors.err("integer overflow, bank exceeds 255", expr.position) + } + } else + errors.err("bankof operator can only take integer values", expr.position) + } + "<<" -> throw FatalAstException("unary << should have been replaced by a const uword") // TODO << is experimental } super.visit(expr) } diff --git a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt index 1b420eea0..2e399deba 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstPreprocessor.kt @@ -85,7 +85,7 @@ class AstPreprocessor(val program: Program, val constval = range.to.constValue(program) if(constval!=null) modifications += IAstModification.ReplaceNode(range.to, constval, range) - } catch (x: SyntaxError) { + } catch (_: SyntaxError) { // syntax errors will be reported later } } @@ -94,7 +94,7 @@ class AstPreprocessor(val program: Program, val constval = range.step.constValue(program) if(constval!=null) modifications += IAstModification.ReplaceNode(range.step, constval, range) - } catch (x: SyntaxError) { + } catch (_: SyntaxError) { // syntax errors will be reported later } } diff --git a/compiler/test/TestCompilerOnRanges.kt b/compiler/test/TestCompilerOnRanges.kt index 6d7d5925a..a976c79ef 100644 --- a/compiler/test/TestCompilerOnRanges.kt +++ b/compiler/test/TestCompilerOnRanges.kt @@ -266,7 +266,7 @@ class TestCompilerOnRanges: FunSpec({ (array as ArrayLiteral).value.size shouldBe 26 val forloop = (statements.dropLast(1).last() as ForLoop) forloop.iterable shouldBe instanceOf() - (forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.UBYTE, -2.0, Position.DUMMY) + (forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.BYTE, -2.0, Position.DUMMY) } test("range with start/end variables should be ok") { @@ -285,7 +285,7 @@ class TestCompilerOnRanges: FunSpec({ val statements = result.compilerAst.entrypoint.statements val forloop = (statements.dropLast(1).last() as ForLoop) forloop.iterable shouldBe instanceOf() - (forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.UBYTE, -2.0, Position.DUMMY) + (forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.BYTE, -2.0, Position.DUMMY) } diff --git a/compiler/test/TestNumbers.kt b/compiler/test/TestNumbers.kt index 2feb2b4d9..598be7e3e 100644 --- a/compiler/test/TestNumbers.kt +++ b/compiler/test/TestNumbers.kt @@ -6,11 +6,7 @@ import io.kotest.matchers.doubles.plusOrMinus import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import io.kotest.matchers.string.shouldContain -import prog8.ast.expressions.NumericLiteral -import prog8.ast.statements.Assignment -import prog8.code.core.DataType import prog8.code.core.InternalCompilerException -import prog8.code.core.Position import prog8.code.core.toHex import prog8.code.target.C64Target import prog8.code.target.cbm.Mflpt5 @@ -162,15 +158,33 @@ class TestNumbers: FunSpec({ sub start() { uword @shared qq = ${'$'}2ff33 cx16.r0 = ${'$'}1fc0f + cx16.r0L = 1234 } } """ val errors = ErrorReporterForTests() compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null - errors.errors.size shouldBe 2 + errors.errors.size shouldBe 6 errors.warnings.size shouldBe 0 errors.errors[0] shouldContain "out of range" - errors.errors[1] shouldContain "out of range" + errors.errors[1] shouldContain "doesn't match" + errors.errors[2] shouldContain "out of range" + errors.errors[3] shouldContain "doesn't match" + errors.errors[4] shouldContain "out of range" + errors.errors[5] shouldContain "cannot assign word to byte" + } + + test("large numeric literals still ok if actual value is small") { + val src=""" + main { + sub start() { + cx16.r1L = %000000000001 + cx16.r2L = ${'$'}000000000001 + } + } + """ + val errors = ErrorReporterForTests() + compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldNotBe null } test("big numbers okay in const expressions if result fits") { @@ -185,7 +199,7 @@ class TestNumbers: FunSpec({ compileText(C64Target(), true, src, writeAssembly = false) shouldNotBe null } - test("signed negative numbers cast to unsigned allowed") { + test("signed negative numbers not implicitly cast to unsigned") { val src=""" main { sub start() { @@ -197,13 +211,14 @@ class TestNumbers: FunSpec({ } } """ - val result = compileText(C64Target(), false, src, writeAssembly = false)!! - val statements = result.compilerAst.entrypoint.statements - statements.size shouldBe 8 - (statements[1] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 32768.0, Position.DUMMY) - (statements[3] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 65535.0, Position.DUMMY) - (statements[5] as Assignment).value shouldBe NumericLiteral(DataType.UBYTE, 255.0, Position.DUMMY) - (statements[6] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 65534.0, Position.DUMMY) - (statements[7] as Assignment).value shouldBe NumericLiteral(DataType.UBYTE, 254.0, Position.DUMMY) + val errors = ErrorReporterForTests() + compileText(C64Target(), false, src, writeAssembly = false, errors=errors) shouldBe null + errors.errors.size shouldBe 6 + errors.errors[0] shouldContain "out of range" + errors.errors[1] shouldContain "WORD doesn't match" + errors.errors[2] shouldContain "out of range" + errors.errors[3] shouldContain "BYTE doesn't match" + errors.errors[4] shouldContain "out of range" + errors.errors[5] shouldContain "BYTE doesn't match" } }) diff --git a/compiler/test/TestNumericLiteral.kt b/compiler/test/TestNumericLiteral.kt index c381598c4..9c7ac30d3 100644 --- a/compiler/test/TestNumericLiteral.kt +++ b/compiler/test/TestNumericLiteral.kt @@ -162,6 +162,14 @@ class TestNumericLiteral: FunSpec({ NumericLiteral.optimalInteger(-1000, Position.DUMMY).number shouldBe -1000.0 NumericLiteral.optimalInteger(1000u, Position.DUMMY).type shouldBe DataType.UWORD NumericLiteral.optimalInteger(1000u, Position.DUMMY).number shouldBe 1000.0 + + NumericLiteral.optimalInteger(DataType.UBYTE, DataType.UWORD, 1, Position.DUMMY).type shouldBe DataType.UWORD + NumericLiteral.optimalInteger(DataType.UWORD, DataType.UBYTE, 1, Position.DUMMY).type shouldBe DataType.UWORD + NumericLiteral.optimalInteger(DataType.UWORD, null, 1, Position.DUMMY).type shouldBe DataType.UWORD + NumericLiteral.optimalInteger(DataType.UBYTE, DataType.UBYTE, -1, Position.DUMMY).type shouldBe DataType.BYTE + NumericLiteral.optimalInteger(DataType.UBYTE, null, -1, Position.DUMMY).type shouldBe DataType.BYTE + NumericLiteral.optimalInteger(DataType.UWORD, DataType.UWORD, -1, Position.DUMMY).type shouldBe DataType.WORD + NumericLiteral.optimalInteger(DataType.UWORD, null, -1, Position.DUMMY).type shouldBe DataType.WORD } test("optimalNumeric") { @@ -183,6 +191,22 @@ class TestNumericLiteral: FunSpec({ NumericLiteral.optimalNumeric(1234.0, Position.DUMMY).number shouldBe 1234.0 NumericLiteral.optimalNumeric(-1234.0, Position.DUMMY).type shouldBe DataType.WORD NumericLiteral.optimalNumeric(-1234.0, Position.DUMMY).number shouldBe -1234.0 + + NumericLiteral.optimalNumeric(DataType.UBYTE, DataType.UWORD, 1.0, Position.DUMMY).type shouldBe DataType.UWORD + NumericLiteral.optimalNumeric(DataType.UWORD, DataType.UBYTE, 1.0, Position.DUMMY).type shouldBe DataType.UWORD + NumericLiteral.optimalNumeric(DataType.UWORD, null, 1.0, Position.DUMMY).type shouldBe DataType.UWORD + NumericLiteral.optimalNumeric(DataType.UBYTE, DataType.UBYTE, -1.0, Position.DUMMY).type shouldBe DataType.BYTE + NumericLiteral.optimalNumeric(DataType.UBYTE, null, -1.0, Position.DUMMY).type shouldBe DataType.BYTE + NumericLiteral.optimalNumeric(DataType.UWORD, DataType.UWORD, -1.0, Position.DUMMY).type shouldBe DataType.WORD + NumericLiteral.optimalNumeric(DataType.UWORD, null, -1.0, Position.DUMMY).type shouldBe DataType.WORD + + NumericLiteral.optimalNumeric(DataType.UBYTE, DataType.UWORD, 1.234, Position.DUMMY).type shouldBe DataType.FLOAT + NumericLiteral.optimalNumeric(DataType.UWORD, DataType.UBYTE, 1.234, Position.DUMMY).type shouldBe DataType.FLOAT + NumericLiteral.optimalNumeric(DataType.UWORD, null, 1.234, Position.DUMMY).type shouldBe DataType.FLOAT + NumericLiteral.optimalNumeric(DataType.UBYTE, DataType.UBYTE, -1.234, Position.DUMMY).type shouldBe DataType.FLOAT + NumericLiteral.optimalNumeric(DataType.UBYTE, null, -1.234, Position.DUMMY).type shouldBe DataType.FLOAT + NumericLiteral.optimalNumeric(DataType.UWORD, DataType.UWORD, -1.234, Position.DUMMY).type shouldBe DataType.FLOAT + NumericLiteral.optimalNumeric(DataType.UWORD, null, -1.234, Position.DUMMY).type shouldBe DataType.FLOAT } test("cast can change value") { diff --git a/compiler/test/TestTypecasts.kt b/compiler/test/TestTypecasts.kt index 329c84eb2..d75b006ce 100644 --- a/compiler/test/TestTypecasts.kt +++ b/compiler/test/TestTypecasts.kt @@ -896,8 +896,7 @@ main { } }""" - val errors = ErrorReporterForTests() - val result = compileText(C64Target(), false, src, writeAssembly = false, errors = errors)!! + val result = compileText(C64Target(), false, src, writeAssembly = false)!! val program = result.compilerAst val st = program.entrypoint.statements st.size shouldBe 1 @@ -909,4 +908,49 @@ main { ifexpr.truevalue shouldBe instanceOf() ifexpr.falsevalue shouldBe instanceOf() } + + test("correct data types of numeric literals in word/byte scenario") { + val src = """ +main { + sub start() { + const uword WIDTH = 40 + const uword WIDER = 400 + cx16.r0 = cx16.r0-1+WIDTH + cx16.r0 = cx16.r0-1+WIDER + cx16.r0 = cx16.r0L * 5 ; byte multiplication + cx16.r0 = cx16.r0L * ${'$'}0005 ; word multiplication + } +}""" + val result = compileText(C64Target(), false, src, writeAssembly = false)!! + val program = result.compilerAst + val st = program.entrypoint.statements + st.size shouldBe 6 + val v1 = (st[2] as Assignment).value as BinaryExpression + v1.operator shouldBe "+" + (v1.left as IdentifierReference).nameInSource shouldBe listOf("cx16","r0") + (v1.right as NumericLiteral).type shouldBe DataType.UWORD + (v1.right as NumericLiteral).number shouldBe 39 + + val v2 = (st[3] as Assignment).value as BinaryExpression + v2.operator shouldBe "+" + (v2.left as IdentifierReference).nameInSource shouldBe listOf("cx16","r0") + (v2.right as NumericLiteral).type shouldBe DataType.UWORD + (v2.right as NumericLiteral).number shouldBe 399 + + val v3 = (st[4] as Assignment).value as TypecastExpression + v3.type shouldBe DataType.UWORD + val v3e = v3.expression as BinaryExpression + v3e.operator shouldBe "*" + (v3e.left as IdentifierReference).nameInSource shouldBe listOf("cx16","r0L") + (v3e.right as NumericLiteral).type shouldBe DataType.UBYTE + (v3e.right as NumericLiteral).number shouldBe 5 + + val v4 = (st[5] as Assignment).value as BinaryExpression + v4.operator shouldBe "*" + val v4t = v4.left as TypecastExpression + v4t.type shouldBe DataType.UWORD + (v4t.expression as IdentifierReference).nameInSource shouldBe listOf("cx16","r0L") + (v4.right as NumericLiteral).type shouldBe DataType.UWORD + (v4.right as NumericLiteral).number shouldBe 5 + } }) diff --git a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt index 72e7a27fa..2b72e13b1 100644 --- a/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt +++ b/compilerAst/src/prog8/ast/antlr/Antlr2Kotlin.kt @@ -464,7 +464,9 @@ private fun IntegerliteralContext.toAst(): NumericLiteralNode { } } 2 -> { - if(literalText.length>8) + if(literalText.length>16) + datatype = DataType.LONG + else if(literalText.length>8) datatype = DataType.UWORD try { integer = literalText.toInt(2) @@ -473,7 +475,9 @@ private fun IntegerliteralContext.toAst(): NumericLiteralNode { } } 16 -> { - if(literalText.length>2) + if(literalText.length>4) + datatype = DataType.LONG + else if(literalText.length>2) datatype = DataType.UWORD try { integer = literalText.toInt(16) @@ -558,7 +562,7 @@ private fun ExpressionContext.toAst(insideParentheses: Boolean=false) : Expressi if (rangefrom!=null && rangeto!=null) { val defaultstep = if(rto.text == "to") 1 else -1 - val step = rangestep?.toAst() ?: NumericLiteral(DataType.UBYTE, defaultstep.toDouble(), toPosition()) + val step = rangestep?.toAst() ?: NumericLiteral.optimalInteger(defaultstep, toPosition()) return RangeExpression(rangefrom.toAst(), rangeto.toAst(), step, toPosition()) } diff --git a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt index 8947d3e9b..e1adaa33d 100644 --- a/compilerAst/src/prog8/ast/expressions/AstExpressions.kt +++ b/compilerAst/src/prog8/ast/expressions/AstExpressions.kt @@ -119,7 +119,13 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid else -> throw ExpressionError("can only take bitwise inversion of int", constval.position) } "not" -> NumericLiteral.fromBoolean(constval.number==0.0, constval.position) - "^" -> NumericLiteral(DataType.UBYTE, (constval.number.toInt() ushr 16 and 255).toDouble(), constval.position) // bank + "^" -> { + val const = constval.number.toInt() + return if(const>0xffffff) + null // number is more than 24 bits; bank byte exceeds 255 + else + NumericLiteral(DataType.UBYTE, (const ushr 16 and 255).toDouble(), constval.position) // bank + } "<<" -> NumericLiteral(DataType.UWORD, (constval.number.toInt() and 65535).toDouble(), constval.position) // address else -> throw FatalAstException("invalid operator") } @@ -226,7 +232,7 @@ class BinaryExpression( dt } else dt - } catch (x: FatalAstException) { + } catch (_: FatalAstException) { InferredTypes.unknown() } } @@ -256,6 +262,25 @@ class BinaryExpression( // word + word -> word // a combination with a float will be float (but give a warning about this!) + // if left or right is a numeric literal, and its value fits in the type of the other operand, use the other's operand type + // EXCEPTION: if the numeric value is a word and the other operand is a byte type (to allow v * $0008 for example) + if (left is NumericLiteral) { + if(!(leftDt in WordDatatypes && rightDt in ByteDatatypes)) { + val optimal = NumericLiteral.optimalNumeric(rightDt, null, left.number, left.position) + if (optimal.type != leftDt && optimal.type isAssignableTo rightDt) { + return optimal.type to left + } + } + } + if (right is NumericLiteral) { + if(!(rightDt in WordDatatypes && leftDt in ByteDatatypes)) { + val optimal = NumericLiteral.optimalNumeric(leftDt, null, right.number, right.position) + if (optimal.type != rightDt && optimal.type isAssignableTo leftDt) { + return optimal.type to right + } + } + } + return when (leftDt) { DataType.BOOL -> { return if(rightDt==DataType.BOOL) @@ -508,6 +533,18 @@ class NumericLiteral(val type: DataType, // only numerical types allowed } } + init { + when(type) { + DataType.UBYTE -> require(numbervalue in 0.0..255.0) + DataType.BYTE -> require(numbervalue in -128.0..127.0) + DataType.UWORD -> require(numbervalue in 0.0..65535.0) + DataType.WORD -> require(numbervalue in -32768.0..32767.0) + DataType.LONG -> require(numbervalue in -2147483647.0..2147483647.0) + DataType.BOOL -> require(numbervalue==0.0 || numbervalue==1.0) + else -> {} + } + } + override val isSimple = true override fun copy() = NumericLiteral(type, number, position) @@ -515,23 +552,11 @@ class NumericLiteral(val type: DataType, // only numerical types allowed fun fromBoolean(bool: Boolean, position: Position) = NumericLiteral(DataType.BOOL, if(bool) 1.0 else 0.0, position) - fun optimalNumeric(origType1: DataType, origType2: DataType?, value: Number, position: Position) : NumericLiteral { - val optimal = optimalNumeric(value, position) - val largestOrig = if(origType2==null) origType1 else if(origType1.largerThan(origType2)) origType1 else origType2 - return if(largestOrig.largerThan(optimal.type)) - NumericLiteral(largestOrig, optimal.number, position) - else - optimal - } + fun optimalNumeric(origType1: DataType, origType2: DataType?, value: Number, position: Position) : NumericLiteral = + fromOptimal(optimalNumeric(value, position), origType1, origType2, position) - fun optimalInteger(origType1: DataType, origType2: DataType?, value: Int, position: Position): NumericLiteral { - val optimal = optimalInteger(value, position) - val largestOrig = if(origType2==null) origType1 else if(origType1.largerThan(origType2)) origType1 else origType2 - return if(largestOrig.largerThan(optimal.type)) - NumericLiteral(largestOrig, optimal.number, position) - else - optimal - } + fun optimalInteger(origType1: DataType, origType2: DataType?, value: Int, position: Position): NumericLiteral = + fromOptimal(optimalInteger(value, position), origType1, origType2, position) fun optimalNumeric(value: Number, position: Position): NumericLiteral { val digits = floor(value.toDouble()) - value.toDouble() @@ -570,6 +595,23 @@ class NumericLiteral(val type: DataType, // only numerical types allowed else -> throw FatalAstException("unsigned integer overflow: $value") } } + + private fun fromOptimal(optimal: NumericLiteral, origType1: DataType, origType2: DataType?, position: Position): NumericLiteral { + var largestOrig = if(origType2==null) origType1 else if(origType1.largerThan(origType2)) origType1 else origType2 + return if(largestOrig.largerThan(optimal.type)) { + if(optimal.number<0 && largestOrig !in SignedDatatypes) { + when(largestOrig){ + DataType.BOOL -> {} + DataType.UBYTE -> largestOrig = DataType.BYTE + DataType.UWORD -> largestOrig = DataType.WORD + else -> throw FatalAstException("invalid dt") + } + } + NumericLiteral(largestOrig, optimal.number, position) + } + else + optimal + } } val asBooleanValue: Boolean = number != 0.0 @@ -655,13 +697,13 @@ class NumericLiteral(val type: DataType, // only numerical types allowed } DataType.BYTE -> { if(targettype==DataType.UBYTE) { - if(number in -128.0..0.0) + if(number in -128.0..0.0 && !implicit) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUByte().toDouble(), position)) else if(number in 0.0..255.0) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) } if(targettype==DataType.UWORD) { - if(number in -32768.0..0.0) + if(number in -32768.0..0.0 && !implicit) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUShort().toDouble(), position)) else if(number in 0.0..65535.0) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) @@ -689,13 +731,13 @@ class NumericLiteral(val type: DataType, // only numerical types allowed if(targettype==DataType.BYTE && number >= -128 && number <=127) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) if(targettype==DataType.UBYTE) { - if(number in -128.0..0.0) + if(number in -128.0..0.0 && !implicit) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUByte().toDouble(), position)) else if(number in 0.0..255.0) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) } if(targettype==DataType.UWORD) { - if(number in -32768.0 .. 0.0) + if(number in -32768.0 .. 0.0 && !implicit) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUShort().toDouble(), position)) else if(number in 0.0..65535.0) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) diff --git a/docs/source/libraries.rst b/docs/source/libraries.rst index 7192ecdb2..06ef736a2 100644 --- a/docs/source/libraries.rst +++ b/docs/source/libraries.rst @@ -102,7 +102,7 @@ msb (x) can do that using ``bankof(x)``. bankof (x) - Get the 'bank' byte from the value x. This means bits 16-24 of that value: bankof($1234567) = $12. + Get the 'bank' byte from the value x. This means bits 16-24 of that value: bankof($123456) = $12. (To get the 16 bit address out of a value simply use ``x & $ffff``) If x is a word or smaller, bankof(x) will always be zero. You can consider this function equivalent to the expression ``lsb(x >> 16)``. diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 6d73effb9..3360efe49 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -3,7 +3,7 @@ TODO make a compiler switch to disable footgun warnings -what to do with bankof(): keep it? add another syntax like \`value or ^value to get the bank byte? +-> added bankof() to get the bank byte of a large integer -> added msw() and lsw() . note: msw() on a 24 bits constant can ALSO be used to get the bank byte because the value, while a word type, will be <=255 -> added unary ^ operator as alternative to bankof() -> added unary << operator as alternative to lsw() / lsb(x>>16) diff --git a/docs/source/variables.rst b/docs/source/variables.rst index cfd66b319..21a66f6f5 100644 --- a/docs/source/variables.rst +++ b/docs/source/variables.rst @@ -190,13 +190,26 @@ which is the PETSCII value for that character. You can prefix it with the desire **bytes versus words:** -- When an integer value ranges from 0..255 the compiler sees it as a ``ubyte``. For -128..127 it's a ``byte``. -- When an integer value ranges from 256..65535 the compiler sees it as a ``uword``. For -32768..32767 it's a ``word``. -- When a hex number has 3 or 4 digits, for example ``$0004``, it is seen as a ``word`` otherwise as a ``byte``. -- When a binary number has 9 to 16 digits, for example ``%1100110011``, it is seen as a ``word`` otherwise as a ``byte``. -- If the number fits in a byte but you really require it as a word value, you'll have to explicitly cast it: ``60 as uword`` - or you can use the full word hexadecimal notation ``$003c``. +Prog8 tries to determine the data type of integer values according to the table below, +and sometimes the context in which they are used. +========================= ================= +value datatype +========================= ================= +-128 .. 127 byte +0 .. 255 ubyte +-32768 .. 32767 word +0 .. 65535 uword +-2147483647 .. 2147483647 long (only for const) +========================= ================= + +If the number fits in a byte but you really require it as a word value, you'll have to explicitly cast it: ``60 as uword`` +or you can use the full word hexadecimal notation ``$003c``. This is useful in expressions where you want a calcuation +to be done on word values, and don't want to explicitly have to cast everything all the time. For instance:: + + ubyte column + uword offset = column * 64 ; does (column * 64) as uword, wrong result? + uword offset = column * $0040 ; does (column as uword) * 64 , a word calculation Only for ``const`` numbers, you can use larger values (32 bits signed integers). The compiler can handle those internally in expressions. As soon as you have to actually store it into a variable, @@ -226,8 +239,6 @@ This saves a lot of memory and may be faster as well. Floating point numbers ^^^^^^^^^^^^^^^^^^^^^^ -You can use underscores to group digits to make long numbers more readable. - Floats are stored in the 5-byte 'MFLPT' format that is used on CBM machines. Floating point support is available on the c64 and cx16 (and virtual) compiler targets. On the c64 and cx16, the rom routines are used for floating point operations, @@ -243,6 +254,10 @@ to worry about this yourself) The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (negative: **-1.7014118345e+38**) +You can use underscores to group digits in floating point literals to make long numbers more readable: +any underscores in the number are ignored by the compiler. +For instance ``30_000.999_999`` is a valid floating point number 30000.999999. + Arrays ^^^^^^ diff --git a/examples/cx16/pcmaudio/adpcm.p8 b/examples/cx16/pcmaudio/adpcm.p8 index af4da39ac..cdd3a30d2 100644 --- a/examples/cx16/pcmaudio/adpcm.p8 +++ b/examples/cx16/pcmaudio/adpcm.p8 @@ -28,7 +28,7 @@ adpcm { ; belong to the left channel and -if it's stereo- the next 4 bytes belong to the right channel. - ubyte[] t_index = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8] + byte[] t_index = [ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8] uword[] @split t_step = [ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, @@ -92,7 +92,7 @@ adpcm { ; elif predicted < -32767: ; predicted = - 32767 - index += t_index[nibble] + index += t_index[nibble] as ubyte if_neg index = 0 else if index >= len(t_step)-1 @@ -128,7 +128,7 @@ adpcm { ; elif predicted < -32767: ; predicted = - 32767 - index_2 += t_index[nibble] + index_2 += t_index[nibble] as ubyte if_neg index_2 = 0 else if index_2 >= len(t_step)-1 diff --git a/examples/test.p8 b/examples/test.p8 index 028b77e77..7410bb8f1 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -4,16 +4,28 @@ main { sub start() { - @($2005) = 0 - txt.print_ub(get_indexed_byte($2000, 5)) - txt.nl() - @($2005) = 123 - txt.print_ub(get_indexed_byte($2000, 5)) + ubyte @shared ub = -1 + uword @shared uw = -5555 + + txt.print_ubhex(bankof($123456), true) + txt.spc() + txt.print_ubhex(msw($123456), true) + txt.spc() + txt.print_ubhex(^$123456, true) txt.nl() - } + txt.print_uwhex(<<$1234567, true) + txt.spc() + txt.print_uwhex(lsw($1234567), true) + txt.spc() + txt.print_uwhex($1234567 & $ffff, true) + txt.nl() - sub get_indexed_byte(uword pointer @R0, ubyte index @R1) -> ubyte { - return @(cx16.r0 + cx16.r1L) + txt.print_uwhex(<<$123456, true) + txt.spc() + txt.print_uwhex(lsw($123456), true) + txt.spc() + txt.print_uwhex($123456 & $ffff, true) + txt.nl() } }