fix type casting issues and unary ^ operator

signed numbers are no longer implicitly converted to unsigned
proper range check on bankof()
This commit is contained in:
Irmen de Jong 2024-12-01 14:28:41 +01:00
parent 58f696d00a
commit 50c3d809dc
18 changed files with 271 additions and 86 deletions

View File

@ -294,6 +294,14 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
if (trunc != number) if (trunc != number)
throw IllegalArgumentException("refused truncating of float to avoid loss of precision @$position") 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) 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 get() = children.single() as PtExpression
init { init {
require(operator in setOf("+", "-", "~", "^", "<<", "not")) { "invalid prefix operator: $operator" } require(operator in setOf("+", "-", "~", "^", "<<", "not")) { "invalid prefix operator: $operator" } // TODO ^ and << are experimental
} }
} }

View File

@ -4,7 +4,7 @@ val AssociativeOperators = setOf("+", "*", "&", "|", "^", "==", "!=", "xor")
val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=") val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
val LogicalOperators = setOf("and", "or", "xor", "not", "in") val LogicalOperators = setOf("and", "or", "xor", "not", "in")
val BitwiseOperators = setOf("&", "|", "^", "~") val BitwiseOperators = setOf("&", "|", "^", "~")
val PrefixOperators = setOf("+", "-", "~", "not") val PrefixOperators = setOf("+", "-", "~", "^", "<<", "not") // TODO ^ and << are experimental
fun invertedComparisonOperator(operator: String) = fun invertedComparisonOperator(operator: String) =
when (operator) { when (operator) {

View File

@ -1089,7 +1089,7 @@ object PetsciiEncoding {
Ok(text.map { Ok(text.map {
try { try {
encodeChar(it, lowercase) encodeChar(it, lowercase)
} catch (x: CharConversionException) { } catch (_: CharConversionException) {
encodeChar(it, !lowercase) encodeChar(it, !lowercase)
} }
}) })
@ -1135,7 +1135,7 @@ object PetsciiEncoding {
Ok(text.map { Ok(text.map {
try { try {
encodeChar(it, lowercase) encodeChar(it, lowercase)
} catch (x: CharConversionException) { } catch (_: CharConversionException) {
encodeChar(it, !lowercase) encodeChar(it, !lowercase)
} }
}) })

View File

@ -275,7 +275,7 @@ private fun compileMain(args: Array<String>): Boolean {
return false return false
else else
compilationResult = result compilationResult = result
} catch (x: AstException) { } catch (_: AstException) {
return false return false
} }

View File

@ -23,7 +23,7 @@ internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller>
"lsw" to { a, p, prg -> oneIntArgOutputInt(a, p, prg, true) { x: Int -> (x and 65535).toDouble() } }, "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()} }, "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()} }, "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, "mkword" to ::builtinMkword,
"clamp__ubyte" to ::builtinClampUByte, "clamp__ubyte" to ::builtinClampUByte,
"clamp__byte" to ::builtinClampByte, "clamp__byte" to ::builtinClampByte,
@ -153,6 +153,15 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
} }
} }
private fun builtinBankof(args: List<Expression>, 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<Expression>, position: Position, program: Program): NumericLiteral { private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteral {
if (args.size != 2) if (args.size != 2)
throw SyntaxError("mkword requires msb and lsb arguments", position) throw SyntaxError("mkword requires msb and lsb arguments", position)

View File

@ -1142,22 +1142,34 @@ internal class AstChecker(private val program: Program,
if(dt==DataType.UNDEFINED) if(dt==DataType.UNDEFINED)
return // any error should be reported elsewhere return // any error should be reported elsewhere
if(expr.operator=="-") { when (expr.operator) {
"-" -> {
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) { if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
errors.err("can only take negative of a signed number type", expr.position) errors.err("can only take negative of a signed number type", expr.position)
} }
} }
else if(expr.operator == "~") { "~" -> {
if(dt !in IntegerDatatypes) if(dt !in IntegerDatatypes)
errors.err("can only use bitwise invert on integer types", expr.position) errors.err("can only use bitwise invert on integer types", expr.position)
else if(dt==DataType.BOOL) else if(dt==DataType.BOOL)
errors.err("bitwise invert is for integer types, use 'not' on booleans", expr.position) errors.err("bitwise invert is for integer types, use 'not' on booleans", expr.position)
} }
else if(expr.operator == "not") { "not" -> {
if(dt!=DataType.BOOL) { if(dt!=DataType.BOOL) {
errors.err("logical not is for booleans", expr.position) 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) super.visit(expr)
} }

View File

@ -85,7 +85,7 @@ class AstPreprocessor(val program: Program,
val constval = range.to.constValue(program) val constval = range.to.constValue(program)
if(constval!=null) if(constval!=null)
modifications += IAstModification.ReplaceNode(range.to, constval, range) modifications += IAstModification.ReplaceNode(range.to, constval, range)
} catch (x: SyntaxError) { } catch (_: SyntaxError) {
// syntax errors will be reported later // syntax errors will be reported later
} }
} }
@ -94,7 +94,7 @@ class AstPreprocessor(val program: Program,
val constval = range.step.constValue(program) val constval = range.step.constValue(program)
if(constval!=null) if(constval!=null)
modifications += IAstModification.ReplaceNode(range.step, constval, range) modifications += IAstModification.ReplaceNode(range.step, constval, range)
} catch (x: SyntaxError) { } catch (_: SyntaxError) {
// syntax errors will be reported later // syntax errors will be reported later
} }
} }

View File

@ -266,7 +266,7 @@ class TestCompilerOnRanges: FunSpec({
(array as ArrayLiteral).value.size shouldBe 26 (array as ArrayLiteral).value.size shouldBe 26
val forloop = (statements.dropLast(1).last() as ForLoop) val forloop = (statements.dropLast(1).last() as ForLoop)
forloop.iterable shouldBe instanceOf<RangeExpression>() forloop.iterable shouldBe instanceOf<RangeExpression>()
(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") { test("range with start/end variables should be ok") {
@ -285,7 +285,7 @@ class TestCompilerOnRanges: FunSpec({
val statements = result.compilerAst.entrypoint.statements val statements = result.compilerAst.entrypoint.statements
val forloop = (statements.dropLast(1).last() as ForLoop) val forloop = (statements.dropLast(1).last() as ForLoop)
forloop.iterable shouldBe instanceOf<RangeExpression>() forloop.iterable shouldBe instanceOf<RangeExpression>()
(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)
} }

View File

@ -6,11 +6,7 @@ import io.kotest.matchers.doubles.plusOrMinus
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain 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.InternalCompilerException
import prog8.code.core.Position
import prog8.code.core.toHex import prog8.code.core.toHex
import prog8.code.target.C64Target import prog8.code.target.C64Target
import prog8.code.target.cbm.Mflpt5 import prog8.code.target.cbm.Mflpt5
@ -162,15 +158,33 @@ class TestNumbers: FunSpec({
sub start() { sub start() {
uword @shared qq = ${'$'}2ff33 uword @shared qq = ${'$'}2ff33
cx16.r0 = ${'$'}1fc0f cx16.r0 = ${'$'}1fc0f
cx16.r0L = 1234
} }
} }
""" """
val errors = ErrorReporterForTests() val errors = ErrorReporterForTests()
compileText(C64Target(), true, src, writeAssembly = false, errors=errors) shouldBe null 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.warnings.size shouldBe 0
errors.errors[0] shouldContain "out of range" 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") { 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 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=""" val src="""
main { main {
sub start() { sub start() {
@ -197,13 +211,14 @@ class TestNumbers: FunSpec({
} }
} }
""" """
val result = compileText(C64Target(), false, src, writeAssembly = false)!! val errors = ErrorReporterForTests()
val statements = result.compilerAst.entrypoint.statements compileText(C64Target(), false, src, writeAssembly = false, errors=errors) shouldBe null
statements.size shouldBe 8 errors.errors.size shouldBe 6
(statements[1] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 32768.0, Position.DUMMY) errors.errors[0] shouldContain "out of range"
(statements[3] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 65535.0, Position.DUMMY) errors.errors[1] shouldContain "WORD doesn't match"
(statements[5] as Assignment).value shouldBe NumericLiteral(DataType.UBYTE, 255.0, Position.DUMMY) errors.errors[2] shouldContain "out of range"
(statements[6] as Assignment).value shouldBe NumericLiteral(DataType.UWORD, 65534.0, Position.DUMMY) errors.errors[3] shouldContain "BYTE doesn't match"
(statements[7] as Assignment).value shouldBe NumericLiteral(DataType.UBYTE, 254.0, Position.DUMMY) errors.errors[4] shouldContain "out of range"
errors.errors[5] shouldContain "BYTE doesn't match"
} }
}) })

View File

@ -162,6 +162,14 @@ class TestNumericLiteral: FunSpec({
NumericLiteral.optimalInteger(-1000, Position.DUMMY).number shouldBe -1000.0 NumericLiteral.optimalInteger(-1000, Position.DUMMY).number shouldBe -1000.0
NumericLiteral.optimalInteger(1000u, Position.DUMMY).type shouldBe DataType.UWORD NumericLiteral.optimalInteger(1000u, Position.DUMMY).type shouldBe DataType.UWORD
NumericLiteral.optimalInteger(1000u, Position.DUMMY).number shouldBe 1000.0 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") { 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).number shouldBe 1234.0
NumericLiteral.optimalNumeric(-1234.0, Position.DUMMY).type shouldBe DataType.WORD NumericLiteral.optimalNumeric(-1234.0, Position.DUMMY).type shouldBe DataType.WORD
NumericLiteral.optimalNumeric(-1234.0, Position.DUMMY).number shouldBe -1234.0 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") { test("cast can change value") {

View File

@ -896,8 +896,7 @@ main {
} }
}""" }"""
val errors = ErrorReporterForTests() val result = compileText(C64Target(), false, src, writeAssembly = false)!!
val result = compileText(C64Target(), false, src, writeAssembly = false, errors = errors)!!
val program = result.compilerAst val program = result.compilerAst
val st = program.entrypoint.statements val st = program.entrypoint.statements
st.size shouldBe 1 st.size shouldBe 1
@ -909,4 +908,49 @@ main {
ifexpr.truevalue shouldBe instanceOf<NumericLiteral>() ifexpr.truevalue shouldBe instanceOf<NumericLiteral>()
ifexpr.falsevalue shouldBe instanceOf<NumericLiteral>() ifexpr.falsevalue shouldBe instanceOf<NumericLiteral>()
} }
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
}
}) })

View File

@ -464,7 +464,9 @@ private fun IntegerliteralContext.toAst(): NumericLiteralNode {
} }
} }
2 -> { 2 -> {
if(literalText.length>8) if(literalText.length>16)
datatype = DataType.LONG
else if(literalText.length>8)
datatype = DataType.UWORD datatype = DataType.UWORD
try { try {
integer = literalText.toInt(2) integer = literalText.toInt(2)
@ -473,7 +475,9 @@ private fun IntegerliteralContext.toAst(): NumericLiteralNode {
} }
} }
16 -> { 16 -> {
if(literalText.length>2) if(literalText.length>4)
datatype = DataType.LONG
else if(literalText.length>2)
datatype = DataType.UWORD datatype = DataType.UWORD
try { try {
integer = literalText.toInt(16) integer = literalText.toInt(16)
@ -558,7 +562,7 @@ private fun ExpressionContext.toAst(insideParentheses: Boolean=false) : Expressi
if (rangefrom!=null && rangeto!=null) { if (rangefrom!=null && rangeto!=null) {
val defaultstep = if(rto.text == "to") 1 else -1 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()) return RangeExpression(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
} }

View File

@ -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) else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
} }
"not" -> NumericLiteral.fromBoolean(constval.number==0.0, 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 "<<" -> NumericLiteral(DataType.UWORD, (constval.number.toInt() and 65535).toDouble(), constval.position) // address
else -> throw FatalAstException("invalid operator") else -> throw FatalAstException("invalid operator")
} }
@ -226,7 +232,7 @@ class BinaryExpression(
dt dt
} else } else
dt dt
} catch (x: FatalAstException) { } catch (_: FatalAstException) {
InferredTypes.unknown() InferredTypes.unknown()
} }
} }
@ -256,6 +262,25 @@ class BinaryExpression(
// word + word -> word // word + word -> word
// a combination with a float will be float (but give a warning about this!) // 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) { return when (leftDt) {
DataType.BOOL -> { DataType.BOOL -> {
return if(rightDt==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 val isSimple = true
override fun copy() = NumericLiteral(type, number, position) 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) = fun fromBoolean(bool: Boolean, position: Position) =
NumericLiteral(DataType.BOOL, if(bool) 1.0 else 0.0, position) NumericLiteral(DataType.BOOL, if(bool) 1.0 else 0.0, position)
fun optimalNumeric(origType1: DataType, origType2: DataType?, value: Number, position: Position) : NumericLiteral { fun optimalNumeric(origType1: DataType, origType2: DataType?, value: Number, position: Position) : NumericLiteral =
val optimal = optimalNumeric(value, position) fromOptimal(optimalNumeric(value, position), origType1, origType2, 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 { fun optimalInteger(origType1: DataType, origType2: DataType?, value: Int, position: Position): NumericLiteral =
val optimal = optimalInteger(value, position) fromOptimal(optimalInteger(value, position), origType1, origType2, 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(value: Number, position: Position): NumericLiteral { fun optimalNumeric(value: Number, position: Position): NumericLiteral {
val digits = floor(value.toDouble()) - value.toDouble() 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") 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 val asBooleanValue: Boolean = number != 0.0
@ -655,13 +697,13 @@ class NumericLiteral(val type: DataType, // only numerical types allowed
} }
DataType.BYTE -> { DataType.BYTE -> {
if(targettype==DataType.UBYTE) { 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)) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUByte().toDouble(), position))
else if(number in 0.0..255.0) else if(number in 0.0..255.0)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
} }
if(targettype==DataType.UWORD) { 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)) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUShort().toDouble(), position))
else if(number in 0.0..65535.0) else if(number in 0.0..65535.0)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) 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) if(targettype==DataType.BYTE && number >= -128 && number <=127)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
if(targettype==DataType.UBYTE) { 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)) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUByte().toDouble(), position))
else if(number in 0.0..255.0) else if(number in 0.0..255.0)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))
} }
if(targettype==DataType.UWORD) { 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)) return ValueAfterCast(true, null, NumericLiteral(targettype, number.toInt().toUShort().toDouble(), position))
else if(number in 0.0..65535.0) else if(number in 0.0..65535.0)
return ValueAfterCast(true, null, NumericLiteral(targettype, number, position)) return ValueAfterCast(true, null, NumericLiteral(targettype, number, position))

View File

@ -102,7 +102,7 @@ msb (x)
can do that using ``bankof(x)``. can do that using ``bankof(x)``.
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``) (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. 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)``. You can consider this function equivalent to the expression ``lsb(x >> 16)``.

View File

@ -3,7 +3,7 @@ TODO
make a compiler switch to disable footgun warnings 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 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 bankof()
-> added unary << operator as alternative to lsw() / lsb(x>>16) -> added unary << operator as alternative to lsw() / lsb(x>>16)

View File

@ -190,13 +190,26 @@ which is the PETSCII value for that character. You can prefix it with the desire
**bytes versus words:** **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``. Prog8 tries to determine the data type of integer values according to the table below,
- When an integer value ranges from 256..65535 the compiler sees it as a ``uword``. For -32768..32767 it's a ``word``. and sometimes the context in which they are used.
- 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``.
========================= =================
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 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, 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 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. 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. 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, 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**) 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 Arrays
^^^^^^ ^^^^^^

View File

@ -28,7 +28,7 @@ adpcm {
; belong to the left channel and -if it's stereo- the next 4 bytes belong to the right channel. ; 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 = [ uword[] @split t_step = [
7, 8, 9, 10, 11, 12, 13, 14, 7, 8, 9, 10, 11, 12, 13, 14,
16, 17, 19, 21, 23, 25, 28, 31, 16, 17, 19, 21, 23, 25, 28, 31,
@ -92,7 +92,7 @@ adpcm {
; elif predicted < -32767: ; elif predicted < -32767:
; predicted = - 32767 ; predicted = - 32767
index += t_index[nibble] index += t_index[nibble] as ubyte
if_neg if_neg
index = 0 index = 0
else if index >= len(t_step)-1 else if index >= len(t_step)-1
@ -128,7 +128,7 @@ adpcm {
; elif predicted < -32767: ; elif predicted < -32767:
; predicted = - 32767 ; predicted = - 32767
index_2 += t_index[nibble] index_2 += t_index[nibble] as ubyte
if_neg if_neg
index_2 = 0 index_2 = 0
else if index_2 >= len(t_step)-1 else if index_2 >= len(t_step)-1

View File

@ -4,16 +4,28 @@
main { main {
sub start() { sub start() {
@($2005) = 0 ubyte @shared ub = -1
txt.print_ub(get_indexed_byte($2000, 5)) uword @shared uw = -5555
txt.nl()
@($2005) = 123 txt.print_ubhex(bankof($123456), true)
txt.print_ub(get_indexed_byte($2000, 5)) txt.spc()
txt.print_ubhex(msw($123456), true)
txt.spc()
txt.print_ubhex(^$123456, true)
txt.nl() 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 { txt.print_uwhex(<<$123456, true)
return @(cx16.r0 + cx16.r1L) txt.spc()
txt.print_uwhex(lsw($123456), true)
txt.spc()
txt.print_uwhex($123456 & $ffff, true)
txt.nl()
} }
} }