mirror of
https://github.com/irmen/prog8.git
synced 2026-10-23 12:02:29 +00:00
Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fad90af6e | ||
|
|
3076318d7e | ||
|
|
9c99110738 | ||
|
|
1457d36997 | ||
|
|
43fb9296dd | ||
|
|
8bf744cc4a | ||
|
|
f1f5f070cf | ||
|
|
191a240d0d | ||
|
|
cafb2e355b | ||
|
|
c99c5751f8 | ||
|
|
435b7e56fa | ||
|
|
14f13fac53 | ||
|
|
f8b5037924 | ||
|
|
d2870cb02e | ||
|
|
8e43fc426c | ||
|
|
ba1b6e127a | ||
|
|
4f36a6958c | ||
|
|
df8f08b4ab | ||
|
|
fd5746d3fc | ||
|
|
6ab74fd51d | ||
|
|
245082ca1d | ||
|
|
845e58ea10 | ||
|
|
2a5dbc1ef7 |
No files matched your search
@@ -244,7 +244,7 @@ Used to call routines at fixed memory addresses (like ROM KERNAL routines or thi
|
||||
- Indentation: 4 spaces for .p8 and .asm files (no tabs)
|
||||
- Character encoding: 6502 targets use PETSCII by default (call `txt.lowercase()` at start for lowercase). Virtual target uses ISO (`%encoding iso` + `txt.iso()`)
|
||||
- Array size inferred from initializer: `str[] types = ["a", "b", "c"]`
|
||||
- Enums: `Enum::Value` syntax (double colon), declared inside a block. Use enums for related values, `const` for standalone
|
||||
- Enums: `Enum::Value` syntax (double colon), declared inside a block. They are syntactic sugar for a list of `const` declarations, not a type. Use enums for related values, `const` for standalone
|
||||
- Avoid `globals.XXXX` — move constants closer to where they're used
|
||||
- Member access through pointers: use `.` for both direct and pointer access. The compiler infers the type. For complex assignment targets, `^^` may be needed: `ptr^^.field = value`
|
||||
- Qualified names: must use full path from top level (e.g., `cx16.r0`, not relative)
|
||||
|
||||
@@ -12,13 +12,11 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: build and install recent 64tass
|
||||
- name: install 64tass
|
||||
run: |
|
||||
sudo apt-get install -y make build-essential
|
||||
git clone --depth=1 https://github.com/irmen/64tass
|
||||
cd 64tass
|
||||
make -j4
|
||||
sudo make install
|
||||
sudo apt update
|
||||
sudo apt install 64tass
|
||||
64tass --version
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v5
|
||||
@@ -26,9 +24,12 @@ jobs:
|
||||
java-version: 17
|
||||
distribution: temurin
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
|
||||
- name: Build and test with Gradle
|
||||
run: |
|
||||
./gradlew build shadowJar --no-daemon
|
||||
./gradlew build shadowJar --no-daemon --console=plain
|
||||
sha256sum -b compiler/build/libs/*-all.jar > compiler/build/libs/hash.txt
|
||||
|
||||
- name: Create compiler shadowJar artifact
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -63,6 +63,7 @@ What does Prog8 provide?
|
||||
- programs run very fast because it's compiled to native machine code
|
||||
- compiled code is very small; much smaller than equivalent C code compiled with CC65, and usually runs faster as well
|
||||
- modularity, symbol scoping, subroutines. No need for forward declarations.
|
||||
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
|
||||
- various data types other than just bytes (16-bit words, long integers, floats, strings)
|
||||
- Structs and typed pointers
|
||||
- 2D arrays (`matrix[row][col]` syntax)
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.io.CharConversionException
|
||||
object PetsciiEncoding {
|
||||
|
||||
// decoding: from Petscii/Screencodes (0-255) to Unicode
|
||||
// character tables used from https://github.com/irmen/cbmcodecs2
|
||||
// character tables used from https://codeberg.org/irmen/cbmcodecs2.git
|
||||
|
||||
private val decodingPetsciiLowercase = charArrayOf(
|
||||
'\u0000', // 0x00 -> \u0000
|
||||
|
||||
@@ -1024,7 +1024,7 @@ class AsmGen6502Internal (
|
||||
assignExpressionToRegister(value, target.register!!)
|
||||
} else {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AX)
|
||||
pointerGen.assignWordReg(PtrTarget(target), RegisterOrPair.AX)
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AX)
|
||||
}
|
||||
}
|
||||
target.datatype.isWord || target.datatype.isPassByRef -> {
|
||||
@@ -1175,6 +1175,7 @@ class AsmGen6502Internal (
|
||||
when {
|
||||
iterations == 0 -> {}
|
||||
iterations == 1 -> translate(stmt.statements)
|
||||
iterations > 65536 -> repeatLongCount(iterations.toLong(), stmt)
|
||||
iterations !in 0..65536 -> throw AssemblyError("invalid number of iterations")
|
||||
iterations <= 256 -> repeatByteCount(iterations, stmt)
|
||||
else -> repeatWordCount(iterations, stmt)
|
||||
@@ -1186,26 +1187,36 @@ class AsmGen6502Internal (
|
||||
val name = asmVariableName(stmt.count as PtIdentifier)
|
||||
when {
|
||||
vardecl.type.isByte -> {
|
||||
assignVariableToRegister(name, RegisterOrPair.Y, stmt.definingISub(), stmt.count.position)
|
||||
assignVariableToRegister(name, RegisterOrPair.Y, stmt.definingISub(), stmt.count.position, vardecl.type.isSigned)
|
||||
repeatCountInY(stmt, endLabel)
|
||||
}
|
||||
vardecl.type.isWord -> {
|
||||
assignVariableToRegister(name, RegisterOrPair.AY, stmt.definingISub(), stmt.count.position)
|
||||
assignVariableToRegister(name, RegisterOrPair.AY, stmt.definingISub(), stmt.count.position, vardecl.type.isSigned)
|
||||
repeatWordCountInAY(endLabel, stmt)
|
||||
}
|
||||
vardecl.type.isLong -> {
|
||||
val reg = CombinedLongRegisters.last()
|
||||
assignVariableToRegister(name, reg, stmt.definingISub(), (stmt.count as PtIdentifier).position, vardecl.type.isSigned)
|
||||
repeatLongCountInReg(reg, endLabel, stmt)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop variable datatype ${vardecl.type}")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
when {
|
||||
stmt.count.type.isByte -> {
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.Y)
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.Y, stmt.count.type.isSigned)
|
||||
repeatCountInY(stmt, endLabel)
|
||||
}
|
||||
stmt.count.type.isWord -> {
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.AY)
|
||||
assignExpressionToRegister(stmt.count, RegisterOrPair.AY, stmt.count.type.isSigned)
|
||||
repeatWordCountInAY(endLabel, stmt)
|
||||
}
|
||||
stmt.count.type.isLong -> {
|
||||
val reg = CombinedLongRegisters.last()
|
||||
assignExpressionToRegister(stmt.count, reg, stmt.count.type.isSigned)
|
||||
repeatLongCountInReg(reg, endLabel, stmt)
|
||||
}
|
||||
else -> throw AssemblyError("invalid loop expression datatype ${stmt.count.type}")
|
||||
}
|
||||
}
|
||||
@@ -1213,6 +1224,75 @@ class AsmGen6502Internal (
|
||||
|
||||
loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun repeatLongCount(iterations: Long, stmt: PtRepeatLoop) {
|
||||
require(iterations > 65536) { "invalid repeat count ${stmt.position}" }
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val counterVar = createTempVarReused(BaseDataType.LONG, true, stmt)
|
||||
|
||||
val bytes = mutableListOf<Int>()
|
||||
var currentN = iterations
|
||||
while (currentN > 0) {
|
||||
val b = (currentN % 256).toInt()
|
||||
bytes.add(b)
|
||||
currentN = if (b == 0) currentN / 256 else currentN / 256 + 1
|
||||
if (currentN == 1L) break
|
||||
}
|
||||
|
||||
for (i in bytes.indices) {
|
||||
val v = bytes[i]
|
||||
val suffix = if (i > 0) "+$i" else ""
|
||||
if (v == 0 && isTargetCpu(CpuType.CPU65C02)) {
|
||||
out(" stz $counterVar$suffix")
|
||||
} else {
|
||||
out(" lda #$v")
|
||||
out(" sta $counterVar$suffix")
|
||||
}
|
||||
}
|
||||
|
||||
out(repeatLabel)
|
||||
translate(stmt.statements)
|
||||
|
||||
for (i in bytes.indices) {
|
||||
val suffix = if (i > 0) "+$i" else ""
|
||||
out(" dec $counterVar$suffix")
|
||||
out(" bne $repeatLabel")
|
||||
}
|
||||
}
|
||||
|
||||
private fun repeatLongCountInReg(reg: RegisterOrPair, endLabel: String, stmt: PtRepeatLoop) {
|
||||
require(reg in CombinedLongRegisters)
|
||||
val repeatLabel = makeLabel("repeat")
|
||||
val regName = "cx16." + reg.startregname()
|
||||
val d1 = makeLabel("skip_dec1")
|
||||
val d2 = makeLabel("skip_dec2")
|
||||
val d3 = makeLabel("skip_dec3")
|
||||
out("""
|
||||
lda $regName
|
||||
ora $regName+1
|
||||
ora $regName+2
|
||||
ora $regName+3
|
||||
beq $endLabel
|
||||
$repeatLabel""")
|
||||
translate(stmt.statements)
|
||||
out("""
|
||||
lda $regName
|
||||
bne $d3
|
||||
lda $regName+1
|
||||
bne $d2
|
||||
lda $regName+2
|
||||
bne $d1
|
||||
dec $regName+3
|
||||
$d1 dec $regName+2
|
||||
$d2 dec $regName+1
|
||||
$d3 dec $regName
|
||||
lda $regName
|
||||
ora $regName+1
|
||||
ora $regName+2
|
||||
ora $regName+3
|
||||
bne $repeatLabel""")
|
||||
out(endLabel)
|
||||
}
|
||||
|
||||
private fun repeatWordCount(iterations: Int, stmt: PtRepeatLoop) {
|
||||
require(iterations in 257..65536) { "invalid repeat count ${stmt.position}" }
|
||||
|
||||
@@ -42,12 +42,129 @@ internal class ForLoopsAsmGen(
|
||||
when {
|
||||
iterableDt.isByteArray -> forOverNonconstByteRange(stmt, iterableDt, range)
|
||||
iterableDt.isWordArray && !iterableDt.isSplitWordArray -> forOverNonconstWordRange(stmt, iterableDt, range)
|
||||
else -> throw AssemblyError("range expression can only be byte or word")
|
||||
iterableDt.isLongArray -> forOverNonconstLongRange(stmt, iterableDt, range)
|
||||
else -> throw AssemblyError("range expression can only be byte, word or long")
|
||||
}
|
||||
|
||||
asmgen.loopEndLabels.removeLast()
|
||||
}
|
||||
|
||||
private fun forOverNonconstLongRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.add(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
|
||||
asmgen.assignExpressionToVariable(range.from, varname, iterableDt.elementType())
|
||||
|
||||
if (stepsize == 1 || stepsize == -1)
|
||||
forOverLongsRangeStepOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
|
||||
else
|
||||
forOverLongsRangeStepGreaterOne(range, varname, iterableDt, loopLabel, endLabel, stmt)
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
|
||||
// Use temporary variable to store the limit
|
||||
val toValueVar = asmgen.createTempVarReused(iterableDt.elementType().base, false, range)
|
||||
asmgen.assignExpressionToVariable(range.to, toValueVar, iterableDt.elementType())
|
||||
|
||||
if (stepsize == 1) {
|
||||
// Increment toValueVar to make the loop inclusive (0 to N runs N+1 times)
|
||||
asmgen.out("""
|
||||
inc $toValueVar
|
||||
bne +
|
||||
inc $toValueVar+1
|
||||
bne +
|
||||
inc $toValueVar+2
|
||||
bne +
|
||||
inc $toValueVar+3
|
||||
+
|
||||
""")
|
||||
} else {
|
||||
// Decrement toValueVar to make the loop inclusive (N downto 0 runs N+1 times)
|
||||
val label1 = asmgen.makeLabel("dec_1")
|
||||
val label2 = asmgen.makeLabel("dec_2")
|
||||
val label3 = asmgen.makeLabel("dec_3")
|
||||
asmgen.out("""
|
||||
lda $toValueVar
|
||||
bne $label1
|
||||
lda $toValueVar+1
|
||||
bne $label2
|
||||
lda $toValueVar+2
|
||||
bne $label3
|
||||
dec $toValueVar+3
|
||||
$label3: dec $toValueVar+2
|
||||
$label2: dec $toValueVar+1
|
||||
$label1: dec $toValueVar
|
||||
""")
|
||||
}
|
||||
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(forloop.statements)
|
||||
|
||||
if (stepsize == 1) {
|
||||
asmgen.out("""
|
||||
inc $varname
|
||||
bne +
|
||||
inc $varname+1
|
||||
bne +
|
||||
inc $varname+2
|
||||
bne +
|
||||
inc $varname+3
|
||||
+ lda $varname
|
||||
cmp $toValueVar
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp $toValueVar+1
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp $toValueVar+2
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp $toValueVar+3
|
||||
bne $loopLabel
|
||||
""")
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
bne +++
|
||||
lda $varname+1
|
||||
bne ++
|
||||
lda $varname+2
|
||||
bne +
|
||||
dec $varname+3
|
||||
+ dec $varname+2
|
||||
+ dec $varname+1
|
||||
+ dec $varname
|
||||
lda $varname
|
||||
cmp $toValueVar
|
||||
bne $loopLabel
|
||||
lda $varname+1
|
||||
cmp $toValueVar+1
|
||||
bne $loopLabel
|
||||
lda $varname+2
|
||||
cmp $toValueVar+2
|
||||
bne $loopLabel
|
||||
lda $varname+3
|
||||
cmp $toValueVar+3
|
||||
bne $loopLabel
|
||||
""")
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun forOverLongsRangeStepGreaterOne(range: PtRange, varname: String, iterableDt: DataType, loopLabel: String, endLabel: String, forloop: PtForLoop) {
|
||||
// TODO: implement 32-bit loop
|
||||
asmgen.romableError("for loops over long ranges with step != 1 not yet fully implemented", forloop.position)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(forloop.statements)
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
|
||||
private fun forOverNonconstByteRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val stepsize = range.step.asConstInteger()!!
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
|
||||
@@ -83,7 +83,7 @@ internal class IfExpressionAsmGen(private val asmgen: AsmGen6502Internal, privat
|
||||
asmgen.out(endLabel)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
expr.type.isWord || expr.type.isString -> {
|
||||
expr.type.isWord || expr.type.isString || expr.type.isPointer -> {
|
||||
asmgen.assignExpressionToRegister(expr.falsevalue, RegisterOrPair.AY)
|
||||
asmgen.jmp(endLabel)
|
||||
asmgen.out(trueLabel)
|
||||
|
||||
+29
-14
@@ -318,14 +318,13 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
SourceStorageKind.REGISTER -> inplacemodificationLongWithVariable(targetVarName, operator, regName(value))
|
||||
SourceStorageKind.MEMORY -> TODO("inplace long modification with memread value ${target.position}")
|
||||
SourceStorageKind.ARRAY -> TODO("inplace long modification with array value ${target.position}")
|
||||
SourceStorageKind.EXPRESSION -> {
|
||||
SourceStorageKind.EXPRESSION ->
|
||||
if(value.expression is PtTypeCast) {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value.expression, target, operator)) return
|
||||
TODO("inplace long modification ${target.position}")
|
||||
} else {
|
||||
TODO("inplace long modification ${target.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -577,24 +576,40 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
internal fun inplacemodificationLongWithVariable(targetVar: String, operator: String, sourceVar: String) {
|
||||
when(operator) {
|
||||
"+" -> {
|
||||
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
|
||||
asmgen.out("""
|
||||
clc
|
||||
ldx #252
|
||||
- lda $targetVar+4,x
|
||||
adc $sourceVar+4,x
|
||||
sta $targetVar+4,x
|
||||
inx
|
||||
bne -""")
|
||||
lda $targetVar+0
|
||||
adc $sourceVar+0
|
||||
sta $targetVar+0
|
||||
lda $targetVar+1
|
||||
adc $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
adc $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
adc $sourceVar+3
|
||||
sta $targetVar+3
|
||||
""")
|
||||
}
|
||||
"-" -> {
|
||||
// need to unroll to correctly keep Carry flag for multi-byte arithmetic
|
||||
asmgen.out("""
|
||||
sec
|
||||
ldx #252
|
||||
- lda $targetVar+4,x
|
||||
sbc $sourceVar+4,x
|
||||
sta $targetVar+4,x
|
||||
inx
|
||||
bne -""")
|
||||
lda $targetVar+0
|
||||
sbc $sourceVar+0
|
||||
sta $targetVar+0
|
||||
lda $targetVar+1
|
||||
sbc $sourceVar+1
|
||||
sta $targetVar+1
|
||||
lda $targetVar+2
|
||||
sbc $sourceVar+2
|
||||
sta $targetVar+2
|
||||
lda $targetVar+3
|
||||
sbc $sourceVar+3
|
||||
sta $targetVar+3
|
||||
""")
|
||||
}
|
||||
"<<" -> {
|
||||
asmgen.out("""
|
||||
|
||||
@@ -33,10 +33,10 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val exprGen
|
||||
extsub.returns.zip(assignmentTargets).forEach { pair ->
|
||||
val returns = pair.first
|
||||
val target = pair.second as PtAssignTarget
|
||||
// consume the register even for void targets to keep indices aligned
|
||||
val isFpRegister = returns.register.registerOrPair in listOf(RegisterOrPair.FAC1, RegisterOrPair.FAC2)
|
||||
val regNumber = if(isFpRegister) fpRegs.removeAt(0) else cpuRegs.removeAt(0)
|
||||
if (!target.void) {
|
||||
// Determine if this return uses an FP register or CPU register
|
||||
val isFpRegister = returns.register.registerOrPair in listOf(RegisterOrPair.FAC1, RegisterOrPair.FAC2)
|
||||
val regNumber = if(isFpRegister) fpRegs.removeAt(0) else cpuRegs.removeAt(0)
|
||||
result += assignCpuRegister(returns, regNumber, target)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,6 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
is PtContainmentCheck -> translate(expr)
|
||||
is PtPointerDeref -> translate(expr)
|
||||
is PtConstant -> {
|
||||
val name = expr.name
|
||||
val slab = expr.memorySlab
|
||||
// Check if the constant has a numeric value or is a memory slab reference
|
||||
if(slab != null) {
|
||||
@@ -1176,8 +1175,22 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
address = address.address.toAddress(),
|
||||
fcallArgs = FunctionCallArgs(argRegisters, returnRegisters)
|
||||
)
|
||||
} else if(address.constbank!=null) {
|
||||
IRInstruction(
|
||||
Opcode.CALLFAR,
|
||||
address = address.address.toAddress(),
|
||||
immediate = address.constbank!!.toInt()
|
||||
)
|
||||
} else {
|
||||
val tr = translateExpression(address.varbank!!)
|
||||
require(tr.dt==IRDataType.BYTE)
|
||||
result += tr.chunks
|
||||
IRInstruction(
|
||||
Opcode.CALLFARVB,
|
||||
address = address.address.toAddress(),
|
||||
reg1 = tr.resultReg
|
||||
)
|
||||
}
|
||||
else TODO("extsub with banked address got called ${callTarget.name} ${fcall.position}")
|
||||
}
|
||||
addInstr(result, call, null)
|
||||
val resultRegs = returnRegisters.filter{it.dt!=IRDataType.FLOAT}.map{it.registerNum.value}
|
||||
|
||||
@@ -347,6 +347,12 @@ internal class ConstantIdentifierReplacer(
|
||||
override fun before(addressOf: AddressOf, parent: Node): Iterable<AstModification> {
|
||||
val constValue = addressOf.constValue(program)
|
||||
if(constValue!=null) {
|
||||
// don't replace &constVar (without array index); leave it for AstChecker to reject as invalid address-of.
|
||||
// But still allow &constArray[idx] (those are valid address-of operations).
|
||||
val target = addressOf.identifier?.targetVarDecl()
|
||||
if(target?.type==VarDeclType.CONST && addressOf.arrayIndex==null) {
|
||||
return noModifications
|
||||
}
|
||||
return listOf(AstReplaceNode(addressOf, constValue, parent))
|
||||
}
|
||||
return noModifications
|
||||
@@ -355,7 +361,7 @@ internal class ConstantIdentifierReplacer(
|
||||
override fun after(identifier: IdentifierReference, parent: Node): Iterable<AstModification> {
|
||||
// replace identifiers that refer to const value, with the value itself
|
||||
// if it's a simple type and if it's not a left hand side variable
|
||||
if (identifier.parent is AssignTarget || identifier.parent is Alias)
|
||||
if (identifier.parent is AssignTarget || identifier.parent is Alias || identifier.parent is AddressOf)
|
||||
return noModifications
|
||||
var forloop = identifier.parent as? ForLoop
|
||||
if (forloop == null)
|
||||
|
||||
@@ -463,12 +463,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, rightVal.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, rightVal.position)
|
||||
rightIDt.isLong && rightVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, rightVal.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, leftVal.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, leftVal.position)
|
||||
leftIDt.isLong && leftVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, leftVal.position)
|
||||
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, rightVal.position)
|
||||
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, rightVal.position)
|
||||
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, rightVal.position)
|
||||
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(BaseDataType.UBYTE, 255.0, leftVal.position)
|
||||
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(BaseDataType.UWORD, 65535.0, leftVal.position)
|
||||
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> NumericLiteral(BaseDataType.LONG, -1.0, leftVal.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -476,12 +476,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
when {
|
||||
leftVal?.number==0.0 -> expr.right
|
||||
rightVal?.number==0.0 -> expr.left
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
rightIDt.isLong && rightVal?.number==-1.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isLong && leftVal?.number==-1.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> PrefixExpression("~", expr.left, expr.left.position)
|
||||
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> PrefixExpression("~", expr.right, expr.right.position)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -489,12 +489,12 @@ class ExpressionSimplifier(private val program: Program, private val errors: IEr
|
||||
when {
|
||||
leftVal?.number==0.0 -> constFalse
|
||||
rightVal?.number==0.0 -> constFalse
|
||||
rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
|
||||
rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
|
||||
rightIDt.isLong && rightVal?.number==-1.0 -> expr.left
|
||||
leftIDt.isBytes && leftVal?.number==255.0 -> expr.right
|
||||
leftIDt.isWords && leftVal?.number==65535.0 -> expr.right
|
||||
leftIDt.isLong && leftVal?.number==-1.0 -> expr.right
|
||||
leftIDt.isBytes && rightIDt.isBytes && rightVal?.number==255.0 -> expr.left
|
||||
leftIDt.isWords && rightIDt.isWords && rightVal?.number==65535.0 -> expr.left
|
||||
leftIDt.isLong && rightIDt.isLong && rightVal?.number==-1.0 -> expr.left
|
||||
leftIDt.isBytes && rightIDt.isBytes && leftVal?.number==255.0 -> expr.right
|
||||
leftIDt.isWords && rightIDt.isWords && leftVal?.number==65535.0 -> expr.right
|
||||
leftIDt.isLong && rightIDt.isLong && leftVal?.number==-1.0 -> expr.right
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub bell() {
|
||||
|
||||
@@ -13,7 +13,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub bell() {
|
||||
|
||||
@@ -872,7 +872,7 @@ exit:
|
||||
}
|
||||
|
||||
sub get_loadaddress(str filename) -> uword {
|
||||
; get the load adress from a PRG file (usually $0801 but it can be different)
|
||||
; get the load address from a PRG file (usually $0801 but it can be different)
|
||||
|
||||
cbm.SETNAM(strings.length(filename), filename)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL)
|
||||
|
||||
@@ -185,7 +185,7 @@ gfx_hires {
|
||||
|
||||
sub vertical_line(uword xx, uword yy, uword lheight, ubyte color) {
|
||||
; highres 4c
|
||||
; use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment is possible
|
||||
; use TWO vera address pointers simultaneously one for reading, one for writing, so auto-increment is possible
|
||||
if lheight==0
|
||||
return
|
||||
position2(xx,yy,true)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
; optimized graphics routines for just the single screen mode: lores 320*240, 256c (8bpp)
|
||||
; bitmap image needs to start at VRAM addres $00000.
|
||||
; bitmap image needs to start at VRAM address $00000.
|
||||
; This is compatible with the CX16's screen mode 128.
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ gfx_lores {
|
||||
|
||||
sub clear_screen(ubyte color) {
|
||||
if verafx.available() {
|
||||
; use verafx cache writes to quicly clear the screen
|
||||
; use verafx cache writes to quickly clear the screen
|
||||
const ubyte vbank = 0
|
||||
const uword vaddr = 0
|
||||
cx16.VERA_CTRL = 0
|
||||
|
||||
@@ -19,6 +19,26 @@ serial {
|
||||
const ubyte REG_DIVISOR_LATCH_LOW = 0 ;same as TXRX, but when bit-7 of line control high
|
||||
const ubyte REG_DIVISOR_LATCH_HI = 1 ;same as IRQE, but when bit-7 of line control high
|
||||
|
||||
; Baud rate divisor values for TL16C2550 with 14.7456 MHz clock
|
||||
; Formula: divisor = 14745600 / (16 * baudrate)
|
||||
enum BAUD {
|
||||
B921600 = $0001,
|
||||
B460800 = $0002,
|
||||
B230400 = $0004,
|
||||
B115200 = $0008,
|
||||
B57600 = $0010,
|
||||
B38400 = $0018,
|
||||
B28800 = $0020,
|
||||
B19200 = $0030,
|
||||
B14400 = $0040,
|
||||
B9600 = $0060,
|
||||
B4800 = $00C0,
|
||||
B2400 = $0180,
|
||||
B1200 = $0300,
|
||||
B600 = $0600,
|
||||
B300 = $0C00,
|
||||
}
|
||||
|
||||
; Bitfield masks for TL16C2550's Line Status Register
|
||||
enum LSR {
|
||||
DR = %00000001, ; Data Ready
|
||||
@@ -54,13 +74,12 @@ serial {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uart1, uart2
|
||||
|
||||
private sub probe(uword address) -> bool {
|
||||
sys.set_irqd()
|
||||
address[REG_INTERRUPT_ENABLE] = $ff
|
||||
bool found = address[REG_INTERRUPT_ENABLE] == $0f ; The high nybble of IER is always clear
|
||||
address[REG_INTERRUPT_ENABLE] = $f0
|
||||
bool found = address[REG_INTERRUPT_ENABLE] & $f0 == 0 ; The high nybble of IER is always clear
|
||||
address[REG_INTERRUPT_ENABLE] = 0
|
||||
if found {
|
||||
address[REG_MODEM_CONTROL] = $ff
|
||||
@@ -73,15 +92,14 @@ serial {
|
||||
}
|
||||
|
||||
sub initialize_uart(uword uart_addr) {
|
||||
; TODO baud rate is currently fixed at 921600, make this configurable
|
||||
; 921600 baud, 8,N,1, AutoFlow Control, FIFOS, no interrupts
|
||||
uart_addr[REG_INTERRUPT_ENABLE] = $00 ; No Interrupts
|
||||
; 921600 baud, 8,N,1, AutoFlow Control, FIFOS, no interrupts
|
||||
uart_addr[REG_LINE_CONTROL] = $80 ; Set DLAB
|
||||
uart_addr[REG_DIVISOR_LATCH_HI] = $00
|
||||
uart_addr[REG_DIVISOR_LATCH_LOW] = $01 ; $0001 = 921600
|
||||
uart_addr[REG_LINE_CONTROL] = $03 ; 8,N,1
|
||||
uart_addr[REG_FIFO_CONTROL] = $C7 ; FIFO enable & reset
|
||||
uart_addr[REG_MODEM_CONTROL] = $23 ; DTR/RTS & AutoFlow Control
|
||||
uart_addr[REG_DIVISOR_LATCH_HI] = msb(BAUD::B921600)
|
||||
uart_addr[REG_DIVISOR_LATCH_LOW] = lsb(BAUD::B921600)
|
||||
uart_addr[REG_LINE_CONTROL] = $03 ; 8,N,1, DLAB off
|
||||
uart_addr[REG_FIFO_CONTROL] = $C3 ; FIFO enable & reset, trigger 14
|
||||
uart_addr[REG_MODEM_CONTROL] = $23 ; DTR/RTS + AutoFlow Control
|
||||
uart_addr[REG_INTERRUPT_ENABLE] = $00 ; No interrupts
|
||||
}
|
||||
|
||||
sub write(uword @zp uart_addr, str data) {
|
||||
@@ -98,7 +116,8 @@ serial {
|
||||
}
|
||||
|
||||
sub read_until(uword @zp uart_addr, str match, ^^ubyte buffer, uword max_size) -> uword {
|
||||
; Reads up to and including the match string from the uart, into buffer.
|
||||
; Reads bytes from the uart into buffer until the match string is found.
|
||||
; The match string itself is also included in the buffer.
|
||||
; Stops when the match string is found or max_size bytes have been read.
|
||||
; Returns the number of bytes read.
|
||||
|
||||
@@ -119,6 +138,7 @@ serial {
|
||||
}
|
||||
@(buffer_ptr) = data = @(uart_addr)
|
||||
buffer_ptr++
|
||||
read_size++
|
||||
if data == @(match_ptr) {
|
||||
match_ptr++
|
||||
if @(match_ptr)==0
|
||||
@@ -131,96 +151,9 @@ serial {
|
||||
return read_size
|
||||
}
|
||||
|
||||
; zimodem routines:
|
||||
|
||||
sub zi_initialize(uword uart_addr) {
|
||||
; initializes the ZiModem on the given uart address
|
||||
zi_uart = uart_addr
|
||||
if zi_uart == 0
|
||||
return ; no uart present
|
||||
|
||||
initialize_uart(zi_uart)
|
||||
; ZiModem sends a version banner of the `ati` command when the ESP32 boots up.
|
||||
; Read it off if present. It is not always ready immediately so wait a tiny bit
|
||||
sys.wait(10)
|
||||
zi_write_cmd(iso:"\x03ate0") ; first send CTRL-C to abort any previous file streams
|
||||
sys.wait(5)
|
||||
if zi_uart[REG_LINE_STATUS] & LSR::DR != 0
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
zi_write_cmd("atq0v1x1f0r1s45=0s40=4096&p0b921600")
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
}
|
||||
|
||||
sub zi_reset() {
|
||||
; reset all zimodem connections
|
||||
serial.zi_write_cmd(iso:"atz")
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
}
|
||||
|
||||
sub zi_write_cmd(str command) {
|
||||
; writes the zimodem command (followed by a proper line ending)
|
||||
write(zi_uart, command)
|
||||
write_eol(zi_uart)
|
||||
}
|
||||
|
||||
sub zi_start_get_file(str filename) -> long {
|
||||
; starts a file download from the given url, returns the size of the file to download.
|
||||
; you can download chunks of the file by repeatedly calling zi_get_file_chunk() until that returns 0
|
||||
write(zi_uart, iso:"at&g\"")
|
||||
write(zi_uart, filename)
|
||||
write(zi_uart, "\"")
|
||||
write_eol(zi_uart)
|
||||
|
||||
if read_one(zi_uart)=='[' {
|
||||
; parse header [ 0 <filesize> <sum> ]
|
||||
ubyte[25] header_buf
|
||||
void read_until(zi_uart, iso:"]\x0d\x0a", &header_buf, sizeof(header_buf))
|
||||
return conv.str2long(&header_buf + 3)
|
||||
} else {
|
||||
discard_until(zi_uart, iso:"RROR\x0d\x0a")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
sub zi_get_file_chunk(^^ubyte @zp buffer, uword buffer_size, long remaining_file_size) -> uword {
|
||||
; read the next chunk of data from the file, into buffer, up to buffer_size bytes.
|
||||
; returns the number of bytes read. 0 if no more data was available.
|
||||
|
||||
uword bytes_to_read = min(remaining_file_size, buffer_size) as uword
|
||||
if bytes_to_read == 0
|
||||
return 0
|
||||
|
||||
repeat bytes_to_read {
|
||||
while zi_uart[REG_LINE_STATUS] & LSR::DR == 0 {
|
||||
; wait until data is present
|
||||
}
|
||||
@(buffer) = @(zi_uart)
|
||||
buffer++
|
||||
}
|
||||
|
||||
return bytes_to_read
|
||||
}
|
||||
|
||||
sub zi_get_ip_address() -> str {
|
||||
; returns the ip address of the zimodem wifi connection
|
||||
ubyte[30] ip_buffer
|
||||
zi_write_cmd(iso:"ati2")
|
||||
void read_until(zi_uart, iso:"OK\x0d\x0a", &ip_buffer, len(ip_buffer))
|
||||
cx16.r0 = &ip_buffer
|
||||
while not strings.isspace(@(cx16.r0)) {
|
||||
cx16.r0++
|
||||
}
|
||||
@(cx16.r0) = 0
|
||||
return &ip_buffer
|
||||
}
|
||||
|
||||
|
||||
; private stuff
|
||||
|
||||
private uword @zp zi_uart ; the uart address to use for ZiModem
|
||||
|
||||
private sub discard_until(uword @zp uart_addr, str match) {
|
||||
; Reads up to and including the match string from the uart, discards all data.
|
||||
sub discard_until(uword @zp uart_addr, str match) {
|
||||
; Reads bytes from the uart until the match string is found, discards all data.
|
||||
; The match string itself is also discarded.
|
||||
|
||||
alias match_ptr = cx16.r0
|
||||
match_ptr = match
|
||||
@@ -239,6 +172,163 @@ serial {
|
||||
}
|
||||
}
|
||||
|
||||
; zimodem routines:
|
||||
|
||||
sub zi_initialize(uword uart_addr) {
|
||||
; initializes the ZiModem on the given uart address
|
||||
zi_uart = uart_addr
|
||||
if zi_uart == 0
|
||||
return ; no uart present
|
||||
|
||||
initialize_uart(zi_uart)
|
||||
; ZiModem sends a version banner of the `ati` command when the ESP32 boots up.
|
||||
; Read it off if present. It is not always ready immediately so wait a tiny bit
|
||||
sys.wait(5)
|
||||
write(zi_uart, "\x03") ; first send CTRL-C (+ no echo) to abort any previous file streams
|
||||
sys.wait(10)
|
||||
zi_write_cmd(iso:"ate0q0v1x1f0r1s45=3&p0&k3")
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
}
|
||||
|
||||
sub zi_reset() {
|
||||
; reset all zimodem connections
|
||||
write(zi_uart, "\x03") ; first send CTRL-C (+ no echo) to abort any previous file streams
|
||||
sys.wait(10)
|
||||
serial.zi_write_cmd(iso:"atz")
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
}
|
||||
|
||||
sub zi_write_cmd(str command) {
|
||||
; writes the zimodem command (followed by a proper line ending)
|
||||
write(zi_uart, command)
|
||||
write_eol(zi_uart)
|
||||
}
|
||||
|
||||
sub zi_start_get_file_hexmode(str filename) -> bool {
|
||||
; starts a file download from the given url. Returns true if ok, false if file could not be found
|
||||
; you can download chunks of the file by repeatedly calling zi_get_file_chunk_hexmode() until that returns 0.
|
||||
; Note: this uses zimodem hex mode transfer for now because binary mode transfer is broken at the moment.
|
||||
zi_write_cmd(iso:"ats45=1") ; enable hex mode transfer (unfortunately this also forces caching mode on the esp32)
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
write(zi_uart, iso:"at&g\"")
|
||||
write(zi_uart, filename)
|
||||
write(zi_uart, "\"\x0d\x0a")
|
||||
|
||||
if read_one(zi_uart)=='[' {
|
||||
; note: zimodem file size in hex mode is bogus (only can go up to $FFFF), just skip the whole header line.
|
||||
discard_until(zi_uart, "\x0d\x0a")
|
||||
return true
|
||||
} else {
|
||||
discard_until(zi_uart, iso:"RROR\x0d\x0a")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
sub zi_start_get_file(str filename) -> long {
|
||||
; TODO: binary file transfer seems unreliable at the moment (text files are still ok, but often freezes on binary files. Could be a problem with my particular X16 serial card or zimodem firmware though), use hex mode transfer for reliable results.
|
||||
; starts a file download from the given url, returns the size of the file to download.
|
||||
; you can download chunks of the file by repeatedly calling zi_get_file_chunk() until that returns 0
|
||||
write(zi_uart, iso:"at&g\"")
|
||||
write(zi_uart, filename)
|
||||
write(zi_uart, "\"\x0d\x0a")
|
||||
|
||||
if read_one(zi_uart)=='[' {
|
||||
; parse header [ 0 <filesize> <sum> ]
|
||||
ubyte[25] header_buf
|
||||
void read_until(zi_uart, iso:"]\x0d\x0a", &header_buf, sizeof(header_buf))
|
||||
return conv.str2long(&header_buf + 3)
|
||||
} else {
|
||||
discard_until(zi_uart, iso:"RROR\x0d\x0a")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
sub zi_get_file_chunk_hexmode(^^ubyte @zp buffer, uword buffer_size) -> uword {
|
||||
; read the next chunk of data from the file (opened in hex mode), into buffer, up to buffer_size bytes (minimum 40 bytes!!)
|
||||
; returns the number of bytes read. 0 if no more data was available.
|
||||
ubyte[90] linebuffer
|
||||
cx16.r0 = read_until(zi_uart, "\x0d\x0a", linebuffer, sizeof(linebuffer))
|
||||
if cx16.r0 == 4 and linebuffer[0] == iso:'O' ; OK\r\n was received -- end of transmission
|
||||
return 0
|
||||
|
||||
ubyte digits = lsb(cx16.r0)-2
|
||||
decode(digits)
|
||||
return digits/2
|
||||
|
||||
private sub decode(ubyte numdigits) {
|
||||
cx16.r1L = 0
|
||||
for cx16.r0L in 0 to numdigits-1 step 2 {
|
||||
buffer[cx16.r1L] = decodehex(cx16.r0L)
|
||||
cx16.r1L++
|
||||
}
|
||||
|
||||
asmsub decodehex(ubyte digitindex @Y) -> ubyte @A {
|
||||
%asm {{
|
||||
lda p8v_linebuffer,y
|
||||
sec
|
||||
sbc #$30
|
||||
cmp #$0a
|
||||
bmi +
|
||||
sbc #$07
|
||||
+ asl a
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda p8v_linebuffer+1,y
|
||||
sec
|
||||
sbc #$30
|
||||
cmp #$0a
|
||||
bmi +
|
||||
sbc #$07
|
||||
+ ora P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub zi_get_file_chunk(^^ubyte @zp buffer, uword buffer_size, long remaining_file_size) -> uword {
|
||||
; TODO: binary file transfer seems unreliable at the moment (text files are still ok, but often freezes on binary files. Could be a problem with my particular X16 serial card or zimodem firmware though), use hex mode transfer for reliable results.
|
||||
; read the next chunk of data from the file, into buffer, up to buffer_size bytes.
|
||||
; returns the number of bytes read. 0 if no more data was available.
|
||||
|
||||
uword bytes_to_read = if msw(remaining_file_size)!=0 then buffer_size else min(lsw(remaining_file_size), buffer_size)
|
||||
|
||||
repeat bytes_to_read {
|
||||
while zi_uart[REG_LINE_STATUS] & LSR::DR == 0 {
|
||||
; wait until data is present
|
||||
}
|
||||
@(buffer) = @(zi_uart)
|
||||
buffer++
|
||||
}
|
||||
|
||||
return bytes_to_read
|
||||
}
|
||||
|
||||
sub zi_end_get_file() {
|
||||
; makes sure the 'OK' response after the actual file data is also read away
|
||||
discard_until(zi_uart, iso:"OK\x0d\x0a")
|
||||
}
|
||||
|
||||
sub zi_get_ip_address() -> str {
|
||||
; returns the ip address of the zimodem wifi connection. Assumes IPV4 only.
|
||||
ubyte[25] ip_buffer
|
||||
zi_write_cmd(iso:"ati2")
|
||||
void read_until(zi_uart, iso:"OK\x0d\x0a", &ip_buffer, len(ip_buffer))
|
||||
cx16.r0 = &ip_buffer
|
||||
while not strings.isspace(@(cx16.r0)) {
|
||||
cx16.r0++
|
||||
}
|
||||
@(cx16.r0) = 0
|
||||
return &ip_buffer
|
||||
}
|
||||
|
||||
|
||||
; private stuff
|
||||
|
||||
private uword @zp zi_uart ; the uart address to use for ZiModem
|
||||
|
||||
private sub read_one(uword @zp uart_addr) -> ubyte {
|
||||
while uart_addr[REG_LINE_STATUS] & LSR::DR == 0 {
|
||||
; wait until data is present
|
||||
|
||||
@@ -14,7 +14,7 @@ const ubyte DEFAULT_WIDTH = 80
|
||||
const ubyte DEFAULT_HEIGHT = 60
|
||||
const long VERA_TEXTMATRIX = $1b000
|
||||
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub bell() {
|
||||
|
||||
@@ -439,7 +439,7 @@ divmod_uw_asm .proc
|
||||
|
||||
dividend = P8ZP_SCRATCH_W1
|
||||
remainder = P8ZP_SCRATCH_W2
|
||||
result = dividend ;save memory by reusing divident to store the result
|
||||
result = dividend ;save memory by reusing dividend to store the result
|
||||
|
||||
cpy #0
|
||||
bne _nonzero
|
||||
|
||||
@@ -212,7 +212,7 @@ _sinecosR8 .char trunc(127.0 * sin(range(180+45) * rad(360.0/180.0)))
|
||||
; some simpler multiplications will be optimized away into faster routines. These will not set the upper 16 bits at all!
|
||||
; - THE RESULT IS ONLY VALID IF THE MULTIPLICATION WAS DONE WITH UWORD ARGUMENTS (or two positive WORD arguments)
|
||||
; as soon as a negative word value (or 2) was used in the multiplication, these upper 16 bits are not valid!!
|
||||
; Suggestion (if you are on the Commander X16): use verafx.muls() to get a hardware accelerated 32 bit signed multplication.
|
||||
; Suggestion (if you are on the Commander X16): use verafx.muls() to get a hardware accelerated 32 bit signed multiplication.
|
||||
%asm {{
|
||||
lda prog8_math.multiply_words.result+2
|
||||
ldy prog8_math.multiply_words.result+3
|
||||
@@ -385,7 +385,7 @@ _quadrant_region_to_direction:
|
||||
|
||||
asmsub atan2(ubyte x1 @R0, ubyte y1 @R1, ubyte x2 @R2, ubyte y2 @R3) -> ubyte @A {
|
||||
;; Calculate the angle, in a 256-degree circle, between two points into A.
|
||||
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
|
||||
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the cartesian plane!
|
||||
;; http://codebase64.net/doku.php?id=base:8bit_atan2_8-bit_angle
|
||||
;; This uses 2 large lookup tables so uses a lot of memory but is super fast.
|
||||
|
||||
|
||||
@@ -416,7 +416,7 @@ exit:
|
||||
}
|
||||
|
||||
sub get_loadaddress(str filename) -> uword {
|
||||
; get the load adress from a PRG file (usually $0801 but it can be different)
|
||||
; get the load address from a PRG file (usually $0801 but it can be different)
|
||||
|
||||
cbm.SETNAM(strings.length(filename), filename)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, 0)
|
||||
|
||||
@@ -12,7 +12,7 @@ txt {
|
||||
const ubyte DEFAULT_WIDTH = 40
|
||||
const ubyte DEFAULT_HEIGHT = 25
|
||||
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly ofcourse. Note: takes a PETSCII encoded character.
|
||||
extsub $FFD2 = chrout(ubyte character @ A) ; for consistency. You can also use cbm.CHROUT directly of course. Note: takes a PETSCII encoded character.
|
||||
|
||||
|
||||
sub bell() {
|
||||
|
||||
@@ -482,7 +482,7 @@ from_scratchW1
|
||||
.pend
|
||||
|
||||
func_peekl .proc
|
||||
; -- read the ;pmg value on the address in AY, into R14:R15
|
||||
; -- read the long value on the address in AY, into R14:R15
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
from_scratchW1
|
||||
|
||||
@@ -716,7 +716,7 @@ exit:
|
||||
}
|
||||
|
||||
sub get_loadaddress(str filename) -> uword {
|
||||
; get the load adress from a PRG file (usually $0801 but it can be different)
|
||||
; get the load address from a PRG file (usually $0801 but it can be different)
|
||||
|
||||
cbm.SETNAM(strings.length(filename), filename)
|
||||
cbm.SETLFS(READ_IO_CHANNEL, drivenumber, READ_IO_CHANNEL)
|
||||
|
||||
@@ -293,7 +293,7 @@ sub interpolate(float v, float inputMin, float inputMax, float outputMin, float
|
||||
}
|
||||
|
||||
asmsub internal_long_AY_to_FAC() {
|
||||
; Used by the compiler to cast a long to a a float in FAC
|
||||
; Used by the compiler to cast a long to a float in FAC
|
||||
; convert the long pointed to by AY into a float and store it in FAC
|
||||
%asm {{
|
||||
sta cx16.r1L
|
||||
@@ -308,7 +308,7 @@ sub interpolate(float v, float inputMin, float inputMax, float outputMin, float
|
||||
}
|
||||
|
||||
private sub internal_long_R1_to_float_AY() {
|
||||
; Used by the compiler to cast a long to a a float variable:
|
||||
; Used by the compiler to cast a long to a float variable:
|
||||
; make the float pointed to by AY equal to the long pointed to by R1 (as float)
|
||||
%asm {{
|
||||
sta cx16.r0L
|
||||
|
||||
@@ -44,7 +44,7 @@ _done
|
||||
}
|
||||
|
||||
sub gnomesort_by_ub(^^ubyte @requirezp ub_keys, ^^uword wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted of course).
|
||||
ubyte @zp pos=1
|
||||
while pos != num_elements {
|
||||
if ub_keys[pos]>=ub_keys[pos-1]
|
||||
@@ -88,7 +88,7 @@ _done
|
||||
}
|
||||
|
||||
sub gnomesort_by_uw(uword @requirezp uw_keys, uword wordvalues, ubyte num_elements) {
|
||||
; Sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
|
||||
; Sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted of course).
|
||||
; both arrays should be no-split array of words. uw_keys are unsigned.
|
||||
; Max number of elements is 128 to keep indexing code size small and fast. Clobbers R0 and R1.
|
||||
ubyte @zp pos=2
|
||||
@@ -161,7 +161,7 @@ _done
|
||||
}
|
||||
|
||||
sub shellsort_by_ub(^^ubyte @requirezp ub_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted of course).
|
||||
num_elements--
|
||||
ubyte @zp gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
@@ -187,7 +187,7 @@ _done
|
||||
}
|
||||
|
||||
sub shellsort_by_uw(^^uword @requirezp uw_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
|
||||
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted of course).
|
||||
; both arrays should be no-split array of words. uw_keys are unsigned.
|
||||
num_elements--
|
||||
ubyte gap
|
||||
|
||||
@@ -7,7 +7,7 @@ strings {
|
||||
|
||||
asmsub length(str string @AY) clobbers(A) -> ubyte @Y {
|
||||
; Returns the number of bytes in the string.
|
||||
; This value is determined during runtime and counts upto the first terminating 0 byte in the string,
|
||||
; This value is determined during runtime and counts up to the first terminating 0 byte in the string,
|
||||
; regardless of the size of the string during compilation time. Don’t confuse this with len and sizeof!
|
||||
|
||||
; uses P8ZP_SCRATCH_W1 to store the string address, do not change this, other routines here may depend on it (as optimization implementation detail)
|
||||
@@ -29,7 +29,7 @@ strings {
|
||||
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
; need to copy the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
sta P8ZP_SCRATCH_W1
|
||||
stx P8ZP_SCRATCH_W1+1
|
||||
lda cx16.r1
|
||||
@@ -56,7 +56,7 @@ _loop dey
|
||||
; Also, you have to make sure yourself that length is smaller or equal to the length of the source string.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
; need to copy the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
stx P8ZP_SCRATCH_B1
|
||||
sta cx16.r0
|
||||
sty cx16.r0+1
|
||||
@@ -96,7 +96,7 @@ _loop dey
|
||||
; Also, you have to make sure yourself that start and length are within bounds of the strings.
|
||||
; Modifies in-place, doesn’t return a value (so can’t be used in an expression).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
; need to copy the cx16 virtual registers to zeropage to be compatible with C64...
|
||||
; substr(source, target, start, length)
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda cx16.r0
|
||||
@@ -131,7 +131,7 @@ _startloop dey
|
||||
; Locates the first position of the given character in the string,
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
|
||||
; need to copy the cx16 virtual registers to zeropage to make this run on C64...
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
stx P8ZP_SCRATCH_B1
|
||||
@@ -157,7 +157,7 @@ _found tya
|
||||
; This is a convenience function that looks for both a CR or LF (byte 13 or byte 10) as being a possible Line Ending.
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
%asm {{
|
||||
; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
|
||||
; need to copy the cx16 virtual registers to zeropage to make this run on C64...
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
@@ -297,7 +297,7 @@ _max_too_small
|
||||
asmsub compare(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting, case-sensitively.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; Note that you can also directly compare strings and string values with each other using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
@@ -311,7 +311,7 @@ _max_too_small
|
||||
asmsub compare_nocase(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting, case-insensitively for PETSCII strings.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; Note that you can also directly compare strings and string values with each other using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
@@ -350,7 +350,7 @@ _c1_zero
|
||||
asmsub compare_nocase_iso(str string1 @R0, str string2 @AY) clobbers(Y) -> byte @A {
|
||||
; Compares two strings for sorting, case-insensitively for ISO strings.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; Note that you can also directly compare strings and string values with each other using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
@@ -525,7 +525,7 @@ _done rts
|
||||
; ? matches any one character. For example, F?? matches FOO but not FU, and ?? matches all two-character strings.
|
||||
; * matches any string, including the empty string.
|
||||
; For example, F* matches all strings starting with F. *O*O* matches all strings with at least two Os. Finally, ?* matches all non-empty strings.
|
||||
; Both the pattern and the string must be NUL-terminated (that it, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
|
||||
; Both the pattern and the string must be NUL-terminated (that is, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
|
||||
; Code taken from http://6502.org/source/strings/patmatch.htm
|
||||
;
|
||||
; Input: cx16.r0: A NUL-terminated, <255-length pattern
|
||||
@@ -562,7 +562,7 @@ next lda $ffff,x ; look at next pattern character MODIFIED
|
||||
cmp #'*' ; is it a star?
|
||||
beq star ; yes, do the complicated stuff
|
||||
iny ; no, let's look at the string
|
||||
cmp #'?' ; is the pattern caracter a ques?
|
||||
cmp #'?' ; is the pattern character a ques?
|
||||
bne reg ; no, it's a regular character
|
||||
lda (strptr),y ; yes, so it will match anything
|
||||
beq fail ; except the end of string
|
||||
@@ -602,7 +602,7 @@ fail clc ; yes, no match found, return with c=0
|
||||
; ? matches any one character. For example, F?? matches FOO but not FU, and ?? matches all two-character strings.
|
||||
; * matches any string, including the empty string.
|
||||
; For example, F* matches all strings starting with F. *O*O* matches all strings with at least two Os. Finally, ?* matches all non-empty strings.
|
||||
; Both the pattern and the string must be NUL-terminated (that it, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
|
||||
; Both the pattern and the string must be NUL-terminated (that is, followed with a 00 byte) and at most 255 characters long (excluding the NUL).
|
||||
; Code taken from http://6502.org/source/strings/patmatch.htm
|
||||
;
|
||||
; Input: cx16.r0: A NUL-terminated, <255-length pattern
|
||||
@@ -651,7 +651,7 @@ next lda $ffff,x ; look at next pattern character MODIFIED
|
||||
cmp #'*' ; is it a star?
|
||||
beq star ; yes, do the complicated stuff
|
||||
iny ; no, let's look at the string
|
||||
cmp #'?' ; is the pattern caracter a ques?
|
||||
cmp #'?' ; is the pattern character a ques?
|
||||
bne reg ; no, it's a regular character
|
||||
lda (strptr),y ; yes, so it will match anything
|
||||
beq fail ; except the end of string
|
||||
|
||||
@@ -300,7 +300,7 @@ diskio {
|
||||
}
|
||||
|
||||
sub get_loadaddress(str filename) -> uword {
|
||||
; get the load adress from a PRG file (usually $0801 but it can be different)
|
||||
; get the load address from a PRG file (usually $0801 but it can be different)
|
||||
if f_open(filename) {
|
||||
uword address
|
||||
void f_read(&address, 2)
|
||||
|
||||
@@ -277,7 +277,7 @@ math {
|
||||
|
||||
sub atan2(ubyte x1, ubyte y1, ubyte x2, ubyte y2) -> ubyte {
|
||||
;; Calculate the angle, in a 256-degree circle, between two points into A.
|
||||
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the carthesian plane!
|
||||
;; The points (x1, y1) and (x2, y2) have to use *unsigned coordinates only* from the positive quadrant in the cartesian plane!
|
||||
%ir {{
|
||||
loadm.b r99100,math.atan2.x1
|
||||
loadm.b r99101,math.atan2.y1
|
||||
|
||||
@@ -50,7 +50,7 @@ sorting {
|
||||
|
||||
|
||||
sub shellsort_by_ub(^^ubyte @requirezp ub_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted ofcourse).
|
||||
; sorts the 'wordvalues' array (no-split array of words) according to the 'ub_keys' array (which also gets sorted of course).
|
||||
num_elements--
|
||||
ubyte @zp gap
|
||||
for gap in [132, 57, 23, 10, 4, 1] {
|
||||
@@ -76,7 +76,7 @@ sorting {
|
||||
}
|
||||
|
||||
sub shellsort_by_uw(^^uword @requirezp uw_keys, ^^uword @requirezp wordvalues, ubyte num_elements) {
|
||||
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted ofcourse).
|
||||
; sorts the 'wordvalues' array according to the 'uw_keys' array (which also gets sorted of course).
|
||||
; both arrays should be no-split array of words. uw_keys are unsigned.
|
||||
num_elements--
|
||||
ubyte gap
|
||||
|
||||
@@ -147,7 +147,7 @@ strings {
|
||||
sub compare(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting (case sensitive).
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; Note that you can also directly compare strings and string values with each other using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%ir {{
|
||||
loadm.w r99000,strings.compare.st1
|
||||
@@ -160,7 +160,7 @@ strings {
|
||||
sub compare_nocase(str st1, str st2) -> byte {
|
||||
; Compares two strings for sorting (case insensitive).
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Note that you can also directly compare strings and string values with eachother using
|
||||
; Note that you can also directly compare strings and string values with each other using
|
||||
; comparison operators ==, < etcetera (this will use strcmp automatically).
|
||||
%ir {{
|
||||
loadm.w r99000,strings.compare_nocase.st1
|
||||
|
||||
@@ -274,8 +274,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
|
||||
BaseDataType.LONG -> {
|
||||
if(!iterableDt.elementType().isInteger)
|
||||
errors.err("long loop variable can only loop over integers", forLoop.position)
|
||||
errors.warn("for loop using a long counter could be very slow", forLoop.position)
|
||||
}
|
||||
|
||||
BaseDataType.FLOAT -> {
|
||||
@@ -698,7 +697,12 @@ internal class AstChecker(private val program: Program,
|
||||
val iterations = repeatLoop.iterations?.constValue(program)
|
||||
if (iterations != null) {
|
||||
require(floor(iterations.number)==iterations.number)
|
||||
if (iterations.number.toInt() > 65536) errors.err("repeat cannot exceed 65536 iterations", iterations.position)
|
||||
if (iterations.number.toInt() > 65536)
|
||||
errors.warn("repeat using a long counter could be very slow", iterations.position)
|
||||
}
|
||||
|
||||
if(repeatLoop.iterations?.inferType(program)?.isLong==true) {
|
||||
errors.warn("repeat using a long counter could be very slow", repeatLoop.iterations!!.position)
|
||||
}
|
||||
|
||||
val ident = repeatLoop.iterations as? IdentifierReference
|
||||
@@ -907,7 +911,7 @@ internal class AstChecker(private val program: Program,
|
||||
val variable=addressOf.identifier?.targetVarDecl()
|
||||
if (variable!=null) {
|
||||
if (variable.type == VarDeclType.CONST && addressOf.arrayIndex == null)
|
||||
errors.err("invalid pointer-of operand type", addressOf.position)
|
||||
errors.err("invalid address-of operand type", addressOf.position)
|
||||
}
|
||||
|
||||
if(addressOf.msb) {
|
||||
|
||||
@@ -47,8 +47,36 @@ internal class BeforeAsmTypecastCleaner(val program: Program,
|
||||
}
|
||||
} else if(sourceDt.isString && typecast.type.isPointer && typecast.type.sub==BaseDataType.UBYTE) {
|
||||
// casting a string to a ^^ubyte is just taking the address of the string.
|
||||
val addr = AddressOf(typecast.expression as IdentifierReference, null, null, false, true, typecast.position)
|
||||
return listOf(AstReplaceNode(typecast, addr, parent))
|
||||
when (typecast.expression) {
|
||||
is IfExpression -> {
|
||||
val ifExpr = typecast.expression as IfExpression
|
||||
val trueCast = TypecastExpression(ifExpr.truevalue, typecast.type, typecast.implicit, typecast.position)
|
||||
val falseCast = TypecastExpression(ifExpr.falsevalue, typecast.type, typecast.implicit, typecast.position)
|
||||
trueCast.linkParents(ifExpr)
|
||||
falseCast.linkParents(ifExpr)
|
||||
ifExpr.truevalue = trueCast
|
||||
ifExpr.falsevalue = falseCast
|
||||
return listOf(AstReplaceNode(typecast, ifExpr, parent))
|
||||
}
|
||||
is BranchConditionExpression -> {
|
||||
val branchExpr = typecast.expression as BranchConditionExpression
|
||||
val trueCast = TypecastExpression(branchExpr.truevalue, typecast.type, typecast.implicit, typecast.position)
|
||||
val falseCast = TypecastExpression(branchExpr.falsevalue, typecast.type, typecast.implicit, typecast.position)
|
||||
trueCast.linkParents(branchExpr)
|
||||
falseCast.linkParents(branchExpr)
|
||||
branchExpr.truevalue = trueCast
|
||||
branchExpr.falsevalue = falseCast
|
||||
return listOf(AstReplaceNode(typecast, branchExpr, parent))
|
||||
}
|
||||
else -> {
|
||||
val identifier = typecast.expression as? IdentifierReference
|
||||
if (identifier != null) {
|
||||
val addr = AddressOf(identifier, null, null, false, true, typecast.position)
|
||||
return listOf(AstReplaceNode(typecast, addr, parent))
|
||||
}
|
||||
errors.err("cannot take address of this expression", typecast.position)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package prog8tests.compiler
|
||||
|
||||
import io.kotest.common.ExperimentalKotest
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.datatest.withData
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
@@ -146,10 +145,8 @@ private fun verifyIrRegisterCount(result: CompilationResult, expectedCount: Int)
|
||||
class TestCompilerOnExamplesC64: FunSpec({
|
||||
|
||||
@OptIn(ExperimentalKotest::class)
|
||||
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
|
||||
this.concurrency = 4
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
this.threads = 4
|
||||
|
||||
val onlyC64 = cartesianProduct(
|
||||
listOf(
|
||||
@@ -176,17 +173,17 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
)
|
||||
|
||||
val target = C64Target()
|
||||
withData(
|
||||
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
|
||||
onlyC64.map { it to prepareTestFiles(it.first.name, it.second, target) }
|
||||
) { (params, prep) ->
|
||||
val filepath = prep.second
|
||||
val optimize = params.second
|
||||
val expectedSize = if (optimize) params.first.c64SizeOptimized else params.first.c64SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, outputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
onlyC64.forEach { (params, optimize) ->
|
||||
val prep = prepareTestFiles(params.name, optimize, target)
|
||||
test(prep.first) {
|
||||
val testOutputDir = tempdir().toPath()
|
||||
val filepath = prep.second
|
||||
val expectedSize = if (optimize) params.c64SizeOptimized else params.c64SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, testOutputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -194,16 +191,14 @@ class TestCompilerOnExamplesC64: FunSpec({
|
||||
class TestCompilerOnExamplesCx16: FunSpec({
|
||||
|
||||
@OptIn(ExperimentalKotest::class)
|
||||
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
|
||||
this.concurrency = 4
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
this.threads = 4
|
||||
|
||||
val onlyCx16 = cartesianProduct(
|
||||
listOf(
|
||||
ExampleSizes("vtui/testvtui", cx16SizeOptimized=3041, cx16SizeUnoptimized=3041),
|
||||
ExampleSizes("pcmaudio/play-adpcm", cx16SizeOptimized=36147, cx16SizeUnoptimized=36645),
|
||||
ExampleSizes("pcmaudio/stream-wav", cx16SizeOptimized=5627, cx16SizeUnoptimized=7863),
|
||||
ExampleSizes("pcmaudio/stream-wav", cx16SizeOptimized=5636, cx16SizeUnoptimized=7878),
|
||||
ExampleSizes("pcmaudio/stream-simple-aflow", cx16SizeOptimized=1850, cx16SizeUnoptimized=3753),
|
||||
ExampleSizes("pcmaudio/stream-simple-poll", cx16SizeOptimized=1564, cx16SizeUnoptimized=3462),
|
||||
ExampleSizes("pcmaudio/vumeter", cx16SizeOptimized=3888, cx16SizeUnoptimized=6381),
|
||||
@@ -245,7 +240,7 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
ExampleSizes("multi-irq-old", cx16SizeOptimized=962, cx16SizeUnoptimized=1410),
|
||||
ExampleSizes("plasma", cx16SizeOptimized=1739, cx16SizeUnoptimized=2066),
|
||||
ExampleSizes("rasterbars", cx16SizeOptimized=1268, cx16SizeUnoptimized=1685),
|
||||
ExampleSizes("serialdownload", cx16SizeOptimized=2471, cx16SizeUnoptimized=3649),
|
||||
ExampleSizes("serialdownload", cx16SizeOptimized=2516, cx16SizeUnoptimized=3945),
|
||||
ExampleSizes("showbmx", cx16SizeOptimized=2745, cx16SizeUnoptimized=5269),
|
||||
ExampleSizes("snow", cx16SizeOptimized=3120, cx16SizeUnoptimized=5295),
|
||||
ExampleSizes("sortingbench", cx16SizeOptimized=2928, cx16SizeUnoptimized=3847),
|
||||
@@ -260,17 +255,17 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
)
|
||||
|
||||
val target = Cx16Target()
|
||||
withData(
|
||||
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
|
||||
onlyCx16.map { it to prepareTestFiles(it.first.name, it.second, target) }
|
||||
) { (params, prep) ->
|
||||
val filepath = prep.second
|
||||
val optimize = params.second
|
||||
val expectedSize = if (optimize) params.first.cx16SizeOptimized else params.first.cx16SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, outputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
onlyCx16.forEach { (params, optimize) ->
|
||||
val prep = prepareTestFiles(params.name, optimize, target)
|
||||
test(prep.first) {
|
||||
val testOutputDir = tempdir().toPath()
|
||||
val filepath = prep.second
|
||||
val expectedSize = if (optimize) params.cx16SizeOptimized else params.cx16SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, testOutputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -278,10 +273,8 @@ class TestCompilerOnExamplesCx16: FunSpec({
|
||||
class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
|
||||
@OptIn(ExperimentalKotest::class)
|
||||
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
|
||||
this.concurrency = 4
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
this.threads = 4
|
||||
|
||||
val bothCx16AndC64 = cartesianProduct(
|
||||
listOf(
|
||||
@@ -315,33 +308,30 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
|
||||
listOf(C64Target(), Cx16Target())
|
||||
)
|
||||
|
||||
withData(
|
||||
nameFn = { "${it.first.name}: ${it.third.first.name}, optimize=${it.second}" },
|
||||
bothCx16AndC64.map {
|
||||
val prep = prepareTestFiles(it.first.name, it.second, it.third)
|
||||
Triple(it.first, it.second, Triple(it.third, prep.first, prep.second))
|
||||
}
|
||||
) { params ->
|
||||
val filepath = params.third.third
|
||||
val optimize = params.second
|
||||
val target = params.third.first
|
||||
val exampleSizes = params.first
|
||||
val expectedSize = when (target) {
|
||||
is C64Target -> if (optimize) exampleSizes.c64SizeOptimized else exampleSizes.c64SizeUnoptimized
|
||||
is Cx16Target -> if (optimize) exampleSizes.cx16SizeOptimized else exampleSizes.cx16SizeUnoptimized
|
||||
else -> 0
|
||||
}
|
||||
val result = compileTheThing(filepath, optimize, target, outputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
bothCx16AndC64.forEach { (exampleSizes, optimize, target) ->
|
||||
val prep = prepareTestFiles(exampleSizes.name, optimize, target)
|
||||
test(prep.first) {
|
||||
val testOutputDir = tempdir().toPath()
|
||||
val filepath = prep.second
|
||||
val expectedSize = when (target) {
|
||||
is C64Target -> if (optimize) exampleSizes.c64SizeOptimized else exampleSizes.c64SizeUnoptimized
|
||||
is Cx16Target -> if (optimize) exampleSizes.cx16SizeOptimized else exampleSizes.cx16SizeUnoptimized
|
||||
else -> 0
|
||||
}
|
||||
val result = compileTheThing(filepath, optimize, target, testOutputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
class TestCompilerOnExamplesVirtual: FunSpec({
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
@OptIn(ExperimentalKotest::class)
|
||||
this.concurrency = 4
|
||||
this.threads = 4
|
||||
|
||||
val onlyVirtual = cartesianProduct(
|
||||
listOf(
|
||||
@@ -361,19 +351,19 @@ class TestCompilerOnExamplesVirtual: FunSpec({
|
||||
)
|
||||
|
||||
val target = VMTarget()
|
||||
withData(
|
||||
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
|
||||
onlyVirtual.map { it to prepareTestFiles(it.first.name, it.second, target) }
|
||||
) { (params, prep) ->
|
||||
val filepath = prep.second
|
||||
val optimize = params.second
|
||||
val expectedInstrCount = if (optimize) params.first.virtualInstrCountOptimized else params.first.virtualInstrCountUnoptimized
|
||||
val expectedRegCount = if (optimize) params.first.virtualRegCountOptimized else params.first.virtualRegCountUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, outputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedInstrCount > 0) {
|
||||
verifyIrInstructionCount(result!!, expectedInstrCount)
|
||||
verifyIrRegisterCount(result, expectedRegCount)
|
||||
onlyVirtual.forEach { (params, optimize) ->
|
||||
val prep = prepareTestFiles(params.name, optimize, target)
|
||||
test(prep.first) {
|
||||
val testOutputDir = tempdir().toPath()
|
||||
val filepath = prep.second
|
||||
val expectedInstrCount = if (optimize) params.virtualInstrCountOptimized else params.virtualInstrCountUnoptimized
|
||||
val expectedRegCount = if (optimize) params.virtualRegCountOptimized else params.virtualRegCountUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, testOutputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedInstrCount > 0) {
|
||||
verifyIrInstructionCount(result!!, expectedInstrCount)
|
||||
verifyIrRegisterCount(result, expectedRegCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -381,10 +371,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
|
||||
class TestCompilerOnExamplesPET32: FunSpec({
|
||||
|
||||
@OptIn(ExperimentalKotest::class)
|
||||
//testExecutionMode = TestExecutionMode.LimitedConcurrency(6)
|
||||
this.concurrency = 4
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
this.threads = 4
|
||||
|
||||
val onlyPET32 = cartesianProduct(
|
||||
listOf(
|
||||
@@ -395,17 +383,17 @@ class TestCompilerOnExamplesPET32: FunSpec({
|
||||
)
|
||||
|
||||
val target = PETTarget()
|
||||
withData(
|
||||
nameFn = { "${it.first.first.name}: ${target.name}, optimize=${it.first.second}" },
|
||||
onlyPET32.map { it to prepareTestFiles(it.first.name, it.second, target) }
|
||||
) { (params, prep) ->
|
||||
val filepath = prep.second
|
||||
val optimize = params.second
|
||||
val expectedSize = if (optimize) params.first.pet32SizeOptimized else params.first.pet32SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, outputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
onlyPET32.forEach { (params, optimize) ->
|
||||
val prep = prepareTestFiles(params.name, optimize, target)
|
||||
test(prep.first) {
|
||||
val testOutputDir = tempdir().toPath()
|
||||
val filepath = prep.second
|
||||
val expectedSize = if (optimize) params.pet32SizeOptimized else params.pet32SizeUnoptimized
|
||||
val result = compileTheThing(filepath, optimize, target, testOutputDir)
|
||||
result shouldNotBe null
|
||||
if (expectedSize > 0) {
|
||||
verifyOutputFileSize(result!!, expectedSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -23,7 +23,11 @@ class TestComparisonIssues : FunSpec({
|
||||
val result = compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)!!
|
||||
val asmFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
|
||||
val asm = asmFile.readText()
|
||||
asm.shouldContainInOrder("ldx", "#252", "lda", "p8v_a+4,x", "adc", "p8v_b+4,x", "sta", "p8v_a+4,x", "inx", "bne")
|
||||
asm.shouldContainInOrder("clc",
|
||||
"lda", "p8v_a+0", "adc", "p8v_b+0", "sta", "p8v_a+0",
|
||||
"lda", "p8v_a+1", "adc", "p8v_b+1", "sta", "p8v_a+1",
|
||||
"lda", "p8v_a+2", "adc", "p8v_b+2", "sta", "p8v_a+2",
|
||||
"lda", "p8v_a+3", "adc", "p8v_b+3", "sta", "p8v_a+3")
|
||||
}
|
||||
|
||||
test("long augmented subtraction generates loop") {
|
||||
@@ -39,7 +43,28 @@ class TestComparisonIssues : FunSpec({
|
||||
val result = compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)!!
|
||||
val asmFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
|
||||
val asm = asmFile.readText()
|
||||
asm.shouldContainInOrder("ldx", "#252", "lda", "p8v_a+4,x", "sbc", "p8v_b+4,x", "sta", "p8v_a+4,x", "inx", "bne")
|
||||
asm.shouldContainInOrder("sec",
|
||||
"lda", "p8v_a+0", "sbc", "p8v_b+0", "sta", "p8v_a+0",
|
||||
"lda", "p8v_a+1", "sbc", "p8v_b+1", "sta", "p8v_a+1",
|
||||
"lda", "p8v_a+2", "sbc", "p8v_b+2", "sta", "p8v_a+2",
|
||||
"lda", "p8v_a+3", "sbc", "p8v_b+3", "sta", "p8v_a+3")
|
||||
}
|
||||
|
||||
test("long augmented addition generates loop with nozp variables") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @nozp a = 1000
|
||||
long @nozp b = 2000
|
||||
a += b
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)!!
|
||||
val asmFile = result.compilationOptions.outputDir.resolve(result.compilerAst.name + ".asm")
|
||||
val asm = asmFile.readText()
|
||||
// verify unrolled structure
|
||||
asm.shouldContainInOrder("clc", "lda", "adc", "sta", "lda", "adc", "sta", "lda", "adc", "sta", "lda", "adc", "sta")
|
||||
}
|
||||
|
||||
test("word <= 0 signed comparison (identifier)") {
|
||||
|
||||
@@ -119,6 +119,43 @@ class TestExecution6502 : FunSpec({
|
||||
machine.cpu.totalCycles shouldBeLessThan 200
|
||||
}
|
||||
|
||||
test("long repeat loops") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
&ubyte poweroff = $f203
|
||||
&long result = $02 ; result is stored in memory at $02
|
||||
|
||||
sub start() {
|
||||
long @shared qq = 0
|
||||
repeat 70000 {
|
||||
qq++
|
||||
}
|
||||
result = qq
|
||||
|
||||
long @shared iters = 70000
|
||||
repeat iters {
|
||||
qq++
|
||||
}
|
||||
result = qq
|
||||
|
||||
poweroff = 1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
// 140000 iterations * ~40 cycles = 5.6 million. Let's use 10 million to be safe.
|
||||
val machine = compileResult!!.simulate(maxCycles = 10000000L)
|
||||
machine.assertMemory(0x02, (140000L and 0xff).toInt())
|
||||
machine.assertMemory(0x03, ((140000L shr 8) and 0xff).toInt())
|
||||
machine.assertMemory(0x04, ((140000L shr 16) and 0xff).toInt())
|
||||
machine.assertMemory(0x05, ((140000L shr 24) and 0xff).toInt())
|
||||
}
|
||||
|
||||
test("top level RTS exits simulator") {
|
||||
val src = $$"""
|
||||
%option no_sysinit
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package prog8tests.codegeneration
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestExperiCodeGen: FunSpec({
|
||||
|
||||
val outputDir = tempdir().toPath()
|
||||
|
||||
test("banked extsub with multiple return values (constbank)") {
|
||||
val src = $$"""
|
||||
main {
|
||||
sub start() {
|
||||
uword ptr
|
||||
ubyte bank
|
||||
void, ptr, bank = mymodule.myfunc(0)
|
||||
}
|
||||
}
|
||||
|
||||
mymodule {
|
||||
const ubyte MYBANK = 1
|
||||
extsub @bank MYBANK $A000 = myfunc(ubyte x @X) -> bool @Pc, uword @XY, ubyte @A
|
||||
}"""
|
||||
compileText(Cx16Target(), false, src, outputDir, experimentalCodegen = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("banked extsub with multiple return values (varbank)") {
|
||||
val src = $$"""
|
||||
main {
|
||||
sub start() {
|
||||
uword ptr
|
||||
ubyte bank
|
||||
void, ptr, bank = mymodule.myfunc(0)
|
||||
}
|
||||
}
|
||||
|
||||
mymodule {
|
||||
ubyte @shared bankvar = 1
|
||||
extsub @bank bankvar $A000 = myfunc(ubyte x @X) -> bool @Pc, uword @XY, ubyte @A
|
||||
}"""
|
||||
compileText(Cx16Target(), false, src, outputDir, experimentalCodegen = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,49 @@
|
||||
package prog8tests.codegeneration
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestLongAugmentedAssignment : FunSpec({
|
||||
val outputDir = tempdir().toPath()
|
||||
|
||||
test("long augmented assignment with literal") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @shared a = 1000
|
||||
a += 500
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
// Should compile successfully
|
||||
compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)
|
||||
}
|
||||
|
||||
test("long augmented assignment with expression") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @shared a = 1000
|
||||
long @shared b = 500
|
||||
a += b + 100
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)
|
||||
}
|
||||
|
||||
test("long augmented assignment with ubyte") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @shared a = 1000
|
||||
ubyte @shared b = 200
|
||||
a += b
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
compileText(Cx16Target(), false, text, outputDir, writeAssembly = true)
|
||||
}
|
||||
})
|
||||
@@ -18,6 +18,7 @@ import prog8.code.target.*
|
||||
import prog8tests.helpers.ErrorReporterForTests
|
||||
import prog8tests.helpers.compileText
|
||||
import prog8tests.helpers.shouldContainInOrder
|
||||
import prog8tests.helpers.simulate
|
||||
import kotlin.io.path.readText
|
||||
|
||||
class TestVariousCodeGen: FunSpec({
|
||||
@@ -904,4 +905,93 @@ main {
|
||||
asm shouldContain "sta (p8v_s),y"
|
||||
}
|
||||
}
|
||||
|
||||
test("IfExpression string to pointer typecast") {
|
||||
val src = """
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
str a = "hello"
|
||||
str b = "world"
|
||||
bool flag = true
|
||||
^^ubyte p = if flag then a else b
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// This should not crash during compilation
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
compileResult shouldNotBe null
|
||||
|
||||
// Also test with virtual target
|
||||
val compileResultVM = compileText(VMTarget(), false, src, outputDir)
|
||||
compileResultVM shouldNotBe null
|
||||
}
|
||||
|
||||
test("BranchConditionExpression string to pointer typecast") {
|
||||
val src = """
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
str a = "hello"
|
||||
str b = "world"
|
||||
^^ubyte p = if_z then a else b
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// This should not crash during compilation
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
compileResult shouldNotBe null
|
||||
}
|
||||
|
||||
test("pointer assignment to variable in 6502 backend") {
|
||||
val src = """
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
ubyte value = 42
|
||||
^^ubyte @shared ptr = 0
|
||||
|
||||
sub start() {
|
||||
ptr = &value
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// This should not crash during compilation
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
compileResult shouldNotBe null
|
||||
|
||||
// Verify it actually works by simulating
|
||||
compileResult!!.simulate()
|
||||
}
|
||||
|
||||
test("pointer assignment to array element in 6502 backend") {
|
||||
val src = """
|
||||
%option no_sysinit
|
||||
%launcher none
|
||||
%address $1000
|
||||
|
||||
main {
|
||||
ubyte value = 42
|
||||
^^ubyte[2] ptrs
|
||||
|
||||
sub start() {
|
||||
ptrs[1] = &value
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val compileResult = compileText(Cx16Target(), false, src, outputDir)
|
||||
compileResult shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -18,7 +18,8 @@ internal fun compileFile(
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true,
|
||||
varshigh: Int? = null,
|
||||
slabshigh: Int? = null
|
||||
slabshigh: Int? = null,
|
||||
experimentalCodegen: Boolean = false
|
||||
) : CompilationResult? {
|
||||
val filepath = fileDir.resolve(fileName)
|
||||
assumeReadableFile(filepath)
|
||||
@@ -33,7 +34,7 @@ internal fun compileFile(
|
||||
showTimings = false,
|
||||
asmListfile = false,
|
||||
includeSourcelines = false,
|
||||
experimentalCodegen = false,
|
||||
experimentalCodegen = experimentalCodegen,
|
||||
dumpVariables = false,
|
||||
dumpSymbols = false,
|
||||
varsHighBank = varshigh,
|
||||
@@ -67,11 +68,13 @@ internal fun compileText(
|
||||
errors: IErrorReporter? = null,
|
||||
writeAssembly: Boolean = true,
|
||||
varshigh: Int? = null,
|
||||
slabshigh: Int? = null
|
||||
slabshigh: Int? = null,
|
||||
experimentalCodegen: Boolean = false
|
||||
) : CompilationResult? {
|
||||
val filePath = outputDir.resolve("on_the_fly_test_${sourceText.hashCode().toUInt().toString(16)}.p8")
|
||||
// we don't assumeNotExists(filePath) - should be ok to just overwrite it
|
||||
filePath.toFile().writeText(sourceText)
|
||||
return compileFile(platform, optimize, filePath.parent, filePath.name, outputDir,
|
||||
errors=errors, writeAssembly=writeAssembly, varshigh=varshigh, slabshigh=slabshigh)
|
||||
errors=errors, writeAssembly=writeAssembly, varshigh=varshigh, slabshigh=slabshigh,
|
||||
experimentalCodegen = experimentalCodegen)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package prog8tests.optimizer
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.engine.spec.tempdir
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestExpressionSimplifier : FunSpec({
|
||||
val outputDir = tempdir().toPath()
|
||||
|
||||
test("long & -1 is correctly simplified") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @shared x = 1000
|
||||
long @shared y = x & -1
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(Cx16Target(), true, text, outputDir, writeAssembly = false)!!
|
||||
val s = result.compilerAst.entrypoint.statements
|
||||
val expr = (s[3] as Assignment).value as BinaryExpression
|
||||
expr.operator shouldBe "&"
|
||||
(expr.left as IdentifierReference).nameInSource shouldBe listOf("x")
|
||||
(expr.right as NumericLiteral).number shouldBe -1.0
|
||||
}
|
||||
|
||||
test("long & 255 is correctly handled") {
|
||||
val text = """
|
||||
main {
|
||||
sub start() {
|
||||
long @shared x = 1000
|
||||
long @shared y = x & 255
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
val result = compileText(Cx16Target(), true, text, outputDir, writeAssembly = false)!!
|
||||
val s = result.compilerAst.entrypoint.statements
|
||||
val expr = (s[3] as Assignment).value as BinaryExpression
|
||||
expr.operator shouldBe "&"
|
||||
(expr.left as IdentifierReference).nameInSource shouldBe listOf("x")
|
||||
(expr.right as NumericLiteral).number shouldBe 255.0
|
||||
}
|
||||
})
|
||||
@@ -654,7 +654,7 @@ data class AddressOf(var identifier: IdentifierReference?, var arrayIndex: Array
|
||||
val target = this.identifier?.targetStatement()
|
||||
val targetVar = target as? VarDecl
|
||||
if(targetVar!=null) {
|
||||
if (targetVar.type == VarDeclType.MEMORY || targetVar.type == VarDeclType.CONST) {
|
||||
if (targetVar.type == VarDeclType.MEMORY || (targetVar.type == VarDeclType.CONST && arrayIndex != null)) {
|
||||
var address = targetVar.value?.constValue(program)?.number
|
||||
if (address != null) {
|
||||
if (arrayIndex != null) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
Prog8 compiler v12.2-BETA2 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prerelease version from git commit 1dbaa6e0 in branch master
|
||||
Prog8 compiler v12.2-SNAPSHOT by Irmen de Jong (irmen@razorvine.net)
|
||||
Prerelease version from git commit 8bf744cc in branch master
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-cx16.p8
|
||||
@@ -788,6 +788,21 @@ LIBRARY MODULE NAME: serial
|
||||
---------------------------
|
||||
|
||||
serial {
|
||||
const uword BAUD::B115200
|
||||
const uword BAUD::B1200
|
||||
const uword BAUD::B14400
|
||||
const uword BAUD::B19200
|
||||
const uword BAUD::B230400
|
||||
const uword BAUD::B2400
|
||||
const uword BAUD::B28800
|
||||
const uword BAUD::B300
|
||||
const uword BAUD::B38400
|
||||
const uword BAUD::B460800
|
||||
const uword BAUD::B4800
|
||||
const uword BAUD::B57600
|
||||
const uword BAUD::B600
|
||||
const uword BAUD::B921600
|
||||
const uword BAUD::B9600
|
||||
const ubyte LSR::BI
|
||||
const ubyte LSR::DR
|
||||
const ubyte LSR::FE
|
||||
@@ -808,14 +823,18 @@ serial {
|
||||
const ubyte REG_SCRATCH
|
||||
const ubyte REG_TXRX_BUFFER
|
||||
detect_uarts () -> uword, uword
|
||||
discard_until (uword uart_addr, str match)
|
||||
initialize_uart (uword uart_addr)
|
||||
read_until (uword uart_addr, str match, ^^ubyte buffer, uword max_size) -> uword
|
||||
write (uword uart_addr, str data)
|
||||
zi_end_get_file ()
|
||||
zi_get_file_chunk (^^ubyte buffer, uword buffer_size, long remaining_file_size) -> uword
|
||||
zi_get_file_chunk_hexmode (^^ubyte buffer, uword buffer_size) -> uword
|
||||
zi_get_ip_address () -> str
|
||||
zi_initialize (uword uart_addr)
|
||||
zi_reset ()
|
||||
zi_start_get_file (str filename) -> long
|
||||
zi_start_get_file_hexmode (str filename) -> bool
|
||||
zi_write_cmd (str command)
|
||||
}
|
||||
|
||||
|
||||
+15
-14
@@ -42,7 +42,7 @@ No system initialization and startup code
|
||||
Variable initialization
|
||||
The library still has to initialize any variables it might use and clear
|
||||
uninitialized "BSS" variables! Otherwise the code will not run predictably as prog8 code.
|
||||
So, the library must still have a "start" entrypoint subroutine like any outher prog8 program,
|
||||
So, the library must still have a "start" entrypoint subroutine like any other prog8 program,
|
||||
that must be called before any other library routine can be called.
|
||||
|
||||
Binary output and loaded into a fixed memory address
|
||||
@@ -79,7 +79,7 @@ Jump table
|
||||
For ease of use, libraries should probably have a fixed "jump table" where the offsets of the
|
||||
library routines stay the same across different versions of the library. Without needing new syntax,
|
||||
there's a trick in Prog8 that you can use to build such a jumptable:
|
||||
add a non-splitted word array at the top of the library main block that contains JMP instructions
|
||||
add a non-split word array at the top of the library main block that contains JMP instructions
|
||||
and the addresses of the individual library subroutines. Do NOT change the order of the subroutines
|
||||
in this table!
|
||||
Also note that the Prog8 compiler will insert a single JMP instruction at the very start of the library,
|
||||
@@ -105,13 +105,14 @@ Assuming the load address of the library is $A000:
|
||||
|
||||
BLOAD "LIBRARY.BIN",8,1,$A000
|
||||
SYS $A000 : REM TO INITIALIZE VARIABLES, REQUIRED!
|
||||
SYS $A004 : REM CALL FIRST ROUTINE
|
||||
SYS $A008 : REM CALL SECOND ROUTINE, ETC.
|
||||
SYS $A003 : REM CALL FIRST ROUTINE
|
||||
SYS $A006 : REM CALL SECOND ROUTINE, ETC.
|
||||
|
||||
|
||||
**From Prog8**
|
||||
.. index:: single: Examples; Library from Prog8
|
||||
|
||||
**From Prog8:**
|
||||
|
||||
The ``diskio`` module actually provides a convenience routine called ``loadlib`` that loads a Prog8-compiled
|
||||
library blob into memory. It internally automatically uses either regular load() or load_raw(),
|
||||
as required by the compilation target (so you don't have to bother with target machine
|
||||
@@ -122,8 +123,8 @@ differences if you want to write portable code)::
|
||||
main {
|
||||
|
||||
extsub $A000 = lib_init() clobbers(A)
|
||||
extsub $A004 = lib_func1() clobbers(A,X,Y)
|
||||
extsub $A008 = lib_func2() clobbers(A,X,Y)
|
||||
extsub $A003 = lib_func1() clobbers(A,X,Y)
|
||||
extsub $A006 = lib_func2() clobbers(A,X,Y)
|
||||
|
||||
sub start() {
|
||||
if diskio.loadlib("library.bin", $a000) != 0 {
|
||||
@@ -144,12 +145,12 @@ differences if you want to write portable code)::
|
||||
|
||||
int main() {
|
||||
void (*lib_init)(void) = (void (*)()) 0xa000;
|
||||
void (*lib_func1)(void) = (void (*)()) 0xa004;
|
||||
void (*lib_func2)(void) = (void (*)()) 0xa008;
|
||||
void (*lib_func1)(void) = (void (*)()) 0xa003;
|
||||
void (*lib_func2)(void) = (void (*)()) 0xa006;
|
||||
|
||||
cbm_k_setlfs(0, 8, 2);
|
||||
cbm_k_setnam("library.bin");
|
||||
cbm_k_load(0, 0xa000);
|
||||
cbm_k_setlfs(0, 8, 2);
|
||||
cbm_k_setnam("library.bin");
|
||||
cbm_k_load(0, 0xa000);
|
||||
|
||||
lib_init();
|
||||
lib_func1();
|
||||
@@ -178,8 +179,8 @@ differences if you want to write portable code)::
|
||||
jsr $ffd2 ; CHROUT
|
||||
|
||||
jsr $A000 ; library init
|
||||
jsr $A004 ; lib func 1
|
||||
jsr $A008 ; lib func 2
|
||||
jsr $A003 ; lib func 1
|
||||
jsr $A006 ; lib func 2
|
||||
|
||||
rts
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ The language
|
||||
- It is meant to sit well above low level assembly code, but still allows that low level access to the system it runs on.
|
||||
Via language features, or even simply by using inline hand-written assembly code.
|
||||
- Prog8 is targeting very CPU and memory constrained 8-bit systems, this reflects many design choices to work within those limitations
|
||||
(single digit Megaherz cpu clock speeds, and memory capacity counted in Kilobytes)
|
||||
(single digit Megahertz cpu clock speeds, and memory capacity counted in Kilobytes)
|
||||
- Identifiers and string literals can contain non-ASCII characters so for example ``knäckebröd`` and ``見せしめ`` are valid identifiers.
|
||||
- There's usually a single statement per line. There is no statement separator.
|
||||
- Semicolon ``;`` is used to start a line comment. Multi-line comments are also possible by enclosing it all in ``/*`` and ``*/``.
|
||||
@@ -43,7 +43,7 @@ Data types
|
||||
- **There is no automatic type enlargement:** all calculations remain within the data type of the operands. Any overflow silently wraps or truncates.
|
||||
You'll have to add explicit casts to increase the size of the value if required.
|
||||
For example when adding two byte variables having values 100 and 200, the result won't be 300, because that doesn't fit in a byte. It will be 44.
|
||||
You'll have to cast one or both of the *operands* to a word type first if you want to accomodate the actual result value of 300.
|
||||
You'll have to cast one or both of the *operands* to a word type first if you want to accommodate the actual result value of 300.
|
||||
Similarly, ``long v = w1 * w2`` doesn't automatically give you the full 32 bits multiplication result, instead it is still constrained in the word range.
|
||||
If you need the full 32 bits result you'll have to call a specialized routine such as ``math.mul32`` or ``math.mul16_last_upper()``.
|
||||
- strings and arrays are allocated once, statically, and never resized.
|
||||
@@ -66,7 +66,7 @@ Variables
|
||||
are moved to the top of the subroutine. A for loop, or if/else blocks do not introduce a new scope.
|
||||
A subroutine (also nested ones) *do* introduce a new scope.
|
||||
- All variables are initialized at the start of the program. There is no random garbage in them: they are zero or any other initialization value you provide.
|
||||
- This als means you can run a Prog8 program multiple times without having to reload it from disk, unlike programs produced by most other compilers targeting these 8 bit platforms.
|
||||
- This also means you can run a Prog8 program multiple times without having to reload it from disk, unlike programs produced by most other compilers targeting these 8 bit platforms.
|
||||
|
||||
|
||||
Subroutines
|
||||
@@ -102,7 +102,7 @@ Pointers and Structs
|
||||
Variables of the ``uword`` datatype can be used as a pointer to one of the possible 65536 memory locations,
|
||||
so the value it points to is always a single byte. This is similar to ``uint8_t*`` from C.
|
||||
You have to deal with the uword manually if the object it points to is something different.
|
||||
- Note that there is the ``peekw`` builtin function that *does* allow you to directy obtain the *word* value at the given memory location.
|
||||
- Note that there is the ``peekw`` builtin function that *does* allow you to directly obtain the *word* value at the given memory location.
|
||||
So if you use this, you can use uword pointers as pointers to word values without much hassle.
|
||||
- "dereferencing" a uword pointer is done via array indexing ``ptr[index]`` (where index value can be 0-65535!) or via the memory read operator ``@(ptr)``, or ``peek/peekw(ptr)``.
|
||||
- Pointers don't have to be a variable, you can immediately access the value of a given memory location using ``@($d020)`` for instance.
|
||||
@@ -125,14 +125,14 @@ Foreign function interface (external/ROM calls)
|
||||
|
||||
- You can use the ``extsub`` keyword to define the call signature of foreign functions (ROM routines or external routines elsewhere in RAM) in a natural way.
|
||||
Calling those generates code that is as efficient or even more efficient as calling regular subroutines.
|
||||
No additional stubs are needed. Y
|
||||
No additional stubs are needed.
|
||||
- High level support of memory banking: an ``extsub`` can be defined with the memory bank number (constant or variable) where the routine is located in,
|
||||
and then when you call it as usual, the compiler takes care of the required bank switching.
|
||||
|
||||
Optimizations
|
||||
-------------
|
||||
- Prog8 contains many compiler optimizations to generate efficient code, but also lacks many optimizations that modern compilers do have.
|
||||
While empirical evidence shows that Prog8 generates more efficent code than some C compilers that also target the same 8 bit systems,
|
||||
While empirical evidence shows that Prog8 generates more efficient code than some C compilers that also target the same 8 bit systems,
|
||||
the optimizations it makes on your code aren't super sophisticated.
|
||||
- For time critical code, it may be worth it to inspect the generated assembly code to see if you can write things differently
|
||||
to help the compiler generate more efficient code (or even replace it with hand written inline assembly altogether).
|
||||
|
||||
+10
-10
@@ -23,7 +23,7 @@ Then you can choose a few ways to get a compiler:
|
||||
|
||||
Arch Linux:
|
||||
Currently, it's available on `AUR <https://wiki.archlinux.org/title/Arch_User_Repository>`_ for Arch Linux and derivative systems.
|
||||
The package is called `"prog8" <https://aur.archlinux.org/packages/prog8>`_. There should be no need to install aything else as
|
||||
The package is called `"prog8" <https://aur.archlinux.org/packages/prog8>`_. There should be no need to install anything else as
|
||||
it can automatically pull in the required dependencies.
|
||||
|
||||
This package, alongside the compiler itself, also globally installs syntax highlighting for ``vim`` and ``nano``.
|
||||
@@ -65,12 +65,12 @@ The most interesting gradle commands to run are probably the ones listed below.
|
||||
``./gradlew installShadowDist``
|
||||
Creates a 'fat-jar' that contains the compiler and all dependencies, in a single
|
||||
executable .jar file, and includes few start scripts to run it.
|
||||
The output can be found in ``.compiler/build/install/prog8c-shadow/``
|
||||
The output can be found in ``./compiler/build/install/prog8c-shadow/``
|
||||
``./gradlew shadowDistZip``
|
||||
Creates a zipfile with the above in it, for easy distribution.
|
||||
This file can be found in ``./compiler/build/distributions/``
|
||||
|
||||
For normal use, the ``installDist`` task should suffice and after succesful completion, you can start the compiler with:
|
||||
For normal use, the ``installDist`` task should suffice and after successful completion, you can start the compiler with:
|
||||
|
||||
``./compiler/build/install/prog8c/bin/prog8c <options> <sourcefile>``
|
||||
|
||||
@@ -163,7 +163,7 @@ One or more .p8 module files
|
||||
This can be useful on emulators/systems that don't parse the breakpoint information in the list file,
|
||||
such as the X16Emu emulator for the Commander X16.
|
||||
Useful instructions to consider are ``brk`` and ``stp``.
|
||||
For example for the Commander X16 emulator, ``stp`` is useful because it can actually tyrigger
|
||||
For example for the Commander X16 emulator, ``stp`` is useful because it can actually trigger
|
||||
a breakpoint halt in the debugger when this is enabled by running the emulator with -debug.
|
||||
|
||||
``-bytes2float <bytes>``
|
||||
@@ -219,7 +219,7 @@ One or more .p8 module files
|
||||
With this option you're basically saying: "Yes, I know I am treading on mighty thin ice here, but I don't want to be reminded about that".
|
||||
|
||||
``-libsearch pattern``
|
||||
Search in the embedded library files for occurences of the given regex pattern.
|
||||
Search in the embedded library files for occurrences of the given regex pattern.
|
||||
|
||||
``-libdump path``
|
||||
Extract all embedded library files into the given output directory.
|
||||
@@ -269,7 +269,7 @@ One or more .p8 module files
|
||||
|
||||
``-nosourcelines``
|
||||
Do not include the original prog8 source code lines as comments in the generated assembly code file,
|
||||
mixed in between the actual generated assembly code. The default behavior is to include the sourcel lines.
|
||||
mixed in between the actual generated assembly code. The default behavior is to include the source lines.
|
||||
|
||||
``-srcdirs <pathlist>``
|
||||
Specify a list of extra paths (separated with ':'), to search in for imported modules.
|
||||
@@ -280,7 +280,7 @@ One or more .p8 module files
|
||||
Sets the target output of the compiler. This option is required.
|
||||
``c64`` = Commodore 64, ``c128`` = Commodore 128, ``cx16`` = Commander X16, ``pet32`` - Commodore PET model 4032,
|
||||
``virtual`` = builtin virtual machine.
|
||||
You can also specify a file name as target, prog8 will when try to read the target
|
||||
You can also specify a file name as target, prog8 will then try to read the target
|
||||
machine's configuration and properties from that configuration file instead of using one of the built-in targets.
|
||||
See :ref:`customizable_target` for details about this.
|
||||
|
||||
@@ -303,7 +303,7 @@ One or more .p8 module files
|
||||
cx16: $A000 - $BFFF ; 8 kB in the specified HIRAM bank (note: no auto bank switching is done, you must make sure yourself that this HIRAM bank is active when accessing these variables!)
|
||||
|
||||
If you use this option, you can no longer use the part of the above memory area that is
|
||||
alotted to the variables, for your own purposes. The output of the 64tass assembler step at the
|
||||
allotted to the variables, for your own purposes. The output of the 64tass assembler step at the
|
||||
end of compilation shows precise details of where and how much memory is used by the variables
|
||||
(it's called 'BSS' section or Gap at the address mentioned above).
|
||||
Assembling the program will fail if there are too many variables to fit in a single high ram bank.
|
||||
@@ -435,7 +435,7 @@ WARNING
|
||||
a compiled program is still produced, but it is a good idea to investigate the warnings that are printed.
|
||||
|
||||
ERROR
|
||||
unrecoverable problem that prevented succesful compilation
|
||||
unrecoverable problem that prevented successful compilation
|
||||
|
||||
|
||||
Compiler doesn't run, complains about "UnsupportedClassVersionError"
|
||||
@@ -486,7 +486,7 @@ you don't use symbols named just a single letter (especially 'a', 'x' and 'y').
|
||||
Sometimes these are interpreted as the CPU register of that name. To avoid such confusions,
|
||||
always use 2 or more letters for symbols in your assembly code.
|
||||
|
||||
'shadowing' warnings form the assembler
|
||||
'shadowing' warnings from the assembler
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Avoid using 'a', 'x' or 'y' as symbols in your inlined assembly code.
|
||||
Also avoid using 64tass' built-in function or type names as symbols in your inlined assembly code.
|
||||
|
||||
@@ -8,7 +8,7 @@ Prog8 documentation - |version|
|
||||
|
|
||||
|
|
||||
|
||||
Prog8 is is a compiled programming language targeting the 8-bit 6502 CPU family.
|
||||
Prog8 is a compiled programming language targeting the 8-bit 6502 CPU family.
|
||||
It aims to provide many conveniences over raw assembly code (even when using a macro assembler),
|
||||
while still being low level enough to create high performance programs.
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ while still being low level enough to create high performance programs.
|
||||
The targeted CPUs are the `6502 <https://en.wikipedia.org/wiki/MOS_Technology_6502>`_ /
|
||||
`6510 <https://en.wikipedia.org/wiki/MOS_Technology_6510>`_ /
|
||||
`65c02 <https://en.wikipedia.org/wiki/MOS_Technology_65C02>`_ microprocessors.
|
||||
They are from the late 1970's and early 1980's and was used in many home computers from that era,
|
||||
They are from the late 1970's and early 1980's and were used in many home computers from that era,
|
||||
such as the `Commodore 64 <https://en.wikipedia.org/wiki/Commodore_64>`_.
|
||||
|
||||
You can compile programs for various machines that are built in into the compiler:
|
||||
You can compile programs for various machines that are built into the compiler:
|
||||
|
||||
* Commander X16 (with 65c02 cpu, 65816 cpu specifics are currently not supported by prog8 itself)
|
||||
* Commodore 64
|
||||
@@ -79,6 +79,7 @@ Language :index:`Features`
|
||||
the compiled program in an emulator and provide debugging information to this emulator.
|
||||
- the language looks like a mix of Python and C so should be quite easy to learn
|
||||
- Modular programming, scoping via module source files, code blocks, and subroutines. No need for forward declarations.
|
||||
- Can make vars and routines private, as a simple way to restrict access and hide implementation details
|
||||
- Provides high level programming constructs but at the same time stay close to the metal;
|
||||
still able to directly use memory addresses and ROM subroutines,
|
||||
and inline assembly to have full control when every register, cycle or byte matters
|
||||
@@ -227,7 +228,7 @@ To use it, configure your editor to launch ``prog8-language-server`` as the LSP
|
||||
|
||||
.. index:: pair: Tools; CTags
|
||||
|
||||
**CTags:** for editors that support them, such as Vim, there is an effort by a member of the cummunity to provide "ctags" files for Prog8.
|
||||
**CTags:** for editors that support them, such as Vim, there is an effort by a member of the community to provide "ctags" files for Prog8.
|
||||
The basic purpose of ctags is to index the definitions from your source code and let you find them easily.
|
||||
It is not really to find each use of print for example, but to find where print is defined.
|
||||
Visit `the Github repository <https://github.com/gillham/prog8ctags>`_ for the files and detailed usage information.
|
||||
|
||||
+35
-14
@@ -282,7 +282,7 @@ sizeof (name) ; sizeof(datatype) ; sizeof(&name) ; sizeof(&&name) ; size
|
||||
|
||||
:index:`memory` (name, size, alignment) ; memory(name)
|
||||
Returns the address of the first location of a statically "reserved" block of memory with the given name.
|
||||
The 3-argument version reserves a block of the given size in bytes and optional alignment, and returns the adress of it.
|
||||
The 3-argument version reserves a block of the given size in bytes and optional alignment, and returns the address of it.
|
||||
The 1-argument version acts only as a reference to a block that must be reserved elsewhere in the program.
|
||||
The name must be a string literal, it cannot be empty or be a variable.
|
||||
This routine can be used to "reserve" parts of the memory where a normal byte array variable would
|
||||
@@ -301,7 +301,7 @@ sizeof (name) ; sizeof(datatype) ; sizeof(&name) ; sizeof(&&name) ; size
|
||||
|
||||
:index:`call` (address) -> uword
|
||||
Calls a subroutine given by its memory address. You cannot pass arguments directly,
|
||||
although it is ofcourse possible to do this via the global ``cx16.r0...`` registers for example.
|
||||
although it is of course possible to do this via the global ``cx16.r0...`` registers for example.
|
||||
It is *not* possible to use cpu registers to pass arguments, because these are clobbered while performing the call!
|
||||
It is assumed the subroutine returns a word value (in AY), if it does not, just add void to the call to ignore the result value.
|
||||
This function effectively creates an "indirect JSR" if you use it on a ``uword`` pointer variable.
|
||||
@@ -382,7 +382,7 @@ bcd
|
||||
Decimal addition and subtraction routines, so for example $0987 + $1111 = $2098 (rather than the usual hex outcome $1a98)
|
||||
Utilizes the BCD mode of the CPU (note: not all 6502 variants support this mode).
|
||||
This mode is useful for example for counting decimal score in a game, to avoid costly conversion to a decimal display string:
|
||||
just print the hexadecimal score representation. (This gets especially noticable with long integers)
|
||||
just print the hexadecimal score representation. (This gets especially noticeable with long integers)
|
||||
Available routines:
|
||||
|
||||
- ``sub addb(byte a, byte b) -> byte``
|
||||
@@ -491,7 +491,7 @@ Decompressors are available for RLE, TSCrunch and ZX0 (Salvador).
|
||||
``decode_tscrunch (uword compressed, uword target)``
|
||||
Decompress a block of data compressed in the TSCrunch format.
|
||||
It has extremely fast decompression (approaching RLE speeds),
|
||||
better compression as RLE, but slightly worse compression ration than LZSA.
|
||||
better compression as RLE, but slightly worse compression ratio than LZSA.
|
||||
See https://github.com/tonysavon/TSCrunch for the compression format and compressor tool.
|
||||
**NOTE:** for speed reasons this decompressor is *not* bank-aware and *not* I/O register aware;
|
||||
it only outputs to a memory buffer somewhere in the active 64 Kb address range.
|
||||
@@ -607,7 +607,7 @@ and allows you to print it anywhere on the screen.
|
||||
``logo ()``
|
||||
prints the logo at the current cursor position
|
||||
``logo_at (column, row)``
|
||||
printss the logo at the given position
|
||||
prints the logo at the given position
|
||||
|
||||
|
||||
diskio
|
||||
@@ -637,7 +637,7 @@ On the Commander X16 it tries to use that machine's fast Kernal loading routines
|
||||
Routines to directly load data into video ram are also present (vload and vload_raw).
|
||||
Also contains a helper function to calculate the file size of a loaded file (although that is truncated
|
||||
to 16 bits, 64Kb)
|
||||
Als contains routines for operating on subdirectories (chdir, mkdir, rmdir), to relabel the disk,
|
||||
Also contains routines for operating on subdirectories (chdir, mkdir, rmdir), to relabel the disk,
|
||||
and to seek in open files.
|
||||
|
||||
Read the :source:`diskio source code <compiler/res/prog8lib/cx16/diskio.p8>`
|
||||
@@ -692,7 +692,7 @@ floats
|
||||
|
||||
.. note::
|
||||
Floating point support is available on most cbm-compatible targets (except the C128 for now), and the virtual target.
|
||||
On the X16, make sure rom bank 4 is still active before doing floationg point operations (it's the bank that contains the fp routines).
|
||||
On the X16, make sure rom bank 4 is still active before doing floating point operations (it's the bank that contains the fp routines).
|
||||
On the C64, you have to make sure the Basic ROM is still banked in (same reason).
|
||||
|
||||
Provides definitions for the ROM/Kernal subroutines and utility routines dealing with floating point variables.
|
||||
@@ -740,7 +740,7 @@ Provides definitions for the ROM/Kernal subroutines and utility routines dealing
|
||||
|
||||
``lerp_fast(v0, v1, t)``
|
||||
Linear interpolation (LERP). Imprecise (but faster) method, which does not guarantee v = v1 when t = 1
|
||||
Teturns an interpolation between two inputs (v0, v1) for a parameter t in the closed unit interval [0.0, 1.0]
|
||||
Returns an interpolation between two inputs (v0, v1) for a parameter t in the closed unit interval [0.0, 1.0]
|
||||
|
||||
``ln (x)``
|
||||
Natural logarithm (base e).
|
||||
@@ -866,7 +866,7 @@ checksumming
|
||||
.. index:: pair: Libraries; checksumming
|
||||
|
||||
``crc16 (uword data, uword length, uword initvalue, uword xorout) -> uword``
|
||||
Returns a CRC-16 checksum over the given data buffer, with configuarable initialization value and final result xor value.
|
||||
Returns a CRC-16 checksum over the given data buffer, with configurable initialization value and final result xor value.
|
||||
For XMODEM type checksum, use initvalue=0 and xorout=0.
|
||||
For IBM-3740 type checksum, use initvalue=$ffff and xorout=0. (this is then equivalent to the cx16.memory_crc routine).
|
||||
Many other types are possible with different values...
|
||||
@@ -909,7 +909,7 @@ large multiplications
|
||||
'''''''''''''''''''''
|
||||
.. index:: pair: Libraries; large multiplications
|
||||
|
||||
``mul32 (woord w1, word w2) -> long``
|
||||
``mul32 (word w1, word w2) -> long``
|
||||
Returns the 32 bits signed long result of w1 * w2
|
||||
|
||||
``mul16_last_upper () -> uword``
|
||||
@@ -1141,7 +1141,7 @@ psg (cx16 only)
|
||||
.. index:: pair: Libraries; psg
|
||||
|
||||
Available for the Cx16 target.
|
||||
**Note: New code should probaly use the psg2 module instead!**
|
||||
**Note: New code should probably use the psg2 module instead!**
|
||||
Contains a simple abstraction for the Vera's PSG (programmable sound generator) to play simple waveforms.
|
||||
It includes an interrupt handler routine for handling automatic ASR volume envelopes as well.
|
||||
|
||||
@@ -1183,7 +1183,12 @@ Generic UART routines:
|
||||
|
||||
``sub read_until(uword uart_addr, str match, ^^ubyte buffer, uword max_size) -> uword``
|
||||
Reads bytes from the UART into buffer until the match string is
|
||||
found or max_size bytes have been read. Returns the number of bytes read.
|
||||
found (the match string itself is also included in the buffer)
|
||||
or max_size bytes have been read. Returns the number of bytes read.
|
||||
|
||||
``sub discard_until(uword uart_addr, str match)``
|
||||
Reads and discards bytes from the UART until the match string is
|
||||
found (the match string itself is also discarded).
|
||||
|
||||
ZiModem wifi routines:
|
||||
|
||||
@@ -1199,12 +1204,28 @@ ZiModem wifi routines:
|
||||
``sub zi_start_get_file(str filename) -> long``
|
||||
Starts a file download from the given URL and returns the file
|
||||
size. Use ``zi_get_file_chunk()`` repeatedly to download the data.
|
||||
Note: binary mode transfer can be unreliable; prefer hex mode.
|
||||
|
||||
``sub zi_start_get_file_hexmode(str filename) -> bool``
|
||||
Starts a file download in hex mode from the given URL.
|
||||
Returns true if the file was found, false otherwise.
|
||||
Use ``zi_get_file_chunk_hexmode()`` repeatedly to download chunks.
|
||||
Hex mode is more reliable than binary mode for file transfers.
|
||||
|
||||
``sub zi_get_file_chunk(^^ubyte buffer, uword buffer_size, long remaining_file_size) -> uword``
|
||||
Reads the next chunk of data (up to buffer_size bytes) from an
|
||||
active file download started with ``zi_start_get_file()``.
|
||||
Returns the number of bytes read, or 0 when done.
|
||||
|
||||
``sub zi_get_file_chunk_hexmode(^^ubyte buffer, uword buffer_size) -> uword``
|
||||
Reads the next chunk of hex-encoded data (up to buffer_size bytes)
|
||||
from an active file download started with ``zi_start_get_file_hexmode()``.
|
||||
Buffer must be at least 40 bytes. Returns the number of bytes decoded, or 0 when done.
|
||||
|
||||
``sub zi_end_get_file()``
|
||||
Consumes the trailing ``OK`` response after downloading a file
|
||||
with ``zi_start_get_file()`` / ``zi_get_file_chunk()``.
|
||||
|
||||
``sub zi_get_ip_address() -> str``
|
||||
Returns the IP address of the ZiModem wifi connection.
|
||||
|
||||
@@ -1219,7 +1240,7 @@ Various sorting routines (gnome sort and shell sort variants) for byte, word and
|
||||
**NOTE:** all word and str arrays have to be @nosplit! Words and pointers need to be consecutive in memory for now.
|
||||
**NOTE:** sorting is done in ascending order.
|
||||
Read the :source:`sorting source code <compiler/res/prog8lib/sorting.p8>`
|
||||
to see what's in there. Also check out the `sortingbech` example.
|
||||
to see what's in there. Also check out the `sortingbench` example.
|
||||
|
||||
|
||||
sprites (cx16 only)
|
||||
@@ -1586,7 +1607,7 @@ There is a very specialized function in the sys module here as well:
|
||||
|
||||
``push_returnaddress (address)``
|
||||
pushes a 16 bit memory address on the CPU hardware stack in the same byte order as a JSR instruction would,
|
||||
which means the next RTS instruction will jump to that address instead.you
|
||||
which means the next RTS instruction will jump to that address instead.
|
||||
You cannot use pushw() for this because the bytes pushed by JSR are different
|
||||
|
||||
|
||||
|
||||
+12
-12
@@ -128,7 +128,7 @@ The ``alias`` statement makes it easier to refer to symbols from other places, a
|
||||
you from having to type the fully scoped name everytime you need to access that symbol.
|
||||
Aliases can be created in any scope except at the module level.
|
||||
An alias is created with ``alias <name> = <target>`` and then you can use ``<name>`` as if it were ``<target>``.
|
||||
It is possible to alias variables, labels and subroutines, and even whole blocks.words
|
||||
It is possible to alias variables, labels and subroutines, and even whole blocks and words
|
||||
The name has to be an unscoped identifier name, the target can be any scoped or unscoped symbol.
|
||||
Please consider using aliases sparingly because it may lead to confusing code if you alias
|
||||
well-known block names for example.
|
||||
@@ -153,7 +153,7 @@ They can only contain *directives*, *variable declarations*, *subroutines* and *
|
||||
}
|
||||
|
||||
The <blockname> must be a valid identifier, and must be unique in the entire program (there's
|
||||
a directive to merge multiple occurences).
|
||||
a directive to merge multiple occurrences).
|
||||
The <address> is optional. If specified it must be a valid memory address such as ``$c000``.
|
||||
It's used to tell the compiler to put the block at a certain position in memory.
|
||||
|
||||
@@ -164,7 +164,7 @@ It's used to tell the compiler to put the block at a certain position in memory.
|
||||
You can use the ``private`` keyword to hide symbols from other blocks - see :ref:`private-symbols`.
|
||||
So, accessing a variable ``counter`` defined in subroutine ``worker`` in block ``main``,
|
||||
can be done from anywhere by using ``main.worker.counter``.
|
||||
Unlike most other programming langues, as soon as a name is scoped,
|
||||
Unlike most other programming languages, as soon as a name is scoped,
|
||||
Prog8 treats it as a name starting in the *global* namespace.
|
||||
Relative name lookup is only performed for *non-scoped* names.
|
||||
|
||||
@@ -372,7 +372,7 @@ Directives
|
||||
|
||||
Level: not at module scope.
|
||||
Defines a debugging breakpoint at this location. See :ref:`debugging`
|
||||
The version with the explamation point '!' at the end can be used even
|
||||
The version with the exclamation point '!' at the end can be used even
|
||||
if the breakpoint follows an expression. If you don't use the '!' version in this case
|
||||
the compiler may think it is just a term in the expression (modulo operator and breakpoint operand value),
|
||||
instead of a breakpoint directive::
|
||||
@@ -888,7 +888,7 @@ using qualified "dotted names"::
|
||||
|
||||
uword address = $4000
|
||||
goto address ; jump via address variable
|
||||
goto address + idx ; jump to an adress that is the result of an expression
|
||||
goto address + idx ; jump to an address that is the result of an expression
|
||||
|
||||
Notice that this is a valid way to end a subroutine (you can either ``return`` from it, or jump
|
||||
to another piece of code that eventually returns).
|
||||
@@ -910,7 +910,7 @@ for the three individual assignments with the same value 42.
|
||||
|
||||
It is also possible to do a multi-value assignment: ``x, y, z = 11, 22, 33``. This is
|
||||
just a shorter way to write the three separate assignments ``x=11`` followed by ``y=22`` followed by ``z=33``.
|
||||
For subroutines that return multiple values, you have to write sucha multi-value assigment statement
|
||||
For subroutines that return multiple values, you have to write such a multi-value assignment statement
|
||||
as well, to assigns those multiple values. Details can be found here: :ref:`multiassign`.
|
||||
|
||||
|
||||
@@ -1285,7 +1285,7 @@ You *can* tell the compiler to not allocate a new variable, but instead to reuse
|
||||
(accessible in the code as ``cx16.r0`` - ``cx16.r15``) for the parameter. This is done by adding a ``@Rx`` tag
|
||||
to the parameter. This can only be done for booleans, byte, and word types.
|
||||
Note: the R0-R15 *virtual registers* are described in more detail below for the Assembly subroutines.
|
||||
Here's an example that reuses the R0 and the R1L (lower byte of R1) virtual registers for the paremeters::
|
||||
Here's an example that reuses the R0 and the R1L (lower byte of R1) virtual registers for the parameters::
|
||||
|
||||
sub get_indexed_byte(uword pointer @R0, ubyte index @R1) -> ubyte {
|
||||
return @(cx16.r0 + cx16.r1L)
|
||||
@@ -1357,7 +1357,7 @@ External subroutines
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
.. index:: pair: Subroutines; External
|
||||
|
||||
Thse define an external subroutine that's implemented outside of the program
|
||||
These define an external subroutine that's implemented outside of the program
|
||||
(for instance, a ROM routine, or a routine in a library loaded elsewhere in RAM).
|
||||
External subroutines are usually defined by compiler library files, with the following syntax::
|
||||
|
||||
@@ -1374,8 +1374,8 @@ make it easier to define jump tables for example, like this::
|
||||
|
||||
const uword APIBASE = $8000
|
||||
extsub APIBASE+0 = firstroutine()
|
||||
extsub APIBASE+10 = secondroutine()
|
||||
extsub APIBASE+20 = thirdroutine()
|
||||
extsub APIBASE+3 = secondroutine()
|
||||
extsub APIBASE+6 = thirdroutine()
|
||||
|
||||
**Banks:** it is possible to declare a non-standard ROM or RAM bank that the routine is living in, with ``@bank`` like this:
|
||||
``extsub @bank 10 $C09F = audio_init()`` to define a routine at $C09F in bank 10. You can also specify a variable for the bank.
|
||||
@@ -1445,13 +1445,13 @@ Remember that you can use ``void`` to skip a value. So for instance::
|
||||
.. sidebar:: register usage
|
||||
|
||||
Subroutines with multiple return values use cpu registers A, Y, and the R0-R15 "virtual registers" to return those,
|
||||
depending on the number of values returend. A floating point value is passed via the FAC 'register'.
|
||||
depending on the number of values returned. A floating point value is passed via the FAC 'register'.
|
||||
Multiple float return values are supported on the virtual target, but limited to a single float on 6502 targets
|
||||
(because the ROM float routines use FAC1/FAC2 as operand registers which would clobber earlier return values).
|
||||
|
||||
|
||||
**Using just one of the values:**
|
||||
Sometimes it is easier to just have a single return value in a subroutine's signagure (even though it
|
||||
Sometimes it is easier to just have a single return value in a subroutine's signature (even though it
|
||||
actually may return multiple values): this avoids having to put ``void`` for all other values if you aren't really interested in those.
|
||||
It also allows it to be called in expressions such as if-statements again.
|
||||
Examples of these second 'convenience' definition are library routines such as ``cbm.STOP2`` and ``cbm.GETIN2``,
|
||||
|
||||
@@ -6,32 +6,32 @@ Apart from the many :source:`examples <examples>` available in the source code r
|
||||
there are also larger pieces of software written using Prog8. Here's a list.
|
||||
|
||||
|
||||
`Assembler <https://github.com/irmen/cx16assem>`_
|
||||
`Assembler <https://codeberg.org/irmen/cx16assem>`_
|
||||
File-based assembler for the Commander X16.
|
||||
|
||||
`Chess <https://github.com/irmen/cx16chess>`_
|
||||
`Chess <https://codeberg.org/irmen/cx16chess>`_
|
||||
Chess game for the Commander X16, with 2-player or computer opponent game modes.
|
||||
|
||||
`Image viewer <https://github.com/irmen/cx16imageviewer>`_
|
||||
`Image viewer <https://codeberg.org/irmen/cx16imageviewer>`_
|
||||
Multi-format image viewer for the Commander X16.
|
||||
Can display cx16 BMX, C64 Koala, C64 Doodle, BMP, PCX and Amiga IFF images, including color cycling.
|
||||
|
||||
`Paint program <https://github.com/irmen/x16paint>`_
|
||||
`Paint program <https://codeberg.org/irmen/cx16paint>`_
|
||||
Bitmap image paint program for the Commander X16, work in progress.
|
||||
|
||||
`Petaxian <https://github.com/cyborgar/Petaxian>`_
|
||||
Galaga type shoot em up game using only petscii graphics. Runs on C64 and Commander X16.
|
||||
|
||||
`Rock Runner <https://github.com/irmen/cx16rockrunner>`_
|
||||
`Rock Runner <https://codeberg.org/irmen/cx16rockrunner>`_
|
||||
Faithful Boulderdash clone, a well known arcade puzzle game from the 80's.
|
||||
where you must collect all diamonds in a level while avoiding the hazards to reach the exit.
|
||||
Can load the thousands of available fan made level files.
|
||||
This game is for the Commander X16.
|
||||
|
||||
`Shell <https://github.com/irmen/cx16shell>`_
|
||||
`Shell <https://codeberg.org/irmen/cx16shell>`_
|
||||
Unix like command shell for the Commander X16.
|
||||
|
||||
`Streaming Music Demo <https://github.com/irmen/cx16musicdemo>`_
|
||||
`Streaming Music Demo <https://codeberg.org/irmen/cx16musicdemo>`_
|
||||
Demoscene like "music demos" for the Commander X16.
|
||||
They display graphics, animated song lyrics, and play a high quality sampled song streamed from disk.
|
||||
|
||||
@@ -55,7 +55,7 @@ Various things:
|
||||
|
||||
`Library blob link example <https://github.com/FearLabsAudio/Prog8_blobLink_example/>`_
|
||||
An example of a simple utility that can link symbols in a main Prog8 program
|
||||
so that they are accessable from an externally loaded library blob.
|
||||
so that they are accessible from an externally loaded library blob.
|
||||
It pre-processes the debug symbols list file at compile time,
|
||||
and substitutes references in a template module file.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Structs and Pointers
|
||||
.. note::
|
||||
Due to a few limitations in the language parser, some pointer related syntax is currently unsupported.
|
||||
The compiler tries its best to give a descriptive error message but sometimes there is still a
|
||||
parser limitation that has to be worked around at the moment. For example, this assigment syntax doesn't parse correctly::
|
||||
parser limitation that has to be worked around at the moment. For example, this assignment syntax doesn't parse correctly::
|
||||
|
||||
^^Node np
|
||||
np[2].field = 9999 ; cannot use this syntax as assignment target right now
|
||||
@@ -152,7 +152,7 @@ You can copy the whole contents of a struct to another one by assigning the dere
|
||||
e1^^ = e2^^ ; copies all fields of e2 into e1
|
||||
|
||||
|
||||
The struct type creates a new name scape, so accessing the fields of a struct is done as usual with the dotted notation.
|
||||
The struct type creates a new name scope, so accessing the fields of a struct is done as usual with the dotted notation.
|
||||
Because it implies pointer dereferencing you can usually omit the explicit `^^`, prog8 will know what it means::
|
||||
|
||||
if e1.ypos > 300
|
||||
@@ -179,7 +179,7 @@ Because it implies pointer dereferencing you can usually omit the explicit `^^`,
|
||||
In the future more cases may be supported.
|
||||
|
||||
.. note::
|
||||
Using structs instead of plain arrays usually results in more and less efficent code being generated.
|
||||
Using structs instead of plain arrays usually results in more and less efficient code being generated.
|
||||
This is because the 6502 CPU is not particularly well equipped to dealing with pointers and accessing struct fields via offsets,
|
||||
as compared to direct variable access or array indexing. The prog8 program code may be easier to work with though!
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ The path you need to provide for the ``library`` variable can be relative (to th
|
||||
and you can use a tilde ``~`` in it like in a shell path, to refer to a user's home directory.
|
||||
Note that library modules not unique to a specific compilation target (for example, `buffers`, `sorting` or `strings`) will
|
||||
be picked up from the internal library files just fine as was always the case. You can still provide custom versions of them
|
||||
in your own library folder ofcourse, like you already could with using the ``-srcdirs`` compiler flag.
|
||||
in your own library folder of course, like you already could with using the ``-srcdirs`` compiler flag.
|
||||
|
||||
Most of the things discussed in the :ref:`portingguide` can and must be configured properly in the target configuration file.
|
||||
You also need to create some essential ``syslib`` library module for the configured target if its name does not match
|
||||
@@ -110,6 +110,7 @@ Footnotes for the Commander X16
|
||||
$00 and $01 are hardwired as Rom and Ram banking registers.
|
||||
|
||||
$02 - $21 are the 16 virtual cx16 registers R0-R15.
|
||||
Note: these virtual registers are not automatically initialized to zero at program startup.
|
||||
|
||||
$22 - $7F are used by Prog8 to put variables in.
|
||||
|
||||
@@ -307,7 +308,7 @@ Here they are, all available in ``cx16``:
|
||||
The order in which the handlers are invoked if multiple interrupts occur simultaneously is: LINE, TIMER1(VIA1), VSYNC, SPRCOL, AFLOW.
|
||||
|
||||
``set_vsync_irq_handler (uword address)``
|
||||
Sets the verical sync interrupt handler routine. Also enables VSYNC interrupts.
|
||||
Sets the vertical sync interrupt handler routine. Also enables VSYNC interrupts.
|
||||
|
||||
``set_line_irq_handler (uword rasterline, uword address)``
|
||||
Sets the rasterline interrupt handler routine to trigger on the specified raster line.
|
||||
|
||||
@@ -168,7 +168,7 @@ Regular subroutines
|
||||
For *single* byte, word, and pointer arguments (not long or float), the values are simply loaded in cpu registers by the caller before calling the subroutine.
|
||||
*The subroutine itself will take care of putting the values into the parameter variables.* This saves on code size because
|
||||
otherwise all callers would have to store the values in those variables themselves.
|
||||
Note that his convention is also still used for subroutines that specify parameters to be put into
|
||||
Note that this convention is also still used for subroutines that specify parameters to be put into
|
||||
one of the *virtual registers* R0-R15, as those are in the end just variables too (see :ref:`reusevirtualregs_params`)
|
||||
The rules are as follows:
|
||||
|
||||
@@ -224,7 +224,7 @@ Float values can be put in the FAC1 or FAC2 floating point 'registers'.
|
||||
The return values also get returned via designated registers, or via processor status flags again.
|
||||
This means that after calling such a routine you can immediately act on the status
|
||||
via a special branch instruction such as ``if_z`` or ``if_cs`` etc.
|
||||
The register/status flag usage is fully specified in the asmsub or extsub signature defintion
|
||||
The register/status flag usage is fully specified in the asmsub or extsub signature definition
|
||||
for both the parameters and the return values::
|
||||
|
||||
extsub $2000 = extfunction(ubyte arg1 @A, uword arg2 @XY, uword arg3 @R0,
|
||||
@@ -261,7 +261,7 @@ Some notes and references into the compiler's source code modules:
|
||||
(``intermediate`` and ``codeGenIntermediate`` modules, and ``virtualmachine`` module for the VM related stuff)
|
||||
Note that this IR is still *targeted to one specific compilation target only*; various properties and all library
|
||||
code for the selected target machine is encoded into the IR. It is *not possible* to eventually create a C64 program
|
||||
from an IR file crated for the CommanderX16 target.
|
||||
from an IR file created for the CommanderX16 target.
|
||||
#. The code generator backends all implement a common interface ``ICodeGeneratorBackend`` defined in the ``codeCore`` module.
|
||||
Currently they get handed the program Ast, Symboltable and several other things.
|
||||
If the code generator wants it can use the ``IRCodeGen`` class from the ``codeGenIntermediate`` module
|
||||
|
||||
@@ -19,16 +19,17 @@ Weird Heisenbug
|
||||
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
- make enums strongly typed instead of just syntactic sugar for ints (see enum-strong-type.md for the plan)
|
||||
- symboldump: some sort of javadocs generated from the p8 source files (instead of just the function signatures). Use markdown for formatting, not html.
|
||||
- why are (interned) strings stored as initialization value in the SymbolTable AND as string nodes in the interned string block? Something seems redundant here?
|
||||
- when implementing unsigned longs: remove the (multiple?) "TODO "hack" to allow unsigned long constants to be used as values for signed longs, without needing a cast
|
||||
- struct/ptr: implement the remaining TODOs in PointerAssignmentsGen.
|
||||
- struct/ptr: support pointer to pointer?
|
||||
- struct/ptr: support for typed function pointers, so that we can call them via functptr^^(args) maybe even without the ^^ operator? (&routine could be typed by default as well then)
|
||||
- struct/ptr: really fixing the pointer dereferencing issues (cursed hybrid beween IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator"
|
||||
- struct/ptr: really fixing the pointer dereferencing issues (cursed hybrid between IdentifierReference, PtrDereferece and PtrIndexedDereference) may require getting rid of scoped identifiers altogether and treat '.' as a "scope or pointer following operator"
|
||||
- struct/ptr: (later, nasty parser problem:) support chaining pointer dereference on function calls that return a pointer. (type checking now fails on stuff like func().field and func().next.field)
|
||||
- Make all constants long by default? or not? (remove type name altogether), reduce to target type implictly if the actual value fits. -> long-consts branch
|
||||
This will break some existing programs that depend on value wrap arounds, but gives more intuitive constant number handling.
|
||||
- Make all constants long by default? or not? (remove type name altogether), reduce to target type implicitly if the actual value fits. -> long-consts branch
|
||||
This will break some existing programs that depend on value wraparound, but gives more intuitive constant number handling.
|
||||
Can give descriptive error message for old syntax that still includes the type name?
|
||||
- add documentation for more library modules instead of just linking to the source code
|
||||
- sizeof(pointer) is now always 2 (an uword), make this a variable in the ICompilationTarget so that it could be 4 at the time we might ad a 32-bits 68000 target for example. Much code assumes word size addresses though.
|
||||
@@ -65,7 +66,7 @@ Romable (%option romable)
|
||||
IR/VM
|
||||
^^^^^
|
||||
- getting it in shape for code generation: the IR file should be able to encode every detail about a prog8 program (the VM doesn't have to actually be able to run all of it though!)
|
||||
- maybe change all branch instructions to have 2 exits (label if branch condition ture, and label if false) instead of 1, and get rid of the implicit "next code chunk" link between chunks.
|
||||
- maybe change all branch instructions to have 2 exits (label if branch condition true, and label if false) instead of 1, and get rid of the implicit "next code chunk" link between chunks.
|
||||
- implement more TODOs in AssignmentGen?
|
||||
- add more optimizations in IRPeepholeOptimizer?
|
||||
- **Multi-Level IR Design**: Consider introducing a High-Level IR (HLIR) layer before the current low-level IR to preserve semantics like loop bounds, array indexing, and structure field access.
|
||||
@@ -83,7 +84,6 @@ IR/VM
|
||||
|
||||
Libraries
|
||||
^^^^^^^^^
|
||||
- add a serialio library to interface with the serial/wifi card, see https://github.com/safiire/cx16_wget/blob/master/uart.s
|
||||
- Add split-word array sorting routines to sorting module?
|
||||
- make a list of all floats.* routines that the compiler expects for full float support?
|
||||
|
||||
|
||||
+13
-14
@@ -75,7 +75,7 @@ Tag Effect
|
||||
@requirezp force the variable into Zero page. If ZP is full, compilation will fail.
|
||||
@nozp force the variable to normal system ram, never place it into zeropage.
|
||||
@shared means the variable is shared with some assembly code and that it cannot be optimized away if not used elsewhere.
|
||||
@nosplit (only valid on (u)word arrays) Store the array as a single inear array instead of a separate array for lsb and msb values
|
||||
@nosplit (only valid on (u)word arrays) Store the array as a single linear array instead of a separate array for lsb and msb values
|
||||
@alignword aligns string or array variable on an even memory address
|
||||
@align64 aligns string or array variable on a 64 byte address interval (example: for C64 sprite data)
|
||||
@alignpage aligns string or array variable on a 256 byte address interval (example: to avoid page boundaries)
|
||||
@@ -177,13 +177,13 @@ Initializing a variable
|
||||
You can specify an initialization value in the variable declaration.
|
||||
This will then be used to initialize the variable with at the start of the subroutine, instead of the default value 0.
|
||||
The provided value doesn't have to be a constant; it can be any expression.
|
||||
It is a shorter notation for declaring the variables and then assigning the values to them in separate assignment statment(s).
|
||||
It is a shorter notation for declaring the variables and then assigning the values to them in separate assignment statement(s).
|
||||
|
||||
There are a few special situations:
|
||||
|
||||
initializing an array: ``ubyte[3] array = [11,22,33]``
|
||||
The initiazation value has to be a range value or an array literal (remember you can use '[4] * 3' and such).
|
||||
Ofcourse the size of the range or the number of values in the array has to match the declared array size.
|
||||
The initialization value has to be a range value or an array literal (remember you can use '[4] * 3' and such).
|
||||
Of course the size of the range or the number of values in the array has to match the declared array size.
|
||||
|
||||
initializing a multi variable declaration with different values: ``ubyte a,b,c = 11,22,33``
|
||||
Here we have separate initialization values for each of the declared variables in that order.
|
||||
@@ -194,7 +194,7 @@ initializing a multi variable declaration with the same value for all: ``ubyte a
|
||||
|
||||
initializing from a subroutine returning multiple result values: ``ubyte a,b,c = multi()``
|
||||
Here the initialization value can also be a subroutine call to a subroutine returning multiple result values, which will then be put
|
||||
into the declared variables in order. Ofcourse the number of values has to match the number of variables.
|
||||
into the declared variables in order. Of course the number of values has to match the number of variables.
|
||||
|
||||
|
||||
Constants
|
||||
@@ -214,9 +214,9 @@ Enums
|
||||
-----
|
||||
.. index:: single: Enums
|
||||
|
||||
There is a more convenient way to define a bunch of constants that belong togther: a "enum".
|
||||
There is a more convenient way to define a bunch of constants that belong together: a "enum".
|
||||
That is a grouped list of constants that get autonumbered for you (unless you override the numeric value yourself).
|
||||
It starts numbering from zero by default. Here's an example::
|
||||
It starts numbering from zero by default. Right now the enum declaration does not define a new type, only a list of constants. Here's an example::
|
||||
|
||||
enum Priority {
|
||||
LOW = 1,
|
||||
@@ -232,7 +232,6 @@ This will define a bunch of constants like so (the "Enum::Field" syntax is speci
|
||||
const ubyte Priority::HIGH = 3
|
||||
const ubyte Priority::EXTREME = 255
|
||||
|
||||
|
||||
Data Types
|
||||
----------
|
||||
.. index:: pair: Data Types; Overview
|
||||
@@ -244,7 +243,7 @@ type identifier type storage size example var declara
|
||||
=============== ======================= ================= =========================================
|
||||
``byte`` signed byte 1 byte = 8 bits ``byte myvar = -22``
|
||||
``ubyte`` unsigned byte 1 byte = 8 bits ``ubyte myvar = $8f``, ``ubyte c = 'a'``
|
||||
``bool`` boolean 1 byte = 8 bits ``bool myvar = true`` or ``bool myvar == false``
|
||||
``bool`` boolean 1 byte = 8 bits ``bool myvar = true`` or ``bool myvar = false``
|
||||
``word`` signed word 2 bytes = 16 bits ``word myvar = -12345``
|
||||
``uword`` unsigned word 2 bytes = 16 bits ``uword myvar = $8fee``
|
||||
``long`` signed 32 bits integer 4 bytes ``long large = $12345678``
|
||||
@@ -366,7 +365,7 @@ any underscores in the number are ignored by the compiler.
|
||||
For instance ``30_000.999_999`` is a valid floating point number 30000.999999.
|
||||
|
||||
.. attention::
|
||||
On the X16, make sure rom bank 4 is still active before doing floationg point operations (it's the bank that contains the fp routines).
|
||||
On the X16, make sure rom bank 4 is still active before doing floating point operations (it's the bank that contains the fp routines).
|
||||
On the C64, you have to make sure the Basic ROM is still banked in (same reason).
|
||||
|
||||
|
||||
@@ -401,7 +400,7 @@ or when adding more stuff to the array later. Here are some examples of arrays::
|
||||
To allow the 6502 CPU to efficiently access values in an array, the array should be small enough to be
|
||||
indexable by a single byte index.
|
||||
This means byte arrays should be <= 256 elements, word arrays <= 256 elements as well (if split, which
|
||||
is the default. When not split, the maximum length is 128. See below for details about this disctinction).t
|
||||
is the default. When not split, the maximum length is 128. See below for details about this distinction).
|
||||
Float arrays should be <= 51 elements.
|
||||
|
||||
Arrays can be initialized with a range expression or an array literal value.
|
||||
@@ -499,9 +498,9 @@ As an optimization, (u)word arrays, pointer arrays, and str arrays are split by
|
||||
one with the LSBs and one with the MSBs of the word values. This is more efficient to access by the 6502 cpu.
|
||||
It also allows a maximum length of 256 for word arrays, where normally it would have been 128.
|
||||
|
||||
For normal prog8 array indexing, the compiler takes care of the distiction for you under water.
|
||||
For normal prog8 array indexing, the compiler takes care of the distinction for you under water.
|
||||
*But for assembly code, or code that otherwise accesses the array elements directly, you have to be aware of the distinction from 'normal' arrays.*
|
||||
In the assembly code, the array is generated as two byte arrays namely ``name_lsb`` and ``name_msb``, immediately following eachother in memory.
|
||||
In the assembly code, the array is generated as two byte arrays namely ``name_lsb`` and ``name_msb``, immediately following each other in memory.
|
||||
|
||||
The ``@nosplit`` tag can be added to the variable declaration to *not* split the array. This is useful for compatibility with
|
||||
code that expects the words to be sequentially in memory (such as the cx16.FB_set_palette routine).
|
||||
@@ -515,7 +514,7 @@ code that expects the words to be sequentially in memory (such as the cx16.FB_se
|
||||
.. note::
|
||||
Array literals are stored as split arrays if they're initializing a split word array, otherwise,
|
||||
they are stored as sequential words! So if you pass one directly to a subroutine (like ``func([1111,2222,3333])``),
|
||||
the array values are sequential in memory. If this is undesiarable (i.e. the subroutine expects a split word array),
|
||||
the array values are sequential in memory. If this is undesirable (i.e. the subroutine expects a split word array),
|
||||
you have to create a normal array variable first and then pass that to the subroutine.
|
||||
|
||||
.. caution::
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Requirements
|
||||
|
||||
### Overview & Goals
|
||||
The goal is to transition Prog8 enums from being merely syntactic sugar for integer constants into a first-class, strongly typed data type in the Prog8 language. This will improve type safety by preventing erroneous assignments or comparisons between different enums or between enums and raw integers.
|
||||
|
||||
### Scope
|
||||
#### In Scope
|
||||
- Definition of a new `EnumSubType` within the compiler's type system to extend existing `DataType`s.
|
||||
- Modifications to `SymbolTable` and `TypeChecker` to enforce enum type strictness.
|
||||
- Updates to AST processing phases (`SimplifiedAstMaker`) to preserve enum declarations instead of lowering them to constants.
|
||||
- Code generation adjustments to handle enum variables correctly.
|
||||
- Verification through new unit and integration tests.
|
||||
|
||||
#### Out of Scope
|
||||
- Architectural changes to non-enum types.
|
||||
- Backend-specific optimizations for enums (unless directly required for correctness).
|
||||
|
||||
# Technical Design
|
||||
|
||||
### Current Implementation
|
||||
Currently, `Enumeration` nodes in the `compilerAst` are lowered into standard constant variable declarations (`const`) early in the `processAst()` phase. This is confirmed by `SimplifiedAstMaker.kt` which expects enums to have been removed.
|
||||
|
||||
### Key Decisions
|
||||
1. **Represent Enum as a `DataType` with `EnumSubType`**: We will implement a new `EnumSubType` class (implementing `ISubType`) in `codeCore`. This will be used by `DataType` to uniquely identify strongly typed enums.
|
||||
2. **Preserve `Enumeration` AST nodes in CompilerAST**: The `SimplifiedAstMaker` will be updated to treat `Enumeration` as a type definition during the initial compiler phases.
|
||||
3. **Reduction in `SimplifiedAstMaker`**: The `SimplifiedAstMaker` will lower `EnumType` to its underlying `Int` type constants when generating the `SimpleAST`. This keeps the SimpleAST, IR, and downstream code generation simple, as they only need to deal with integers.
|
||||
4. **Strict Type Checking & Casting**: The `TypeChecker` will be updated to disallow *implicit* conversions for enums. *Explicit* casting between an Enum and its underlying integer type will be allowed. Casting *between* different Enum types will be explicitly prohibited.
|
||||
5. **SimplifiedAstMaker Erasure & Folding**: The `SimplifiedAstMaker` will be updated to perform the enum erasure (lowering `EnumType` to its underlying `Int` type constants) *and* the associated constant folding for enum-typed expressions that can be reduced to integers at that point. This approach centralizes the erasure logic and keeps the `ConstantFoldingOptimizer` focused on general-purpose expression simplifications.
|
||||
6. **AST Printer Updates**: `AstToSourceTextConverter` (`printAst1`) will be updated to recognize and correctly output the new strongly typed enum node structure. `AstPrinter` (`printAst2`) does not need changes as it operates on the erased `SimpleAST`.
|
||||
|
||||
### Proposed Changes
|
||||
- **`codeCore`**: Add `BaseDataType.ENUM` and introduce `EnumSubType` class implementing `ISubType` to extend `DataType`.
|
||||
- **`compilerAst` / `compiler`**: Modify `SimplifiedAstMaker` and `AstPreprocessor` to stop lowering `Enumeration` to constants. Update `AstToSourceTextConverter` to support printing the typed enum nodes.
|
||||
- **`simpleAst` / `SymbolTable`**: Update the `SymbolTable` and `TypeChecker` to register, validate enum types, and handle explicit casting rules (disallow implicit enum conversion, disallow cross-enum conversion).
|
||||
- **Code Generators**: No changes needed. The enum type is erased to its underlying integer representation during the `SimplifiedAstMaker` phase, and the code generators only deal with plain integer values.
|
||||
|
||||
### Risk & Mitigations
|
||||
- **Existing Code Compatibility**: Existing programs relying on implicit enum-to-integer conversion will break. This is intentional to improve type safety.
|
||||
- *Mitigation*: Provide clear compiler error messages when implicit conversion fails, and document the new explicit casting requirement.
|
||||
|
||||
# Testing
|
||||
|
||||
### Validation Approach
|
||||
- **Type Checking Tests**: Add test cases that explicitly verify:
|
||||
- Implicit assignment/comparison of incompatible types (expect failure).
|
||||
- Explicit cast (Enum ↔ Int) (expect success).
|
||||
- Explicit cast between different Enum types (expect failure).
|
||||
- Compare two different enum types (expect failure).
|
||||
- Use an enum member without proper type qualification.
|
||||
- **AST Printing Verification**: Use `-printast1` and `-printast2` to verify that strongly typed enum nodes are correctly displayed in the AST output.
|
||||
- **Runtime Verification**: Ensure that the code generation produces correct code for enums (e.g., correct values, proper size) using the `virtual` target and `vmtrace`.
|
||||
+1
-1
@@ -65,7 +65,7 @@ main {
|
||||
}
|
||||
}
|
||||
|
||||
; Put the new ball possition
|
||||
; Put the new ball position
|
||||
txt.setcc(BX[lp], BY[lp], sc:'●', BC[lp])
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ main {
|
||||
; Scroll the bottom half (approx.) of the character screen 1 character to the left
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
; Without clever split-screen tricks, the C64 is not fast enough to scroll the whole
|
||||
; screen smootly without tearing. So for simplicity it's constrained to less rows
|
||||
; screen smoothly without tearing. So for simplicity it's constrained to less rows
|
||||
; such that what is scrolled, *does* scrolls smoothly.
|
||||
; For maximum performance the scrolling is done in unrolled assembly code.
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ irq {
|
||||
|
||||
; Here is the actual multiplexing routine.
|
||||
; it's a raster irq just after the start of the sprite,
|
||||
; that updates the Y position of all the sprits,
|
||||
; that updates the Y position of all the sprites,
|
||||
; and registers a new raster irq for that next row of sprites.
|
||||
; If the bottom of the screen is reached, it resets the X position of the sprites as well,
|
||||
; and moves the sprites back to the top of the screen.
|
||||
@@ -56,7 +56,7 @@ irq {
|
||||
sprites.set_sprites_X(first_sprite_X) ; we can now update the X positions without risk of sprite tearing
|
||||
system_irq = true
|
||||
} else {
|
||||
; only set the new Y positions. But it's possible to change other attributes as well ofcourse (colors, x-position, data)
|
||||
; only set the new Y positions. But it's possible to change other attributes as well of course (colors, x-position, data)
|
||||
; but raster timing is critical for that if you want to avoid tearing and glitches. Can probably not be done here at this raster position...
|
||||
sprites.set_sprites_Y(sprites_Y)
|
||||
c64.SPXY[0]++
|
||||
|
||||
@@ -147,7 +147,7 @@ main {
|
||||
}
|
||||
}
|
||||
|
||||
; Dark starfield so it doesnt distract from bullets and text
|
||||
; Dark starfield so it doesn't distract from bullets and text
|
||||
ubyte[20] starfieldCols = [
|
||||
14,10,12,15,14,13,12,11,10,14,
|
||||
14,10,14,15,14,13,12,11,10,12
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
|
||||
; compatiblity layer
|
||||
; compatibility layer
|
||||
cbm {
|
||||
%option no_symbol_prefixing, ignore_unused
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
%option no_sysinit
|
||||
|
||||
|
||||
; NOTE: This is a small example of the Kernal's AUDIO routines. They target the Vara PSG but also the FM YM2151.
|
||||
; NOTE: This is a small example of the Kernal's AUDIO routines. They target the Vera PSG but also the FM YM2151.
|
||||
; There is also the "psg" library module that implements some Vera PSG routines
|
||||
; of its own, but that's not used here at all. See the "cx16/bdmusic" example for that.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ main {
|
||||
cx16.VERA_L1_MAPBASE = 0
|
||||
cx16.VERA_L1_TILEBASE = 0
|
||||
|
||||
; limit display heigth to 200 pixels to have enough vram for 14 backbuffers
|
||||
; limit display height to 200 pixels to have enough vram for 14 backbuffers
|
||||
const ubyte vstart = 20
|
||||
const ubyte vheight = 200
|
||||
cx16.VERA_CTRL = %00000010
|
||||
|
||||
@@ -158,7 +158,7 @@ main {
|
||||
}
|
||||
|
||||
matrix_math {
|
||||
%option verafxmuls ; accellerate all word-multiplications in this block using Vera FX hardware muls
|
||||
%option verafxmuls ; accelerate all word-multiplications in this block using Vera FX hardware muls
|
||||
|
||||
; storage for rotated coordinates
|
||||
word[shipdata.totalNumberOfPoints] rotatedx
|
||||
|
||||
@@ -55,7 +55,7 @@ main {
|
||||
}
|
||||
|
||||
matrix_math {
|
||||
%option verafxmuls ; accellerate all word-multiplications in this block using Vera FX hardware muls
|
||||
%option verafxmuls ; accelerate all word-multiplications in this block using Vera FX hardware muls
|
||||
|
||||
; vertices
|
||||
word[] xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
; It is hardwired to play a 16 bit stereo PCM file,
|
||||
; but you can type in the sample rate you want to play it in.
|
||||
;
|
||||
; It uses simple polling on the AFLOW flag rather than actually using a IRQ hander,
|
||||
; It uses simple polling on the AFLOW flag rather than actually using a IRQ handler,
|
||||
; to see when to refill the FIFO buffer.
|
||||
;
|
||||
; The sample data is simply read using diskio.f_read() routine, but that uses MACPTR internally for fast loads.
|
||||
|
||||
@@ -23,19 +23,21 @@ main {
|
||||
txt.nl()
|
||||
txt.nl()
|
||||
|
||||
long size = serial.zi_start_get_file("http://mockhttp.org/get?arg=hello-from-prog8-serial")
|
||||
str url = "http://mockhttp.org/get?arg=hello-from-prog8-serial"
|
||||
long size = serial.zi_start_get_file(url)
|
||||
if size>0 {
|
||||
txt.print("downloading file of size: ")
|
||||
txt.print("downloading: ")
|
||||
txt.print(url)
|
||||
txt.print("\nfile size: ")
|
||||
txt.print_l(size)
|
||||
txt.nl()
|
||||
|
||||
ubyte[256] buffer
|
||||
while size>0 {
|
||||
uword readsize = serial.zi_get_file_chunk(&buffer, sizeof(buffer), size)
|
||||
uword readsize = serial.zi_get_file_chunk(&buffer, sizeof(buffer)-1, size)
|
||||
size -= readsize
|
||||
for cx16.r0L in 0 to lsb(readsize-1) {
|
||||
txt.chrout(buffer[cx16.r0L])
|
||||
}
|
||||
@(&buffer+readsize) = 0
|
||||
txt.print(buffer)
|
||||
}
|
||||
|
||||
txt.nl()
|
||||
@@ -43,6 +45,10 @@ main {
|
||||
} else {
|
||||
txt.print("file not found\n")
|
||||
}
|
||||
|
||||
serial.zi_end_get_file()
|
||||
|
||||
txt.print("\ndownload complete")
|
||||
serial.zi_reset()
|
||||
|
||||
} else {
|
||||
|
||||
@@ -533,7 +533,7 @@ blocklogic {
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
|
||||
ubyte[16] edgecolors = [11, 1, 10, 0, 10, 13, 14, 1, 7, 7, 11, 12, 15, 1, 1, 1] ; highlighed colors for the edges
|
||||
ubyte[16] edgecolors = [11, 1, 10, 0, 10, 13, 14, 1, 7, 7, 11, 12, 15, 1, 1, 1] ; highlighted colors for the edges
|
||||
|
||||
uword[] blocks = [&blockI, &blockJ, &blockL, &blockO, &blockS, &blockT, &blockZ]
|
||||
|
||||
|
||||
+3
-3
@@ -89,7 +89,7 @@ carve_restart_after_repath:
|
||||
stackptr--
|
||||
if stackptr==255 {
|
||||
; stack empty.
|
||||
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
|
||||
; repath if we are not done yet. (this is a workaround for the prog8 256 array length limit)
|
||||
if cells_to_carve!=0 {
|
||||
if repath()
|
||||
goto carve_restart_after_repath
|
||||
@@ -104,7 +104,7 @@ carve_restart_after_repath:
|
||||
stackptr++
|
||||
if stackptr==0 {
|
||||
; stack overflow, we can't track our path any longer.
|
||||
; repath if we are not done yet. (this is a workaround for the prog8 256 array lenght limit)
|
||||
; repath if we are not done yet. (this is a workaround for the prog8 256 array length limit)
|
||||
if cells_to_carve!=0 {
|
||||
if repath()
|
||||
goto carve_restart_after_repath
|
||||
@@ -138,7 +138,7 @@ carve_restart_after_repath:
|
||||
|
||||
sub repath() -> bool {
|
||||
; repath: try to find a new start cell with possible directions.
|
||||
; we limit our number of searches so that the algorith doesn't get stuck
|
||||
; we limit our number of searches so that the algorithm doesn't get stuck
|
||||
; for too long on bad rng... just accept a few unused cells in that case.
|
||||
repeat 255 {
|
||||
do {
|
||||
|
||||
+10
-36
@@ -1,44 +1,18 @@
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
%encoding iso
|
||||
|
||||
main {
|
||||
sub start() {
|
||||
ubyte v1, v2
|
||||
|
||||
cx16.r0L = 99
|
||||
|
||||
v1 = inline_paramless_single()
|
||||
txt.print_ub(v1)
|
||||
long @shared @nozp size = 147320
|
||||
while size>0 {
|
||||
size--
|
||||
if size & $ff == 0 {
|
||||
txt.chrout('.')
|
||||
}
|
||||
}
|
||||
txt.nl()
|
||||
v1, v2 = inline_paramless_multi()
|
||||
txt.print_ub(v1)
|
||||
txt.spc()
|
||||
txt.print_ub(v2)
|
||||
txt.nl()
|
||||
|
||||
v1 = inline_single( cx16.r0L)
|
||||
txt.print_ub(v1)
|
||||
txt.nl()
|
||||
v1, v2 = inline_multi( cx16.r0L)
|
||||
txt.print_ub(v1)
|
||||
txt.spc()
|
||||
txt.print_ub(v2)
|
||||
txt.nl()
|
||||
}
|
||||
|
||||
inline sub inline_paramless_single() -> ubyte {
|
||||
return cx16.r0L+100
|
||||
}
|
||||
|
||||
inline sub inline_paramless_multi() -> ubyte, ubyte {
|
||||
return cx16.r0L+10, cx16.r0L+20
|
||||
}
|
||||
|
||||
inline sub inline_single(ubyte v) -> ubyte {
|
||||
return v+100
|
||||
}
|
||||
|
||||
inline sub inline_multi(ubyte v) -> ubyte, ubyte {
|
||||
return v+10, v+20
|
||||
;;sys.poweroff_system()
|
||||
}
|
||||
}
|
||||
@@ -726,7 +726,7 @@ planet {
|
||||
str[] @nosplit words92 = ["wasp", "moth", "grub", "ant", "\xB2"]
|
||||
str[] @nosplit words93 = ["poet", "arts graduate", "yak", "snail", "slug"]
|
||||
str[] @nosplit words94 = ["tropical", "dense", "rain", "impenetrable", "exuberant"]
|
||||
str[] @nosplit words95 = ["funny", "wierd", "unusual", "strange", "peculiar"]
|
||||
str[] @nosplit words95 = ["funny", "weird", "unusual", "strange", "peculiar"]
|
||||
str[] @nosplit words96 = ["frequent", "occasional", "unpredictable", "dreadful", "deadly"]
|
||||
str[] @nosplit words97 = ["\x82 \x81 for \x8A", "\x82 \x81 for \x8A and \x8A", "\x88 by \x89", "\x82 \x81 for \x8A but \x88 by \x89", "a\x90 \x91"]
|
||||
str[] @nosplit words98 = ["\x9B", "mountain", "edible", "tree", "spotted"]
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@ org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
org.gradle.configuration-cache=false
|
||||
kotlin.code.style=official
|
||||
version=12.2-BETA2
|
||||
version=12.2-BETA3
|
||||
@@ -98,8 +98,8 @@ class IRFileReader {
|
||||
program.addGlobalInits(initGlobals)
|
||||
blocks.forEach{ program.addBlock(it) }
|
||||
|
||||
program.linkChunks()
|
||||
program.convertAsmChunks()
|
||||
program.linkChunks()
|
||||
program.validate()
|
||||
|
||||
return program
|
||||
|
||||
@@ -57,7 +57,7 @@ NOTE: status flags are only affected by the CMP instruction or explicit CLC/SEC,
|
||||
Instruction set is mostly a load/store architecture, there are few instructions operating on memory directly.
|
||||
|
||||
Value types: integers (.b=byte=8 bits, .w=word=16 bits, .l=long=32 bits) and float (.f=64 bits). Omitting it defaults to b if the instruction requires a type.
|
||||
There is no distinction between signed and unsigned. Instead, a different instruction is used if a distinction should be made (for example div and divs).
|
||||
There is no distinction between signed and unsigned for many instructions. Instead, a different instruction is used if a distinction should be made (for example div and divs).
|
||||
Floating point operations are just 'f' typed regular instructions, however there are a few unique fp conversion instructions.
|
||||
|
||||
NOTE: Labels in source text should always start with an underscore.
|
||||
@@ -65,7 +65,7 @@ NOTE: Labels in source text should always start with an underscore.
|
||||
|
||||
LOAD/STORE
|
||||
----------
|
||||
All have type b or w or f.
|
||||
All have type b or w or l or f.
|
||||
|
||||
load reg1, value - load immediate value into register. If you supply a symbol, loads the *address* of the symbol! (variable values are loaded from memory via the loadm instruction)
|
||||
loadm reg1, address - load reg1 with value at memory address
|
||||
@@ -124,7 +124,7 @@ returni number - like return, but also returns the immedi
|
||||
|
||||
BRANCHING and CONDITIONALS
|
||||
--------------------------
|
||||
All have type b or w except the branches that only check status bits.
|
||||
All have type b or w or l except the branches that only check status bits.
|
||||
|
||||
bstcc address - branch to location if Status bit Carry is clear
|
||||
bstcs address - branch to location if Status bit Carry is set
|
||||
@@ -158,7 +158,7 @@ bgesr reg1, reg2, address - jump to location in program given by l
|
||||
|
||||
ARITHMETIC
|
||||
----------
|
||||
All have type b or w or f. Note: result types are the same as operand types! E.g. byte*byte->byte.
|
||||
All have type b or w or l or f. Note: result types are the same as operand types! E.g. byte*byte->byte.
|
||||
|
||||
exts reg1, reg2 - reg1 = signed extension of reg2 (byte to word, or word to long) (note: unlike M68k, exts.b -> word and exts.w -> long.)
|
||||
ext reg1, reg2 - reg1 = unsigned extension of reg2 (which in practice just means clearing the MSB / MSW) (note: unlike M68k, ext.b -> word and ext.w -> long. )
|
||||
@@ -174,18 +174,18 @@ addm reg1, address - memory at address += reg1
|
||||
subr reg1, reg2 - reg1 -= reg2
|
||||
sub reg1, value - reg1 -= value
|
||||
subm reg1, address - memory at address -= reg1
|
||||
mulr reg1, reg2 - unsigned multiply reg1 *= reg2 note: byte*byte->byte, no type extension to word!
|
||||
mulsr reg1, reg2 - signed multiply reg1 *= reg2 note: byte*byte->byte, no type extension to word!
|
||||
mul reg1, value - unsigned multiply reg1 *= value note: byte*byte->byte, no type extension to word!
|
||||
muls reg1, value - signed multiply reg1 *= value note: byte*byte->byte, no type extension to word!
|
||||
mulm reg1, address - unsigned memory at address *= reg2 note: byte*byte->byte, no type extension to word!
|
||||
mulsm reg1, address - signed memory at address *= reg2 note: byte*byte->byte, no type extension to word!
|
||||
mulr reg1, reg2 - unsigned multiply reg1 *= reg2 note: byte*byte->byte, no type extension!
|
||||
mulsr reg1, reg2 - signed multiply reg1 *= reg2 note: byte*byte->byte, no type extension!
|
||||
mul reg1, value - unsigned multiply reg1 *= value note: byte*byte->byte, no type extension!
|
||||
muls reg1, value - signed multiply reg1 *= value note: byte*byte->byte, no type extension!
|
||||
mulm reg1, address - unsigned memory at address *= reg2 note: byte*byte->byte, no type extension!
|
||||
mulsm reg1, address - signed memory at address *= reg2 note: byte*byte->byte, no type extension!
|
||||
divr reg1, reg2 - unsigned division reg1 /= reg2 note: division by zero yields max int $ff/$ffff
|
||||
divsr reg1, reg2 - signed division reg1 /= reg2 note: division by zero yields max signed int 127 / 32767
|
||||
divsr reg1, reg2 - signed division reg1 /= reg2 note: division by zero yields max signed int 127 / 32767 / 2147483647
|
||||
div reg1, value - unsigned division reg1 /= value note: division by zero yields max int $ff/$ffff
|
||||
divs reg1, value - signed division reg1 /= value note: division by zero yields max signed int 127 / 32767
|
||||
divs reg1, value - signed division reg1 /= value note: division by zero yields max signed int 127 / 32767 / 2147483647
|
||||
divm reg1, address - memory at address /= reg2 note: division by zero yields max int $ff/$ffff
|
||||
divsm reg1, address - signed memory at address /= reg2 note: division by zero yields max signed int 127 / 32767
|
||||
divsm reg1, address - signed memory at address /= reg2 note: division by zero yields max signed int 127 / 32767 / 2147483647
|
||||
modr reg1, reg2 - remainder (modulo) of unsigned division reg1 %= reg2 note: division by zero yields max signed int $ff/$ffff
|
||||
mod reg1, value - remainder (modulo) of unsigned division reg1 %= value note: division by zero yields max signed int $ff/$ffff
|
||||
modsr reg1, reg2 - remainder (modulo) of signed division reg1 %= reg2 note: division by zero yields max signed long
|
||||
@@ -205,7 +205,7 @@ NOTE: because mul/div are constrained (truncated) to remain in 8 or 16 or 32 bit
|
||||
|
||||
LOGICAL/BITWISE
|
||||
---------------
|
||||
All have type b or w.
|
||||
All have type b or w or l.
|
||||
|
||||
andr reg1, reg2 - reg1 = reg1 bitwise and reg2
|
||||
and reg1, value - reg1 = reg1 bitwise and value
|
||||
@@ -709,7 +709,6 @@ val instructionFormats = mutableMapOf(
|
||||
Opcode.LOADX to InstructionFormat.from("BWL,>r1,<r2,<a | F,>fr1,<r1,<a"),
|
||||
Opcode.LOADR to InstructionFormat.from("BWL,>r1,<r2 | F,>fr1,<fr2"),
|
||||
Opcode.LOADHA to InstructionFormat.from("B,>r1"),
|
||||
Opcode.LOADHA to InstructionFormat.from("B,>r1"),
|
||||
Opcode.LOADHX to InstructionFormat.from("B,>r1"),
|
||||
Opcode.LOADHY to InstructionFormat.from("B,>r1"),
|
||||
Opcode.LOADHAX to InstructionFormat.from("W,>r1"),
|
||||
@@ -724,7 +723,6 @@ val instructionFormats = mutableMapOf(
|
||||
Opcode.STOREZI to InstructionFormat.from("BWL,<r1,<i | F,<r1,<i"),
|
||||
Opcode.STOREZX to InstructionFormat.from("BWL,<r1,>a | F,<r1,>a"),
|
||||
Opcode.STOREHA to InstructionFormat.from("B,<r1"),
|
||||
Opcode.STOREHA to InstructionFormat.from("B,<r1"),
|
||||
Opcode.STOREHX to InstructionFormat.from("B,<r1"),
|
||||
Opcode.STOREHY to InstructionFormat.from("B,<r1"),
|
||||
Opcode.STOREHAX to InstructionFormat.from("W,<r1"),
|
||||
@@ -893,12 +891,12 @@ class FunctionCallArgs(
|
||||
data class IRInstruction(
|
||||
val opcode: Opcode,
|
||||
val type: IRDataType?=null,
|
||||
val reg1: Int?=null, // 0-$ffff
|
||||
val reg2: Int?=null, // 0-$ffff
|
||||
val reg3: Int?=null, // 0-$ffff
|
||||
val fpReg1: RegisterNum?=null, // 0-$ffff
|
||||
val fpReg2: RegisterNum?=null, // 0-$ffff
|
||||
val immediate: Int?=null, // 0-$ff or $ffff if word
|
||||
val reg1: Int?=null, // 0-99999
|
||||
val reg2: Int?=null, // 0-99999
|
||||
val reg3: Int?=null, // 0-99999
|
||||
val fpReg1: RegisterNum?=null, // 0-99999
|
||||
val fpReg2: RegisterNum?=null, // 0-99999
|
||||
val immediate: Int?=null, // 0-$ff or $ffff or $ffffffff
|
||||
val immediateFp: Double?=null,
|
||||
val address: MemoryAddress? = null, // 0-$ffff
|
||||
val labelSymbol: String?=null, // symbolic label name as alternative to address (so only for Branch/jump/call Instructions!)
|
||||
@@ -950,7 +948,7 @@ data class IRInstruction(
|
||||
if(format.fpReg2==OperandDirection.UNUSED) require(fpReg2==null) { "invalid fpReg2" }
|
||||
if(format.immediate) {
|
||||
if(type==IRDataType.FLOAT) {
|
||||
if(opcode !in setOf(Opcode.LOADI, Opcode.STOREI, Opcode.STOREZI))
|
||||
if(opcode !in setOf(Opcode.LOAD, Opcode.LOADI, Opcode.STOREI, Opcode.STOREZI))
|
||||
requireNotNull(immediateFp) { "missing immediate fp value" }
|
||||
}
|
||||
else
|
||||
@@ -1223,7 +1221,7 @@ data class IRInstruction(
|
||||
IRDataType.BYTE -> append("r${returnspec.registerNum.value}.b@$cpuReg")
|
||||
IRDataType.WORD -> append("r${returnspec.registerNum.value}.w@$cpuReg")
|
||||
IRDataType.LONG -> append("r${returnspec.registerNum.value}.l@$cpuReg")
|
||||
IRDataType.FLOAT -> append("r${returnspec.registerNum.value}.f@$cpuReg")
|
||||
IRDataType.FLOAT -> append("fr${returnspec.registerNum.value}.f@$cpuReg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,11 +122,16 @@ class IRProgram(val name: String,
|
||||
blocks.forEach { block ->
|
||||
block.children.forEach { child ->
|
||||
when(child) {
|
||||
is IRAsmSubroutine -> result[child.asmChunk.label] = child.asmChunk
|
||||
is IRAsmSubroutine -> result[child.label] = child.asmChunk
|
||||
is IRCodeChunk -> result[child.label] = child
|
||||
is IRInlineAsmChunk -> result[child.label] = child
|
||||
is IRInlineBinaryChunk -> result[child.label] = child
|
||||
is IRSubroutine -> result.putAll(child.chunks.associateBy { it.label })
|
||||
is IRSubroutine -> {
|
||||
result.putAll(child.chunks.associateBy { it.label })
|
||||
if (child.chunks.isNotEmpty()) {
|
||||
result[child.label] = child.chunks.first()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,17 +165,12 @@ class IRProgram(val name: String,
|
||||
|
||||
fun linkCodeChunk(chunk: IRCodeChunk, next: IRCodeChunkBase?) {
|
||||
// link sequential chunks
|
||||
val jump = chunk.instructions.lastOrNull()?.opcode
|
||||
if (jump == null || jump !in OpcodesThatBranchUnconditionally) {
|
||||
val lastInstr = chunk.instructions.lastOrNull()
|
||||
if (lastInstr == null || lastInstr.opcode !in OpcodesThatBranchUnconditionally) {
|
||||
// no jump at the end, so link to next chunk (if it exists)
|
||||
if(next!=null) {
|
||||
when (next) {
|
||||
is IRCodeChunk if chunk.instructions.lastOrNull()?.opcode !in OpcodesThatBranchUnconditionally -> chunk.next = next
|
||||
is IRInlineAsmChunk -> chunk.next = next
|
||||
is IRInlineBinaryChunk -> chunk.next =next
|
||||
else -> throw AssemblyError("code chunk followed by invalid chunk type $next")
|
||||
}
|
||||
}
|
||||
chunk.next = next
|
||||
} else {
|
||||
chunk.next = null
|
||||
}
|
||||
|
||||
// link all jump and branching instructions to their target
|
||||
@@ -180,46 +180,48 @@ class IRProgram(val name: String,
|
||||
// it's a call to an address (extsub most likely)
|
||||
requireNotNull(it.address)
|
||||
} else {
|
||||
it.branchTarget = labeledChunks.getValue(it.labelSymbol)
|
||||
it.branchTarget = labeledChunks[it.labelSymbol] ?: throw AssemblyError("Missing jump/call target: ${it.labelSymbol}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun linkBaseChunk(chunk: IRCodeChunkBase, next: IRCodeChunkBase?) {
|
||||
when (chunk) {
|
||||
is IRCodeChunk -> linkCodeChunk(chunk, next)
|
||||
is IRInlineAsmChunk -> {
|
||||
val lastInstr = chunk.instructions.lastOrNull()
|
||||
if (lastInstr == null || lastInstr.opcode !in OpcodesThatBranchUnconditionally)
|
||||
chunk.next = next
|
||||
else
|
||||
chunk.next = null
|
||||
}
|
||||
is IRInlineBinaryChunk -> {
|
||||
chunk.next = next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun linkSubroutineChunks(sub: IRSubroutine) {
|
||||
sub.chunks.withIndex().forEach { (index, chunk) ->
|
||||
|
||||
val next = if(index<sub.chunks.size-1) sub.chunks[index + 1] else null
|
||||
|
||||
when (chunk) {
|
||||
is IRCodeChunk -> {
|
||||
linkCodeChunk(chunk, next)
|
||||
}
|
||||
is IRInlineAsmChunk -> {
|
||||
if(next!=null) {
|
||||
val lastInstr = chunk.instructions.lastOrNull()
|
||||
if(lastInstr==null || lastInstr.opcode !in OpcodesThatBranchUnconditionally)
|
||||
chunk.next = next
|
||||
}
|
||||
}
|
||||
is IRInlineBinaryChunk -> { }
|
||||
}
|
||||
val next = if(index < sub.chunks.size - 1) sub.chunks[index + 1] else null
|
||||
linkBaseChunk(chunk, next)
|
||||
}
|
||||
}
|
||||
|
||||
blocks.forEach { block ->
|
||||
block.children.forEachIndexed { index, child ->
|
||||
val next = if(index<block.children.lastIndex) block.children[index+1] as? IRCodeChunkBase else null
|
||||
val next = if(index < block.children.lastIndex) block.children[index+1] as? IRCodeChunkBase else null
|
||||
when (child) {
|
||||
is IRAsmSubroutine -> child.asmChunk.next = next
|
||||
is IRCodeChunk -> child.next = next
|
||||
is IRInlineAsmChunk -> child.next = next
|
||||
is IRInlineBinaryChunk -> child.next = next
|
||||
is IRAsmSubroutine -> linkBaseChunk(child.asmChunk, next)
|
||||
is IRCodeChunk -> linkBaseChunk(child, next)
|
||||
is IRInlineAsmChunk -> linkBaseChunk(child, next)
|
||||
is IRInlineBinaryChunk -> linkBaseChunk(child, next)
|
||||
is IRSubroutine -> linkSubroutineChunks(child)
|
||||
}
|
||||
}
|
||||
}
|
||||
linkCodeChunk(globalInits, globalInits.next)
|
||||
linkBaseChunk(globalInits, globalInits.next)
|
||||
}
|
||||
|
||||
fun validate() {
|
||||
@@ -243,10 +245,11 @@ class IRProgram(val name: String,
|
||||
chunk.instructions.forEach { instr ->
|
||||
if(instr.labelSymbol!=null && instr.opcode in OpcodesThatBranch) {
|
||||
if(instr.opcode==Opcode.JUMPI) {
|
||||
when(val pointervar = st.lookup(instr.labelSymbol)!!) {
|
||||
is IRStStaticVariable -> require(pointervar.dt.isUnsignedWord)
|
||||
is IRStMemVar -> require(pointervar.dt.isUnsignedWord)
|
||||
else -> throw AssemblyError("weird pointervar type")
|
||||
val symbol = st.lookup(instr.labelSymbol) ?: throw AssemblyError("Missing jump target symbol: ${instr.labelSymbol}")
|
||||
when(symbol) {
|
||||
is IRStStaticVariable -> require(symbol.dt.isUnsignedWord)
|
||||
is IRStMemVar -> require(symbol.dt.isUnsignedWord)
|
||||
else -> throw AssemblyError("Invalid jump target symbol type: ${instr.labelSymbol}")
|
||||
}
|
||||
}
|
||||
else if(!instr.labelSymbol.startsWith('$') && !instr.labelSymbol.first().isDigit())
|
||||
|
||||
@@ -130,7 +130,7 @@ fun parseIRCodeLine(line: String): Either<IRInstruction, String> {
|
||||
}
|
||||
if(format.sysCall) {
|
||||
val call = parseCall(rest)
|
||||
val syscallNum = call.address?.toInt() ?: parseIRValue(call.target ?: "").toInt()
|
||||
val syscallNum = call.address?.toInt() ?: (call.target?.let { parseIRValue(it).toInt() } ?: throw IRParseException("Missing syscall number"))
|
||||
return left(IRInstruction(Opcode.SYSCALL, immediate = syscallNum, fcallArgs = FunctionCallArgs(call.args, call.returns)))
|
||||
} else if (format.funcCall) {
|
||||
val call = parseCall(rest)
|
||||
|
||||
@@ -4,9 +4,6 @@ import prog8.code.core.toHex
|
||||
import prog8.code.target.IVirtualMachineRunner
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.intermediate.*
|
||||
import java.awt.Color
|
||||
import java.awt.GraphicsEnvironment
|
||||
import java.io.IOException
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -41,8 +38,8 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
private const val MIPS_COUNTER_MASK = 0xffffff
|
||||
}
|
||||
|
||||
private var fileOutputStream: java.io.RandomAccessFile? = null
|
||||
private var fileInputStream: java.io.RandomAccessFile? = null
|
||||
internal var fileOutputStream: java.io.RandomAccessFile? = null
|
||||
internal var fileInputStream: java.io.RandomAccessFile? = null
|
||||
val memory = Memory()
|
||||
val machine = VMTarget()
|
||||
val program: List<IRCodeChunk>
|
||||
@@ -466,8 +463,15 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
}
|
||||
|
||||
private fun InsLOAD(i: IRInstruction) {
|
||||
if(i.type==IRDataType.FLOAT)
|
||||
registers.setFloat(i.fpReg1!!, i.immediateFp!!)
|
||||
if(i.type==IRDataType.FLOAT) {
|
||||
if (i.immediateFp != null)
|
||||
registers.setFloat(i.fpReg1!!, i.immediateFp!!)
|
||||
else {
|
||||
if (i.labelSymbol == null)
|
||||
throw IllegalArgumentException("expected LOAD of immediate or labelsymbol")
|
||||
registers.setFloat(i.fpReg1!!, i.address!!.value.toDouble())
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(i.immediate!=null) {
|
||||
setResultReg(i.reg1!!, i.immediate!!, i.type!!)
|
||||
@@ -1543,468 +1547,6 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
}
|
||||
|
||||
|
||||
internal fun statusbitsNZ(value: Int, type: IRDataType) {
|
||||
statusZero = value == 0
|
||||
when(type) {
|
||||
IRDataType.BYTE -> statusNegative = (value and 0x80) == 0x80
|
||||
IRDataType.WORD -> statusNegative = (value and 0x8000) == 0x8000
|
||||
IRDataType.LONG -> statusNegative = value < 0
|
||||
IRDataType.FLOAT -> { /* floats don't change the status bits */ }
|
||||
}
|
||||
}
|
||||
|
||||
private fun statusbitsComparison(comparison: Int, type: IRDataType) {
|
||||
if(comparison==0) {
|
||||
statusZero = true
|
||||
statusCarry = true
|
||||
} else if(comparison>0) {
|
||||
statusZero = false
|
||||
statusCarry = true
|
||||
} else {
|
||||
statusZero = false
|
||||
statusCarry = false
|
||||
}
|
||||
when(type) {
|
||||
IRDataType.BYTE -> statusNegative = (comparison and 0x80)!=0
|
||||
IRDataType.WORD -> statusNegative = (comparison and 0x8000)!=0
|
||||
IRDataType.LONG -> statusNegative = comparison<0
|
||||
IRDataType.FLOAT -> { /* floats don't change the status bits */ }
|
||||
}
|
||||
}
|
||||
|
||||
private fun statusbitsComparisonWithOverflow(comparison: Int, leftOrig: Int, type: IRDataType) {
|
||||
statusbitsComparison(comparison, type)
|
||||
when(type) {
|
||||
IRDataType.BYTE -> {
|
||||
val signBit = 0x80
|
||||
val leftSign = leftOrig and signBit
|
||||
val rightSign = (leftOrig - comparison) and signBit
|
||||
statusOverflow = ((leftSign xor rightSign) and (leftSign xor (comparison and signBit))) != 0
|
||||
}
|
||||
IRDataType.WORD -> {
|
||||
val signBit = 0x8000
|
||||
val leftSign = leftOrig and signBit
|
||||
val rightSign = (leftOrig - comparison) and signBit
|
||||
statusOverflow = ((leftSign xor rightSign) and (leftSign xor (comparison and signBit))) != 0
|
||||
}
|
||||
IRDataType.LONG -> {
|
||||
val leftSign = leftOrig < 0
|
||||
val rightSign = (leftOrig - comparison) < 0
|
||||
val resultSign = comparison < 0
|
||||
statusOverflow = leftSign != rightSign && leftSign != resultSign
|
||||
}
|
||||
IRDataType.FLOAT -> { /* floats don't set overflow */ }
|
||||
}
|
||||
}
|
||||
|
||||
private fun plusMinusMultAnyByte(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getUB(reg1)
|
||||
val right = registers.getUB(reg2)
|
||||
val result = when(operator) {
|
||||
"+" -> left + right
|
||||
"-" -> left - right
|
||||
"*" -> left * right
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setUB(reg1, result.toUByte())
|
||||
}
|
||||
|
||||
private fun multiplyAnyByteSigned(reg1: Int, reg2: Int) {
|
||||
val left = registers.getSB(reg1)
|
||||
val right = registers.getSB(reg2)
|
||||
val result = left * right
|
||||
registers.setSB(reg1, result.toByte())
|
||||
}
|
||||
|
||||
private fun plusMinusMultConstByte(operator: String, reg1: Int, value: UByte) {
|
||||
val left = registers.getUB(reg1)
|
||||
val result = when(operator) {
|
||||
"+" -> left + value
|
||||
"-" -> left - value
|
||||
"*" -> left * value
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setUB(reg1, result.toUByte())
|
||||
}
|
||||
|
||||
private fun multiplyConstByteSigned(reg1: Int, value: Byte) {
|
||||
val left = registers.getSB(reg1)
|
||||
val result = left * value
|
||||
registers.setSB(reg1, result.toByte())
|
||||
}
|
||||
|
||||
private fun plusMinusMultAnyByteInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val memvalue = memory.getUB(address)
|
||||
val operand = registers.getUB(reg1)
|
||||
val result = when(operator) {
|
||||
"+" -> memvalue + operand
|
||||
"-" -> memvalue - operand
|
||||
"*" -> memvalue * operand
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
memory.setUB(address, result.toUByte())
|
||||
}
|
||||
|
||||
private fun multiplyAnyByteSignedInplace(reg1: Int, address: UInt) {
|
||||
val memvalue = memory.getSB(address)
|
||||
val operand = registers.getSB(reg1)
|
||||
val result = memvalue * operand
|
||||
memory.setSB(address, result.toByte())
|
||||
}
|
||||
|
||||
private fun divModByteSigned(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getSB(reg1)
|
||||
val right = registers.getSB(reg2)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toByte()) 127
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toByte()) 127
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setSB(reg1, result.toByte())
|
||||
}
|
||||
|
||||
private fun divModConstByteSigned(operator: String, reg1: Int, value: Byte) {
|
||||
val left = registers.getSB(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(value==0.toByte()) 127
|
||||
else left / value
|
||||
}
|
||||
"%" -> {
|
||||
if(value==0.toByte()) 127
|
||||
else left % value
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setSB(reg1, result.toByte())
|
||||
}
|
||||
|
||||
private fun divModByteSignedInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val left = memory.getSB(address)
|
||||
val right = registers.getSB(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toByte()) 127
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toByte()) 127
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
memory.setSB(address, result.toByte())
|
||||
}
|
||||
|
||||
private fun divOrModByteUnsigned(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getUB(reg1)
|
||||
val right = registers.getUB(reg2)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toUByte()) 0xffu
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toUByte()) 0u
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setUB(reg1, result.toUByte())
|
||||
}
|
||||
|
||||
private fun divOrModConstByteUnsigned(operator: String, reg1: Int, value: UByte) {
|
||||
val left = registers.getUB(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(value==0.toUByte()) 0xffu
|
||||
else left / value
|
||||
}
|
||||
"%" -> {
|
||||
if(value==0.toUByte()) 0u
|
||||
else left % value
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setUB(reg1, result.toUByte())
|
||||
}
|
||||
|
||||
private fun divAndModUByte(reg1: Int, reg2: Int) {
|
||||
val left = registers.getUB(reg1)
|
||||
val right = registers.getUB(reg2)
|
||||
val division = if(right==0.toUByte()) 0xffu else left / right
|
||||
val remainder = if(right==0.toUByte()) 0u else left % right
|
||||
valueStack.add(division.toUByte())
|
||||
valueStack.add(remainder.toUByte())
|
||||
}
|
||||
|
||||
private fun divAndModConstUByte(reg1: Int, value: UByte) {
|
||||
val left = registers.getUB(reg1)
|
||||
val division = if(value==0.toUByte()) 0xffu else left / value
|
||||
val remainder = if(value==0.toUByte()) 0u else left % value
|
||||
valueStack.add(division.toUByte())
|
||||
valueStack.add(remainder.toUByte())
|
||||
}
|
||||
|
||||
private fun divAndModUWord(reg1: Int, reg2: Int) {
|
||||
val left = registers.getUW(reg1)
|
||||
val right = registers.getUW(reg2)
|
||||
val division = if(right==0.toUShort()) 0xffffu else left / right
|
||||
val remainder = if(right==0.toUShort()) 0u else left % right
|
||||
valueStack.pushw(division.toUShort())
|
||||
valueStack.pushw(remainder.toUShort())
|
||||
}
|
||||
|
||||
private fun divAndModConstUWord(reg1: Int, value: UShort) {
|
||||
val left = registers.getUW(reg1)
|
||||
val division = if(value==0.toUShort()) 0xffffu else left / value
|
||||
val remainder = if(value==0.toUShort()) 0u else left % value
|
||||
valueStack.pushw(division.toUShort())
|
||||
valueStack.pushw(remainder.toUShort())
|
||||
}
|
||||
|
||||
private fun divAndModSByte(reg1: Int, reg2: Int) {
|
||||
val left = registers.getSB(reg1)
|
||||
val right = registers.getSB(reg2)
|
||||
val division = if(right==0.toByte()) 127 else left / right
|
||||
val remainder = if(right==0.toByte()) 0 else left % right
|
||||
valueStack.add(division.toUByte())
|
||||
valueStack.add(remainder.toUByte())
|
||||
}
|
||||
|
||||
private fun divAndModConstSByte(reg1: Int, value: Byte) {
|
||||
val left = registers.getSB(reg1)
|
||||
val division = if(value==0.toByte()) 127 else left / value
|
||||
val remainder = if(value==0.toByte()) 0 else left % value
|
||||
valueStack.add(division.toUByte())
|
||||
valueStack.add(remainder.toUByte())
|
||||
}
|
||||
|
||||
private fun divAndModSWord(reg1: Int, reg2: Int) {
|
||||
val left = registers.getSW(reg1)
|
||||
val right = registers.getSW(reg2)
|
||||
val division = if(right==0.toShort()) 32767 else left / right
|
||||
val remainder = if(right==0.toShort()) 0 else left % right
|
||||
valueStack.pushw(division.toUShort())
|
||||
valueStack.pushw(remainder.toUShort())
|
||||
}
|
||||
|
||||
private fun divAndModConstSWord(reg1: Int, value: Short) {
|
||||
val left = registers.getSW(reg1)
|
||||
val division = if(value==0.toShort()) 32767 else left / value
|
||||
val remainder = if(value==0.toShort()) 0 else left % value
|
||||
valueStack.pushw(division.toUShort())
|
||||
valueStack.pushw(remainder.toUShort())
|
||||
}
|
||||
|
||||
private fun divModByteUnsignedInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val left = memory.getUB(address)
|
||||
val right = registers.getUB(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toUByte()) 0xffu
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toUByte()) 0u
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
memory.setUB(address, result.toUByte())
|
||||
}
|
||||
|
||||
private fun plusMinusMultAnyWord(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getUW(reg1)
|
||||
val right = registers.getUW(reg2)
|
||||
val result: UInt
|
||||
when(operator) {
|
||||
"+" -> result = left + right
|
||||
"-" -> result = left - right
|
||||
"*" -> {
|
||||
result = left.toUInt() * right
|
||||
mul16LastUpper = result shr 16
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setUW(reg1, result.toUShort())
|
||||
}
|
||||
|
||||
private fun multiplyAnyWordSigned(reg1: Int, reg2: Int) {
|
||||
val left = registers.getSW(reg1)
|
||||
val right = registers.getSW(reg2)
|
||||
val result = left.toInt() * right
|
||||
mul16LastUpper = result.toUInt() shr 16
|
||||
registers.setSW(reg1, result.toShort())
|
||||
}
|
||||
|
||||
private fun plusMinusMultConstWord(operator: String, reg1: Int, value: UShort) {
|
||||
val left = registers.getUW(reg1)
|
||||
val result: UInt
|
||||
when(operator) {
|
||||
"+" -> result = left + value
|
||||
"-" -> result = left - value
|
||||
"*" -> {
|
||||
result = left.toUInt() * value
|
||||
mul16LastUpper = result shr 16
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setUW(reg1, result.toUShort())
|
||||
}
|
||||
|
||||
private fun multiplyConstWordSigned(reg1: Int, value: Short) {
|
||||
val left = registers.getSW(reg1)
|
||||
val result = left.toInt() * value
|
||||
mul16LastUpper = result.toUInt() shr 16
|
||||
registers.setSW(reg1, result.toShort())
|
||||
}
|
||||
|
||||
private fun plusMinusMultAnyWordInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val memvalue = memory.getUW(address)
|
||||
val operand = registers.getUW(reg1)
|
||||
val result: UInt
|
||||
when(operator) {
|
||||
"+" -> result = memvalue + operand
|
||||
"-" -> result = memvalue - operand
|
||||
"*" -> {
|
||||
result = memvalue.toUInt() * operand
|
||||
mul16LastUpper = result shr 16
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
memory.setUW(address, result.toUShort())
|
||||
}
|
||||
|
||||
private fun multiplyAnyWordSignedInplace(reg1: Int, address: UInt) {
|
||||
val memvalue = memory.getSW(address)
|
||||
val operand = registers.getSW(reg1)
|
||||
val result = memvalue.toInt() * operand
|
||||
mul16LastUpper = result.toUInt() shr 16
|
||||
memory.setSW(address, result.toShort())
|
||||
}
|
||||
|
||||
private fun divOrModWordUnsigned(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getUW(reg1)
|
||||
val right = registers.getUW(reg2)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toUShort()) 0xffffu
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toUShort()) 0u
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setUW(reg1, result.toUShort())
|
||||
}
|
||||
|
||||
private fun divOrModConstWordUnsigned(operator: String, reg1: Int, value: UShort) {
|
||||
val left = registers.getUW(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(value==0.toUShort()) 0xffffu
|
||||
else left / value
|
||||
}
|
||||
"%" -> {
|
||||
if(value==0.toUShort()) 0u
|
||||
else left % value
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setUW(reg1, result.toUShort())
|
||||
}
|
||||
|
||||
private fun divModWordUnsignedInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val left = memory.getUW(address)
|
||||
val right = registers.getUW(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toUShort()) 0xffffu
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toUShort()) 0u
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
memory.setUW(address, result.toUShort())
|
||||
}
|
||||
|
||||
private fun divModWordSigned(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getSW(reg1)
|
||||
val right = registers.getSW(reg2)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toShort()) 32767
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toShort()) 32767
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setSW(reg1, result.toShort())
|
||||
}
|
||||
|
||||
private fun divModConstWordSigned(operator: String, reg1: Int, value: Short) {
|
||||
val left = registers.getSW(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(value==0.toShort()) 32767
|
||||
else left / value
|
||||
}
|
||||
"%" -> {
|
||||
if(value==0.toShort()) 32767
|
||||
else left % value
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setSW(reg1, result.toShort())
|
||||
}
|
||||
|
||||
private fun divModWordSignedInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val left = memory.getSW(address)
|
||||
val right = registers.getSW(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toShort()) 32767
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toShort()) 32767
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
memory.setSW(address, result.toShort())
|
||||
}
|
||||
|
||||
private fun arithFloat(left: Double, operator: String, right: Double): Double = when(operator) {
|
||||
"+" -> left + right
|
||||
"-" -> left - right
|
||||
"*" -> left * right
|
||||
"/" -> {
|
||||
if(right==0.0) Double.MAX_VALUE
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.0) Double.MAX_VALUE
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
|
||||
private fun InsBIT(i: IRInstruction) {
|
||||
if (i.type!! == IRDataType.BYTE) {
|
||||
val value = memory.getUB(i.address!!.value)
|
||||
@@ -2345,7 +1887,7 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
}
|
||||
IRDataType.LONG -> {
|
||||
val memvalue = memory.getSL(address)
|
||||
memory.setSL(address, memvalue shr operand)
|
||||
memory.setSL(address, memvalue ushr operand)
|
||||
}
|
||||
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
@@ -2367,7 +1909,7 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
IRDataType.LONG -> {
|
||||
val value = registers.getSL(i.reg1!!)
|
||||
statusCarry = (value and 1)!=0
|
||||
registers.setSL(i.reg1!!, value shr 1)
|
||||
registers.setSL(i.reg1!!, value ushr 1)
|
||||
}
|
||||
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
@@ -2390,7 +1932,7 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
IRDataType.LONG -> {
|
||||
val value = memory.getSL(address)
|
||||
statusCarry = (value and 1)!=0
|
||||
memory.setSL(address, value shr 1)
|
||||
memory.setSL(address, value ushr 1)
|
||||
}
|
||||
IRDataType.FLOAT -> throw IllegalArgumentException("invalid float type for this instruction $i")
|
||||
}
|
||||
@@ -2938,398 +2480,8 @@ class VirtualMachine(irProgram: IRProgram) {
|
||||
nextPc()
|
||||
}
|
||||
|
||||
private fun getBranchOperands(i: IRInstruction): Pair<Int, Int> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt())
|
||||
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), registers.getSW(i.reg2!!).toInt())
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), registers.getSL(i.reg2!!))
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for branch instruction")
|
||||
}
|
||||
}
|
||||
internal var window: GraphicsWindow? = null
|
||||
|
||||
private fun getBranchOperandsImm(i: IRInstruction): Pair<Int, Int> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), i.immediate!!)
|
||||
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), i.immediate!!)
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), i.immediate!!)
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for branch instruction")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBranchOperandsU(i: IRInstruction): Pair<UInt, UInt> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), registers.getUB(i.reg2!!).toUInt())
|
||||
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), registers.getUW(i.reg2!!).toUInt())
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), registers.getSL(i.reg2!!).toUInt())
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for branch instruction")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBranchOperandsImmU(i: IRInstruction): Pair<UInt, UInt> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), i.immediate!!.toUInt())
|
||||
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), i.immediate!!.toUInt())
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), i.immediate!!.toUInt())
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for branch instruction")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLogicalOperandsU(i: IRInstruction): Pair<UInt, UInt> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), registers.getUB(i.reg2!!).toUInt())
|
||||
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), registers.getUW(i.reg2!!).toUInt())
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), registers.getSL(i.reg2!!).toUInt())
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for logical instruction")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLogicalOperandsS(i: IRInstruction): Pair<Int, Int> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt())
|
||||
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), registers.getSW(i.reg2!!).toInt())
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), registers.getSL(i.reg2!!))
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for logical instruction")
|
||||
}
|
||||
}
|
||||
|
||||
private fun plusMinusMultAnyLong(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getSL(reg1)
|
||||
val right = registers.getSL(reg2)
|
||||
val result: Int = when(operator) {
|
||||
"+" -> left + right
|
||||
"-" -> left - right
|
||||
"*" -> left * right
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
private fun plusMinusMultConstLong(operator: String, reg1: Int, value: Int) {
|
||||
val left = registers.getSL(reg1)
|
||||
val result: Int = when(operator) {
|
||||
"+" -> left + value
|
||||
"-" -> left - value
|
||||
"*" -> left * value
|
||||
else -> throw IllegalArgumentException("operator long $operator")
|
||||
}
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
private fun plusMinusMultAnyLongInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val memvalue = memory.getSL(address)
|
||||
val operand = registers.getSL(reg1)
|
||||
val result: Int = when(operator) {
|
||||
"+" -> memvalue + operand
|
||||
"-" -> memvalue - operand
|
||||
"*" -> memvalue * operand
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
memory.setSL(address, result)
|
||||
}
|
||||
|
||||
private fun multiplyAnyLongSigned(reg1: Int, reg2: Int) {
|
||||
val result = registers.getSL(reg1) * registers.getSL(reg2)
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
private fun multiplyConstLongSigned(reg1: Int, value: Int) {
|
||||
val result = registers.getSL(reg1) * value
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
private fun multiplyAnyLongSignedInplace(reg1: Int, address: UInt) {
|
||||
val result = memory.getSL(address) * registers.getSL(reg1)
|
||||
memory.setSL(address, result)
|
||||
}
|
||||
|
||||
private fun divModLongSigned(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getSL(reg1)
|
||||
val right = registers.getSL(reg2)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0) Int.MAX_VALUE
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0) Int.MAX_VALUE
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator long $operator")
|
||||
}
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
private fun divModConstLongSigned(operator: String, reg1: Int, immediate: Int) {
|
||||
val left = registers.getSL(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(immediate==0) Int.MAX_VALUE
|
||||
else left / immediate
|
||||
}
|
||||
"%" -> {
|
||||
if(immediate==0) Int.MAX_VALUE
|
||||
else left % immediate
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator long $operator")
|
||||
}
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
private fun divModLongSignedInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val left = memory.getSL(address)
|
||||
val right = registers.getSL(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0) Int.MAX_VALUE
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0) Int.MAX_VALUE
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator long $operator")
|
||||
}
|
||||
memory.setSL(address, result)
|
||||
}
|
||||
|
||||
|
||||
private var window: GraphicsWindow? = null
|
||||
|
||||
fun gfx_enable(mode: UByte) {
|
||||
window = when(mode.toInt()) {
|
||||
0 -> GraphicsWindow(320, 240, 3)
|
||||
1 -> GraphicsWindow(640, 480, 2)
|
||||
else -> throw IllegalArgumentException("invalid screen mode")
|
||||
}
|
||||
window!!.start()
|
||||
}
|
||||
|
||||
fun gfx_clear(color: UByte) {
|
||||
window?.clear(color.toInt())
|
||||
}
|
||||
|
||||
fun gfx_plot(x: UShort, y: UShort, color: UByte) {
|
||||
window?.plot(x.toInt(), y.toInt(), color.toInt())
|
||||
}
|
||||
|
||||
fun gfx_getpixel(x: UShort, y: UShort): UByte {
|
||||
return if(window==null)
|
||||
0u
|
||||
else {
|
||||
val color = Color(window!!.getpixel(x.toInt(), y.toInt()))
|
||||
color.green.toUByte()
|
||||
}
|
||||
}
|
||||
|
||||
fun gfx_text(xx: UShort, yy: UShort, textptr: UShort, color: UByte) {
|
||||
val text = memory.getString(textptr.toUInt())
|
||||
window?.drawText(xx.toInt(), yy.toInt(), text, color.toInt())
|
||||
}
|
||||
|
||||
fun gfx_close() {
|
||||
window?.close()
|
||||
}
|
||||
|
||||
fun waitvsync() {
|
||||
// note: not a real vsync, but a sleep for approx. the duration of a frame
|
||||
val ms = try {
|
||||
val mode = GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice.displayMode
|
||||
if(mode!=null && mode.refreshRate>0) 1000L / mode.refreshRate else 16L
|
||||
} catch(_: Exception) {
|
||||
16L
|
||||
}
|
||||
try {
|
||||
Thread.sleep(ms)
|
||||
} catch(_: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
}
|
||||
}
|
||||
|
||||
fun randomSeed(seed1: UShort, seed2: UShort) {
|
||||
randomGenerator = Random(((seed1.toUInt() shl 16) or seed2.toUInt()).toInt())
|
||||
}
|
||||
|
||||
fun randomSeedFloat(seed: Double) {
|
||||
randomGeneratorFloats = Random(seed.toBits())
|
||||
}
|
||||
|
||||
fun open_file_read(name: String): Int {
|
||||
try {
|
||||
fileInputStream = java.io.RandomAccessFile(name, "r")
|
||||
} catch (_: IOException) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
fun open_file_write(name: String): Int {
|
||||
try {
|
||||
fileOutputStream = java.io.RandomAccessFile(name, "rw")
|
||||
} catch (_: IOException) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
fun close_file_read() {
|
||||
fileInputStream?.close()
|
||||
fileInputStream = null
|
||||
}
|
||||
|
||||
fun close_file_write() {
|
||||
fileOutputStream?.close()
|
||||
fileOutputStream = null
|
||||
}
|
||||
|
||||
fun read_file_byte(): Pair<Boolean, UByte> {
|
||||
return if (fileInputStream == null)
|
||||
false to 0u
|
||||
else {
|
||||
try {
|
||||
val byte = fileInputStream!!.read()
|
||||
if (byte >= 0)
|
||||
true to byte.toUByte()
|
||||
else
|
||||
false to 0u
|
||||
} catch (_: IOException) {
|
||||
false to 0u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun write_file_byte(byte: UByte): Boolean {
|
||||
if (fileOutputStream == null)
|
||||
return false
|
||||
try {
|
||||
fileOutputStream!!.write(byte.toInt())
|
||||
return true
|
||||
} catch (_: IOException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun seek_file(position: Int): Boolean {
|
||||
return if (fileInputStream == null)
|
||||
false
|
||||
else {
|
||||
try {
|
||||
fileInputStream!!.seek(position.toLong())
|
||||
true
|
||||
} catch (_: IOException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun tell_file_pos(): Int {
|
||||
return if (fileInputStream == null)
|
||||
0
|
||||
else {
|
||||
try {
|
||||
fileInputStream!!.filePointer.toInt()
|
||||
} catch (_: IOException) {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun tell_file_size(): Int {
|
||||
return if (fileInputStream == null)
|
||||
0
|
||||
else {
|
||||
try {
|
||||
fileInputStream!!.length().toInt()
|
||||
} catch (_: IOException) {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.pushw(value: UShort) {
|
||||
add((value and 255u).toUByte())
|
||||
add((value.toInt() ushr 8).toUByte())
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.pushl(value: Int) {
|
||||
val uint = value.toUInt()
|
||||
add((uint and 255u).toUByte())
|
||||
add((uint shr 8 and 255u).toUByte())
|
||||
add((uint shr 16 and 255u).toUByte())
|
||||
add((uint shr 24 and 255u).toUByte())
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.pushf(value: Double) {
|
||||
// push float; lsb first, msb last
|
||||
var bits = value.toBits()
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.popw(): UShort {
|
||||
val msb = removeLast()
|
||||
val lsb = removeLast()
|
||||
return ((msb.toInt() shl 8) + lsb.toInt()).toUShort()
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.popl(): Int {
|
||||
val b0 = removeLast().toUInt()
|
||||
val b1 = removeLast().toUInt()
|
||||
val b2 = removeLast().toUInt()
|
||||
val b3 = removeLast().toUInt()
|
||||
return ((b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3).toInt()
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.popf(): Double {
|
||||
// pop float; lsb is on bottom, msb on top
|
||||
val b0 = removeLast().toLong()
|
||||
val b1 = removeLast().toLong()
|
||||
val b2 = removeLast().toLong()
|
||||
val b3 = removeLast().toLong()
|
||||
val b4 = removeLast().toLong()
|
||||
val b5 = removeLast().toLong()
|
||||
val b6 = removeLast().toLong()
|
||||
val b7 = removeLast().toLong()
|
||||
val bits = b7 +
|
||||
(1L shl 8)*b6 +
|
||||
(1L shl 16)*b5 +
|
||||
(1L shl 24)*b4 +
|
||||
(1L shl 32)*b3 +
|
||||
(1L shl 40)*b2 +
|
||||
(1L shl 48)*b1 +
|
||||
(1L shl 56)*b0
|
||||
return Double.fromBits(bits)
|
||||
}
|
||||
|
||||
// probably called via reflection
|
||||
|
||||
@@ -0,0 +1,638 @@
|
||||
package prog8.vm
|
||||
|
||||
import prog8.intermediate.IRDataType
|
||||
import prog8.intermediate.IRInstruction
|
||||
|
||||
internal fun VirtualMachine.statusbitsNZ(value: Int, type: IRDataType) {
|
||||
statusZero = value == 0
|
||||
when(type) {
|
||||
IRDataType.BYTE -> statusNegative = (value and 0x80) == 0x80
|
||||
IRDataType.WORD -> statusNegative = (value and 0x8000) == 0x8000
|
||||
IRDataType.LONG -> statusNegative = value < 0
|
||||
IRDataType.FLOAT -> { /* floats don't change the status bits */ }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.statusbitsComparison(comparison: Int, type: IRDataType) {
|
||||
if(comparison==0) {
|
||||
statusZero = true
|
||||
statusCarry = true
|
||||
} else if(comparison>0) {
|
||||
statusZero = false
|
||||
statusCarry = true
|
||||
} else {
|
||||
statusZero = false
|
||||
statusCarry = false
|
||||
}
|
||||
when(type) {
|
||||
IRDataType.BYTE -> statusNegative = (comparison and 0x80)!=0
|
||||
IRDataType.WORD -> statusNegative = (comparison and 0x8000)!=0
|
||||
IRDataType.LONG -> statusNegative = comparison<0
|
||||
IRDataType.FLOAT -> { /* floats don't change the status bits */ }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.statusbitsComparisonWithOverflow(comparison: Int, leftOrig: Int, type: IRDataType) {
|
||||
statusbitsComparison(comparison, type)
|
||||
when(type) {
|
||||
IRDataType.BYTE -> {
|
||||
val signBit = 0x80
|
||||
val leftSign = leftOrig and signBit
|
||||
val rightSign = (leftOrig - comparison) and signBit
|
||||
statusOverflow = ((leftSign xor rightSign) and (leftSign xor (comparison and signBit))) != 0
|
||||
}
|
||||
IRDataType.WORD -> {
|
||||
val signBit = 0x8000
|
||||
val leftSign = leftOrig and signBit
|
||||
val rightSign = (leftOrig - comparison) and signBit
|
||||
statusOverflow = ((leftSign xor rightSign) and (leftSign xor (comparison and signBit))) != 0
|
||||
}
|
||||
IRDataType.LONG -> {
|
||||
val leftSign = leftOrig < 0
|
||||
val rightSign = (leftOrig - comparison) < 0
|
||||
val resultSign = comparison < 0
|
||||
statusOverflow = leftSign != rightSign && leftSign != resultSign
|
||||
}
|
||||
IRDataType.FLOAT -> { /* floats don't set overflow */ }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.plusMinusMultAnyByte(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getUB(reg1)
|
||||
val right = registers.getUB(reg2)
|
||||
val result = when(operator) {
|
||||
"+" -> left + right
|
||||
"-" -> left - right
|
||||
"*" -> left * right
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setUB(reg1, result.toUByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.multiplyAnyByteSigned(reg1: Int, reg2: Int) {
|
||||
val left = registers.getSB(reg1)
|
||||
val right = registers.getSB(reg2)
|
||||
val result = left * right
|
||||
registers.setSB(reg1, result.toByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.plusMinusMultConstByte(operator: String, reg1: Int, value: UByte) {
|
||||
val left = registers.getUB(reg1)
|
||||
val result = when(operator) {
|
||||
"+" -> left + value
|
||||
"-" -> left - value
|
||||
"*" -> left * value
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setUB(reg1, result.toUByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.multiplyConstByteSigned(reg1: Int, value: Byte) {
|
||||
val left = registers.getSB(reg1)
|
||||
val result = left * value
|
||||
registers.setSB(reg1, result.toByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.plusMinusMultAnyByteInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val memvalue = memory.getUB(address)
|
||||
val operand = registers.getUB(reg1)
|
||||
val result = when(operator) {
|
||||
"+" -> memvalue + operand
|
||||
"-" -> memvalue - operand
|
||||
"*" -> memvalue * operand
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
memory.setUB(address, result.toUByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.multiplyAnyByteSignedInplace(reg1: Int, address: UInt) {
|
||||
val memvalue = memory.getSB(address)
|
||||
val operand = registers.getSB(reg1)
|
||||
val result = memvalue * operand
|
||||
memory.setSB(address, result.toByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModByteSigned(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getSB(reg1)
|
||||
val right = registers.getSB(reg2)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toByte()) 127
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toByte()) 0
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setSB(reg1, result.toByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModConstByteSigned(operator: String, reg1: Int, value: Byte) {
|
||||
val left = registers.getSB(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(value==0.toByte()) 127
|
||||
else left / value
|
||||
}
|
||||
"%" -> {
|
||||
if(value==0.toByte()) 0
|
||||
else left % value
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setSB(reg1, result.toByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModByteSignedInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val left = memory.getSB(address)
|
||||
val right = registers.getSB(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toByte()) 127
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toByte()) 0
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
memory.setSB(address, result.toByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divOrModByteUnsigned(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getUB(reg1)
|
||||
val right = registers.getUB(reg2)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toUByte()) 0xffu
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toUByte()) 0u
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setUB(reg1, result.toUByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divOrModConstByteUnsigned(operator: String, reg1: Int, value: UByte) {
|
||||
val left = registers.getUB(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(value==0.toUByte()) 0xffu
|
||||
else left / value
|
||||
}
|
||||
"%" -> {
|
||||
if(value==0.toUByte()) 0u
|
||||
else left % value
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
registers.setUB(reg1, result.toUByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divAndModUByte(reg1: Int, reg2: Int) {
|
||||
val left = registers.getUB(reg1)
|
||||
val right = registers.getUB(reg2)
|
||||
val division = if(right==0.toUByte()) 0xffu else left / right
|
||||
val remainder = if(right==0.toUByte()) 0u else left % right
|
||||
valueStack.add(division.toUByte())
|
||||
valueStack.add(remainder.toUByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divAndModConstUByte(reg1: Int, value: UByte) {
|
||||
val left = registers.getUB(reg1)
|
||||
val division = if(value==0.toUByte()) 0xffu else left / value
|
||||
val remainder = if(value==0.toUByte()) 0u else left % value
|
||||
valueStack.add(division.toUByte())
|
||||
valueStack.add(remainder.toUByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divAndModUWord(reg1: Int, reg2: Int) {
|
||||
val left = registers.getUW(reg1)
|
||||
val right = registers.getUW(reg2)
|
||||
val division = if(right==0.toUShort()) 0xffffu else left / right
|
||||
val remainder = if(right==0.toUShort()) 0u else left % right
|
||||
valueStack.pushw(division.toUShort())
|
||||
valueStack.pushw(remainder.toUShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divAndModConstUWord(reg1: Int, value: UShort) {
|
||||
val left = registers.getUW(reg1)
|
||||
val division = if(value==0.toUShort()) 0xffffu else left / value
|
||||
val remainder = if(value==0.toUShort()) 0u else left % value
|
||||
valueStack.pushw(division.toUShort())
|
||||
valueStack.pushw(remainder.toUShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divAndModSByte(reg1: Int, reg2: Int) {
|
||||
val left = registers.getSB(reg1)
|
||||
val right = registers.getSB(reg2)
|
||||
val division = if(right==0.toByte()) 127 else left / right
|
||||
val remainder = if(right==0.toByte()) 0 else left % right
|
||||
valueStack.add(division.toUByte())
|
||||
valueStack.add(remainder.toUByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divAndModConstSByte(reg1: Int, value: Byte) {
|
||||
val left = registers.getSB(reg1)
|
||||
val division = if(value==0.toByte()) 127 else left / value
|
||||
val remainder = if(value==0.toByte()) 0 else left % value
|
||||
valueStack.add(division.toUByte())
|
||||
valueStack.add(remainder.toUByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divAndModSWord(reg1: Int, reg2: Int) {
|
||||
val left = registers.getSW(reg1)
|
||||
val right = registers.getSW(reg2)
|
||||
val division = if(right==0.toShort()) 32767 else left / right
|
||||
val remainder = if(right==0.toShort()) 0 else left % right
|
||||
valueStack.pushw(division.toUShort())
|
||||
valueStack.pushw(remainder.toUShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divAndModConstSWord(reg1: Int, value: Short) {
|
||||
val left = registers.getSW(reg1)
|
||||
val division = if(value==0.toShort()) 32767 else left / value
|
||||
val remainder = if(value==0.toShort()) 0 else left % value
|
||||
valueStack.pushw(division.toUShort())
|
||||
valueStack.pushw(remainder.toUShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModByteUnsignedInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val left = memory.getUB(address)
|
||||
val right = registers.getUB(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toUByte()) 0xffu
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toUByte()) 0u
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator byte $operator")
|
||||
}
|
||||
memory.setUB(address, result.toUByte())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.plusMinusMultAnyWord(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getUW(reg1)
|
||||
val right = registers.getUW(reg2)
|
||||
val result: UInt
|
||||
when(operator) {
|
||||
"+" -> result = left + right
|
||||
"-" -> result = left - right
|
||||
"*" -> {
|
||||
result = left.toUInt() * right
|
||||
mul16LastUpper = result shr 16
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setUW(reg1, result.toUShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.multiplyAnyWordSigned(reg1: Int, reg2: Int) {
|
||||
val left = registers.getSW(reg1)
|
||||
val right = registers.getSW(reg2)
|
||||
val result = left.toInt() * right
|
||||
mul16LastUpper = result.toUInt() shr 16
|
||||
registers.setSW(reg1, result.toShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.plusMinusMultConstWord(operator: String, reg1: Int, value: UShort) {
|
||||
val left = registers.getUW(reg1)
|
||||
val result: UInt
|
||||
when(operator) {
|
||||
"+" -> result = left + value
|
||||
"-" -> result = left - value
|
||||
"*" -> {
|
||||
result = left.toUInt() * value
|
||||
mul16LastUpper = result shr 16
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setUW(reg1, result.toUShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.multiplyConstWordSigned(reg1: Int, value: Short) {
|
||||
val left = registers.getSW(reg1)
|
||||
val result = left.toInt() * value
|
||||
mul16LastUpper = result.toUInt() shr 16
|
||||
registers.setSW(reg1, result.toShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.plusMinusMultAnyWordInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val memvalue = memory.getUW(address)
|
||||
val operand = registers.getUW(reg1)
|
||||
val result: UInt
|
||||
when(operator) {
|
||||
"+" -> result = memvalue + operand
|
||||
"-" -> result = memvalue - operand
|
||||
"*" -> {
|
||||
result = memvalue.toUInt() * operand
|
||||
mul16LastUpper = result shr 16
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
memory.setUW(address, result.toUShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.multiplyAnyWordSignedInplace(reg1: Int, address: UInt) {
|
||||
val memvalue = memory.getSW(address)
|
||||
val operand = registers.getSW(reg1)
|
||||
val result = memvalue.toInt() * operand
|
||||
mul16LastUpper = result.toUInt() shr 16
|
||||
memory.setSW(address, result.toShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divOrModWordUnsigned(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getUW(reg1)
|
||||
val right = registers.getUW(reg2)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toUShort()) 0xffffu
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toUShort()) 0u
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setUW(reg1, result.toUShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divOrModConstWordUnsigned(operator: String, reg1: Int, value: UShort) {
|
||||
val left = registers.getUW(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(value==0.toUShort()) 0xffffu
|
||||
else left / value
|
||||
}
|
||||
"%" -> {
|
||||
if(value==0.toUShort()) 0u
|
||||
else left % value
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setUW(reg1, result.toUShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModWordUnsignedInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val left = memory.getUW(address)
|
||||
val right = registers.getUW(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toUShort()) 0xffffu
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toUShort()) 0u
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
memory.setUW(address, result.toUShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModWordSigned(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getSW(reg1)
|
||||
val right = registers.getSW(reg2)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toShort()) 32767
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toShort()) 0
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setSW(reg1, result.toShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModConstWordSigned(operator: String, reg1: Int, value: Short) {
|
||||
val left = registers.getSW(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(value==0.toShort()) 32767
|
||||
else left / value
|
||||
}
|
||||
"%" -> {
|
||||
if(value==0.toShort()) 0
|
||||
else left % value
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setSW(reg1, result.toShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModWordSignedInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val left = memory.getSW(address)
|
||||
val right = registers.getSW(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0.toShort()) 32767
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.toShort()) 0
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
memory.setSW(address, result.toShort())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.arithFloat(left: Double, operator: String, right: Double): Double = when(operator) {
|
||||
"+" -> left + right
|
||||
"-" -> left - right
|
||||
"*" -> left * right
|
||||
"/" -> {
|
||||
if(right==0.0) Double.MAX_VALUE
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0.0) 0.0
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.getBranchOperands(i: IRInstruction): Pair<Int, Int> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt())
|
||||
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), registers.getSW(i.reg2!!).toInt())
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), registers.getSL(i.reg2!!))
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for branch instruction")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.getBranchOperandsImm(i: IRInstruction): Pair<Int, Int> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), i.immediate!!)
|
||||
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), i.immediate!!)
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), i.immediate!!)
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for branch instruction")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.getBranchOperandsU(i: IRInstruction): Pair<UInt, UInt> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), registers.getUB(i.reg2!!).toUInt())
|
||||
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), registers.getUW(i.reg2!!).toUInt())
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), registers.getSL(i.reg2!!).toUInt())
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for branch instruction")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.getBranchOperandsImmU(i: IRInstruction): Pair<UInt, UInt> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), i.immediate!!.toUInt())
|
||||
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), i.immediate!!.toUInt())
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), i.immediate!!.toUInt())
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for branch instruction")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.getLogicalOperandsU(i: IRInstruction): Pair<UInt, UInt> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getUB(i.reg1!!).toUInt(), registers.getUB(i.reg2!!).toUInt())
|
||||
IRDataType.WORD -> Pair(registers.getUW(i.reg1!!).toUInt(), registers.getUW(i.reg2!!).toUInt())
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!).toUInt(), registers.getSL(i.reg2!!).toUInt())
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for logical instruction")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.getLogicalOperandsS(i: IRInstruction): Pair<Int, Int> {
|
||||
return when(i.type) {
|
||||
IRDataType.BYTE -> Pair(registers.getSB(i.reg1!!).toInt(), registers.getSB(i.reg2!!).toInt())
|
||||
IRDataType.WORD -> Pair(registers.getSW(i.reg1!!).toInt(), registers.getSW(i.reg2!!).toInt())
|
||||
IRDataType.LONG -> Pair(registers.getSL(i.reg1!!), registers.getSL(i.reg2!!))
|
||||
IRDataType.FLOAT -> {
|
||||
throw IllegalArgumentException("can't use float here")
|
||||
}
|
||||
null -> throw IllegalArgumentException("need type for logical instruction")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.plusMinusMultAnyLong(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getSL(reg1)
|
||||
val right = registers.getSL(reg2)
|
||||
val result: Int = when(operator) {
|
||||
"+" -> left + right
|
||||
"-" -> left - right
|
||||
"*" -> left * right
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.plusMinusMultConstLong(operator: String, reg1: Int, value: Int) {
|
||||
val left = registers.getSL(reg1)
|
||||
val result: Int = when(operator) {
|
||||
"+" -> left + value
|
||||
"-" -> left - value
|
||||
"*" -> left * value
|
||||
else -> throw IllegalArgumentException("operator long $operator")
|
||||
}
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.plusMinusMultAnyLongInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val memvalue = memory.getSL(address)
|
||||
val operand = registers.getSL(reg1)
|
||||
val result: Int = when(operator) {
|
||||
"+" -> memvalue + operand
|
||||
"-" -> memvalue - operand
|
||||
"*" -> memvalue * operand
|
||||
else -> throw IllegalArgumentException("operator word $operator")
|
||||
}
|
||||
memory.setSL(address, result)
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.multiplyAnyLongSigned(reg1: Int, reg2: Int) {
|
||||
val result = registers.getSL(reg1) * registers.getSL(reg2)
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.multiplyConstLongSigned(reg1: Int, value: Int) {
|
||||
val result = registers.getSL(reg1) * value
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.multiplyAnyLongSignedInplace(reg1: Int, address: UInt) {
|
||||
val result = memory.getSL(address) * registers.getSL(reg1)
|
||||
memory.setSL(address, result)
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModLongSigned(operator: String, reg1: Int, reg2: Int) {
|
||||
val left = registers.getSL(reg1)
|
||||
val right = registers.getSL(reg2)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0) Int.MAX_VALUE
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0) 0
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator long $operator")
|
||||
}
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModConstLongSigned(operator: String, reg1: Int, immediate: Int) {
|
||||
val left = registers.getSL(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(immediate==0) Int.MAX_VALUE
|
||||
else left / immediate
|
||||
}
|
||||
"%" -> {
|
||||
if(immediate==0) 0
|
||||
else left % immediate
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator long $operator")
|
||||
}
|
||||
registers.setSL(reg1, result)
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.divModLongSignedInplace(operator: String, reg1: Int, address: UInt) {
|
||||
val left = memory.getSL(address)
|
||||
val right = registers.getSL(reg1)
|
||||
val result = when(operator) {
|
||||
"/" -> {
|
||||
if(right==0) Int.MAX_VALUE
|
||||
else left / right
|
||||
}
|
||||
"%" -> {
|
||||
if(right==0) 0
|
||||
else left % right
|
||||
}
|
||||
else -> throw IllegalArgumentException("operator long $operator")
|
||||
}
|
||||
memory.setSL(address, result)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package prog8.vm
|
||||
|
||||
internal fun ArrayDeque<UByte>.pushw(value: UShort) {
|
||||
add((value and 255u).toUByte())
|
||||
add((value.toInt() ushr 8).toUByte())
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.pushl(value: Int) {
|
||||
val uint = value.toUInt()
|
||||
add((uint and 255u).toUByte())
|
||||
add((uint shr 8 and 255u).toUByte())
|
||||
add((uint shr 16 and 255u).toUByte())
|
||||
add((uint shr 24 and 255u).toUByte())
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.pushf(value: Double) {
|
||||
// push float; lsb first, msb last
|
||||
var bits = value.toBits()
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
bits = bits ushr 8
|
||||
add(bits.toUByte())
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.popw(): UShort {
|
||||
val msb = removeLast()
|
||||
val lsb = removeLast()
|
||||
return ((msb.toInt() shl 8) + lsb.toInt()).toUShort()
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.popl(): Int {
|
||||
val b0 = removeLast().toUInt()
|
||||
val b1 = removeLast().toUInt()
|
||||
val b2 = removeLast().toUInt()
|
||||
val b3 = removeLast().toUInt()
|
||||
return ((b0 shl 24) or (b1 shl 16) or (b2 shl 8) or b3).toInt()
|
||||
}
|
||||
|
||||
internal fun ArrayDeque<UByte>.popf(): Double {
|
||||
// pop float; lsb is on bottom, msb on top
|
||||
val b0 = removeLast().toLong()
|
||||
val b1 = removeLast().toLong()
|
||||
val b2 = removeLast().toLong()
|
||||
val b3 = removeLast().toLong()
|
||||
val b4 = removeLast().toLong()
|
||||
val b5 = removeLast().toLong()
|
||||
val b6 = removeLast().toLong()
|
||||
val b7 = removeLast().toLong()
|
||||
val bits = b7 +
|
||||
(1L shl 8)*b6 +
|
||||
(1L shl 16)*b5 +
|
||||
(1L shl 24)*b4 +
|
||||
(1L shl 32)*b3 +
|
||||
(1L shl 40)*b2 +
|
||||
(1L shl 48)*b1 +
|
||||
(1L shl 56)*b0
|
||||
return Double.fromBits(bits)
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
package prog8.vm
|
||||
|
||||
import java.awt.Color
|
||||
import java.awt.GraphicsEnvironment
|
||||
import java.io.IOException
|
||||
import kotlin.random.Random
|
||||
|
||||
internal fun VirtualMachine.gfx_enable(mode: UByte) {
|
||||
window = when(mode.toInt()) {
|
||||
0 -> GraphicsWindow(320, 240, 3)
|
||||
1 -> GraphicsWindow(640, 480, 2)
|
||||
else -> throw IllegalArgumentException("invalid screen mode")
|
||||
}
|
||||
window!!.start()
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.gfx_clear(color: UByte) {
|
||||
window?.clear(color.toInt())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.gfx_plot(x: UShort, y: UShort, color: UByte) {
|
||||
window?.plot(x.toInt(), y.toInt(), color.toInt())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.gfx_getpixel(x: UShort, y: UShort): UByte {
|
||||
return if(window==null)
|
||||
0u
|
||||
else {
|
||||
val color = Color(window!!.getpixel(x.toInt(), y.toInt()))
|
||||
color.green.toUByte()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.gfx_text(xx: UShort, yy: UShort, textptr: UShort, color: UByte) {
|
||||
val text = memory.getString(textptr.toUInt())
|
||||
window?.drawText(xx.toInt(), yy.toInt(), text, color.toInt())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.gfx_close() {
|
||||
window?.close()
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.waitvsync() {
|
||||
// note: not a real vsync, but a sleep for approx. the duration of a frame
|
||||
val ms = try {
|
||||
val mode = GraphicsEnvironment.getLocalGraphicsEnvironment().defaultScreenDevice.displayMode
|
||||
if(mode!=null && mode.refreshRate>0) 1000L / mode.refreshRate else 16L
|
||||
} catch(_: Exception) {
|
||||
16L
|
||||
}
|
||||
try {
|
||||
Thread.sleep(ms)
|
||||
} catch(_: InterruptedException) {
|
||||
Thread.currentThread().interrupt()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.randomSeed(seed1: UShort, seed2: UShort) {
|
||||
randomGenerator = Random(((seed1.toUInt() shl 16) or seed2.toUInt()).toInt())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.randomSeedFloat(seed: Double) {
|
||||
randomGeneratorFloats = Random(seed.toBits())
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.open_file_read(name: String): Int {
|
||||
try {
|
||||
fileInputStream = java.io.RandomAccessFile(name, "r")
|
||||
} catch (_: IOException) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.open_file_write(name: String): Int {
|
||||
try {
|
||||
fileOutputStream = java.io.RandomAccessFile(name, "rw")
|
||||
} catch (_: IOException) {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.close_file_read() {
|
||||
fileInputStream?.close()
|
||||
fileInputStream = null
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.close_file_write() {
|
||||
fileOutputStream?.close()
|
||||
fileOutputStream = null
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.read_file_byte(): Pair<Boolean, UByte> {
|
||||
return if (fileInputStream == null)
|
||||
false to 0u
|
||||
else {
|
||||
try {
|
||||
val byte = fileInputStream!!.read()
|
||||
if (byte >= 0)
|
||||
true to byte.toUByte()
|
||||
else
|
||||
false to 0u
|
||||
} catch (_: IOException) {
|
||||
false to 0u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.write_file_byte(byte: UByte): Boolean {
|
||||
if (fileOutputStream == null)
|
||||
return false
|
||||
try {
|
||||
fileOutputStream!!.write(byte.toInt())
|
||||
return true
|
||||
} catch (_: IOException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.seek_file(position: Int): Boolean {
|
||||
return if (fileInputStream == null)
|
||||
false
|
||||
else {
|
||||
try {
|
||||
fileInputStream!!.seek(position.toLong())
|
||||
true
|
||||
} catch (_: IOException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.tell_file_pos(): Int {
|
||||
return if (fileInputStream == null)
|
||||
0
|
||||
else {
|
||||
try {
|
||||
fileInputStream!!.filePointer.toInt()
|
||||
} catch (_: IOException) {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun VirtualMachine.tell_file_size(): Int {
|
||||
return if (fileInputStream == null)
|
||||
0
|
||||
else {
|
||||
try {
|
||||
fileInputStream!!.length().toInt()
|
||||
} catch (_: IOException) {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,10 @@ import io.kotest.assertions.throwables.shouldThrowWithMessage
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.collections.shouldBeEmpty
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.OutputType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.ZeropageType
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
@@ -224,4 +227,128 @@ class TestVm: FunSpec( {
|
||||
vm.run(false)
|
||||
vm.registers.getSL(1) shouldBe Int.MAX_VALUE
|
||||
}
|
||||
|
||||
test("vm register usage: LOADI") {
|
||||
val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget())
|
||||
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
|
||||
val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY)
|
||||
val code = IRCodeChunk(startSub.label, null)
|
||||
|
||||
// BWL: memory[registers[reg2] + offset] -> registers[reg1]
|
||||
code += IRInstruction(Opcode.LOADI, IRDataType.WORD, reg1=1, reg2=2, immediate=10)
|
||||
// FLOAT: memory[registers[reg1] + offset] -> registers[fpReg1]
|
||||
code += IRInstruction(Opcode.LOADI, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, immediate=20)
|
||||
code += IRInstruction(Opcode.RETURN)
|
||||
|
||||
startSub += code
|
||||
block += startSub
|
||||
program.addBlock(block)
|
||||
|
||||
val vm = VirtualMachine(program)
|
||||
vm.registers.setUW(2, 1000u)
|
||||
vm.memory.setUW(1010u, 1234u)
|
||||
vm.memory.setFloat(1020u, 3.14)
|
||||
vm.run(false)
|
||||
|
||||
vm.registers.getUW(1) shouldBe 1234u
|
||||
vm.registers.getFloat(RegisterNum(3)) shouldBe 3.14
|
||||
}
|
||||
|
||||
test("vm register usage: LOADX") {
|
||||
val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget())
|
||||
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
|
||||
val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY)
|
||||
val code = IRCodeChunk(startSub.label, null)
|
||||
|
||||
// BWL: memory[address + registers[reg2]] -> registers[reg1]
|
||||
code += IRInstruction(Opcode.LOADX, IRDataType.WORD, reg1=1, reg2=2, address=1000u.toAddress())
|
||||
// FLOAT: memory[address + registers[reg1]] -> registers[fpReg1]
|
||||
code += IRInstruction(Opcode.LOADX, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, address=2000u.toAddress())
|
||||
code += IRInstruction(Opcode.RETURN)
|
||||
|
||||
startSub += code
|
||||
block += startSub
|
||||
program.addBlock(block)
|
||||
|
||||
val vm = VirtualMachine(program)
|
||||
vm.registers.setUB(2, 10u)
|
||||
vm.memory.setUW(1010u, 5678u)
|
||||
vm.memory.setFloat(2010u, 2.718)
|
||||
vm.run(false)
|
||||
|
||||
vm.registers.getUW(1) shouldBe 5678u
|
||||
vm.registers.getFloat(RegisterNum(3)) shouldBe 2.718
|
||||
}
|
||||
|
||||
test("vm register usage: STOREI") {
|
||||
val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget())
|
||||
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
|
||||
val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY)
|
||||
val code = IRCodeChunk(startSub.label, null)
|
||||
|
||||
// BWL: registers[reg1] -> memory[registers[reg2] + offset]
|
||||
code += IRInstruction(Opcode.STOREI, IRDataType.WORD, reg1=1, reg2=2, immediate=10)
|
||||
// FLOAT: registers[fpReg1] -> memory[registers[reg1] + offset]
|
||||
code += IRInstruction(Opcode.STOREI, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, immediate=20)
|
||||
code += IRInstruction(Opcode.RETURN)
|
||||
|
||||
startSub += code
|
||||
block += startSub
|
||||
program.addBlock(block)
|
||||
|
||||
val vm = VirtualMachine(program)
|
||||
vm.registers.setUW(1, 9999u)
|
||||
vm.registers.setFloat(RegisterNum(3), 1.23)
|
||||
vm.registers.setUW(2, 1000u)
|
||||
vm.run(false)
|
||||
|
||||
vm.memory.getUW(1010u) shouldBe 9999u
|
||||
vm.memory.getFloat(1020u) shouldBe 1.23
|
||||
}
|
||||
|
||||
test("vm register usage: STOREX") {
|
||||
val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget())
|
||||
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
|
||||
val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY)
|
||||
val code = IRCodeChunk(startSub.label, null)
|
||||
|
||||
// BWL: registers[reg1] -> memory[address + registers[reg2]]
|
||||
code += IRInstruction(Opcode.STOREX, IRDataType.WORD, reg1=1, reg2=2, address=1000u.toAddress())
|
||||
// FLOAT: registers[fpReg1] -> memory[address + registers[reg1]]
|
||||
code += IRInstruction(Opcode.STOREX, IRDataType.FLOAT, fpReg1=RegisterNum(3), reg1=2, address=2000u.toAddress())
|
||||
code += IRInstruction(Opcode.RETURN)
|
||||
|
||||
startSub += code
|
||||
block += startSub
|
||||
program.addBlock(block)
|
||||
|
||||
val vm = VirtualMachine(program)
|
||||
vm.registers.setUW(1, 8888u)
|
||||
vm.registers.setFloat(RegisterNum(3), 4.56)
|
||||
vm.registers.setUB(2, 10u)
|
||||
vm.run(false)
|
||||
|
||||
vm.memory.getUW(1010u) shouldBe 8888u
|
||||
vm.memory.getFloat(2010u) shouldBe 4.56
|
||||
}
|
||||
|
||||
test("vm register usage: LSR.l") {
|
||||
val program = IRProgram("test", IRSymbolTable(), getTestOptions(), VMTarget())
|
||||
val block = IRBlock("main", false, IRBlock.Options(), Position.DUMMY)
|
||||
val startSub = IRSubroutine("main.test", emptyList(), emptyList(), Position.DUMMY)
|
||||
val code = IRCodeChunk(startSub.label, null)
|
||||
|
||||
code += IRInstruction(Opcode.LSR, IRDataType.LONG, reg1=1)
|
||||
code += IRInstruction(Opcode.RETURN)
|
||||
|
||||
startSub += code
|
||||
block += startSub
|
||||
program.addBlock(block)
|
||||
|
||||
val vm = VirtualMachine(program)
|
||||
vm.registers.setSL(1, -1) // 0xFFFFFFFF
|
||||
vm.run(false)
|
||||
// Logical shift right of 0xFFFFFFFF should be 0x7FFFFFFF (2147483647)
|
||||
vm.registers.getSL(1) shouldBe 2147483647
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user