Compare commits

...

1 Commits

Author SHA1 Message Date
7b2da74233 trying to make all constants long type 2025-06-01 17:49:28 +02:00
15 changed files with 204 additions and 865 deletions

View File

@ -69,6 +69,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"prog8_lib_stringcompare" -> funcStringCompare(fcall, resultRegister)
"prog8_lib_square_byte" -> funcSquare(fcall, BaseDataType.UBYTE, resultRegister)
"prog8_lib_square_word" -> funcSquare(fcall, BaseDataType.UWORD, resultRegister)
"len" -> throw AssemblyError("len() should have been replaced by the actual constant number")
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
}

View File

@ -35,14 +35,6 @@ class ConstantFoldingOptimizer(private val program: Program, private val errors:
override fun after(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
if(numLiteral.type==BaseDataType.LONG) {
// see if LONG values may be reduced to something smaller
val smaller = NumericLiteral.optimalInteger(numLiteral.number.toInt(), numLiteral.position)
if(smaller.type!=BaseDataType.LONG) {
return listOf(IAstModification.ReplaceNode(numLiteral, smaller, parent))
}
}
if(parent is Assignment) {
val iDt = parent.target.inferType(program)
if(iDt.isKnown && !iDt.isBool && !(iDt issimpletype numLiteral.type)) {

View File

@ -314,13 +314,14 @@ internal class ConstantIdentifierReplacer(
return noModifications
try {
val cval = identifier.constValue(program) ?: return noModifications
val cvalue = identifier.constValue(program) ?: return noModifications
val arrayIdx = identifier.parent as? ArrayIndexedExpression
if(arrayIdx!=null && cval.type.isNumeric) {
if(arrayIdx!=null && cvalue.type.isNumeric) {
// special case when the identifier is used as a pointer var
// var = constpointer[x] --> var = @(constvalue+x) [directmemoryread]
// constpointer[x] = var -> @(constvalue+x) [directmemorywrite] = var
val add = BinaryExpression(NumericLiteral(cval.type, cval.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
val dt = if(cvalue.type == BaseDataType.LONG) NumericLiteral.optimalInteger(cvalue.number.toInt(), cvalue.position).type else cvalue.type
val add = BinaryExpression(NumericLiteral(dt, cvalue.number, identifier.position), "+", arrayIdx.indexer.indexExpr, identifier.position)
return if(arrayIdx.parent is AssignTarget) {
val memwrite = DirectMemoryWrite(add, identifier.position)
val assignTarget = AssignTarget(null, null, memwrite, null, false, identifier.position)
@ -331,18 +332,19 @@ internal class ConstantIdentifierReplacer(
}
}
when {
cval.type.isNumericOrBool -> {
cvalue.type.isNumericOrBool -> {
if(parent is AddressOf)
return noModifications // cannot replace the identifier INSIDE the addr-of here, let's do it later.
val dt = if(cvalue.type == BaseDataType.LONG) NumericLiteral.optimalInteger(cvalue.number.toInt(), cvalue.position).type else cvalue.type
return listOf(
IAstModification.ReplaceNode(
identifier,
NumericLiteral(cval.type, cval.number, identifier.position),
NumericLiteral(dt, cvalue.number, identifier.position),
identifier.parent
)
)
}
cval.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
cvalue.type.isPassByRef -> throw InternalCompilerException("pass-by-reference type should not be considered a constant")
else -> return noModifications
}
} catch (x: UndefinedSymbolError) {
@ -452,15 +454,20 @@ internal class ConstantIdentifierReplacer(
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
val rangeType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
return if(rangeType.isByte) {
val rangeType2 = rangeExpr.inferType(program).getOr(DataType.UBYTE)
return if(rangeType2.isByte) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(rangeType.base, it.toDouble(), decl.value!!.position) }.toTypedArray(),
constRange.map { NumericLiteral(rangeType2.base, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
require(rangeType.sub!=null)
require(rangeType2.sub!=null)
val subDt = if(rangeType2.sub == BaseDataType.LONG) {
val first = NumericLiteral.optimalInteger(constRange.first, Position.DUMMY).type
val last = NumericLiteral.optimalInteger(constRange.last, Position.DUMMY).type
if(last.largerSizeThan(first)) last else first
} else rangeType2.sub!!
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(rangeType.sub!!, it.toDouble(), decl.value!!.position) }.toTypedArray(),
constRange.map { NumericLiteral(subDt, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}

View File

@ -76,7 +76,7 @@ internal class AstChecker(private val program: Program,
}
}
checkLongType(identifier)
checkResidualLongType(identifier)
val stmt = identifier.targetStatement(program)
if(stmt==null)
errors.undefined(identifier.nameInSource, identifier.position)
@ -328,7 +328,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(numLiteral: NumericLiteral) {
checkLongType(numLiteral)
checkResidualLongType(numLiteral)
}
private fun hasReturnOrExternalJumpOrRts(scope: IStatementContainer): Boolean {
@ -709,7 +709,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(addressOf: AddressOf) {
checkLongType(addressOf)
checkResidualLongType(addressOf)
val variable=addressOf.identifier.targetVarDecl()
if (variable!=null) {
if (variable.type == VarDeclType.CONST && addressOf.arrayIndex == null)
@ -1164,7 +1164,7 @@ internal class AstChecker(private val program: Program,
}
}
checkLongType(expr)
checkResidualLongType(expr)
val dt = expr.expression.inferType(program).getOrUndef()
if(!dt.isUndefined) {
when (expr.operator) {
@ -1221,7 +1221,7 @@ internal class AstChecker(private val program: Program,
override fun visit(expr: BinaryExpression) {
super.visit(expr)
checkLongType(expr)
checkResidualLongType(expr)
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
@ -1333,7 +1333,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(typecast: TypecastExpression) {
checkLongType(typecast)
checkResidualLongType(typecast)
if(typecast.type.isIterable)
errors.err("cannot type cast to string or array type", typecast.position)
@ -1384,7 +1384,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(functionCallExpr: FunctionCallExpression) {
checkLongType(functionCallExpr)
checkResidualLongType(functionCallExpr)
// this function call is (part of) an expression, which should be in a statement somewhere.
val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
@ -1601,7 +1601,7 @@ internal class AstChecker(private val program: Program,
}
args.forEach{
checkLongType(it)
checkResidualLongType(it)
}
}
@ -1612,7 +1612,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
checkLongType(arrayIndexedExpression)
checkResidualLongType(arrayIndexedExpression)
val target = arrayIndexedExpression.arrayvar.targetStatement(program)
if(target is VarDecl) {
if(!target.datatype.isIterable && !target.datatype.isUnsignedWord)
@ -1779,12 +1779,15 @@ internal class AstChecker(private val program: Program,
errors.err("%asm containing IR code cannot be translated to 6502 assembly", inlineAssembly.position)
}
private fun checkLongType(expression: Expression) {
private fun checkResidualLongType(expression: Expression) {
if(expression.inferType(program) issimpletype BaseDataType.LONG) {
if((expression.parent as? VarDecl)?.type!=VarDeclType.CONST) {
if (expression.parent !is RepeatLoop) {
if (errors.noErrorForLine(expression.position))
errors.err("integer overflow", expression.position)
val constvalue = expression.constValue(program)
if(constvalue==null || constvalue.number>65535 || constvalue.number <= -32768) {
if (expression.parent !is RepeatLoop) {
if (errors.noErrorForLine(expression.position))
errors.err("integer overflow", expression.position)
}
}
}
}

View File

@ -186,6 +186,23 @@ class AstPreprocessor(val program: Program,
return makeUnSplitArray(decl)
}
if(decl.type == VarDeclType.CONST && decl.datatype.isInteger && !decl.datatype.isLong) {
// All const vardecls are changed to LONG type, but ONLY if their scope is not an anonymous scope.
// This is because elsewhere in this processor, variables from anonymous scopes are first moved
// up to the subroutine scope, and this would otherwise interfere in the same modification iteration.
if(parent !is AnonymousScope) {
errors.info("byte or word const (deprecated) converted to long const", decl.position)
val longdecl = decl.copy(DataType.forDt(BaseDataType.LONG))
val declvalue = decl.value!!.constValue(program)
if(declvalue!=null) {
val longvalue = NumericLiteral(BaseDataType.LONG, declvalue.number, declvalue.position)
longdecl.value = longvalue
longvalue.linkParents(longdecl)
}
return listOf(IAstModification.ReplaceNode(decl, longdecl, parent))
}
}
return noModifications
}

View File

@ -165,24 +165,32 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
}
}
// if one of the operands is a long const, convert to a smaller type const if possible
val constLeft = expr.left.constValue(program)
val constRight = expr.right.constValue(program)
if(constLeft?.type == BaseDataType.LONG) {
val small1 = NumericLiteral.optimalInteger(constLeft.number.toInt(), constLeft.position)
var small = NumericLiteral.optimalInteger(small1.type, rightDt.getOrUndef().base, small1.number.toInt(), small1.position)
val (commonDt, toFix) = BinaryExpression.commonDatatype(DataType.forDt(small.type), rightDt.getOrUndef(), small, expr.right)
if(toFix != null && toFix===small) {
small = NumericLiteral(commonDt.base, small.number, small.position)
}
return listOf(IAstModification.ReplaceNode(expr.left, small, expr))
}
if(constRight?.type == BaseDataType.LONG) {
val small1 = NumericLiteral.optimalInteger(constRight.number.toInt(), constRight.position)
var small = NumericLiteral.optimalInteger(small1.type, leftDt.getOrUndef().base, small1.number.toInt(), small1.position)
val (commonDt, toFix) = BinaryExpression.commonDatatype(DataType.forDt(small.type), leftDt.getOrUndef(), small, expr.left)
if(toFix != null && toFix===small) {
small = NumericLiteral(commonDt.base, small.number, small.position)
}
return listOf(IAstModification.ReplaceNode(expr.right, small, expr))
}
if((expr.operator!="<<" && expr.operator!=">>") || !leftDt.isWords || !rightDt.isBytes) {
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOrUndef(), rightDt.getOrUndef(), expr.left, expr.right)
if(toFix!=null) {
if(commonDt.isBool) {
// don't automatically cast to bool
errors.err("left and right operands aren't the same type: $leftDt vs $rightDt", expr.position)
} else {
val modifications = mutableListOf<IAstModification>()
when {
toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt.base, expr)
toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt.base, expr)
else -> throw FatalAstException("confused binary expression side")
}
return modifications
}
}
val modifications = makeCommonDt(leftDt.getOrUndef(), rightDt.getOrUndef(), expr)
if(modifications.isNotEmpty())
return modifications
}
}
@ -200,6 +208,26 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return noModifications
}
private fun makeCommonDt(leftDt: DataType, rightDt: DataType, expr: BinaryExpression): List<IAstModification> {
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt, rightDt, expr.left, expr.right)
if(toFix!=null) {
if(commonDt.isBool) {
// don't automatically cast to bool
errors.err("left and right operands aren't the same type: $leftDt vs $rightDt", expr.position)
} else {
val modifications = mutableListOf<IAstModification>()
when {
toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt.base, expr)
toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt.base, expr)
else -> throw FatalAstException("confused binary expression side")
}
return modifications
}
}
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// see if a typecast is needed to convert the value's type into the proper target type
val valueItype = assignment.value.inferType(program)

View File

@ -53,8 +53,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
} else {
// don't make it signed if it was unsigned and vice versa
if(valueDt.isSigned && decl.datatype.isUnsigned ||
valueDt.isUnsigned && decl.datatype.isSigned) {
if(!decl.datatype.isLong && (valueDt.isSigned && decl.datatype.isUnsigned || valueDt.isUnsigned && decl.datatype.isSigned)) {
val constValue = decl.value!!.constValue(program)!!
errors.err("value '${constValue.number}' out of range for ${decl.datatype}", constValue.position)
} else {
@ -63,8 +62,14 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
}
}
}
VarDeclType.MEMORY -> if(!valueType.isWords && !valueType.isBytes)
throw FatalAstException("value type for a memory var should be word or byte (address)")
VarDeclType.MEMORY -> {
if(!valueType.isWords && !valueType.isBytes) {
val constVal = decl.value?.constValue(program)
if(constVal == null || constVal.number>65535) {
throw FatalAstException("value type for a memory var should be word or byte (address) ${decl.position}")
}
}
}
}
}

View File

@ -131,9 +131,10 @@ class TestNumbers: FunSpec({
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target(), true, src, outputDir, writeAssembly = false, errors=errors) shouldNotBe null
errors.errors.size shouldBe 0
errors.infos.size shouldBe 2
errors.infos[0] shouldContain "converted to float"
errors.infos[1] shouldContain "converted to float"
val nondeprecations = errors.infos.filter { "deprecat" !in it }
nondeprecations.size shouldBe 2
nondeprecations[0] shouldContain "converted to float"
nondeprecations[1] shouldContain "converted to float"
}
test("implicit float conversion error if not enabled") {

View File

@ -31,9 +31,10 @@ class TestAstChecks: FunSpec({
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target(), true, text, outputDir, writeAssembly = true, errors=errors) shouldNotBe null
errors.errors.size shouldBe 0
errors.infos.size shouldBe 2
errors.infos[0] shouldContain "converted to float"
errors.infos[1] shouldContain "converted to float"
val nondeprecations = errors.infos.filter { "deprecat" !in it }
nondeprecations.size shouldBe 2
nondeprecations[0] shouldContain "converted to float"
nondeprecations[1] shouldContain "converted to float"
}
test("can't assign label or subroutine without using address-of") {

View File

@ -2,6 +2,7 @@ package prog8tests.ast
import io.kotest.core.spec.style.FunSpec
import io.kotest.engine.spec.tempdir
import io.kotest.matchers.comparables.shouldBeGreaterThanOrEqualTo
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
@ -17,13 +18,14 @@ import prog8.code.core.BaseDataType
import prog8.code.core.Position
import prog8.code.target.C64Target
import prog8.code.target.Cx16Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.compileText
class TestConst: FunSpec({
val outputDir = tempdir().toPath()
test("const folding multiple scenarios +/-") {
val source = """
main {
@ -327,7 +329,7 @@ main {
assignAddr2.operator shouldBe "+"
}
test("out of range const byte and word give correct error") {
test("deprecated typed consts get converted with message") {
var src="""
main {
sub start() {
@ -335,16 +337,25 @@ main {
const word MIN_WORD = -32769
const byte MAX_BYTE = 128
const word MAX_WORD = 32768
const long MAX_LONG = 999999999
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), true, src, outputDir, writeAssembly = false, errors=errors) shouldBe null
errors.errors.size shouldBe 4
errors.errors[0] shouldContain "out of range"
errors.errors[1] shouldContain "out of range"
errors.errors[2] shouldContain "out of range"
errors.errors[3] shouldContain "out of range"
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
val result = compileText(VMTarget(), true, src, outputDir, writeAssembly = false, errors=errors)!!
val st = result.compilerAst.entrypoint.statements
st.size shouldBe 5
(st[0] as VarDecl).datatype.isLong shouldBe true
(st[1] as VarDecl).datatype.isLong shouldBe true
(st[2] as VarDecl).datatype.isLong shouldBe true
(st[3] as VarDecl).datatype.isLong shouldBe true
(st[4] as VarDecl).datatype.isLong shouldBe true
errors.errors.size shouldBe 0
errors.infos.size shouldBeGreaterThanOrEqualTo 4
errors.infos[0] shouldContain "converted to long"
errors.infos[1] shouldContain "converted to long"
errors.infos[2] shouldContain "converted to long"
errors.infos[3] shouldContain "converted to long"
}
test("out of range var byte and word give correct error") {
@ -466,4 +477,19 @@ main {
compileText(C64Target(), false, src, outputDir, writeAssembly = false) shouldNotBe null
}
test("const in scope ok") {
val src="""
main {
sub start() {
repeat {
const ubyte MAX_WORDS = 8
cx16.r0L = MAX_WORDS
}
}
}"""
compileText(C64Target(), true, src, outputDir, writeAssembly = false) shouldNotBe null
}
})

View File

@ -403,7 +403,8 @@ main {
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
val result = compileText(C64Target(), optimize=false, src, outputDir, writeAssembly=false, errors=errors)!!
errors.warnings.size shouldBe 6
errors.infos.size shouldBe 0
val nondeprecations = errors.infos.filter { "deprecat" !in it }
nondeprecations.size shouldBe 0
errors.warnings.all { "dirty variable" in it } shouldBe true
val start = result.compilerAst.entrypoint
val st = start.statements

View File

@ -167,7 +167,7 @@ class BinaryExpression(
when {
node===left -> left = replacement
node===right -> right = replacement
else -> throw FatalAstException("invalid replace, no child $node")
else -> throw FatalAstException("invalid replace in $this, no child $node")
}
replacement.parent = this
}
@ -434,7 +434,7 @@ data class AddressOf(var identifier: IdentifierReference, var arrayIndex: ArrayI
arrayIndex = replacement
replacement.parent = this
} else {
throw FatalAstException("invalid replace, no child node $node")
throw FatalAstException("invalid replace in $this, no child node $node")
}
}
@ -453,8 +453,9 @@ data class AddressOf(var identifier: IdentifierReference, var arrayIndex: ArrayI
if (index != null) {
address += when {
target.datatype.isUnsignedWord -> index
target.datatype.isLong -> index
target.datatype.isArray -> program.memsizer.memorySize(targetVar.datatype, index)
else -> throw FatalAstException("need array or uword ptr")
else -> throw FatalAstException("need array or uword ptr, got ${target.datatype}")
}
} else
return null
@ -588,6 +589,7 @@ class NumericLiteral(val type: BaseDataType, // only numerical types allowed
BaseDataType.BOOL -> {}
BaseDataType.UBYTE -> largestOrig = BaseDataType.BYTE
BaseDataType.UWORD -> largestOrig = BaseDataType.WORD
BaseDataType.LONG -> largestOrig = BaseDataType.LONG
else -> throw FatalAstException("invalid dt")
}
}

View File

@ -56,6 +56,7 @@ object InferredTypes {
val isBool = datatype?.isBool==true
val isBytes = datatype?.isByte==true
val isWords = datatype?.isWord==true
val isLong = datatype?.isLong==true
val isInteger = datatype?.isInteger==true
val isNumeric = datatype?.isNumeric==true
val isNumericOrBool = datatype?.isNumericOrBool==true

View File

@ -1,6 +1,13 @@
TODO
====
- swirl example (and rockrunner) is broken with long const now (code is missing?)
- const values should always either be of type long or float, this is how they were usually treated in const expression evaluation already anyway
TODO: add new syntax where const declarations don't need the type anymore (and it's always set to long in the parser)
BUT!!! how to deal with variables turned into constants? Can't be LONG constants, it will fuck up the type restriction system (types shouldn't grow)
STRUCTS: are being developed in their own separate branch for now, called "structs".
Idea is to make it feature complete in the IR/Virtual target, then merge it to master?, and then start building the 6502 code generation for it.
@ -17,7 +24,13 @@ Future Things and Ideas
- Kotlin: can we use inline value classes in certain spots?
- add float support to the configurable compiler targets
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
- Change scoping rules for qualified symbols so that they don't always start from the root but behave like other programming languages (look in local scope first), maybe only when qualified symbol starts with '.' such as: .local.value = 33
- [problematic due to using 64tass:] better support for building library programs, where unused .proc are NOT deleted from the assembly.
Perhaps replace all uses of .proc/.pend/.endproc by .block/.bend will fix that with a compiler flag?
But all library code written in asm uses .proc already..... (textual search/replace when writing the actual asm?)
Maybe propose a patch to 64tass itself that will treat .proc as .block ?
Once new codegen is written that is based on the IR, this point is mostly moot anyway as that will have its own dead code removal on the IR level.
- Change scoping rules for qualified symbols so that they don't always start from the root but behave like other programming languages (look in local scope first)
- something to reduce the need to use fully qualified names all the time. 'with' ? Or 'using <prefix>'?
- Improve register load order in subroutine call args assignments:
in certain situations (need examples!), the "wrong" order of evaluation of function call arguments is done which results
@ -25,11 +38,10 @@ Future Things and Ideas
Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEvaluating() and argumentsViaRegisters().
- Does it make codegen easier if everything is an expression? Start with the PtProgram ast classes, change statements to expressions that have (new) VOID data type
- Can we support signed % (remainder) somehow?
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful if we have typed pointers. (addressed in 'struct' branch)
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful if we have typed pointers.
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
(this is already done hardcoded for several of the builtin functions)
- [much work:] more support for (64tass) SEGMENTS in the prog8 syntax itself?
- ability to use a sub instead of only a var for @bank ? what for though? dynamic bank/overlay loading?
- [much work:] more support for (64tass) SEGMENTS ?
- Zig-like try-based error handling where the V flag could indicate error condition? and/or BRK to jump into monitor on failure? (has to set BRK vector for that) But the V flag is also set on certain normal instructions
@ -66,6 +78,7 @@ Libraries
- pet32 target: make syslib more complete (missing kernal routines)?
- need help with: PET disk routines (OPEN, SETLFS etc are not exposed as kernal calls)
- c128 target: make syslib more complete (missing kernal routines)?
- VM: implement the last diskio support (file listings)
Optimizations
@ -73,7 +86,22 @@ Optimizations
- Compilation speed: try to join multiple modifications in 1 result in the AST processors instead of returning it straight away every time
- Compare output of some Oscar64 samples to what prog8 does for the equivalent code (see https://github.com/drmortalwombat/OscarTutorials/tree/main and https://github.com/drmortalwombat/oscar64/tree/main/samples)
- Multi-value returns of normal subroutines: Can FAC then be used for floats as well again? Those are now not supported for multi-value returns.
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression"
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
for instance, vars used inside loops first, then loopvars, then uwords used as pointers (or these first??), then the rest
- various optimizers skip stuff if compTarget.name==VMTarget.NAME. Once 6502-codegen is done from IR code, those checks should probably be removed, or be made permanent
STRUCTS?
--------
- declare struct *type*, or directly declare the variable itself? Problem with the latter is: you cannot easily define multiple variables of the same struct type.
- can contain only numeric types (byte,word,float) - no nested structs, no reference types (strings, arrays) inside structs
- only as a reference type (uword pointer). This removes a lot of the problems related to introducing a variable length value type.
- arrays of struct is just an array of uword pointers. Can even be @split?
- need to introduce typed pointer datatype in prog8
- STR remains the type for a string literal (so we can keep doing register-indexed addressing directly on it)
- ARRAY remains the type for an array literal (so we can keep doing register-indexed addressing directly on it)
- we probably need to have a STRBYREF and ARRAYBYREF if we deal with a pointer to a string / array (such as when passing it to a function)
the subtype of those should include the declared element type and the declared length of the string / array

View File

@ -1,800 +1,26 @@
%import textio
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
txt.plot(0, 49)
bytesoverflow()
bytesoverflow_jump()
bytesoverflow_jump_indirect()
bytessmall()
bytessmall_jump()
bytessmall_jump_indirect()
bytes99()
bytes100()
bytes101()
words()
zerobytes()
zerowords()
}
const uword screenwidth = 80
const ubyte ten = 10
ubyte @shared vten = 10
sub zerobytes() {
byte @shared sb = -100
byte @shared p = 0
const byte cb = -100
txt.print("\nsigned bytes with 0\n")
txt.print("expected: ")
txt.print_b(cb)
txt.spc()
txt.print_bool(cb>0)
txt.spc()
txt.print_bool(cb>=0)
txt.spc()
txt.print_bool(cb<0)
txt.spc()
txt.print_bool(cb<=0)
sub start() {
; TODO should print 3 , 3
cx16.r0L = msb(vten * screenwidth) ; TODO in main , vten is casted to uword
txt.print_ub(cx16.r0L)
txt.nl()
txt.print_ub(msb(vten * screenwidth)) ; TODO in main, vten is casted to uword
txt.nl()
txt.print(" calc'd: ")
txt.print_b(sb)
txt.spc()
txt.print_bool(sb>0)
txt.spc()
txt.print_bool(sb>=0)
txt.spc()
txt.print_bool(sb<0)
txt.spc()
txt.print_bool(sb<=0)
txt.nl()
; ok; prints 0, 0
; cx16.r0L = msb(vten * 80)
; txt.print_ub(cx16.r0L)
; txt.nl()
; txt.print_ub(msb(vten * 80))
; txt.nl()
txt.print("calc'd 2: ")
txt.print_b(sb)
txt.spc()
txt.print_bool(sb>p)
txt.spc()
txt.print_bool(sb>=p)
txt.spc()
txt.print_bool(sb<p)
txt.spc()
txt.print_bool(sb<=p)
txt.nl()
txt.print(" if stmt: ")
txt.print_b(sb)
txt.spc()
if sb>0 txt.print("true ") else txt.print("false ")
if sb>=0 txt.print("true ") else txt.print("false ")
if sb<0 txt.print("true ") else txt.print("false ")
if sb<=0 txt.print("true ") else txt.print("false ")
txt.nl()
txt.print(" ifstmt2: ")
txt.print_b(sb)
txt.spc()
if sb>p txt.print("true ") else txt.print("false ")
if sb>=p txt.print("true ") else txt.print("false ")
if sb<p txt.print("true ") else txt.print("false ")
if sb<=p txt.print("true ") else txt.print("false ")
txt.nl()
}
sub zerowords() {
word @shared sbw = -30000
word @shared pw = 0
const word cbw = -30000
txt.print("\nsigned words\n")
txt.print("expected: ")
txt.print_w(cbw)
txt.spc()
txt.print_bool(cbw>0)
txt.spc()
txt.print_bool(cbw>=0)
txt.spc()
txt.print_bool(cbw<0)
txt.spc()
txt.print_bool(cbw<=0)
txt.nl()
txt.print(" calc'd: ")
txt.print_w(sbw)
txt.spc()
txt.print_bool(sbw>0)
txt.spc()
txt.print_bool(sbw>=0)
txt.spc()
txt.print_bool(sbw<0)
txt.spc()
txt.print_bool(sbw<=0)
txt.nl()
txt.print("calc'd 2: ")
txt.print_w(sbw)
txt.spc()
txt.print_bool(sbw>pw)
txt.spc()
txt.print_bool(sbw>=pw)
txt.spc()
txt.print_bool(sbw<pw)
txt.spc()
txt.print_bool(sbw<=pw)
txt.nl()
txt.print(" if stmt: ")
txt.print_w(sbw)
txt.spc()
if sbw>0 txt.print("true ") else txt.print("false ")
if sbw>=0 txt.print("true ") else txt.print("false ")
if sbw<0 txt.print("true ") else txt.print("false ")
if sbw<=0 txt.print("true ") else txt.print("false ")
txt.nl()
txt.print(" ifstmt2: ")
txt.print_w(sbw)
txt.spc()
if sbw>pw txt.print("true ") else txt.print("false ")
if sbw>=pw txt.print("true ") else txt.print("false ")
if sbw<pw txt.print("true ") else txt.print("false ")
if sbw<=pw txt.print("true ") else txt.print("false ")
txt.nl()
}
sub bytes99() {
const byte cb = 99
byte @shared sb = 99
byte @shared p = 100
txt.print("\nsigned bytes, 99\n")
txt.print("expected: ")
txt.print_b(100)
txt.spc()
txt.print_bool(cb>100)
txt.spc()
txt.print_bool(cb>=100)
txt.spc()
txt.print_bool(cb<100)
txt.spc()
txt.print_bool(cb<=100)
txt.nl()
txt.print(" calc'd: ")
txt.print_b(100)
txt.spc()
txt.print_bool(sb>100)
txt.spc()
txt.print_bool(sb>=100)
txt.spc()
txt.print_bool(sb<100)
txt.spc()
txt.print_bool(sb<=100)
txt.nl()
txt.print("calc'd 2: ")
txt.print_b(p)
txt.spc()
txt.print_bool(sb>p)
txt.spc()
txt.print_bool(sb>=p)
txt.spc()
txt.print_bool(sb<p)
txt.spc()
txt.print_bool(sb<=p)
txt.nl()
txt.print(" if stmt: ")
txt.print_b(100)
txt.spc()
if sb>100 txt.print("true ") else txt.print("false ")
if sb>=100 txt.print("true ") else txt.print("false ")
if sb<100 txt.print("true ") else txt.print("false ")
if sb<=100 txt.print("true ") else txt.print("false ")
txt.nl()
txt.print(" ifstmt2: ")
txt.print_b(p)
txt.spc()
if sb>p txt.print("true ") else txt.print("false ")
if sb>=p txt.print("true ") else txt.print("false ")
if sb<p txt.print("true ") else txt.print("false ")
if sb<=p txt.print("true ") else txt.print("false ")
txt.nl()
}
sub bytes100() {
const byte cb = 100
byte @shared sb = 100
byte @shared p = 100
txt.print("\nsigned bytes, 100\n")
txt.print("expected: ")
txt.print_b(100)
txt.spc()
txt.print_bool(cb>100)
txt.spc()
txt.print_bool(cb>=100)
txt.spc()
txt.print_bool(cb<100)
txt.spc()
txt.print_bool(cb<=100)
txt.nl()
txt.print(" calc'd: ")
txt.print_b(100)
txt.spc()
txt.print_bool(sb>100)
txt.spc()
txt.print_bool(sb>=100)
txt.spc()
txt.print_bool(sb<100)
txt.spc()
txt.print_bool(sb<=100)
txt.nl()
txt.print("calc'd 2: ")
txt.print_b(p)
txt.spc()
txt.print_bool(sb>p)
txt.spc()
txt.print_bool(sb>=p)
txt.spc()
txt.print_bool(sb<p)
txt.spc()
txt.print_bool(sb<=p)
txt.nl()
txt.print(" if stmt: ")
txt.print_b(100)
txt.spc()
if sb>100 txt.print("true ") else txt.print("false ")
if sb>=100 txt.print("true ") else txt.print("false ")
if sb<100 txt.print("true ") else txt.print("false ")
if sb<=100 txt.print("true ") else txt.print("false ")
txt.nl()
txt.print(" ifstmt2: ")
txt.print_b(p)
txt.spc()
if sb>p txt.print("true ") else txt.print("false ")
if sb>=p txt.print("true ") else txt.print("false ")
if sb<p txt.print("true ") else txt.print("false ")
if sb<=p txt.print("true ") else txt.print("false ")
txt.nl()
}
sub bytes101() {
const byte cb = 101
byte @shared sb = 101
byte @shared p = 100
txt.print("\nsigned bytes, 101\n")
txt.print("expected: ")
txt.print_b(100)
txt.spc()
txt.print_bool(cb>100)
txt.spc()
txt.print_bool(cb>=100)
txt.spc()
txt.print_bool(cb<100)
txt.spc()
txt.print_bool(cb<=100)
txt.nl()
txt.print(" calc'd: ")
txt.print_b(100)
txt.spc()
txt.print_bool(sb>100)
txt.spc()
txt.print_bool(sb>=100)
txt.spc()
txt.print_bool(sb<100)
txt.spc()
txt.print_bool(sb<=100)
txt.nl()
txt.print("calc'd 2: ")
txt.print_b(p)
txt.spc()
txt.print_bool(sb>p)
txt.spc()
txt.print_bool(sb>=p)
txt.spc()
txt.print_bool(sb<p)
txt.spc()
txt.print_bool(sb<=p)
txt.nl()
txt.print(" if stmt: ")
txt.print_b(100)
txt.spc()
if sb>100 txt.print("true ") else txt.print("false ")
if sb>=100 txt.print("true ") else txt.print("false ")
if sb<100 txt.print("true ") else txt.print("false ")
if sb<=100 txt.print("true ") else txt.print("false ")
txt.nl()
txt.print(" ifstmt2: ")
txt.print_b(p)
txt.spc()
if sb>p txt.print("true ") else txt.print("false ")
if sb>=p txt.print("true ") else txt.print("false ")
if sb<p txt.print("true ") else txt.print("false ")
if sb<=p txt.print("true ") else txt.print("false ")
txt.nl()
}
sub bytesoverflow() {
byte @shared sb = -100
byte @shared p = 100
const byte cb = -100
txt.print("\nsigned bytes, overflow\n")
txt.print("expected: ")
txt.print_b(100)
txt.spc()
txt.print_bool(cb>100)
txt.spc()
txt.print_bool(cb>=100)
txt.spc()
txt.print_bool(cb<100)
txt.spc()
txt.print_bool(cb<=100)
txt.nl()
txt.print(" calc'd: ")
txt.print_b(100)
txt.spc()
txt.print_bool(sb>100)
txt.spc()
txt.print_bool(sb>=100)
txt.spc()
txt.print_bool(sb<100)
txt.spc()
txt.print_bool(sb<=100)
txt.nl()
txt.print("calc'd 2: ")
txt.print_b(p)
txt.spc()
txt.print_bool(sb>p)
txt.spc()
txt.print_bool(sb>=p)
txt.spc()
txt.print_bool(sb<p)
txt.spc()
txt.print_bool(sb<=p)
txt.nl()
txt.print(" if stmt: ")
txt.print_b(100)
txt.spc()
if sb>100 txt.print("true ") else txt.print("false ")
if sb>=100 txt.print("true ") else txt.print("false ")
if sb<100 txt.print("true ") else txt.print("false ")
if sb<=100 txt.print("true ") else txt.print("false ")
txt.nl()
txt.print(" ifstmt2: ")
txt.print_b(p)
txt.spc()
if sb>p txt.print("true ") else txt.print("false ")
if sb>=p txt.print("true ") else txt.print("false ")
if sb<p txt.print("true ") else txt.print("false ")
if sb<=p txt.print("true ") else txt.print("false ")
txt.nl()
}
sub bytessmall() {
byte @shared sb = -10
byte @shared p = 10
const byte cb = -10
txt.print("\nsigned bytes, small value\n")
txt.print("expected: ")
txt.print_b(10)
txt.spc()
txt.print_bool(cb>10)
txt.spc()
txt.print_bool(cb>=10)
txt.spc()
txt.print_bool(cb<10)
txt.spc()
txt.print_bool(cb<=10)
txt.nl()
txt.print(" calc'd: ")
txt.print_b(10)
txt.spc()
txt.print_bool(sb>10)
txt.spc()
txt.print_bool(sb>=10)
txt.spc()
txt.print_bool(sb<10)
txt.spc()
txt.print_bool(sb<=10)
txt.nl()
txt.print("calc'd 2: ")
txt.print_b(p)
txt.spc()
txt.print_bool(sb>p)
txt.spc()
txt.print_bool(sb>=p)
txt.spc()
txt.print_bool(sb<p)
txt.spc()
txt.print_bool(sb<=p)
txt.nl()
txt.print(" if stmt: ")
txt.print_b(10)
txt.spc()
if sb>10 txt.print("true ") else txt.print("false ")
if sb>=10 txt.print("true ") else txt.print("false ")
if sb<10 txt.print("true ") else txt.print("false ")
if sb<=10 txt.print("true ") else txt.print("false ")
txt.nl()
txt.print(" ifstmt2: ")
txt.print_b(p)
txt.spc()
if sb>p txt.print("true ") else txt.print("false ")
if sb>=p txt.print("true ") else txt.print("false ")
if sb<p txt.print("true ") else txt.print("false ")
if sb<=p txt.print("true ") else txt.print("false ")
txt.nl()
}
sub bytesoverflow_jump() {
byte @shared sb = -100
byte @shared p = 100
const byte cb = -100
txt.print("\nsigned bytes, overflow, jmp after if\n")
txt.print("expected: ")
txt.print_b(100)
txt.spc()
txt.print_bool(cb>100)
txt.spc()
txt.print_bool(cb>=100)
txt.spc()
txt.print_bool(cb<100)
txt.spc()
txt.print_bool(cb<=100)
txt.nl()
txt.print(" calc'd: ")
txt.print_b(100)
txt.spc()
if sb>100 goto jump1
else { txt.print("false ") goto next1 }
jump1:
txt.print("true ")
next1:
if sb>=100 goto jump2
else { txt.print("false ") goto next2 }
jump2:
txt.print("true ")
next2:
if sb<100 goto jump3
else { txt.print("false ") goto next3 }
jump3:
txt.print("true ")
next3:
if sb<=100 goto jump4
else { txt.print("false ") goto next4 }
jump4:
txt.print("true ")
next4:
txt.nl()
txt.print("calc'd 2: ")
txt.print_b(p)
txt.spc()
if sb>p goto jump1b
else { txt.print("false ") goto next1b }
jump1b:
txt.print("true ")
next1b:
if sb>=p goto jump2b
else { txt.print("false ") goto next2b }
jump2b:
txt.print("true ")
next2b:
if sb<p goto jump3b
else { txt.print("false ") goto next3b }
jump3b:
txt.print("true ")
next3b:
if sb<=p goto jump4b
else { txt.print("false ") goto next4b }
jump4b:
txt.print("true ")
next4b:
txt.nl()
}
sub bytesoverflow_jump_indirect() {
byte @shared sb = -100
byte @shared p = 100
const byte cb = -100
txt.print("\nsigned bytes, overflow, jmp indirect after if\n")
txt.print("expected: ")
txt.print_b(100)
txt.spc()
txt.print_bool(cb>100)
txt.spc()
txt.print_bool(cb>=100)
txt.spc()
txt.print_bool(cb<100)
txt.spc()
txt.print_bool(cb<=100)
txt.nl()
txt.print(" calc'd: ")
txt.print_b(100)
txt.spc()
uword tgt = &jump1
if sb>100 goto tgt
else { txt.print("false ") goto next1 }
jump1:
txt.print("true ")
next1:
tgt = &jump2
if sb>=100 goto tgt
else { txt.print("false ") goto next2 }
jump2:
txt.print("true ")
next2:
tgt = &jump3
if sb<100 goto tgt
else { txt.print("false ") goto next3 }
jump3:
txt.print("true ")
next3:
tgt = &jump4
if sb<=100 goto tgt
else { txt.print("false ") goto next4 }
jump4:
txt.print("true ")
next4:
txt.nl()
txt.print("calc'd 2: ")
txt.print_b(p)
txt.spc()
tgt = &jump1b
if sb>p goto tgt
else { txt.print("false ") goto next1b }
jump1b:
txt.print("true ")
next1b:
tgt = &jump2b
if sb>=p goto tgt
else { txt.print("false ") goto next2b }
jump2b:
txt.print("true ")
next2b:
tgt = &jump3b
if sb<p goto tgt
else { txt.print("false ") goto next3b }
jump3b:
txt.print("true ")
next3b:
tgt = &jump4b
if sb<=p goto tgt
else { txt.print("false ") goto next4b }
jump4b:
txt.print("true ")
next4b:
txt.nl()
}
sub bytessmall_jump() {
byte @shared sb = -10
byte @shared p = 10
const byte cb = -10
txt.print("\nsigned bytes, small value, jmp after if\n")
txt.print("expected: ")
txt.print_b(10)
txt.spc()
txt.print_bool(cb>10)
txt.spc()
txt.print_bool(cb>=10)
txt.spc()
txt.print_bool(cb<10)
txt.spc()
txt.print_bool(cb<=10)
txt.nl()
txt.print(" calc'd: ")
txt.print_b(10)
txt.spc()
if sb>10 goto jump1
else { txt.print("false ") goto next1 }
jump1:
txt.print("true ")
next1:
if sb>=10 goto jump2
else { txt.print("false ") goto next2 }
jump2:
txt.print("true ")
next2:
if sb<10 goto jump3
else { txt.print("false ") goto next3 }
jump3:
txt.print("true ")
next3:
if sb<=10 goto jump4
else { txt.print("false ") goto next4 }
jump4:
txt.print("true ")
next4:
txt.nl()
txt.print("calc'd 2: ")
txt.print_b(p)
txt.spc()
if sb>p goto jump1b
else { txt.print("false ") goto next1b }
jump1b:
txt.print("true ")
next1b:
if sb>=p goto jump2b
else { txt.print("false ") goto next2b }
jump2b:
txt.print("true ")
next2b:
if sb<p goto jump3b
else { txt.print("false ") goto next3b }
jump3b:
txt.print("true ")
next3b:
if sb<=p goto jump4b
else { txt.print("false ") goto next4b }
jump4b:
txt.print("true ")
next4b:
txt.nl()
}
sub bytessmall_jump_indirect() {
byte @shared sb = -10
byte @shared p = 10
const byte cb = -10
txt.print("\nsigned bytes, small value, jmp indirect after if\n")
txt.print("expected: ")
txt.print_b(10)
txt.spc()
txt.print_bool(cb>10)
txt.spc()
txt.print_bool(cb>=10)
txt.spc()
txt.print_bool(cb<10)
txt.spc()
txt.print_bool(cb<=10)
txt.nl()
txt.print(" calc'd: ")
txt.print_b(10)
txt.spc()
uword tgt = &jump1
if sb>10 goto tgt
else { txt.print("false ") goto next1 }
jump1:
txt.print("true ")
next1:
tgt = &jump2
if sb>=10 goto tgt
else { txt.print("false ") goto next2 }
jump2:
txt.print("true ")
next2:
tgt = &jump3
if sb<10 goto tgt
else { txt.print("false ") goto next3 }
jump3:
txt.print("true ")
next3:
tgt = &jump4
if sb<=10 goto tgt
else { txt.print("false ") goto next4 }
jump4:
txt.print("true ")
next4:
txt.nl()
txt.print("calc'd 2: ")
txt.print_b(p)
txt.spc()
tgt = &jump1b
if sb>p goto tgt
else { txt.print("false ") goto next1b }
jump1b:
txt.print("true ")
next1b:
tgt = &jump2b
if sb>=p goto tgt
else { txt.print("false ") goto next2b }
jump2b:
txt.print("true ")
next2b:
tgt = &jump3b
if sb<p goto tgt
else { txt.print("false ") goto next3b }
jump3b:
txt.print("true ")
next3b:
tgt = &jump4b
if sb<=p goto tgt
else { txt.print("false ") goto next4b }
jump4b:
txt.print("true ")
next4b:
txt.nl()
}
sub words() {
word @shared sbw = -30000
word @shared pw = 30000
const word cbw = -30000
txt.print("\nsigned words\n")
txt.print("expected: ")
txt.print_w(cbw)
txt.spc()
txt.print_bool(cbw>30000)
txt.spc()
txt.print_bool(cbw>=30000)
txt.spc()
txt.print_bool(cbw<30000)
txt.spc()
txt.print_bool(cbw<=30000)
txt.nl()
txt.print(" calc'd: ")
txt.print_w(sbw)
txt.spc()
txt.print_bool(sbw>30000)
txt.spc()
txt.print_bool(sbw>=30000)
txt.spc()
txt.print_bool(sbw<30000)
txt.spc()
txt.print_bool(sbw<=30000)
txt.nl()
txt.print("calc'd 2: ")
txt.print_w(sbw)
txt.spc()
txt.print_bool(sbw>pw)
txt.spc()
txt.print_bool(sbw>=pw)
txt.spc()
txt.print_bool(sbw<pw)
txt.spc()
txt.print_bool(sbw<=pw)
txt.nl()
txt.print(" if stmt: ")
txt.print_w(sbw)
txt.spc()
if sbw>30000 txt.print("true ") else txt.print("false ")
if sbw>=30000 txt.print("true ") else txt.print("false ")
if sbw<30000 txt.print("true ") else txt.print("false ")
if sbw<=30000 txt.print("true ") else txt.print("false ")
txt.nl()
txt.print(" ifstmt2: ")
txt.print_w(sbw)
txt.spc()
if sbw>pw txt.print("true ") else txt.print("false ")
if sbw>=pw txt.print("true ") else txt.print("false ")
if sbw<pw txt.print("true ") else txt.print("false ")
if sbw<=pw txt.print("true ") else txt.print("false ")
txt.nl()
}
}