mirror of
https://github.com/irmen/prog8.git
synced 2025-01-23 00:31:14 +00:00
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:
parent
58f696d00a
commit
50c3d809dc
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
@ -275,7 +275,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
return false
|
||||
else
|
||||
compilationResult = result
|
||||
} catch (x: AstException) {
|
||||
} catch (_: AstException) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -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() } },
|
||||
"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<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 {
|
||||
if (args.size != 2)
|
||||
throw SyntaxError("mkword requires msb and lsb arguments", position)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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<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") {
|
||||
@ -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<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)
|
||||
}
|
||||
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
})
|
||||
|
@ -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") {
|
||||
|
@ -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<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
|
||||
}
|
||||
})
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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)``.
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
^^^^^^
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user