mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f8d99e3ed | ||
|
|
23474360ec | ||
|
|
81c255c450 | ||
|
|
ef23d52ed7 | ||
|
|
220ab773aa | ||
|
|
e3e5bff7bb | ||
|
|
7b9a841b2a | ||
|
|
40423911ef | ||
|
|
582a70b046 | ||
|
|
5b63590ebf | ||
|
|
2b6510dc19 | ||
|
|
9d49589d73 | ||
|
|
125b66c929 | ||
|
|
5255f1c052 | ||
|
|
a6ba05d60c | ||
|
|
41e963b04b | ||
|
|
6ff75bef29 | ||
|
|
72c16d0d32 | ||
|
|
94653e5c8c | ||
|
|
3e2b2a698d | ||
|
|
ae04f5aee8 | ||
|
|
5c56267662 | ||
|
|
e55ce5504e | ||
|
|
fb1e89d9ef | ||
|
|
bc550a4549 | ||
|
|
ebdea9cf76 | ||
|
|
09ec508f82 | ||
|
|
d06e9ea7f6 | ||
|
|
a36bdc54fd |
No files matched your search
Generated
+5
-5
@@ -1,11 +1,11 @@
|
||||
<component name="libraryTable">
|
||||
<library name="eclipse.lsp4j" type="repository">
|
||||
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.23.1" />
|
||||
<properties maven-id="org.eclipse.lsp4j:org.eclipse.lsp4j:0.24.0" />
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.23.1/org.eclipse.lsp4j-0.23.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.23.1/org.eclipse.lsp4j.jsonrpc-0.23.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.11.0/gson-2.11.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.27.0/error_prone_annotations-2.27.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j/0.24.0/org.eclipse.lsp4j-0.24.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/eclipse/lsp4j/org.eclipse.lsp4j.jsonrpc/0.24.0/org.eclipse.lsp4j.jsonrpc-0.24.0.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/code/gson/gson/2.12.1/gson-2.12.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/com/google/errorprone/error_prone_annotations/2.36.0/error_prone_annotations-2.36.0.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
||||
Generated
+1
-1
@@ -22,7 +22,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="openjdk-11" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="openjdk-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+1
@@ -17,6 +17,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/intermediate/intermediate.iml" filepath="$PROJECT_DIR$/intermediate/intermediate.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/prog8.iml" filepath="$PROJECT_DIR$/.idea/modules/prog8.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/simpleAst/simpleAst.iml" filepath="$PROJECT_DIR$/simpleAst/simpleAst.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/virtualmachine/virtualmachine.iml" filepath="$PROJECT_DIR$/virtualmachine/virtualmachine.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
|
||||
@@ -63,10 +63,11 @@ What does Prog8 provide?
|
||||
- various data types other than just bytes (16-bit words, floats, strings)
|
||||
- floating point math is supported on certain targets
|
||||
- access to most Kernal ROM routines as external subroutine definitions you can call normally
|
||||
- tight control over Zeropage usage
|
||||
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
|
||||
- automatic static variable allocations, automatic string and array variables and string sharing
|
||||
- high-level program optimizations
|
||||
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
|
||||
- conditional branches that map 1:1 to cpu status flags
|
||||
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
|
||||
- ``in`` expression for concise and efficient multi-value/containment check
|
||||
@@ -93,8 +94,7 @@ What does Prog8 provide?
|
||||
- "c64": Commodore-64 (6502 like CPU)
|
||||
- "c128": Commodore-128 (6502 like CPU - the Z80 cpu mode is not supported)
|
||||
- "pet32": Commodore PET (limited support)
|
||||
- "atari": Atari 8 bit such as 800XL (experimental)
|
||||
- "neo": Neo6502 (experimental)
|
||||
- via external configurable targets: Atari 800 XL, Neo6502, NES, C64OS, ...
|
||||
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for different machines (just change the compiler target flag)
|
||||
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ circles {
|
||||
|
||||
sub draw(bool use_kernal, uword max_time) -> uword {
|
||||
if use_kernal
|
||||
void cx16.set_screen_mode(128)
|
||||
cx16.set_screen_mode(128)
|
||||
else
|
||||
gfx_lores.graphics_mode()
|
||||
|
||||
@@ -33,7 +33,7 @@ circles {
|
||||
}
|
||||
|
||||
if use_kernal
|
||||
void cx16.set_screen_mode(3)
|
||||
cx16.set_screen_mode(3)
|
||||
else {
|
||||
gfx_lores.text_mode()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
%import sprites
|
||||
%import coroutines
|
||||
%import math
|
||||
|
||||
|
||||
animsprites {
|
||||
uword num_iterations
|
||||
ubyte[64] sx
|
||||
ubyte[64] sy
|
||||
ubyte[64] sc
|
||||
ubyte[64] dx
|
||||
ubyte[64] dy
|
||||
uword maximum_duration
|
||||
|
||||
sub benchmark(uword max_duration) -> uword {
|
||||
maximum_duration = max_duration
|
||||
math.rndseed(1122,9876)
|
||||
cx16.set_screen_mode(3)
|
||||
cx16.mouse_config2(1)
|
||||
sprites.set_mousepointer_hand()
|
||||
repeat 64
|
||||
void coroutines.add(animsprite, 0)
|
||||
cx16.mouse_config2(0)
|
||||
|
||||
cbm.SETTIM(0,0,0)
|
||||
coroutines.run(supervisor)
|
||||
|
||||
sprites.reset(0, 64)
|
||||
return num_iterations
|
||||
}
|
||||
|
||||
sub supervisor() -> bool {
|
||||
if cbm.RDTIM16() >= maximum_duration {
|
||||
coroutines.killall()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
sub animsprite() {
|
||||
num_iterations++
|
||||
; set up the sprite
|
||||
ubyte sprnum = coroutines.current()
|
||||
cx16.r6L, cx16.r7 = sprites.get_data_ptr(0)
|
||||
sprites.init(sprnum, cx16.r6L, cx16.r7, sprites.SIZE_16, sprites.SIZE_16, sprites.COLORS_256, 0)
|
||||
sx[sprnum] = math.rnd()
|
||||
sy[sprnum] = math.rnd()
|
||||
sc[sprnum] = math.rnd()
|
||||
dx[sprnum] = if math.rnd()&1 == 1 1 else 255
|
||||
dy[sprnum] = if math.rnd()&1 == 1 1 else 255
|
||||
|
||||
; move the sprite around
|
||||
while sc[sprnum]!=0 {
|
||||
animate(sprnum)
|
||||
void coroutines.yield()
|
||||
sprnum = coroutines.current()
|
||||
}
|
||||
|
||||
sub animate(ubyte spr) {
|
||||
defer sc[spr]--
|
||||
sprites.pos(spr, sx[spr], sy[spr])
|
||||
sx[spr] += dx[spr]
|
||||
sy[spr] += dy[spr]
|
||||
}
|
||||
|
||||
; end the task but replace it with a fresh animated sprite task
|
||||
void coroutines.add(animsprite, 0)
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
%import b_queens
|
||||
%import b_textelite
|
||||
%import b_maze
|
||||
%import b_sprites
|
||||
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
@@ -29,7 +30,7 @@ main {
|
||||
sub start() {
|
||||
ubyte benchmark_number
|
||||
|
||||
void cx16.set_screen_mode(3)
|
||||
cx16.set_screen_mode(3)
|
||||
txt.color2(1, 6)
|
||||
txt.clear_screen()
|
||||
|
||||
@@ -74,10 +75,14 @@ main {
|
||||
benchmark_score[benchmark_number] = textelite.bench(120)
|
||||
benchmark_number++
|
||||
|
||||
announce_benchmark("sprites-coroutines-defer")
|
||||
benchmark_score[benchmark_number] = animsprites.benchmark(300)
|
||||
benchmark_number++
|
||||
|
||||
benchmark_names[benchmark_number] = 0
|
||||
benchmark_score[benchmark_number] = 0
|
||||
|
||||
void cx16.set_screen_mode(3)
|
||||
cx16.set_screen_mode(3)
|
||||
txt.uppercase()
|
||||
txt.color2(1, 6)
|
||||
uword final_score
|
||||
@@ -99,7 +104,7 @@ main {
|
||||
|
||||
sub announce_benchmark(str name) {
|
||||
benchmark_names[benchmark_number] = name
|
||||
void cx16.set_screen_mode(3)
|
||||
cx16.set_screen_mode(3)
|
||||
txt.uppercase()
|
||||
txt.color2(1, 6)
|
||||
txt.clear_screen()
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package prog8.code
|
||||
|
||||
|
||||
// the automatically generated module where all string literals are interned to:
|
||||
const val INTERNED_STRINGS_MODULENAME = "prog8_interned_strings"
|
||||
|
||||
// all automatically generated labels everywhere need to have the same label name prefix:
|
||||
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
|
||||
@@ -11,6 +11,7 @@ class CompilationOptions(val output: OutputType,
|
||||
val zpAllowed: List<UIntRange>,
|
||||
val floats: Boolean,
|
||||
val noSysInit: Boolean,
|
||||
val romable: Boolean,
|
||||
val compTarget: ICompilationTarget,
|
||||
// these are set later, based on command line arguments or options in the source code:
|
||||
var loadAddress: UInt,
|
||||
|
||||
@@ -6,6 +6,7 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":simpleAst"))
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.code.GENERATED_LABEL_PREFIX
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.ICodeGeneratorBackend
|
||||
import prog8.code.StNode
|
||||
import prog8.code.StNodeType
|
||||
import prog8.code.SymbolTable
|
||||
@@ -39,7 +42,7 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
|
||||
when(node) {
|
||||
is PtAsmSub, is PtSub -> node.name = "p8s_${node.name}"
|
||||
is PtBlock -> node.name = "p8b_${node.name}"
|
||||
is PtLabel -> if(!node.name.startsWith(PtLabel.GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}" // don't prefix autogenerated labels
|
||||
is PtLabel -> if(!node.name.startsWith(GENERATED_LABEL_PREFIX)) node.name = "p8l_${node.name}" // don't prefix autogenerated labels
|
||||
is PtConstant -> node.name = "p8c_${node.name}"
|
||||
is PtVariable, is PtMemMapped, is PtSubroutineParameter -> node.name = "p8v_${node.name}"
|
||||
}
|
||||
@@ -90,9 +93,35 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
|
||||
node.children.forEach { prefixSymbols(it) }
|
||||
}
|
||||
|
||||
fun maybePrefixFunctionCallsAndIdentifierReferences(node: PtNode) {
|
||||
if(node is PtFunctionCall) {
|
||||
// function calls to subroutines defined in a block that does NOT have NoSymbolPrefixing, still have to be prefixed at the call site
|
||||
val stNode = st.lookup(node.name)!!
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
functionCallsToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
else if (node is PtIdentifier) {
|
||||
// identifier references to things defined in a block that does NOT have NoSymbolPrefixing, still have to be prefixed at the referencing point
|
||||
var lookupName = node.name
|
||||
if(node.type.isSplitWordArray && (lookupName.endsWith("_lsb") || lookupName.endsWith("_msb"))) {
|
||||
lookupName = lookupName.dropLast(4)
|
||||
}
|
||||
val stNode = st.lookup(lookupName) ?: throw AssemblyError("unknown identifier $node")
|
||||
if(stNode.astNode!!.definingBlock()?.options?.noSymbolPrefixing!=true) {
|
||||
val index = node.parent.children.indexOf(node)
|
||||
nodesToPrefix += node.parent to index
|
||||
}
|
||||
}
|
||||
node.children.forEach { maybePrefixFunctionCallsAndIdentifierReferences(it) }
|
||||
}
|
||||
|
||||
program.allBlocks().forEach { block ->
|
||||
if (!block.options.noSymbolPrefixing) {
|
||||
prefixSymbols(block)
|
||||
} else {
|
||||
maybePrefixFunctionCallsAndIdentifierReferences(block)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +138,9 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
|
||||
functionCallsToPrefix.reversed().forEach { (parent, index) ->
|
||||
val node = parent.children[index]
|
||||
if(node is PtFunctionCall) {
|
||||
parent.children[index] = node.prefix(parent)
|
||||
val prefixedName = PtIdentifier(node.name, DataType.forDt(BaseDataType.UNDEFINED), Position.DUMMY).prefix(parent, st)
|
||||
val prefixedNode = node.withNewName(prefixedName.name)
|
||||
parent.children[index] = prefixedNode
|
||||
} else {
|
||||
throw AssemblyError("expected PtFunctionCall")
|
||||
}
|
||||
@@ -121,14 +152,14 @@ class AsmGen6502(val prefixSymbols: Boolean, private val lastGeneratedLabelSeque
|
||||
|
||||
private fun prefixScopedName(name: String, type: Char): String {
|
||||
if('.' !in name) {
|
||||
if(name.startsWith(PtLabel.GENERATED_LABEL_PREFIX))
|
||||
if(name.startsWith(GENERATED_LABEL_PREFIX))
|
||||
return name
|
||||
return "p8${type}_$name"
|
||||
}
|
||||
val parts = name.split('.')
|
||||
val firstPrefixed = "p8b_${parts[0]}"
|
||||
val lastPart = parts.last()
|
||||
val lastPrefixed = if(lastPart.startsWith(PtLabel.GENERATED_LABEL_PREFIX)) lastPart else "p8${type}_$lastPart"
|
||||
val lastPrefixed = if(lastPart.startsWith(GENERATED_LABEL_PREFIX)) lastPart else "p8${type}_$lastPart"
|
||||
// the parts in between are assumed to be subroutine scopes.
|
||||
val inbetweenPrefixed = parts.drop(1).dropLast(1).map{ "p8s_$it" }
|
||||
val prefixed = listOf(firstPrefixed) + inbetweenPrefixed + listOf(lastPrefixed)
|
||||
@@ -169,9 +200,13 @@ private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
|
||||
else this
|
||||
}
|
||||
|
||||
private fun PtFunctionCall.prefix(parent: PtNode): PtFunctionCall {
|
||||
val newName = prefixScopedName(name, 's')
|
||||
val call = PtFunctionCall(newName, void, type, position)
|
||||
//private fun PtFunctionCall.prefix(targetType: Char): PtFunctionCall {
|
||||
// val newName = prefixScopedName(name, targetType)
|
||||
// return this.withNewName(newName)
|
||||
//}
|
||||
|
||||
private fun PtFunctionCall.withNewName(name: String): PtFunctionCall {
|
||||
val call = PtFunctionCall(name, void, type, position)
|
||||
call.children.addAll(children)
|
||||
call.children.forEach { it.parent = call }
|
||||
call.parent = parent
|
||||
@@ -687,8 +722,8 @@ class AsmGen6502Internal (
|
||||
RegisterOrPair.Y -> assignmentAsmGen.assignRegisterByte(target, reg.asCpuRegister(), target.datatype.isSigned, true)
|
||||
RegisterOrPair.AX,
|
||||
RegisterOrPair.AY,
|
||||
RegisterOrPair.XY,
|
||||
in Cx16VirtualRegisters -> assignmentAsmGen.assignRegisterpairWord(target, reg)
|
||||
RegisterOrPair.XY -> assignmentAsmGen.assignRegisterpairWord(target, reg)
|
||||
in Cx16VirtualRegisters -> assignmentAsmGen.assignVirtualRegister(target, reg)
|
||||
RegisterOrPair.FAC1 -> assignmentAsmGen.assignFAC1float(target)
|
||||
RegisterOrPair.FAC2 -> assignmentAsmGen.assignFAC2float(target)
|
||||
else -> throw AssemblyError("invalid register")
|
||||
@@ -727,7 +762,7 @@ class AsmGen6502Internal (
|
||||
}
|
||||
|
||||
assignExpressionToRegister(value, RegisterOrPair.A)
|
||||
assignRegister(RegisterOrPair.A, target)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A, target.datatype.isSigned, false)
|
||||
}
|
||||
target.datatype.isWord || target.datatype.isPassByRef -> {
|
||||
assignExpressionToRegister(value, RegisterOrPair.AY)
|
||||
@@ -1063,25 +1098,32 @@ $repeatLabel""")
|
||||
|
||||
private fun translate(ret: PtReturn) {
|
||||
val returnvalue = ret.children.singleOrNull()
|
||||
val sub = ret.definingSub()!!
|
||||
val returnRegs = sub.returnsWhatWhere()
|
||||
|
||||
if(returnvalue!=null) {
|
||||
val sub = ret.definingSub()!!
|
||||
val returnReg = sub.returnsWhatWhere().single()
|
||||
if (sub.returns.single().isNumericOrBool==true) {
|
||||
assignExpressionToRegister(returnvalue as PtExpression, returnReg.first.registerOrPair!!)
|
||||
assignExpressionToRegister(returnvalue as PtExpression, returnRegs.single().first.registerOrPair!!)
|
||||
}
|
||||
else {
|
||||
// all else take its address and assign that also to AY register pair
|
||||
val addrofValue = PtAddressOf(returnvalue.position)
|
||||
addrofValue.add(returnvalue as PtIdentifier)
|
||||
addrofValue.parent = ret.parent
|
||||
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.first.registerOrPair!!, false)
|
||||
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnRegs.single().first.registerOrPair!!, false)
|
||||
}
|
||||
}
|
||||
else if(ret.children.size>1) {
|
||||
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
val registersReverseOrder = Cx16VirtualRegisters.reversed()
|
||||
ret.children.zip(registersReverseOrder).forEach { (value, register) ->
|
||||
assignExpressionToRegister(value as PtExpression, register)
|
||||
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
|
||||
// (this allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
// to avoid register clobbering, assign the first return value last in row.
|
||||
val assigns = ret.children.zip(returnRegs).map { it.first to it.second }
|
||||
assigns.drop(1).forEach {
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.REGISTER, this, it.second.second, null, it.first.position, register = it.second.first.registerOrPair!!)
|
||||
assignExpressionTo(it.first as PtExpression, tgt)
|
||||
}
|
||||
assigns.first().also {
|
||||
assignExpressionToRegister(it.first as PtExpression, it.second.first.registerOrPair!!)
|
||||
}
|
||||
}
|
||||
out(" rts")
|
||||
@@ -1342,13 +1384,14 @@ $repeatLabel""")
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
val varname = asmSymbolName(pointervar)
|
||||
out(" ldy #${256-constOffset} ; negative offset $constOffset")
|
||||
out(" dec $varname+1 | sta ($varname),y | inc $varname+1") // temporarily make MSB 1 less
|
||||
out(" dec $varname+1 | sta ($varname),y | inc $varname+1") // temporarily make MSB 1 less to be able to use the negative Y offset
|
||||
return true
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
out(" pha")
|
||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.forDt(BaseDataType.UWORD))
|
||||
out(" ldy #${256-constOffset} ; negative offset $constOffset")
|
||||
out(" dec P8ZP_SCRATCH_W2+1 | sta (P8ZP_SCRATCH_W2),y | inc P8ZP_SCRATCH_W2+1") // temporarily make MSB 1 less
|
||||
out(" dec P8ZP_SCRATCH_W2+1 | pla | sta (P8ZP_SCRATCH_W2),y") // temporarily make MSB 1 less to be able to use the negative Y offset
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1372,13 +1415,13 @@ $repeatLabel""")
|
||||
if(pointervar!=null && isZpVar(pointervar)) {
|
||||
val varname = asmSymbolName(pointervar)
|
||||
out(" ldy #${256-constOffset} ; negative offset $constOffset")
|
||||
out(" dec $varname+1 | lda ($varname),y | inc $varname+1") // temporarily make MSB 1 less
|
||||
out(" dec $varname+1 | lda ($varname),y | inc $varname+1") // temporarily make MSB 1 less to be able to use the negative Y offset
|
||||
return true
|
||||
} else {
|
||||
// copy the pointer var to zp first
|
||||
assignExpressionToVariable(ptrAndIndex.first, "P8ZP_SCRATCH_W2", DataType.forDt(BaseDataType.UWORD))
|
||||
out(" ldy #${256-constOffset} ; negative offset $constOffset")
|
||||
out(" dec P8ZP_SCRATCH_W2+1 | lda (P8ZP_SCRATCH_W2),y | inc P8ZP_SCRATCH_W2+1") // temporarily make MSB 1 less
|
||||
out(" dec P8ZP_SCRATCH_W2+1 | lda (P8ZP_SCRATCH_W2),y") // temporarily make MSB 1 less to be able to use the negative Y offset
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1495,7 +1538,7 @@ $repeatLabel""")
|
||||
|
||||
internal fun makeLabel(postfix: String): String {
|
||||
generatedLabelSequenceNumber++
|
||||
return "${PtLabel.GENERATED_LABEL_PREFIX}${generatedLabelSequenceNumber}_$postfix"
|
||||
return "$GENERATED_LABEL_PREFIX${generatedLabelSequenceNumber}_$postfix"
|
||||
}
|
||||
|
||||
internal fun assignConstFloatToPointerAY(number: PtNumber) {
|
||||
@@ -1704,6 +1747,14 @@ $repeatLabel""")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun romableWarning(problem: String, pos: Position) {
|
||||
if(options.romable) {
|
||||
// until the code generation can provide an alternative, we have to report about code generated that is incompatible with ROMable code mode...
|
||||
errors.warn("problem for ROMable code: $problem", pos)
|
||||
out(" .error \"ROMable code selected but incompatible code was generated: $problem\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.GENERATED_LABEL_PREFIX
|
||||
import prog8.code.StConstant
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtLabel
|
||||
import prog8.code.core.ICompilationTarget
|
||||
|
||||
|
||||
@@ -362,7 +362,7 @@ or *_afterif labels.
|
||||
This gets generated after certain if conditions, and only the branch instruction is needed in these cases.
|
||||
*/
|
||||
|
||||
val autoLabelPrefix = PtLabel.GENERATED_LABEL_PREFIX
|
||||
val autoLabelPrefix = GENERATED_LABEL_PREFIX
|
||||
if(first=="beq +" && second=="lda #1" && third=="+") {
|
||||
if((fourth.startsWith("beq $autoLabelPrefix") || fourth.startsWith("bne $autoLabelPrefix")) &&
|
||||
(fourth.endsWith("_shortcut") || fourth.endsWith("_afterif") || fourth.endsWith("_shortcut:") || fourth.endsWith("_afterif:"))) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.PtLabel
|
||||
import prog8.code.GENERATED_LABEL_PREFIX
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C128Target
|
||||
import prog8.code.target.C64Target
|
||||
@@ -131,7 +132,7 @@ internal class AssemblyProgram(
|
||||
}
|
||||
|
||||
private fun removeGeneratedLabelsFromMonlist() {
|
||||
val pattern = Regex("""al (\w+) \S+${PtLabel.GENERATED_LABEL_PREFIX}.+?""")
|
||||
val pattern = Regex("""al (\w+) \S+$GENERATED_LABEL_PREFIX.+?""")
|
||||
val lines = viceMonListFile.toFile().readLines()
|
||||
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
||||
for (line in lines) {
|
||||
|
||||
@@ -236,6 +236,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
jsr $jsrfar
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
asmgen.romableWarning("self-modifying code for jsrfar", fcall.position) // TODO
|
||||
}
|
||||
|
||||
// note that by convention the values in A+Y registers are now the return value of the call.
|
||||
@@ -292,6 +293,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
jsr $jsrfar
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
asmgen.romableWarning("self-modifying code for jsrfar", fcall.position) // TODO
|
||||
}
|
||||
|
||||
// note that by convention the values in A+Y registers are now the return value of the call.
|
||||
@@ -346,7 +348,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
cmp ${asmgen.asmVariableName(arg2)}
|
||||
+""")
|
||||
}
|
||||
is PtBool -> TODO("word compare against bool")
|
||||
is PtBool -> TODO("word compare against bool ${arg2.position}")
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
@@ -411,12 +413,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
is PtArrayIndexer -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable)
|
||||
asmgen.out(" lda ${varname},x | lsr a | bcc + | ora #\$80 |+ | sta ${varname},x")
|
||||
asmgen.out(" lda ${varname},x | lsr a | bcc + | ora #$80 |+ | sta ${varname},x")
|
||||
}
|
||||
is PtMemoryByte -> {
|
||||
if (what.address is PtNumber) {
|
||||
val number = (what.address as PtNumber).number
|
||||
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
|
||||
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #$80 |+ | sta ${number.toHex()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
|
||||
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
|
||||
@@ -424,7 +426,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
|
||||
asmgen.out(" lda $variable | lsr a | bcc + | ora #$80 |+ | sta $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
@@ -435,13 +437,13 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable)
|
||||
if(what.splitWords)
|
||||
asmgen.out(" lsr ${varname}_msb,x | ror ${varname}_lsb,x | bcc + | lda ${varname}_msb,x | ora #\$80 | sta ${varname}_msb,x |+ ")
|
||||
asmgen.out(" lsr ${varname}_msb,x | ror ${varname}_lsb,x | bcc + | lda ${varname}_msb,x | ora #$80 | sta ${varname}_msb,x |+ ")
|
||||
else
|
||||
asmgen.out(" lsr ${varname}+1,x | ror ${varname},x | bcc + | lda ${varname}+1,x | ora #\$80 | sta ${varname}+1,x |+ ")
|
||||
asmgen.out(" lsr ${varname}+1,x | ror ${varname},x | bcc + | lda ${varname}+1,x | ora #$80 | sta ${varname}+1,x |+ ")
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
|
||||
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #$80 | sta $variable+1 |+ ")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
@@ -478,6 +480,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out("""
|
||||
plp
|
||||
+ ror ${'$'}ffff,x ; modified""")
|
||||
asmgen.romableWarning("self-modifying code (ror)", fcall.position) // TODO
|
||||
} else {
|
||||
if(!what.address.isSimple()) asmgen.out(" php") // save Carry
|
||||
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
|
||||
@@ -486,6 +489,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
sta (+) + 1
|
||||
sty (+) + 2
|
||||
+ ror ${'$'}ffff ; modified""")
|
||||
asmgen.romableWarning("self-modifying code (ror)", fcall.position) // TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -527,12 +531,12 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
is PtArrayIndexer -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(what, CpuRegister.X)
|
||||
val varname = asmgen.asmVariableName(what.variable)
|
||||
asmgen.out(" lda ${varname},x | cmp #\$80 | rol a | sta ${varname},x")
|
||||
asmgen.out(" lda ${varname},x | cmp #$80 | rol a | sta ${varname},x")
|
||||
}
|
||||
is PtMemoryByte -> {
|
||||
if (what.address is PtNumber) {
|
||||
val number = (what.address as PtNumber).number
|
||||
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
|
||||
asmgen.out(" lda ${number.toHex()} | cmp #$80 | rol a | sta ${number.toHex()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
|
||||
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
|
||||
@@ -540,7 +544,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
|
||||
asmgen.out(" lda $variable | cmp #$80 | rol a | sta $variable")
|
||||
}
|
||||
else -> throw AssemblyError("weird type")
|
||||
}
|
||||
@@ -594,6 +598,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
asmgen.out("""
|
||||
plp
|
||||
+ rol ${'$'}ffff,x ; modified""")
|
||||
asmgen.romableWarning("self-modifying code (rol)", fcall.position) // TODO
|
||||
} else {
|
||||
if(!what.address.isSimple()) asmgen.out(" php") // save Carry
|
||||
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
|
||||
@@ -602,6 +607,7 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
sta (+) + 1
|
||||
sty (+) + 2
|
||||
+ rol ${'$'}ffff ; modified""")
|
||||
asmgen.romableWarning("self-modifying code (rol)", fcall.position) // TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.IPtSubroutine
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.ast.PtSub
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.core.RegisterOrStatusflag
|
||||
|
||||
|
||||
internal fun IPtSubroutine.returnsWhatWhere(): List<Pair<RegisterOrStatusflag, DataType>> {
|
||||
when(this) {
|
||||
is PtAsmSub -> {
|
||||
return returns
|
||||
}
|
||||
is PtSub -> {
|
||||
// for non-asm subroutines, determine the return registers based on the type of the return values
|
||||
|
||||
when(returns.size) {
|
||||
0 -> return emptyList()
|
||||
1 -> {
|
||||
val returntype = returns.single()
|
||||
val register = when {
|
||||
returntype.isByteOrBool -> RegisterOrStatusflag(RegisterOrPair.A, null)
|
||||
returntype.isWord -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||
returntype.isFloat -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
|
||||
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||
}
|
||||
return listOf(Pair(register, returntype))
|
||||
}
|
||||
else -> {
|
||||
// TODO for multi-value results, put the first one in register(s) and only the rest in the virtual registers?
|
||||
throw AssemblyError("multi-value returns from a normal subroutine are not put into registers, this routine shouldn't have been called in this scenario")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -111,6 +111,7 @@ internal class ForLoopsAsmGen(
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
beq $endLabel
|
||||
$incdec $varname""")
|
||||
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
|
||||
asmgen.jmp(loopLabel)
|
||||
asmgen.out(endLabel)
|
||||
|
||||
@@ -159,6 +160,7 @@ $modifiedLabel cmp #0 ; modified
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
bmi $loopLabel
|
||||
beq $loopLabel""")
|
||||
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
@@ -167,6 +169,7 @@ $modifiedLabel cmp #0 ; modified
|
||||
sta $varname
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
bpl $loopLabel""")
|
||||
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
|
||||
}
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
@@ -223,6 +226,7 @@ $modifiedLabel cmp #0 ; modified
|
||||
lda $varname
|
||||
$modifiedLabel2 cmp #0 ; modified
|
||||
beq $endLabel""")
|
||||
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
|
||||
if(stepsize==1) {
|
||||
asmgen.out("""
|
||||
+ inc $varname
|
||||
@@ -266,6 +270,7 @@ $modifiedLabel2 lda #0 ; modified
|
||||
bcc $endLabel
|
||||
bcs $loopLabel
|
||||
$endLabel""")
|
||||
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
|
||||
} else {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
@@ -283,6 +288,7 @@ $modifiedLabel lda #0 ; modified
|
||||
eor #$80
|
||||
+ bpl $loopLabel
|
||||
$endLabel""")
|
||||
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -313,6 +319,7 @@ $modifiedLabel sbc #0 ; modified
|
||||
eor #$80
|
||||
+ bpl $loopLabel
|
||||
$endLabel""")
|
||||
asmgen.romableWarning("self-modifying code (forloop over range)", stmt.position) // TODO
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("range expression can only be byte or word")
|
||||
@@ -391,6 +398,7 @@ $endLabel""")
|
||||
$loopLabel lda ${65535.toHex()} ; modified
|
||||
beq $endLabel
|
||||
sta ${asmgen.asmVariableName(stmt.variable)}""")
|
||||
asmgen.romableWarning("self-modifying code (forloop over iterable)", stmt.position) // TODO
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
inc $loopLabel+1
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.StNodeType
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
||||
@@ -32,8 +33,14 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
|
||||
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
|
||||
|
||||
val symbol = asmgen.symbolTable.lookup(call.name)
|
||||
val sub = symbol?.astNode as IPtSubroutine
|
||||
val symbol = asmgen.symbolTable.lookup(call.name)!!
|
||||
if(symbol.type == StNodeType.LABEL) {
|
||||
require(call.void)
|
||||
asmgen.out(" jsr ${asmgen.asmSymbolName(symbol.scopedName)}")
|
||||
return
|
||||
}
|
||||
|
||||
val sub = symbol.astNode as IPtSubroutine
|
||||
val subAsmName = asmgen.asmSymbolName(call.name)
|
||||
|
||||
if(sub is PtAsmSub) {
|
||||
@@ -65,6 +72,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
)
|
||||
asmgen.romableWarning("self-modifying code for cx16 banked jsr", call.position) // TODO
|
||||
}
|
||||
"c64" -> {
|
||||
asmgen.out("""
|
||||
@@ -78,6 +86,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
)
|
||||
asmgen.romableWarning("self-modifying code for c64 banked jsr", call.position) // TODO
|
||||
}
|
||||
"c128" -> {
|
||||
asmgen.out("""
|
||||
@@ -91,6 +100,7 @@ internal class FunctionCallAsmGen(private val program: PtProgram, private val as
|
||||
.word $subAsmName ; ${sub.address!!.address.toHex()}
|
||||
+ .byte 0 ; modified"""
|
||||
)
|
||||
asmgen.romableWarning("self-modifying code for c128 banked jsr", call.position) // TODO
|
||||
}
|
||||
else -> throw AssemblyError("callfar is not supported on the selected compilation target")
|
||||
}
|
||||
|
||||
@@ -652,6 +652,7 @@ internal class ProgramAndVarsGen(
|
||||
it.initializationStringValue!!.second,
|
||||
it.initializationStringValue!!.first
|
||||
)
|
||||
asmgen.romableWarning("inlined variable (${it.dt}, ${it.name})", Position.DUMMY) // TODO
|
||||
}
|
||||
alignedStrings.sortedBy { it.align }.forEach {
|
||||
outputStringvar(
|
||||
@@ -660,13 +661,16 @@ internal class ProgramAndVarsGen(
|
||||
it.initializationStringValue!!.second,
|
||||
it.initializationStringValue!!.first
|
||||
)
|
||||
asmgen.romableWarning("inlined variable (${it.dt}, ${it.name})", Position.DUMMY) // TODO
|
||||
}
|
||||
|
||||
notAlignedOther.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
asmgen.romableWarning("inlined variable (${it.dt}, ${it.name})", Position.DUMMY) // TODO
|
||||
}
|
||||
alignedOther.sortedBy { it.align }.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
asmgen.romableWarning("inlined variable (${it.dt}, ${it.name})", Position.DUMMY) // TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ internal class AnyExprAsmGen(
|
||||
asmgen.assignExpressionToRegister(right, RegisterOrPair.FAC2, true)
|
||||
if (!right.isSimple()) asmgen.popFAC1()
|
||||
}
|
||||
else -> TODO("don't know how to evaluate float expression for selected compilation target")
|
||||
else -> TODO("don't know how to evaluate float expression for selected compilation target ${left.position}")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package prog8.codegen.cpu6502.assignment
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
import prog8.codegen.cpu6502.returnsWhatWhere
|
||||
|
||||
|
||||
internal enum class TargetStorageKind {
|
||||
|
||||
@@ -9,7 +9,6 @@ import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
import prog8.codegen.cpu6502.VariableAllocator
|
||||
import prog8.codegen.cpu6502.returnsWhatWhere
|
||||
|
||||
|
||||
internal class AssignmentAsmGen(
|
||||
@@ -44,7 +43,7 @@ internal class AssignmentAsmGen(
|
||||
if(extsub!=null) {
|
||||
require(extsub.returns.size>=2)
|
||||
if(extsub.returns.any { it.type.isFloat })
|
||||
TODO("deal with (multiple?) FP return registers")
|
||||
TODO("deal with (multiple?) FP return registers ${assignment.position}")
|
||||
|
||||
asmgen.translate(values)
|
||||
|
||||
@@ -154,7 +153,7 @@ internal class AssignmentAsmGen(
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
else TODO("array target for multi-value assignment") // Not done yet due to result register clobbering complexity
|
||||
else TODO("array target for multi-value assignment ${target.position}") // Not done yet due to result register clobbering complexity
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +241,7 @@ internal class AssignmentAsmGen(
|
||||
targetDt.isSignedWord -> assignVariableWord(assign.target, variable, assign.source.datatype)
|
||||
targetDt.isUnsignedWord -> {
|
||||
if(assign.source.datatype.isPassByRef)
|
||||
assignAddressOf(assign.target, variable, false, null, null)
|
||||
assignAddressOf(assign.target, variable, false, assign.source.datatype, assign.source.array ?: PtNumber(BaseDataType.UBYTE, 0.0, assign.position))
|
||||
else
|
||||
assignVariableWord(assign.target, variable, assign.source.datatype)
|
||||
}
|
||||
@@ -610,11 +609,13 @@ internal class AssignmentAsmGen(
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
asmgen.translateFunctionCall(value)
|
||||
if(sub is PtSub && sub.returns.size>1) {
|
||||
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
val registersReverseOrder = Cx16VirtualRegisters.reversed()
|
||||
assign.targets.zip(registersReverseOrder).forEach { (target, register) ->
|
||||
if(target.kind!=TargetStorageKind.VOID)
|
||||
assignVirtualRegister(target, register)
|
||||
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
|
||||
// (this allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
val returnRegs = sub.returnsWhatWhere()
|
||||
assign.targets.zip(returnRegs).forEach { target ->
|
||||
if(target.first.kind != TargetStorageKind.VOID) {
|
||||
asmgen.assignRegister(target.second.first.registerOrPair!!, target.first)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val target = assign.target
|
||||
@@ -700,13 +701,16 @@ internal class AssignmentAsmGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
|
||||
internal fun assignVirtualRegister(target: AsmAssignTarget, register: RegisterOrPair) {
|
||||
// Note: while the virtual register R0-R15 can hold a word value,
|
||||
// the actual datatype that gets assigned is determined by the assignment target.
|
||||
// This can be a single byte!
|
||||
when {
|
||||
target.datatype.isByteOrBool -> {
|
||||
if(register in Cx16VirtualRegisters) {
|
||||
asmgen.out(" lda cx16.${register.toString().lowercase()}L")
|
||||
} else {
|
||||
TODO("LDA byte from $register")
|
||||
TODO("LDA byte from $register ${target.position}")
|
||||
}
|
||||
assignRegisterByte(target, CpuRegister.A, false, false)
|
||||
}
|
||||
@@ -850,7 +854,7 @@ internal class AssignmentAsmGen(
|
||||
RegisterOrPair.A -> "a"
|
||||
RegisterOrPair.X -> "x"
|
||||
RegisterOrPair.Y -> "y"
|
||||
else -> TODO("comparison to word register")
|
||||
else -> TODO("comparison to word register ${expr.position}")
|
||||
}
|
||||
val assignTrue = PtInlineAssembly("\tld${reg} #1", false, assign.target.position)
|
||||
val assignFalse = PtInlineAssembly("\tld${reg} #0", false, assign.target.position)
|
||||
@@ -1841,8 +1845,7 @@ internal class AssignmentAsmGen(
|
||||
"==" -> {
|
||||
val dt = expr.left.type
|
||||
when {
|
||||
dt.isBool -> TODO("compare bool to 0")
|
||||
dt.isByte -> {
|
||||
dt.isBool || dt.isByte -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt.isSigned)
|
||||
asmgen.out("""
|
||||
cmp #0
|
||||
@@ -1877,8 +1880,7 @@ internal class AssignmentAsmGen(
|
||||
"!=" -> {
|
||||
val dt = expr.left.type
|
||||
when {
|
||||
dt.isBool -> TODO("compare bool to 0")
|
||||
dt.isByte -> {
|
||||
dt.isBool || dt.isByte -> {
|
||||
assignExpressionToRegister(expr.left, RegisterOrPair.A, dt.isSigned)
|
||||
asmgen.out(" beq + | lda #1")
|
||||
asmgen.out("+")
|
||||
@@ -2609,6 +2611,7 @@ $endLabel""")
|
||||
|
||||
private fun assignAddressOf(target: AsmAssignTarget, sourceName: String, msb: Boolean, arrayDt: DataType?, arrayIndexExpr: PtExpression?) {
|
||||
if(arrayIndexExpr!=null) {
|
||||
val arrayName = if(arrayDt!!.isSplitWordArray) sourceName+"_lsb" else sourceName // the _lsb split array comes first in memory
|
||||
val constIndex = arrayIndexExpr.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
if (arrayDt?.isUnsignedWord==true) {
|
||||
@@ -2624,10 +2627,10 @@ $endLabel""")
|
||||
}
|
||||
else {
|
||||
if(constIndex>0) {
|
||||
val offset = if(arrayDt!!.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
|
||||
asmgen.out(" lda #<($sourceName + $offset) | ldy #>($sourceName + $offset)")
|
||||
val offset = if(arrayDt.isSplitWordArray) constIndex else program.memsizer.memorySize(arrayDt, constIndex) // add arrayIndexExpr * elementsize to the address of the array variable.
|
||||
asmgen.out(" lda #<($arrayName + $offset) | ldy #>($arrayName + $offset)")
|
||||
} else {
|
||||
asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
|
||||
asmgen.out(" lda #<$arrayName | ldy #>$arrayName")
|
||||
}
|
||||
}
|
||||
assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
@@ -2651,9 +2654,9 @@ $endLabel""")
|
||||
else {
|
||||
assignExpressionToRegister(arrayIndexExpr, RegisterOrPair.A, false)
|
||||
asmgen.out("""
|
||||
ldy #>$sourceName
|
||||
ldy #>$arrayName
|
||||
clc
|
||||
adc #<$sourceName
|
||||
adc #<$arrayName
|
||||
bcc +
|
||||
iny
|
||||
+""")
|
||||
|
||||
@@ -965,6 +965,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
|
||||
asmgen.out(" sta (+) + 1 | sty (+) + 2")
|
||||
asmgen.out("+\tinc ${'$'}ffff\t; modified")
|
||||
asmgen.romableWarning("self-modifying code (access pointer)", pointervar.position) // TODO
|
||||
} else {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" clc | adc #$value")
|
||||
@@ -976,6 +977,7 @@ internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
asmgen.assignExpressionToRegister(pointervar, RegisterOrPair.AY)
|
||||
asmgen.out(" sta (+) + 1 | sty (+) + 2")
|
||||
asmgen.out("+\tdec ${'$'}ffff\t; modified")
|
||||
asmgen.romableWarning("self-modifying code (access pointer)", pointervar.position) // TODO
|
||||
} else {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
asmgen.out(" sec | sbc #$value")
|
||||
|
||||
@@ -26,6 +26,7 @@ class TestCodegen: FunSpec({
|
||||
zpAllowed = CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
|
||||
@@ -6,6 +6,7 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":simpleAst"))
|
||||
implementation(project(":intermediate"))
|
||||
implementation(project(":codeGenIntermediate"))
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
@@ -13,5 +13,6 @@
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -3,8 +3,8 @@ package prog8.codegen.experimental
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IAssemblyProgram
|
||||
import prog8.code.core.ICodeGeneratorBackend
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.ICodeGeneratorBackend
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
|
||||
@@ -7,6 +7,7 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":simpleAst"))
|
||||
implementation(project(":intermediate"))
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="library" name="io.kotest.assertions.core.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
|
||||
@@ -41,15 +41,16 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
} else {
|
||||
val normalsub = codeGen.symbolTable.lookup(values.name) as? StSub
|
||||
if (normalsub != null) {
|
||||
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
val registersReverseOrder = Cx16VirtualRegisters.reversed()
|
||||
normalsub.returns.zip(assignmentTargets).zip(registersReverseOrder).forEach {
|
||||
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
|
||||
// (this allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
val returnregs = (normalsub.astNode!! as IPtSubroutine).returnsWhatWhere()
|
||||
normalsub.returns.zip(assignmentTargets).zip(returnregs).forEach {
|
||||
val target = it.first.second as PtAssignTarget
|
||||
if(!target.void) {
|
||||
val assignSingle = PtAssignment(assignment.position, assignment.isVarInitializer)
|
||||
assignSingle.add(target)
|
||||
assignSingle.add(PtIdentifier("cx16.${it.second.toString().lowercase()}", it.first.first, assignment.position))
|
||||
result += translateRegularAssign(assignSingle)
|
||||
val reg = it.second.first
|
||||
val regnum = codeGen.registers.next(irType(it.second.second))
|
||||
val p = StExtSubParameter(reg, it.second.second)
|
||||
result += assignCpuRegister(p, regnum, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +75,7 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
RegisterOrPair.AX -> IRInstruction(Opcode.LOADHAX, IRDataType.WORD, reg1=regNum)
|
||||
RegisterOrPair.AY -> IRInstruction(Opcode.LOADHAY, IRDataType.WORD, reg1=regNum)
|
||||
RegisterOrPair.XY -> IRInstruction(Opcode.LOADHXY, IRDataType.WORD, reg1=regNum)
|
||||
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, IRDataType.WORD, reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
|
||||
in Cx16VirtualRegisters -> IRInstruction(Opcode.LOADM, irType(returns.type), reg1=regNum, labelSymbol = "cx16.${returns.register.registerOrPair.toString().lowercase()}")
|
||||
null -> {
|
||||
TODO("assign CPU status flag ${returns.register.statusflag!!}")
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StNode
|
||||
import prog8.code.StExtSub
|
||||
import prog8.code.StNodeType
|
||||
import prog8.code.StSub
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
@@ -579,7 +580,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
internal fun translate(fcall: PtFunctionCall): ExpressionCodeResult {
|
||||
val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)
|
||||
val callTarget = codeGen.symbolTable.lookup(fcall.name)!!
|
||||
|
||||
if(callTarget.scopedName in listOf("sys.push", "sys.pushw", "sys.pop", "sys.popw", "floats.push", "floats.pop")) {
|
||||
// special case, these should be inlined, or even use specialized instructions. Instead of doing a normal subroutine call.
|
||||
@@ -635,7 +636,10 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
// return value(s)
|
||||
val returnRegSpecs = if(fcall.void) emptyList() else {
|
||||
// TODO: for current implemenation of the call convention in case of multiple return values,
|
||||
// a list of Ir virtual registers to hold the results is NOT correct (they're loaded into AY + R15..R0 instead!)
|
||||
// So we use an empty list to avoid confusion here. This may change in a future version.
|
||||
val returnRegSpecs = if(fcall.void || callTarget.returns.size>1) emptyList() else {
|
||||
callTarget.returns.map {
|
||||
val returnIrType = irType(it)
|
||||
FunctionCallArgs.RegSpec(returnIrType, codeGen.registers.next(returnIrType), null)
|
||||
@@ -653,7 +657,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else
|
||||
ExpressionCodeResult(result, returnRegSpec.dt, returnRegSpec.registerNum, -1)
|
||||
} else {
|
||||
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
|
||||
// so the actual result of the expression here is 'void' (doesn't use IR virtual registers at all)
|
||||
ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
@@ -671,25 +675,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else
|
||||
argRegisters.add(FunctionCallArgs.ArgumentSpec("", null, FunctionCallArgs.RegSpec(paramDt, tr.resultReg, parameter.register)))
|
||||
result += tr.chunks
|
||||
when(parameter.register.registerOrPair) {
|
||||
RegisterOrPair.A -> addInstr(result, IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.X -> addInstr(result, IRInstruction(Opcode.STOREHX, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.Y -> addInstr(result, IRInstruction(Opcode.STOREHY, IRDataType.BYTE, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.AX -> addInstr(result, IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.AY -> addInstr(result, IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.XY -> addInstr(result, IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=tr.resultReg), null)
|
||||
RegisterOrPair.FAC1 -> addInstr(result, IRInstruction(Opcode.STOREHFACZERO, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
|
||||
RegisterOrPair.FAC2 -> addInstr(result, IRInstruction(Opcode.STOREHFACONE, IRDataType.FLOAT, fpReg1 = tr.resultFpReg), null)
|
||||
in Cx16VirtualRegisters -> {
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, paramDt, reg1=tr.resultReg, labelSymbol = "cx16.${parameter.register.registerOrPair.toString().lowercase()}"), null)
|
||||
}
|
||||
null -> when(parameter.register.statusflag) {
|
||||
// TODO: do the statusflag argument as last
|
||||
Statusflag.Pc -> addInstr(result, IRInstruction(Opcode.LSR, paramDt, reg1=tr.resultReg), null)
|
||||
else -> throw AssemblyError("weird statusflag as param")
|
||||
}
|
||||
else -> throw AssemblyError("unsupported register arg")
|
||||
}
|
||||
result += codeGen.setCpuRegister(parameter.register, paramDt, tr.resultReg, tr.resultFpReg)
|
||||
}
|
||||
|
||||
if(callTarget.returns.size>1)
|
||||
@@ -770,7 +756,18 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
else
|
||||
ExpressionCodeResult(result, returnRegSpec!!.dt, finalReturnRegister, -1)
|
||||
}
|
||||
else -> throw AssemblyError("invalid node type")
|
||||
else -> {
|
||||
if(callTarget.type == StNodeType.LABEL) {
|
||||
require(fcall.void)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val args = FunctionCallArgs(emptyList(), emptyList())
|
||||
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol = fcall.name, fcallArgs = args), null)
|
||||
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
|
||||
}
|
||||
else {
|
||||
throw AssemblyError("invalid node type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class IRCodeGen(
|
||||
verifyNameScoping(program, symbolTable)
|
||||
changeGlobalVarInits(symbolTable)
|
||||
|
||||
val irSymbolTable = IRSymbolTable.fromAstSymboltable(symbolTable)
|
||||
val irSymbolTable = convertStToIRSt(symbolTable)
|
||||
val irProg = IRProgram(program.name, irSymbolTable, options, program.encoding)
|
||||
|
||||
// collect global variables initializers
|
||||
@@ -1759,12 +1759,20 @@ class IRCodeGen(
|
||||
private fun translate(ret: PtReturn): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(ret.children.size>1) {
|
||||
// multi-value returns are passed throug cx16.R15 down to R0 (allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
val registersReverseOrder = Cx16VirtualRegisters.reversed()
|
||||
for ((value, register) in ret.children.zip(registersReverseOrder)) {
|
||||
// note: multi-value returns are passed throug A or AY (for the first value) then cx16.R15 down to R0
|
||||
// (this allows unencumbered use of many Rx registers if you don't return that many values)
|
||||
// make sure to assign the first value as the last in the sequence, to avoid clobbering the AY registers afterwards
|
||||
val returnRegs = ret.definingISub()!!.returnsWhatWhere()
|
||||
val values = ret.children.zip(returnRegs)
|
||||
for ((value, register) in values.drop(1)) {
|
||||
val tr = expressionEval.translateExpression(value as PtExpression)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, tr.dt, reg1=tr.resultReg, labelSymbol = "cx16.${register.toString().lowercase()}"), null)
|
||||
result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1)
|
||||
}
|
||||
values.first().also { (value, register) ->
|
||||
val tr = expressionEval.translateExpression(value as PtExpression)
|
||||
addToResult(result, tr, tr.resultReg, -1)
|
||||
result += setCpuRegister(register.first, irType(register.second), tr.resultReg, -1)
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.RETURN), null)
|
||||
return result
|
||||
@@ -1891,7 +1899,7 @@ class IRCodeGen(
|
||||
private var labelSequenceNumber = 0
|
||||
internal fun createLabelName(): String {
|
||||
labelSequenceNumber++
|
||||
return "${PtLabel.GENERATED_LABEL_PREFIX}$labelSequenceNumber"
|
||||
return "${GENERATED_LABEL_PREFIX}$labelSequenceNumber"
|
||||
}
|
||||
|
||||
internal fun translateBuiltinFunc(call: PtBuiltinFunctionCall): ExpressionCodeResult
|
||||
@@ -1913,4 +1921,28 @@ class IRCodeGen(
|
||||
}
|
||||
|
||||
fun registerTypes(): Map<Int, IRDataType> = registers.getTypes()
|
||||
|
||||
fun setCpuRegister(registerOrFlag: RegisterOrStatusflag, paramDt: IRDataType, resultReg: Int, resultFpReg: Int): IRCodeChunk {
|
||||
val chunk = IRCodeChunk(null, null)
|
||||
when(registerOrFlag.registerOrPair) {
|
||||
RegisterOrPair.A -> chunk += IRInstruction(Opcode.STOREHA, IRDataType.BYTE, reg1=resultReg)
|
||||
RegisterOrPair.X -> chunk += IRInstruction(Opcode.STOREHX, IRDataType.BYTE, reg1=resultReg)
|
||||
RegisterOrPair.Y -> chunk += IRInstruction(Opcode.STOREHY, IRDataType.BYTE, reg1=resultReg)
|
||||
RegisterOrPair.AX -> chunk += IRInstruction(Opcode.STOREHAX, IRDataType.WORD, reg1=resultReg)
|
||||
RegisterOrPair.AY -> chunk += IRInstruction(Opcode.STOREHAY, IRDataType.WORD, reg1=resultReg)
|
||||
RegisterOrPair.XY -> chunk += IRInstruction(Opcode.STOREHXY, IRDataType.WORD, reg1=resultReg)
|
||||
RegisterOrPair.FAC1 -> chunk += IRInstruction(Opcode.STOREHFACZERO, IRDataType.FLOAT, fpReg1 = resultFpReg)
|
||||
RegisterOrPair.FAC2 -> chunk += IRInstruction(Opcode.STOREHFACONE, IRDataType.FLOAT, fpReg1 = resultFpReg)
|
||||
in Cx16VirtualRegisters -> {
|
||||
chunk += IRInstruction(Opcode.STOREM, paramDt, reg1=resultReg, labelSymbol = "cx16.${registerOrFlag.registerOrPair.toString().lowercase()}")
|
||||
}
|
||||
null -> when(registerOrFlag.statusflag) {
|
||||
// TODO: do the statusflag argument as last
|
||||
Statusflag.Pc -> chunk += IRInstruction(Opcode.LSR, paramDt, reg1=resultReg)
|
||||
else -> throw AssemblyError("weird statusflag as param")
|
||||
}
|
||||
else -> throw AssemblyError("unsupported register arg")
|
||||
}
|
||||
return chunk
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
package prog8.codegen.intermediate
|
||||
|
||||
import prog8.code.StArrayElement
|
||||
import prog8.code.StConstant
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.StMemorySlab
|
||||
import prog8.code.StNodeType
|
||||
import prog8.code.StStaticVariable
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.core.DataType
|
||||
import prog8.intermediate.IRStArrayElement
|
||||
import prog8.intermediate.IRStConstant
|
||||
import prog8.intermediate.IRStMemVar
|
||||
import prog8.intermediate.IRStMemorySlab
|
||||
import prog8.intermediate.IRStStaticVariable
|
||||
import prog8.intermediate.IRSymbolTable
|
||||
|
||||
|
||||
fun convertStToIRSt(sourceSt: SymbolTable?): IRSymbolTable {
|
||||
val st = IRSymbolTable()
|
||||
if (sourceSt != null) {
|
||||
sourceSt.flat.forEach {
|
||||
when(it.value.type) {
|
||||
StNodeType.STATICVAR -> st.add(convert(it.value as StStaticVariable))
|
||||
StNodeType.MEMVAR -> st.add(convert(it.value as StMemVar))
|
||||
StNodeType.CONSTANT -> st.add(convert(it.value as StConstant))
|
||||
StNodeType.MEMORYSLAB -> st.add(convert(it.value as StMemorySlab))
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
st.validate()
|
||||
|
||||
st.allVariables().forEach { variable ->
|
||||
variable.onetimeInitializationArrayValue?.let {
|
||||
it.forEach { arrayElt ->
|
||||
val addrOfSymbol = arrayElt.addressOfSymbol
|
||||
if (addrOfSymbol != null) {
|
||||
require(addrOfSymbol.contains('.')) {
|
||||
"pointer var in array should be properly scoped: ${addrOfSymbol} in ${variable.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
|
||||
private fun convert(variable: StStaticVariable): IRStStaticVariable {
|
||||
|
||||
fun convertArrayElt(elt: StArrayElement): IRStArrayElement = if(elt.boolean!=null)
|
||||
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
|
||||
else
|
||||
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
|
||||
|
||||
val scopedName: String
|
||||
if('.' in variable.name) {
|
||||
scopedName = variable.name
|
||||
return IRStStaticVariable(variable.name,
|
||||
variable.dt,
|
||||
variable.initializationNumericValue,
|
||||
variable.initializationStringValue,
|
||||
variable.initializationArrayValue?.map { convertArrayElt(it) },
|
||||
variable.length,
|
||||
variable.zpwish,
|
||||
variable.align)
|
||||
} else {
|
||||
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
|
||||
if(array==null)
|
||||
return null
|
||||
val newArray = mutableListOf<IRStArrayElement>()
|
||||
array.forEach {
|
||||
if(it.addressOfSymbol!=null) {
|
||||
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
|
||||
newArray.add(IRStArrayElement(null, null, target.scopedName))
|
||||
} else {
|
||||
newArray.add(convertArrayElt(it))
|
||||
}
|
||||
}
|
||||
return newArray
|
||||
}
|
||||
scopedName = variable.scopedName
|
||||
return IRStStaticVariable(scopedName,
|
||||
variable.dt,
|
||||
variable.initializationNumericValue,
|
||||
variable.initializationStringValue,
|
||||
fixupAddressOfInArray(variable.initializationArrayValue),
|
||||
variable.length,
|
||||
variable.zpwish,
|
||||
variable.align
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun convert(variable: StMemVar): IRStMemVar {
|
||||
val scopedName: String
|
||||
if('.' in variable.name) {
|
||||
scopedName = variable.name
|
||||
return IRStMemVar(
|
||||
variable.name,
|
||||
variable.dt,
|
||||
variable.address,
|
||||
variable.length
|
||||
)
|
||||
} else {
|
||||
scopedName = try {
|
||||
variable.scopedName
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
variable.name
|
||||
}
|
||||
return IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun convert(constant: StConstant): IRStConstant {
|
||||
val dt = DataType.forDt(constant.dt)
|
||||
val scopedName = if('.' in constant.name) {
|
||||
constant.name
|
||||
} else {
|
||||
try {
|
||||
constant.scopedName
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
constant.name
|
||||
}
|
||||
}
|
||||
return IRStConstant(scopedName, dt, constant.value)
|
||||
}
|
||||
|
||||
|
||||
private fun convert(variable: StMemorySlab): IRStMemorySlab {
|
||||
return if('.' in variable.name)
|
||||
IRStMemorySlab(variable.name, variable.size, variable.align)
|
||||
else
|
||||
IRStMemorySlab("prog8_slabs.${variable.name}", variable.size, variable.align)
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
|
||||
|
||||
|
||||
*/
|
||||
@@ -3,8 +3,8 @@ package prog8.codegen.vm
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IAssemblyProgram
|
||||
import prog8.code.core.ICodeGeneratorBackend
|
||||
import prog8.code.IAssemblyProgram
|
||||
import prog8.code.ICodeGeneratorBackend
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
|
||||
@@ -21,6 +21,7 @@ class TestIRPeepholeOpt: FunSpec({
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = true,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
|
||||
@@ -23,6 +23,7 @@ class TestVmCodeGen: FunSpec({
|
||||
zpAllowed = CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu
|
||||
|
||||
@@ -23,9 +23,11 @@ fun Program.constantFold(errors: IErrorReporter, options: CompilationOptions) {
|
||||
|
||||
val optimizer = ConstantFoldingOptimizer(this, errors)
|
||||
optimizer.visit(this)
|
||||
while (errors.noErrors() && optimizer.applyModifications() > 0) {
|
||||
var tries=0
|
||||
while (errors.noErrors() && optimizer.applyModifications() > 0 && tries++ < 100000) {
|
||||
optimizer.visit(this)
|
||||
}
|
||||
require(tries<100000) { "endless loop in constantfold" }
|
||||
|
||||
if (errors.noErrors()) {
|
||||
replacer.visit(this)
|
||||
|
||||
@@ -10,7 +10,7 @@ import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.internedStringsModuleName
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.compiler.CallGraph
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ class UnusedCodeRemover(private val program: Program,
|
||||
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars) {
|
||||
if (block.name != internedStringsModuleName && "ignore_unused" !in block.options()) {
|
||||
if (block.name != INTERNED_STRINGS_MODULENAME && "ignore_unused" !in block.options()) {
|
||||
if (!block.statements.any { it is Subroutine && it.hasBeenInlined })
|
||||
errors.info("removing unused block '${block.name}'", block.position)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ plugins {
|
||||
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
implementation(project(":simpleAst"))
|
||||
implementation(project(":codeOptimizers"))
|
||||
implementation(project(":compilerAst"))
|
||||
implementation(project(":codeGenCpu6502"))
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
<orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
|
||||
<orderEntry type="library" name="io.kotest.framework.datatest" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="simpleAst" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<orderEntry type="module" module-name="codeOptimizers" />
|
||||
<orderEntry type="module" module-name="codeGenCpu6502" />
|
||||
<orderEntry type="module" module-name="codeGenExperimental" />
|
||||
<orderEntry type="module" module-name="codeGenIntermediate" />
|
||||
<orderEntry type="module" module-name="virtualmachine" />
|
||||
<orderEntry type="module" module-name="intermediate" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intermediate" />
|
||||
<orderEntry type="library" name="antlr.antlr4" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -445,6 +445,7 @@ sys {
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
@@ -1056,6 +1057,7 @@ cx16 {
|
||||
&byte r15sH = $1bff
|
||||
|
||||
asmsub save_virtual_registers() clobbers(A,Y) {
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda cx16.r0,y
|
||||
|
||||
@@ -334,10 +334,10 @@ hline_zero2
|
||||
; @(pixaddr) |= ormask[lsb(px) & 7]
|
||||
; }
|
||||
|
||||
inline asmsub plot(uword plotx @XY, ubyte ploty @A) clobbers (A, X, Y) {
|
||||
inline asmsub plot(uword plotx @AX, ubyte ploty @Y) clobbers (A, X, Y) {
|
||||
%asm {{
|
||||
stx p8b_graphics.p8v_internal_plotx
|
||||
sty p8b_graphics.p8v_internal_plotx+1
|
||||
sta p8b_graphics.p8v_internal_plotx
|
||||
stx p8b_graphics.p8v_internal_plotx+1
|
||||
jsr p8b_graphics.p8s_internal_plot
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -460,6 +460,7 @@ sys {
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
@@ -1072,6 +1073,7 @@ cx16 {
|
||||
&byte r15sH = $cfff
|
||||
|
||||
asmsub save_virtual_registers() clobbers(A,Y) {
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda cx16.r0,y
|
||||
|
||||
@@ -352,6 +352,7 @@ asmsub str2word(str string @AY) -> word @AY {
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
; result in AY, number of characters processed also remains in cx16.r15 if you want to use it!! (0 = error)
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
_result = P8ZP_SCRATCH_W1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
@@ -539,6 +540,7 @@ asmsub internal_uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @
|
||||
; output in internal_uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
|
||||
; (these are terminated by a zero byte so they can be easily printed)
|
||||
; also returns Y = 100's, A = 10's, X = 1's
|
||||
; TODO: Romable
|
||||
|
||||
%asm {{
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
; - call killall() to kill all tasks.
|
||||
; - IMPORTANT: if you add the same subroutine multiple times, IT CANNOT DEPEND ON ANY LOCAL VARIABLES OR R0-R15 TO KEEP STATE. NOT EVEN REPEAT LOOP COUNTERS.
|
||||
; Those are all shared in the different tasks! You HAVE to use a mechanism around the userdata value (pointer?) to keep separate state elsewhere!
|
||||
; - IMPORTANT: ``defer`` cannot be used inside a coroutine that is reused for multiple tasks!!!
|
||||
|
||||
coroutines {
|
||||
%option ignore_unused
|
||||
|
||||
@@ -332,7 +332,7 @@ cx16 {
|
||||
&ubyte VERA_FX_X_POS_S = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_Y_POS_S = VERA_BASE + $000a
|
||||
&ubyte VERA_FX_POLY_FILL_L = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000cF
|
||||
&ubyte VERA_FX_POLY_FILL_H = VERA_BASE + $000c
|
||||
&uword VERA_FX_POLY_FILL = VERA_BASE + $000b
|
||||
&ubyte VERA_FX_CACHE_L = VERA_BASE + $0009
|
||||
&ubyte VERA_FX_CACHE_M = VERA_BASE + $000a
|
||||
@@ -981,6 +981,7 @@ asmsub vpoke_mask(ubyte bank @A, uword address @R0, ubyte mask @X, ubyte value @
|
||||
}
|
||||
|
||||
asmsub save_virtual_registers() clobbers(A,Y) {
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda cx16.r0,y
|
||||
@@ -1010,6 +1011,7 @@ asmsub restore_virtual_registers() clobbers(A,Y) {
|
||||
|
||||
asmsub save_vera_context() clobbers(A) {
|
||||
; -- use this at the start of your IRQ handler if it uses Vera registers, to save the state
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
; note cannot store this on cpu hardware stack because this gets called as a subroutine
|
||||
lda cx16.VERA_ADDR_L
|
||||
@@ -1356,12 +1358,12 @@ _continue iny
|
||||
|
||||
sub reset_system() {
|
||||
; -- Soft-reset the system back to initial power-on Basic prompt.
|
||||
sys.reset_system()
|
||||
goto sys.reset_system
|
||||
}
|
||||
|
||||
sub poweroff_system() {
|
||||
; -- use the SMC to shutdown the computer
|
||||
void cx16.i2c_write_byte($42, $01, $00)
|
||||
goto sys.poweroff_system
|
||||
}
|
||||
|
||||
sub set_led_state(bool on) {
|
||||
@@ -1448,6 +1450,7 @@ _modified
|
||||
}
|
||||
|
||||
asmsub restore_irq() clobbers(A) {
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
sei
|
||||
lda _orig_irqvec
|
||||
@@ -1527,16 +1530,21 @@ asmsub set_rasterline(uword line @AY) {
|
||||
; (note: this is an asmsub on purpose! don't change into a normal sub)
|
||||
%asm {{
|
||||
sei
|
||||
ldx #$42
|
||||
- ldx #$42
|
||||
ldy #2
|
||||
lda #0
|
||||
jmp cx16.i2c_write_byte
|
||||
jsr cx16.i2c_write_byte
|
||||
bra - ; to work around an issue where the routine does not in fact immediately reset the system
|
||||
; !notreached!
|
||||
}}
|
||||
}
|
||||
|
||||
sub poweroff_system() {
|
||||
; use the SMC to shutdown the computer
|
||||
void cx16.i2c_write_byte($42, $01, $00)
|
||||
; in a loop for the event where the shutdown command does not in fact immediately shutdown the system
|
||||
repeat {
|
||||
void cx16.i2c_write_byte($42, $01, $00)
|
||||
}
|
||||
}
|
||||
|
||||
asmsub wait(uword jiffies @AY) clobbers(X) {
|
||||
@@ -1770,6 +1778,7 @@ _no_msb_size
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
|
||||
@@ -64,6 +64,8 @@ multiply_words .proc
|
||||
; The routine also works for NEGATIVE (signed) word values, but ONLY the lower 16 bits of the result are correct then!
|
||||
; Prog8 only uses those so that's not an issue, but math.mul16_last_upper() no longer gives the correct result here.
|
||||
|
||||
; TODO: Romable
|
||||
|
||||
; mult62.a
|
||||
; from: https://github.com/TobyLobster/multiply_test/blob/main/tests/mult62.a
|
||||
; based on Dr Jefyll, http://forum.6502.org/viewtopic.php?f=9&t=689&start=0#p19958
|
||||
@@ -187,6 +189,7 @@ result .byte 0,0,0,0 ; routine could be faster if this were in Zeropage.
|
||||
|
||||
divmod_b_asm .proc
|
||||
; signed byte division: make everything positive and fix sign afterwards
|
||||
; TODO: Romable
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
eor P8ZP_SCRATCH_B1
|
||||
@@ -286,6 +289,7 @@ divmod_uw_asm .proc
|
||||
; input: P8ZP_SCRATCH_W1 in ZP: 16 bit number, A/Y: 16 bit divisor
|
||||
; output: P8ZP_SCRATCH_W2 in ZP: 16 bit remainder, A/Y: 16 bit division result
|
||||
; division by zero will result in quotient = 65535 and remainder = divident
|
||||
; TODO: Romable
|
||||
|
||||
|
||||
dividend = P8ZP_SCRATCH_W1
|
||||
@@ -329,6 +333,7 @@ randword .proc
|
||||
; -- 16 bit pseudo random number generator into AY
|
||||
; default seed = $00c2 $1137
|
||||
; routine from https://codebase64.org/doku.php?id=base:x_abc_random_number_generator_8_16_bit
|
||||
; TODO: Romable
|
||||
inc x1
|
||||
clc
|
||||
x1=*+1
|
||||
|
||||
@@ -360,6 +360,7 @@ _no_msb_size
|
||||
}
|
||||
|
||||
asmsub save_prog8_internals() {
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
lda P8ZP_SCRATCH_B1
|
||||
sta save_SCRATCH_ZPB1
|
||||
@@ -603,6 +604,7 @@ cx16 {
|
||||
&byte r15sH = $7fff
|
||||
|
||||
asmsub save_virtual_registers() clobbers(A,Y) {
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
ldy #31
|
||||
- lda cx16.r0,y
|
||||
|
||||
@@ -184,6 +184,7 @@ func_sort_uw .proc
|
||||
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
|
||||
; input: address of array to sort in P8ZP_SCRATCH_W1, length in A
|
||||
; first: subtract 2 of the pointer
|
||||
; TODO: Romable
|
||||
asl a
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
@@ -246,6 +247,7 @@ func_sort_w .proc
|
||||
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
|
||||
; input: address of array to sort in P8ZP_SCRATCH_W1, length in A
|
||||
; first: subtract 2 of the pointer
|
||||
; TODO: Romable
|
||||
asl a
|
||||
sta P8ZP_SCRATCH_B1
|
||||
lda P8ZP_SCRATCH_W1
|
||||
|
||||
@@ -146,6 +146,7 @@ memcopy16_up .proc
|
||||
memset .proc
|
||||
; -- fill memory from (P8ZP_SCRATCH_W1), length XY, with value in A.
|
||||
; clobbers X, Y
|
||||
; TODO: Romable
|
||||
stx P8ZP_SCRATCH_B1
|
||||
sty _save_reg
|
||||
ldy #0
|
||||
@@ -268,6 +269,7 @@ strcpy .proc
|
||||
|
||||
strcmp_expression .proc
|
||||
; -- compare strings, result in A
|
||||
; TODO: Romable
|
||||
lda _arg_s2
|
||||
ldy _arg_s2+1
|
||||
sta P8ZP_SCRATCH_W2
|
||||
@@ -302,6 +304,34 @@ _c1_zero lda (P8ZP_SCRATCH_W2),y
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
strncmp_mem .proc
|
||||
; -- compares strings in s1 (AY) and s2 (P8ZP_SCRATCH_W2).
|
||||
; Compares up to maximum length specified in X.
|
||||
; Returns -1,0,1 in A, depending on the ordering. Clobbers X & Y.
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldy #0
|
||||
_loop lda (P8ZP_SCRATCH_W1),y
|
||||
beq _c1_zero
|
||||
cmp (P8ZP_SCRATCH_W2),y
|
||||
beq _equal
|
||||
bmi _less
|
||||
lda #1
|
||||
rts
|
||||
_less lda #-1
|
||||
rts
|
||||
_equal dex
|
||||
bne +
|
||||
lda #0
|
||||
rts
|
||||
+ iny
|
||||
bne _loop
|
||||
_c1_zero lda (P8ZP_SCRATCH_W2),y
|
||||
beq +
|
||||
lda #-1
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
|
||||
strlen .proc
|
||||
; -- returns the number of bytes in the string in AY, in Y. Clobbers A.
|
||||
|
||||
@@ -140,6 +140,7 @@ compression {
|
||||
; -- Decodes "ByteRun1" (aka PackBits) RLE compressed data. Control byte value 128 ends the decoding.
|
||||
; Also stops decompressing when the maxsize has been reached. Returns the size of the decompressed data.
|
||||
; Instead of a source buffer, you provide a callback function that must return the next byte to compress in A.
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
sta _cb_mod1+1
|
||||
sty _cb_mod1+2
|
||||
|
||||
@@ -154,6 +154,7 @@ _found tya
|
||||
asmsub rfind(uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc {
|
||||
; Locates the first position of the given character in the string, starting from the right.
|
||||
; returns Carry set if found + index in A, or Carry clear if not found (and A will be 255, an invalid index).
|
||||
; TODO: Romable
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_B1
|
||||
sta _str
|
||||
@@ -242,6 +243,19 @@ _str .word 0
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub ncompare(uword string1 @R0, uword string2 @AY, ubyte length @X) clobbers(X, Y) -> byte @A {
|
||||
; Compares two strings for sorting.
|
||||
; Returns -1 (255), 0 or 1, meaning: string1 sorts before, equal or after string2.
|
||||
; Only compares the strings from index 0 up to the length argument.
|
||||
%asm {{
|
||||
sta P8ZP_SCRATCH_W2
|
||||
sty P8ZP_SCRATCH_W2+1
|
||||
lda cx16.r0
|
||||
ldy cx16.r0+1
|
||||
jmp prog8_lib.strncmp_mem
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub lower(uword st @AY) -> ubyte @Y {
|
||||
; Lowercases the petscii string in-place. Returns length of the string.
|
||||
; (for efficiency, non-letter characters > 128 will also not be left intact,
|
||||
|
||||
@@ -27,6 +27,7 @@ diskio {
|
||||
; Note that no list of pointers of some form is returned, the names are just squashed together.
|
||||
; If you really need a list of pointers to the names, that is pretty straightforward to construct by iterating over the names
|
||||
; and registering when the next one starts after the 0-byte separator.
|
||||
; TODO IMPLEMENT THIS
|
||||
txt.print("@TODO: list_filenames\n")
|
||||
sys.clear_carry()
|
||||
return 0
|
||||
@@ -37,6 +38,7 @@ diskio {
|
||||
sub lf_start_list(uword pattern_ptr) -> bool {
|
||||
; -- start an iterative file listing with optional pattern matching.
|
||||
; note: only a single iteration loop can be active at a time!
|
||||
; TODO IMPLEMENT THIS
|
||||
txt.print("@TODO: lf_start_list\n")
|
||||
return false
|
||||
}
|
||||
@@ -45,11 +47,13 @@ diskio {
|
||||
; -- retrieve the next entry from an iterative file listing session.
|
||||
; results will be found in list_blocks, list_filename, and list_filetype.
|
||||
; if it returns false though, there are no more entries (or an error occurred).
|
||||
; TODO IMPLEMENT THIS
|
||||
txt.print("@TODO: lf_next_entry\n")
|
||||
return false
|
||||
}
|
||||
|
||||
sub lf_end_list() {
|
||||
; TODO IMPLEMENT THIS
|
||||
txt.print("@TODO: lf_end_list\n")
|
||||
}
|
||||
|
||||
|
||||
@@ -336,6 +336,9 @@ fun parseMainModule(filepath: Path,
|
||||
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
|
||||
}
|
||||
|
||||
if(compilerOptions.romable && compilerOptions.floats)
|
||||
errors.err("When ROMable code is selected, floating point support is not available", program.toplevelModule.position)
|
||||
|
||||
errors.report()
|
||||
|
||||
return Triple(program, compilerOptions, importedFiles)
|
||||
@@ -352,6 +355,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
|
||||
val allOptions = program.modules.flatMap { it.options() }.toSet()
|
||||
val floatsEnabled = "enable_floats" in allOptions
|
||||
var noSysInit = "no_sysinit" in allOptions
|
||||
var rombale = "romable" in allOptions
|
||||
var zpType: ZeropageType =
|
||||
if (zpoption == null)
|
||||
if (floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
|
||||
@@ -408,7 +412,7 @@ internal fun determineCompilationOptions(program: Program, compTarget: ICompilat
|
||||
|
||||
return CompilationOptions(
|
||||
outputType, launcherType,
|
||||
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit,
|
||||
zpType, zpReserved, zpAllowed, floatsEnabled, noSysInit, rombale,
|
||||
compTarget, 0u, 0xffffu
|
||||
)
|
||||
}
|
||||
|
||||
@@ -79,9 +79,14 @@ class ModuleImporter(private val program: Program,
|
||||
if("$moduleName.p8" == import.position.file)
|
||||
throw SyntaxError("cannot import self", import.position)
|
||||
|
||||
val existing = program.modules.singleOrNull { it.name == moduleName }
|
||||
if (existing!=null)
|
||||
val existing = program.modules.singleOrNull { it.name.equals(moduleName, ignoreCase = true) }
|
||||
if (existing!=null) {
|
||||
if(existing.name != moduleName) {
|
||||
errors.err("module import name '$moduleName' differs in case only from already known name '${existing.name}'", import.position)
|
||||
return null
|
||||
}
|
||||
return existing
|
||||
}
|
||||
|
||||
// try internal library first
|
||||
val moduleResourceSrc = getModuleFromResource("$moduleName.p8", compilationTargetName)
|
||||
|
||||
@@ -1033,14 +1033,14 @@ internal class AstChecker(private val program: Program,
|
||||
err("this directive may only occur in a block or at module level")
|
||||
if(directive.args.isEmpty())
|
||||
err("missing option directive argument(s)")
|
||||
else if(directive.args.map{it.string in arrayOf("enable_floats", "force_output", "no_sysinit", "merge", "verafxmuls", "no_symbol_prefixing", "ignore_unused")}.any { !it })
|
||||
else if(directive.args.map{it.string in arrayOf("enable_floats", "force_output", "no_sysinit", "merge", "verafxmuls", "no_symbol_prefixing", "ignore_unused", "romable")}.any { !it })
|
||||
err("invalid option directive argument(s)")
|
||||
if(directive.parent is Block) {
|
||||
if(directive.args.any {it.string !in arrayOf("force_output", "merge", "verafxmuls", "no_symbol_prefixing", "ignore_unused")})
|
||||
err("using an option that is not valid for blocks")
|
||||
}
|
||||
if(directive.parent is Module) {
|
||||
if(directive.args.any {it.string !in arrayOf("enable_floats", "no_sysinit", "no_symbol_prefixing", "ignore_unused")})
|
||||
if(directive.args.any {it.string !in arrayOf("enable_floats", "no_sysinit", "no_symbol_prefixing", "ignore_unused", "romable")})
|
||||
err("using an option that is not valid for modules")
|
||||
}
|
||||
if(directive.args.any { it.string=="verafxmuls" } && compilerOptions.compTarget.name != Cx16Target.NAME)
|
||||
@@ -1276,17 +1276,6 @@ internal class AstChecker(private val program: Program,
|
||||
// str+str and str*number have already been const evaluated before we get here.
|
||||
errors.err("no computational or logical expressions with strings or arrays are possible", expr.position)
|
||||
}
|
||||
} else {
|
||||
if(expr.left is TypecastExpression && expr.right is NumericLiteral && !(expr.right.inferType(program) issimpletype BaseDataType.FLOAT)) {
|
||||
val origLeftDt = (expr.left as TypecastExpression).expression.inferType(program).getOrUndef()
|
||||
if(rightDt.largerSizeThan(origLeftDt) && !(expr.right as NumericLiteral).cast(origLeftDt.base, true).isValid)
|
||||
errors.err("operands are not the same type", expr.right.position)
|
||||
}
|
||||
if(expr.right is TypecastExpression && expr.left is NumericLiteral && !(expr.left.inferType(program) issimpletype BaseDataType.FLOAT)) {
|
||||
val origRightDt = (expr.right as TypecastExpression).expression.inferType(program).getOrUndef()
|
||||
if(leftDt.largerSizeThan(origRightDt) && !(expr.left as NumericLiteral).cast(origRightDt.base, true).isValid)
|
||||
errors.err("operands are not the same type", expr.right.position)
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt.isBool || rightDt.isBool ||
|
||||
@@ -1386,8 +1375,10 @@ internal class AstChecker(private val program: Program,
|
||||
if(targetStatement!=null)
|
||||
checkFunctionCall(targetStatement, functionCallExpr.args, functionCallExpr.position)
|
||||
|
||||
val builtinFunctionName = functionCallExpr.target.nameInSource.singleOrNull()
|
||||
|
||||
// warn about sgn(unsigned) this is likely a mistake
|
||||
if(functionCallExpr.target.nameInSource.last()=="sgn") {
|
||||
if(builtinFunctionName=="sgn") {
|
||||
val sgnArgType = functionCallExpr.args.first().inferType(program)
|
||||
if(sgnArgType issimpletype BaseDataType.UBYTE || sgnArgType issimpletype BaseDataType.UWORD)
|
||||
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCallExpr.args.first().position)
|
||||
@@ -1411,7 +1402,7 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCallExpr.target.nameInSource.singleOrNull() in listOf("peek", "peekw")) {
|
||||
if(builtinFunctionName in listOf("peek", "peekw")) {
|
||||
val pointervar = functionCallExpr.args[0] as? IdentifierReference
|
||||
if(pointervar!=null)
|
||||
checkPointer(pointervar)
|
||||
@@ -1420,6 +1411,14 @@ internal class AstChecker(private val program: Program,
|
||||
checkPointer(binexpr.left as IdentifierReference)
|
||||
}
|
||||
|
||||
if(builtinFunctionName=="memory") {
|
||||
val str = functionCallExpr.args[0] as? StringLiteral
|
||||
if(str==null)
|
||||
errors.err("memory name argument must be a string literal", functionCallExpr.args[0].position)
|
||||
else if(str.value.isEmpty())
|
||||
errors.err("memory name argument cannot be empty string", functionCallExpr.args[0].position)
|
||||
}
|
||||
|
||||
super.visit(functionCallExpr)
|
||||
}
|
||||
|
||||
@@ -1523,8 +1522,11 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(target is Label && args.isNotEmpty())
|
||||
errors.err("cannot use arguments when calling a label", position)
|
||||
if (target is Label) {
|
||||
errors.warn("\uD83D\uDCA3 footgun: calling label as subroutine (JSR) is tricky", position)
|
||||
if (args.isNotEmpty())
|
||||
errors.err("cannot use arguments when calling a label", position)
|
||||
}
|
||||
|
||||
if(target is Subroutine) {
|
||||
if(target.isAsmSubroutine) {
|
||||
@@ -1664,7 +1666,7 @@ internal class AstChecker(private val program: Program,
|
||||
val constvalues = whenChoice.values!!.map { it.constValue(program) to it.position }
|
||||
for((constvalue, pos) in constvalues) {
|
||||
when {
|
||||
constvalue == null -> errors.err("choice value must be a constant", pos)
|
||||
constvalue == null -> errors.err("choice values must be constant numbers", pos)
|
||||
!constvalue.type.isIntegerOrBool -> errors.err("choice value must be a byte or word", pos)
|
||||
!(conditionType issimpletype constvalue.type) -> {
|
||||
if(conditionType.isKnown) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.code.internedStringsModuleName
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
|
||||
internal class VerifyFunctionArgTypes(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : IAstVisitor {
|
||||
|
||||
@@ -22,7 +22,7 @@ internal class VerifyFunctionArgTypes(val program: Program, val options: Compila
|
||||
}
|
||||
|
||||
// remove unused strings from interned strings block
|
||||
val internedBlock = program.allBlocks.singleOrNull { it.name == internedStringsModuleName }
|
||||
val internedBlock = program.allBlocks.singleOrNull { it.name == INTERNED_STRINGS_MODULENAME }
|
||||
internedBlock?.statements?.withIndex()?.reversed()?.forEach { (index, st) ->
|
||||
if(st is VarDecl && st.scopedName !in allStringRefs) {
|
||||
internedBlock.statements.removeAt(index)
|
||||
|
||||
@@ -12,7 +12,7 @@ import io.kotest.matchers.string.shouldContain
|
||||
import prog8.ast.Program
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.code.internedStringsModuleName
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.compiler.ModuleImporter
|
||||
import prog8.parser.ParseError
|
||||
import prog8tests.helpers.*
|
||||
@@ -176,7 +176,7 @@ class TestModuleImporter: FunSpec({
|
||||
}
|
||||
}
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||
program.modules[0].name shouldBe internedStringsModuleName
|
||||
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ class TestModuleImporter: FunSpec({
|
||||
}
|
||||
}
|
||||
withClue("imported module with error in it should not be present") { program.modules.size shouldBe 1 }
|
||||
program.modules[0].name shouldBe internedStringsModuleName
|
||||
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
|
||||
importer.errors.report()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ class TestGoldenRam: FunSpec({
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = VMTarget(),
|
||||
loadAddress = 999u,
|
||||
memtopAddress = 0xffffu
|
||||
|
||||
@@ -7,7 +7,7 @@ import io.kotest.matchers.shouldNotBe
|
||||
import io.kotest.matchers.string.shouldContain
|
||||
import io.kotest.matchers.string.shouldStartWith
|
||||
import prog8.code.core.ZeropageType
|
||||
import prog8.code.internedStringsModuleName
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.code.target.VMTarget
|
||||
@@ -40,7 +40,7 @@ main {
|
||||
}
|
||||
withClue("module order in parse tree") {
|
||||
moduleNames.drop(1) shouldBe listOf(
|
||||
internedStringsModuleName,
|
||||
INTERNED_STRINGS_MODULENAME,
|
||||
"textio",
|
||||
"syslib",
|
||||
"conv",
|
||||
@@ -101,7 +101,7 @@ main {
|
||||
withClue("module order in parse tree") {
|
||||
program.modules.map { it.name } shouldBe
|
||||
listOf(
|
||||
internedStringsModuleName,
|
||||
INTERNED_STRINGS_MODULENAME,
|
||||
filenameBase,
|
||||
"textio", "syslib", "conv", "shared_cbm_textio_functions", "floats", "shared_floats_functions", "prog8_math", "prog8_lib"
|
||||
)
|
||||
|
||||
@@ -1159,4 +1159,20 @@ main {
|
||||
compileText(VMTarget(), true, src, writeAssembly = true) shouldNotBe null
|
||||
compileText(C64Target(), true, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("boolean comparisons without optimization can be assembled") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
bool @shared pre_start, xxx
|
||||
|
||||
if (pre_start != false and xxx) {
|
||||
return
|
||||
} else if (pre_start != false and xxx) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -445,4 +445,42 @@ WARN name 'var1Warn' shadows occurrence at...
|
||||
errors.errors[3] shouldContain "internalOk"
|
||||
errors.errors[3] shouldContain "line 11"
|
||||
}
|
||||
|
||||
test("ast node linkage and lookups ok even with no symbol prefixing") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
thing.routine()
|
||||
}
|
||||
}
|
||||
|
||||
thing {
|
||||
%option no_symbol_prefixing
|
||||
|
||||
sub routine() {
|
||||
other.something()
|
||||
other.counter++
|
||||
other.asmsomething()
|
||||
}
|
||||
}
|
||||
|
||||
other {
|
||||
sub something() {
|
||||
}
|
||||
|
||||
asmsub asmsomething() {
|
||||
%asm {{
|
||||
nop
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
uword @shared counter
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
compileText(C64Target(), false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
})
|
||||
@@ -954,4 +954,17 @@ main {
|
||||
(v4.right as NumericLiteral).type shouldBe BaseDataType.UWORD
|
||||
(v4.right as NumericLiteral).number shouldBe 5
|
||||
}
|
||||
|
||||
test("allow comparisons against constant values with different type") {
|
||||
val src = """
|
||||
main {
|
||||
sub start() {
|
||||
const uword MAX_CAVE_WIDTH = 440 ; word here to avoid having to cast to word all the time
|
||||
|
||||
if cx16.r0L > MAX_CAVE_WIDTH
|
||||
return
|
||||
}
|
||||
}"""
|
||||
compileText(C64Target(), false, src, writeAssembly = false) shouldNotBe null
|
||||
}
|
||||
})
|
||||
@@ -47,6 +47,7 @@ class TestAbstractZeropage: FunSpec({
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = C64Target(),
|
||||
loadAddress = 999u,
|
||||
memtopAddress = 0xffffu
|
||||
@@ -67,6 +68,7 @@ class TestC64Zeropage: FunSpec({
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = false,
|
||||
romable = false,
|
||||
compTarget = c64target, loadAddress = 999u, memtopAddress = 0xffffu
|
||||
))
|
||||
|
||||
@@ -82,33 +84,33 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testZpFloatEnable") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
var result = zp.allocate("", DataType.forDt(BaseDataType.FLOAT), null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled floats" }
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
result = zp2.allocate("", DataType.forDt(BaseDataType.FLOAT), null, null, errors)
|
||||
result.expectError { "should be allocation error due to disabled ZP use" }
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
zp3.allocate("", DataType.forDt(BaseDataType.FLOAT), null, null, errors)
|
||||
}
|
||||
|
||||
test("testZpModesWithFloats") {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
}
|
||||
shouldThrow<InternalCompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
}
|
||||
}
|
||||
|
||||
test("testZpDontuse") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.DONTUSE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
println(zp.free)
|
||||
zp.availableBytes() shouldBe 0
|
||||
val result = zp.allocate("", DataType.forDt(BaseDataType.BYTE), null, null, errors)
|
||||
@@ -116,13 +118,13 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 17
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 87
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
zp3.availableBytes() shouldBe 96
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
zp4.availableBytes() shouldBe 207
|
||||
zp4.allocate("test", DataType.forDt(BaseDataType.UBYTE), null, null, errors)
|
||||
zp4.availableBytes() shouldBe 206
|
||||
@@ -131,7 +133,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 207
|
||||
4u shouldNotBeIn zp1.free
|
||||
35u shouldNotBeIn zp1.free
|
||||
@@ -142,7 +144,7 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldBeIn zp1.free
|
||||
255u shouldBeIn zp1.free
|
||||
199u shouldBeIn zp1.free
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 107
|
||||
4u shouldNotBeIn zp2.free
|
||||
35u shouldNotBeIn zp2.free
|
||||
@@ -153,14 +155,14 @@ class TestC64Zeropage: FunSpec({
|
||||
200u shouldNotBeIn zp2.free
|
||||
255u shouldNotBeIn zp2.free
|
||||
199u shouldBeIn zp2.free
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FLOATSAFE, listOf(50u .. 100u, 200u..255u), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 107
|
||||
4u shouldBeIn zp3.free
|
||||
35u shouldNotBeIn zp3.free
|
||||
}
|
||||
|
||||
test("testBasicsafeAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
zp.availableBytes() shouldBe 17
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
@@ -182,7 +184,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testFullAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
zp.availableBytes() shouldBe 207
|
||||
zp.hasByteAvailable() shouldBe true
|
||||
zp.hasWordAvailable() shouldBe true
|
||||
@@ -213,7 +215,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testEfficientAllocation") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, c64target, 999u, 0xffffu))
|
||||
zp.availableBytes() shouldBe 17
|
||||
zp.allocate("", DataType.forDt(BaseDataType.WORD), null, null, errors).getOrElse{throw it}.address shouldBe 0x04u
|
||||
zp.allocate("", DataType.forDt(BaseDataType.UBYTE), null, null, errors).getOrElse{throw it}.address shouldBe 0x06u
|
||||
@@ -231,7 +233,7 @@ class TestC64Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedLocations") {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, c64target, 999u, 0xffffu))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, c64target, 999u, 0xffffu))
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
@@ -244,18 +246,18 @@ class TestCx16Zeropage: FunSpec({
|
||||
val cx16target = Cx16Target()
|
||||
|
||||
test("testReservedLocations") {
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
withClue("zp _B1 and _REG must be next to each other to create a word") {
|
||||
zp.SCRATCH_B1 + 1u shouldBe zp.SCRATCH_REG
|
||||
}
|
||||
}
|
||||
|
||||
test("testFreeSpacesBytes") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, cx16target, 999u, 0xffffu))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, true, false, false, cx16target, 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 88
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp2 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
zp2.availableBytes() shouldBe 175
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
zp3.availableBytes() shouldBe 216
|
||||
zp3.allocate("test", DataType.forDt(BaseDataType.UBYTE), null, null, errors)
|
||||
zp3.availableBytes() shouldBe 215
|
||||
@@ -264,7 +266,7 @@ class TestCx16Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("testReservedSpace") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
zp1.availableBytes() shouldBe 216
|
||||
0x22u shouldBeIn zp1.free
|
||||
0x80u shouldBeIn zp1.free
|
||||
@@ -274,7 +276,7 @@ class TestCx16Zeropage: FunSpec({
|
||||
}
|
||||
|
||||
test("preallocated zp vars") {
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, cx16target, 999u, 0xffffu))
|
||||
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, false, false, cx16target, 999u, 0xffffu))
|
||||
zp1.allocatedVariables["test"] shouldBe null
|
||||
zp1.allocatedVariables["cx16.r0"] shouldNotBe null
|
||||
zp1.allocatedVariables["cx16.r15"] shouldNotBe null
|
||||
|
||||
@@ -7,7 +7,7 @@ import prog8.ast.AstToSourceTextConverter
|
||||
import prog8.ast.Module
|
||||
import prog8.ast.Program
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.code.internedStringsModuleName
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.parser.ParseError
|
||||
import prog8.parser.Prog8Parser.parseModule
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
@@ -42,7 +42,7 @@ class TestAstToSourceText: AnnotationSpec() {
|
||||
fun testMentionsInternedStringsModule() {
|
||||
val orig = SourceCode.Text("\n")
|
||||
val (txt, _) = roundTrip(parseModule(orig))
|
||||
txt shouldContain Regex(";.*$internedStringsModuleName")
|
||||
txt shouldContain Regex(";.*$INTERNED_STRINGS_MODULENAME")
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -15,7 +15,7 @@ import prog8.ast.statements.Block
|
||||
import prog8.code.ast.PtBlock
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.source.SourceCode
|
||||
import prog8.code.internedStringsModuleName
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.target.C64Target
|
||||
import prog8tests.helpers.DummyFunctions
|
||||
import prog8tests.helpers.DummyMemsizer
|
||||
@@ -28,7 +28,7 @@ class TestProgram: FunSpec({
|
||||
test("withNameBuiltinsAndMemsizer") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
program.modules.size shouldBe 1
|
||||
program.modules[0].name shouldBe internedStringsModuleName
|
||||
program.modules[0].name shouldBe INTERNED_STRINGS_MODULENAME
|
||||
program.modules[0].program shouldBeSameInstanceAs program
|
||||
program.modules[0].parent shouldBeSameInstanceAs program.namespace
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class TestProgram: FunSpec({
|
||||
test("withInternedStringsModule") {
|
||||
val program = Program("foo", DummyFunctions, DummyMemsizer, DummyStringEncoder)
|
||||
val m = program.modules[0]
|
||||
m.name shouldBe internedStringsModuleName
|
||||
m.name shouldBe INTERNED_STRINGS_MODULENAME
|
||||
|
||||
val retVal = program.moveModuleToFront(m)
|
||||
retVal shouldBeSameInstanceAs program
|
||||
|
||||
@@ -400,6 +400,45 @@ main {
|
||||
cx16.r2 = &addresses
|
||||
}
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(C64Target(), optimize=false, src, writeAssembly=true, errors=errors) shouldNotBe null
|
||||
errors.errors.size shouldBe 0
|
||||
errors.warnings.size shouldBe 0
|
||||
}
|
||||
|
||||
test("assigning address of split arrays without explicitly taking address works") {
|
||||
val src="""
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
uword[] texts1 = [ 1,2,3 ]
|
||||
uword[] @nosplit texts2 = [ 1,2,3 ]
|
||||
|
||||
cx16.r4 = texts1
|
||||
cx16.r5 = texts2
|
||||
|
||||
; txt.print_uwhex(&texts1, true)
|
||||
; txt.spc()
|
||||
; txt.print_uwhex(cx16.r4, true)
|
||||
; txt.nl()
|
||||
; txt.print_uwhex(&texts2, true)
|
||||
; txt.spc()
|
||||
; txt.print_uwhex(cx16.r5, true)
|
||||
; txt.nl()
|
||||
|
||||
cx16.r4 = &texts1
|
||||
cx16.r5 = &texts2
|
||||
|
||||
; txt.print_uwhex(&texts1, true)
|
||||
; txt.spc()
|
||||
; txt.print_uwhex(cx16.r4, true)
|
||||
; txt.nl()
|
||||
; txt.print_uwhex(&texts2, true)
|
||||
; txt.spc()
|
||||
; txt.print_uwhex(cx16.r5, true)
|
||||
; txt.nl()
|
||||
}
|
||||
}"""
|
||||
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
|
||||
compileText(C64Target(), optimize=false, src, writeAssembly=true, errors=errors) shouldNotBe null
|
||||
|
||||
@@ -78,7 +78,7 @@ class TestAsmGenSymbols: StringSpec({
|
||||
|
||||
fun createTestAsmGen6502(program: Program): AsmGen6502Internal {
|
||||
val errors = ErrorReporterForTests()
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, C64Target(), 999u, 0xffffu)
|
||||
val options = CompilationOptions(OutputType.RAW, CbmPrgLauncherType.NONE, ZeropageType.FULL, emptyList(), CompilationOptions.AllZeropageAllowed, false, true, false, C64Target(), 999u, 0xffffu)
|
||||
val astchecker = AstChecker(program, errors, options)
|
||||
astchecker.visit(program)
|
||||
errors.report()
|
||||
|
||||
@@ -22,6 +22,13 @@ main {
|
||||
txt.print("name2 fail!\n")
|
||||
}
|
||||
|
||||
if (strings.ncompare(name, "aaa", 3)==0) or (strings.ncompare(name, "johm", 3)==0) or (strings.ncompare(name, "bbb", 3)==0) {
|
||||
txt.print("name1 ok\n")
|
||||
}
|
||||
if (strings.ncompare(name, "aaa", 2)==0) or (strings.ncompare(name, "zzz", 2)==0) or (strings.ncompare(name, "bbb", 2)==0) {
|
||||
txt.print("name2 fail!\n")
|
||||
}
|
||||
|
||||
if name=="aaa" or name=="john" or name=="bbb"
|
||||
txt.print("name1b ok\n")
|
||||
if name=="aaa" or name=="zzz" or name=="bbb"
|
||||
|
||||
@@ -4,9 +4,9 @@ import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.ast.PtLabel
|
||||
import prog8.code.GENERATED_LABEL_PREFIX
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.core.*
|
||||
import prog8.code.internedStringsModuleName
|
||||
import prog8.code.source.SourceCode
|
||||
|
||||
/*********** Everything starts from here, the Program; zero or more modules *************/
|
||||
@@ -23,8 +23,8 @@ class Program(val name: String,
|
||||
|
||||
init {
|
||||
// insert a container module for all interned strings later
|
||||
val internedStringsModule = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(internedStringsModuleName))
|
||||
val block = Block(internedStringsModuleName, null, mutableListOf(), true, Position.DUMMY)
|
||||
val internedStringsModule = Module(mutableListOf(), Position.DUMMY, SourceCode.Generated(INTERNED_STRINGS_MODULENAME))
|
||||
val block = Block(INTERNED_STRINGS_MODULENAME, null, mutableListOf(), true, Position.DUMMY)
|
||||
val directive = Directive("%option", listOf(DirectiveArg("no_symbol_prefixing", null, Position.DUMMY)), Position.DUMMY)
|
||||
block.statements.add(directive)
|
||||
directive.linkParents(block)
|
||||
@@ -67,7 +67,7 @@ class Program(val name: String,
|
||||
}
|
||||
|
||||
val toplevelModule: Module
|
||||
get() = modules.first { it.name!= internedStringsModuleName }
|
||||
get() = modules.first { it.name!= INTERNED_STRINGS_MODULENAME }
|
||||
|
||||
private val internedStringsReferenceCounts = mutableMapOf<VarDecl, Int>()
|
||||
|
||||
@@ -81,8 +81,8 @@ class Program(val name: String,
|
||||
}
|
||||
|
||||
val internedStringsBlock = modules
|
||||
.first { it.name == internedStringsModuleName }.statements
|
||||
.first { it is Block && it.name == internedStringsModuleName } as Block
|
||||
.first { it.name == INTERNED_STRINGS_MODULENAME }.statements
|
||||
.first { it is Block && it.name == INTERNED_STRINGS_MODULENAME } as Block
|
||||
|
||||
fun addNewInternedStringvar(string: StringLiteral): Pair<List<String>, VarDecl> {
|
||||
val varName = "string_${internedStringsBlock.statements.size}"
|
||||
@@ -93,7 +93,7 @@ class Program(val name: String,
|
||||
)
|
||||
internedStringsBlock.statements.add(decl)
|
||||
decl.linkParents(internedStringsBlock)
|
||||
return Pair(listOf(internedStringsModuleName, decl.name), decl)
|
||||
return Pair(listOf(INTERNED_STRINGS_MODULENAME, decl.name), decl)
|
||||
}
|
||||
|
||||
val existingDecl = internedStringsBlock.statements.filterIsInstance<VarDecl>().singleOrNull {
|
||||
@@ -133,8 +133,8 @@ class Program(val name: String,
|
||||
fun removeStrings(modules: List<Module>) {
|
||||
if(removals.isNotEmpty()) {
|
||||
val internedStringsBlock = modules
|
||||
.first { it.name == internedStringsModuleName }.statements
|
||||
.first { it is Block && it.name == internedStringsModuleName } as Block
|
||||
.first { it.name == INTERNED_STRINGS_MODULENAME }.statements
|
||||
.first { it is Block && it.name == INTERNED_STRINGS_MODULENAME } as Block
|
||||
removals.forEach { scopedname ->
|
||||
val decl = internedStringsBlock.statements.filterIsInstance<VarDecl>().single { decl -> decl.scopedName == scopedname }
|
||||
val numRefs = program.internedStringsReferenceCounts.getValue(decl) - 1
|
||||
@@ -151,7 +151,7 @@ class Program(val name: String,
|
||||
|
||||
fun makeLabel(postfix: String): String {
|
||||
generatedLabelSequenceNumber++
|
||||
return "${PtLabel.GENERATED_LABEL_PREFIX}${generatedLabelSequenceNumber}_$postfix"
|
||||
return "$GENERATED_LABEL_PREFIX${generatedLabelSequenceNumber}_$postfix"
|
||||
}
|
||||
|
||||
fun makeLabel(postfix: String, position: Position): Label {
|
||||
|
||||
@@ -9,7 +9,7 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.code.internedStringsModuleName
|
||||
import prog8.code.INTERNED_STRINGS_MODULENAME
|
||||
import prog8.code.target.encodings.JapaneseCharacterConverter
|
||||
import java.io.CharConversionException
|
||||
import java.util.*
|
||||
@@ -112,6 +112,7 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
|
||||
BaseDataType.UBYTE -> NumericLiteral(BaseDataType.UBYTE, (constval.number.toInt().inv() and 255).toDouble(), constval.position)
|
||||
BaseDataType.WORD -> NumericLiteral(BaseDataType.WORD, constval.number.toInt().inv().toDouble(), constval.position)
|
||||
BaseDataType.UWORD -> NumericLiteral(BaseDataType.UWORD, (constval.number.toInt().inv() and 65535).toDouble(), constval.position)
|
||||
BaseDataType.LONG -> NumericLiteral(BaseDataType.LONG, constval.number.toInt().inv().toDouble(), constval.position)
|
||||
else -> throw ExpressionError("can only take bitwise inversion of int", constval.position)
|
||||
}
|
||||
"not" -> NumericLiteral.fromBoolean(constval.number==0.0, constval.position)
|
||||
@@ -1250,7 +1251,7 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
|
||||
return false
|
||||
|
||||
val scope=decl.definingModule
|
||||
return scope.name==internedStringsModuleName
|
||||
return scope.name==INTERNED_STRINGS_MODULENAME
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1359,6 +1360,14 @@ class ContainmentCheck(var element: Expression,
|
||||
return NumericLiteral.fromBoolean(exists, position)
|
||||
}
|
||||
}
|
||||
is RangeExpression -> {
|
||||
if(elementConst.type.isInteger) {
|
||||
val intprogression = (iterable as RangeExpression).toConstantIntegerRange()
|
||||
if (intprogression!=null) {
|
||||
return NumericLiteral.fromBoolean(intprogression.contains(elementConst.number.toInt()), position)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v11.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v11.2 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-c128.p8
|
||||
@@ -246,6 +246,7 @@ strings {
|
||||
lstripped (str s) -> str
|
||||
ltrim (str s)
|
||||
ltrimmed (str s) -> str
|
||||
ncompare (uword string1 @R0, uword string2 @AY, ubyte length @X) -> clobbers (X,Y) -> byte @A
|
||||
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
|
||||
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
|
||||
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v11.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v11.2 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-c64.p8
|
||||
@@ -250,7 +250,7 @@ graphics {
|
||||
horizontal_line (uword xx, ubyte yy, uword length)
|
||||
internal_plot (ubyte ploty @Y) -> clobbers (A,X,Y)
|
||||
line (uword x1, ubyte y1, uword x2, ubyte y2)
|
||||
plot (uword plotx @XY, ubyte ploty @A) -> clobbers (A,X,Y)
|
||||
plot (uword plotx @AX, ubyte ploty @Y) -> clobbers (A,X,Y)
|
||||
rect (uword xx, ubyte yy, uword width, ubyte height)
|
||||
vertical_line (uword xx, ubyte yy, ubyte height)
|
||||
}
|
||||
@@ -370,6 +370,7 @@ strings {
|
||||
lstripped (str s) -> str
|
||||
ltrim (str s)
|
||||
ltrimmed (str s) -> str
|
||||
ncompare (uword string1 @R0, uword string2 @AY, ubyte length @X) -> clobbers (X,Y) -> byte @A
|
||||
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
|
||||
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
|
||||
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v11.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v11.2 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-cx16.p8
|
||||
@@ -673,6 +673,7 @@ strings {
|
||||
lstripped (str s) -> str
|
||||
ltrim (str s)
|
||||
ltrimmed (str s) -> str
|
||||
ncompare (uword string1 @R0, uword string2 @AY, ubyte length @X) -> clobbers (X,Y) -> byte @A
|
||||
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
|
||||
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
|
||||
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v11.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v11.2 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-pet32.p8
|
||||
@@ -199,6 +199,7 @@ strings {
|
||||
lstripped (str s) -> str
|
||||
ltrim (str s)
|
||||
ltrimmed (str s) -> str
|
||||
ncompare (uword string1 @R0, uword string2 @AY, ubyte length @X) -> clobbers (X,Y) -> byte @A
|
||||
pattern_match (str string @AY, str pattern @R0) -> clobbers (Y) -> bool @A
|
||||
rfind (uword string @AY, ubyte character @X) -> ubyte @A, bool @Pc
|
||||
right (uword source @AY, ubyte length @X, uword target @R1) -> clobbers (A,Y)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
Prog8 compiler v11.1 by Irmen de Jong (irmen@razorvine.net)
|
||||
Prog8 compiler v11.2 by Irmen de Jong (irmen@razorvine.net)
|
||||
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
|
||||
|
||||
Compiling program import-all-virtual.p8
|
||||
|
||||
@@ -28,6 +28,7 @@ You can compile programs for various machines that are built in into the compile
|
||||
* Commodore PET (limited support)
|
||||
* any other 65(C)02 target machine or setup can be configured to a great extent in a user written configuration file.
|
||||
There are some examples included for the Atari 800 XL, NEO6502 and such.
|
||||
* some users have been experimenting with a NES and a C64OS target as well.
|
||||
|
||||
Some language features are mentioned below, and you can also read :ref:`comparingprog8` if you
|
||||
want to quickly read about how Prog8 compares to well-known other languages.
|
||||
@@ -95,6 +96,8 @@ Features
|
||||
and inline assembly to have full control when every register, cycle or byte matters
|
||||
- Variables are all allocated statically, no memory allocation overhead
|
||||
- Variable data types include signed and unsigned bytes and words, arrays, strings.
|
||||
- Tight control over Zeropage usage
|
||||
- Programs can be restarted after exiting (i.e. run them multiple times without having to reload everything), due to automatic variable (re)initializations.
|
||||
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
|
||||
- ``when`` statement to avoid if-else chains
|
||||
- ``in`` expression for concise and efficient multi-value/containment test
|
||||
@@ -109,7 +112,6 @@ Features
|
||||
- Identifiers can contain Unicode Letters, so ``knäckebröd``, ``приблизительно``, ``見せしめ`` and ``π`` are all valid identifiers.
|
||||
- Subroutines can return more than one result value
|
||||
- Advanced code optimizations to make the resulting program smaller and faster
|
||||
- Programs can be restarted after exiting (i.e. run them multiple times without having to reload everything), due to automatic variable (re)initializations.
|
||||
- Supports the sixteen 'virtual' 16-bit registers R0 to R15 as defined on the Commander X16. You can look at them as general purpose global variables. These are also available on the other compilation targets!
|
||||
- On the Commander X16: Support for low level system features such as Vera Fx, which includes 16x16 bits multiplication in hardware and fast memory copy and fill.
|
||||
- 50 Kb of available program RAM size on the C64 by default; because Basic ROM is banked out altogether
|
||||
|
||||
@@ -187,7 +187,8 @@ sizeof (name) ; sizeof (number)
|
||||
|
||||
memory (name, size, alignment)
|
||||
Returns the address of the first location of a statically "reserved" block of memory of the given size in bytes,
|
||||
with the given name. The block is *uninitialized memory*; unlike other variables in Prog8 it is *not* set to zero at the start of the program!
|
||||
with the given name. The name must be a string literal, it cannot be empty or be a variable.
|
||||
The block is *uninitialized memory*; unlike other variables in Prog8 it is *not* set to zero at the start of the program!
|
||||
(if that is required, you can do so yourself using ``memset``).
|
||||
If you specify an alignment value >1, it means the block of memory will
|
||||
be aligned to such a dividable address in memory, for instance an alignment of $100 means the
|
||||
@@ -899,6 +900,12 @@ Provides string manipulation routines.
|
||||
using ``==``, ``<`` etcetera (it will use strings.compare for you under water automatically).
|
||||
This even works when dealing with uword (pointer) variables when comparing them to a string type.
|
||||
|
||||
``ncompare (string1, string2, length) -> ubyte result``
|
||||
Compares two strings up to the number of characters in the length parameter.
|
||||
Returns -1, 0 or 1 depending on whether string1 sorts before, equal or after string2.
|
||||
Note that lengths of 0 or 1 evaluate the same. The first character is always compared.
|
||||
A length larger than either string will function identically to compare.
|
||||
|
||||
``copy (from, to) -> ubyte length``
|
||||
Copy a string to another, overwriting that one. Returns the length of the string that was copied.
|
||||
Often you don't have to call this explicitly and can just write ``string1 = string2``
|
||||
|
||||
@@ -63,10 +63,11 @@ Subroutine
|
||||
|
||||
Label
|
||||
This is a named position in your code where you can jump to from another place.
|
||||
You can jump to it with a jump statement elsewhere. It is also possible to use a
|
||||
subroutine call to a label (but without parameters and return value).
|
||||
A label is an identifier followed by a colon ``:``. It's ok to put the next statement on
|
||||
the same line, immediately after the label.
|
||||
You can jump to it with a goto statement. It is also possible to use a
|
||||
subroutine call to a label (but without parameters and return value), however 🦶🔫 footgun warning:
|
||||
doing that is tricky because it makes for weird control flow and interferes with defers.
|
||||
|
||||
Scope
|
||||
Also known as 'namespace', this is a named box around the symbols defined in it.
|
||||
@@ -429,6 +430,7 @@ Directives
|
||||
- ``ignore_unused`` (block or module) suppress warnings about unused variables and subroutines. Instead, these will be silently stripped.
|
||||
This option is useful in library modules that contain many more routines beside the ones that you actually use.
|
||||
- ``verafxmuls`` (block, cx16 target only) uses Vera FX hardware word multiplication on the CommanderX16 for all word multiplications in this block. Warning: this may interfere with IRQs and other Vera operations, so use this only when you know what you're doing. It's safer to explicitly use ``verafx.muls()``.
|
||||
- ``romable`` (module) *WORK-IN-PROGRESS/EXPERIMENTAL* make sure that the generated code is suitable for running in ROM (so no self-modifying code and such)
|
||||
|
||||
|
||||
.. data:: %output <type>
|
||||
@@ -1170,9 +1172,9 @@ So for instance::
|
||||
|
||||
asmsub multisub() -> uword @AY, bool @Pc, ubyte @X { ... }
|
||||
|
||||
.. sidebar:: usage of cx16.r0-cx16.r15
|
||||
.. sidebar:: register usage
|
||||
|
||||
Subroutines with multiple return values use the "virtual registers" to return those.
|
||||
Subroutines with multiple return values use cpu registers A, Y, and the R0-R15 "virtual registers" to return those.
|
||||
Using those virtual registers during the calculation of the values in the return statement should be avoided.
|
||||
Otherwise you risk overwriting an earlier return value in the sequence.
|
||||
|
||||
|
||||
@@ -155,8 +155,9 @@ Regular subroutines
|
||||
|
||||
- for an ``asmsub`` or ``extsub`` the subroutine's signature specifies the output registers that contain the values explicitly,
|
||||
just as for a single return value.
|
||||
- for regular subroutines, the compiler will use the "virtual registers" cx16.r0-cx16.r15, from r15 down to r0, for the
|
||||
result values left to right. This may change in a future compiler version.
|
||||
- for regular subroutines, the compiler will return the first of the return values via the cpu register ``A``` (or ``A + Y``` if it's a word value),
|
||||
just like for subroutines that only return a single value.
|
||||
The remainder of the return values are returned via the "virtual registers" cx16.r16-cx16.r0 (using R15 first and counting down to R0).
|
||||
|
||||
|
||||
**Builtin functions can be different:**
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
TODO
|
||||
====
|
||||
|
||||
- Look at github PR for improved romability (see github issue 149) Also search for "TODO: Romable"
|
||||
|
||||
...
|
||||
|
||||
|
||||
Future Things and Ideas
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- Look at github PR for improved romability (see github issue 149)
|
||||
- allow integer range as when choice? because 1,2,3,4,5 is already allowed, so perhaps 1 to 5 should be allowed too? However, [1,2,3,4,5] usually is the desugared equivalent of 1 to 5 and choice values can't be arrays.
|
||||
- const values should always either be of type long or float, this is how they were usually treated in const expression evaluation already anyway
|
||||
- Kotlin: can we use inline value classes in certain spots?
|
||||
- add float support to the configurable compiler targets
|
||||
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
|
||||
@@ -25,8 +28,6 @@ Future Things and Ideas
|
||||
Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEvaluating() and argumentsViaRegisters().
|
||||
- Does it make codegen easier if everything is an expression? Start with the PtProgram ast , get rid of the statements there -> expressions that have Void data type
|
||||
- Can we support signed % (remainder) somehow?
|
||||
- instead of copy-pasting inline asmsubs, make them into a 64tass macro and use that instead.
|
||||
that will allow them to be reused from custom user written assembly code as well.
|
||||
- Multidimensional arrays and chained indexing, purely as syntactic sugar over regular arrays. Probaby only useful if we have typed pointers.
|
||||
- make a form of "manual generics" possible like: varsub routine(T arg)->T where T is expanded to a specific type
|
||||
(this is already done hardcoded for several of the builtin functions)
|
||||
@@ -57,10 +58,8 @@ IR/VM
|
||||
|
||||
Libraries
|
||||
---------
|
||||
- Sorting module gnomesort_uw could be optimized more by fully rewriting it in asm? Shellshort seems consistently faster even if most of the words are already sorted.
|
||||
- Add split-word array sorting routines to sorting module?
|
||||
- See if the raster interrupt handler on the C64 can be tweaked to be a more stable raster irq
|
||||
- add even more general raster irq routines to build some sort of "copper list" , like Oscar64 has?
|
||||
- pet32 target: make syslib more complete (missing kernal routines)?
|
||||
- need help with: PET disk routines (OPEN, SETLFS etc are not exposed as kernal calls)
|
||||
- c128 target: make syslib more complete (missing kernal routines)?
|
||||
@@ -70,9 +69,9 @@ Libraries
|
||||
Optimizations
|
||||
-------------
|
||||
|
||||
- Sorting module gnomesort_uw could be optimized more by fully rewriting it in asm? Shellshort seems consistently faster even if most of the words are already sorted.
|
||||
- Compare output of some Oscar64 samples to what prog8 does for the equivalent code (see https://github.com/drmortalwombat/OscarTutorials/tree/main and https://github.com/drmortalwombat/oscar64/tree/main/samples)
|
||||
- Multi-value returns of normal subroutines: use cpu register A or AY for the first one and only start using virtual registers for the rest.
|
||||
Can FAC then be used for floats as well again? Those are now not supported for multi-value returns.
|
||||
- Multi-value returns of normal subroutines: Can FAC then be used for floats as well again? Those are now not supported for multi-value returns.
|
||||
- Optimize the IfExpression code generation to be more like regular if-else code. (both 6502 and IR) search for "TODO don't store condition as expression"
|
||||
- VariableAllocator: can we think of a smarter strategy for allocating variables into zeropage, rather than first-come-first-served?
|
||||
for instance, vars used inside loops first, then loopvars, then uwords used as pointers (or these first??), then the rest
|
||||
|
||||
+24
-15
@@ -1,25 +1,34 @@
|
||||
%import textio
|
||||
%zeropage basicsafe
|
||||
%option no_sysinit
|
||||
|
||||
|
||||
main {
|
||||
|
||||
sub start() {
|
||||
const uword x=128
|
||||
sub start() {
|
||||
uword[] texts1 = [ 1,2,3 ]
|
||||
uword[] @nosplit texts2 = [ 1,2,3 ]
|
||||
|
||||
const uword MAX_CAVE_WIDTH = 40 ; word here to avoid having to cast to word all the time
|
||||
ubyte @shared width
|
||||
cx16.r4 = texts1
|
||||
cx16.r5 = texts2
|
||||
|
||||
if cx16.r0L==x
|
||||
return
|
||||
if cx16.r0L>x
|
||||
return
|
||||
if cx16.r0L<x
|
||||
return
|
||||
; txt.print_uwhex(&texts1, true)
|
||||
; txt.spc()
|
||||
; txt.print_uwhex(cx16.r4, true)
|
||||
; txt.nl()
|
||||
; txt.print_uwhex(&texts2, true)
|
||||
; txt.spc()
|
||||
; txt.print_uwhex(cx16.r5, true)
|
||||
; txt.nl()
|
||||
|
||||
if width<3 or width>MAX_CAVE_WIDTH ; TODO optimize this too
|
||||
return
|
||||
cx16.r4 = &texts1
|
||||
cx16.r5 = &texts2
|
||||
|
||||
cx16.r0L = x ; already gets optimized to a byte value
|
||||
; txt.print_uwhex(&texts1, true)
|
||||
; txt.spc()
|
||||
; txt.print_uwhex(cx16.r4, true)
|
||||
; txt.nl()
|
||||
; txt.print_uwhex(&texts2, true)
|
||||
; txt.spc()
|
||||
; txt.print_uwhex(cx16.r5, true)
|
||||
; txt.nl()
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -3,4 +3,4 @@ org.gradle.console=rich
|
||||
org.gradle.parallel=true
|
||||
org.gradle.daemon=true
|
||||
kotlin.code.style=official
|
||||
version=11.1
|
||||
version=11.2
|
||||
@@ -1,4 +1,4 @@
|
||||
package prog8.code
|
||||
package prog8
|
||||
|
||||
/**
|
||||
* By convention, the right side of an `Either` is used to hold successful values.
|
||||
@@ -1,6 +1,5 @@
|
||||
package prog8.intermediate
|
||||
|
||||
import prog8.code.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.code.target.getCompilationTargetByName
|
||||
@@ -92,6 +91,7 @@ class IRFileReader {
|
||||
var loadAddress = target.PROGRAM_LOAD_ADDRESS
|
||||
var memtop = target.PROGRAM_MEMTOP_ADDRESS
|
||||
var optimize = true
|
||||
var romable = false
|
||||
var outputDir = Path("")
|
||||
|
||||
if(text.isNotBlank()) {
|
||||
@@ -114,6 +114,7 @@ class IRFileReader {
|
||||
}
|
||||
"outputDir" -> outputDir = Path(value)
|
||||
"optimize" -> optimize = value.toBoolean()
|
||||
"romable" -> romable = value.toBoolean()
|
||||
else -> throw IRParseException("illegal OPTION $name")
|
||||
}
|
||||
}
|
||||
@@ -125,8 +126,9 @@ class IRFileReader {
|
||||
zeropage,
|
||||
zpReserved,
|
||||
zpAllowed,
|
||||
false,
|
||||
false,
|
||||
false, // TODO always false?
|
||||
false, // TODO always false?
|
||||
romable,
|
||||
target,
|
||||
loadAddress,
|
||||
memtop,
|
||||
@@ -151,7 +153,7 @@ class IRFileReader {
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseVarsWithoutInit(reader: XMLEventReader): List<StStaticVariable> {
|
||||
private fun parseVarsWithoutInit(reader: XMLEventReader): List<IRStStaticVariable> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="VARIABLESNOINIT") { "missing VARIABLESNOINIT" }
|
||||
@@ -162,7 +164,7 @@ class IRFileReader {
|
||||
emptyList()
|
||||
else {
|
||||
val varPattern = Regex("(?<type>.+?)(?<arrayspec>\\[.+?\\])? (?<name>.+) zp=(?<zp>.+?)\\s?(split=(?<split>.+?))?\\s?(align=(?<align>.+?))?")
|
||||
val variables = mutableListOf<StStaticVariable>()
|
||||
val variables = mutableListOf<IRStStaticVariable>()
|
||||
text.lineSequence().forEach { line ->
|
||||
// example: uword main.start.qq2 zp=DONTCARE
|
||||
val match = varPattern.matchEntire(line) ?: throw IRParseException("invalid VARIABLESNOINIT $line")
|
||||
@@ -179,14 +181,14 @@ class IRFileReader {
|
||||
val zp = if(zpwish.isBlank()) ZeropageWish.DONTCARE else ZeropageWish.valueOf(zpwish)
|
||||
// val isSplit = if(split.isBlank()) false else split.toBoolean()
|
||||
val align = if(alignment.isBlank()) 0u else alignment.toUInt()
|
||||
val newVar = StStaticVariable(name, dt, null, null, arraysize, zp, align.toInt(), null)
|
||||
val newVar = IRStStaticVariable(name, dt, null, null, null, arraysize, zp, align.toInt())
|
||||
variables.add(newVar)
|
||||
}
|
||||
return variables
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseConstants(reader: XMLEventReader): List<StConstant> {
|
||||
private fun parseConstants(reader: XMLEventReader): List<IRStConstant> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="CONSTANTS") { "missing CONSTANTS" }
|
||||
@@ -197,7 +199,7 @@ class IRFileReader {
|
||||
emptyList()
|
||||
else {
|
||||
val constantPattern = Regex("(.+?) (.+)=(.*?)")
|
||||
val constants = mutableListOf<StConstant>()
|
||||
val constants = mutableListOf<IRStConstant>()
|
||||
text.lineSequence().forEach { line ->
|
||||
// examples:
|
||||
// uword main.start.qq2=0
|
||||
@@ -207,13 +209,13 @@ class IRFileReader {
|
||||
throw IRParseException("unscoped name: $name")
|
||||
val dt = parseDatatype(type, false)
|
||||
val value = parseIRValue(valueStr)
|
||||
constants.add(StConstant(name, dt.base, value, null))
|
||||
constants.add(IRStConstant(name, dt, value))
|
||||
}
|
||||
return constants
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseVariables(reader: XMLEventReader): List<StStaticVariable> {
|
||||
private fun parseVariables(reader: XMLEventReader): List<IRStStaticVariable> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="VARIABLESWITHINIT") { "missing VARIABLESWITHINIT" }
|
||||
@@ -224,7 +226,7 @@ class IRFileReader {
|
||||
emptyList()
|
||||
else {
|
||||
val varPattern = Regex("(?<type>.+?)(?<arrayspec>\\[.+?\\])? (?<name>.+)=(?<value>.*?) zp=(?<zp>.+?)\\s?(split=(?<split>.+?))?\\s?(align=(?<align>.+?))?")
|
||||
val variables = mutableListOf<StStaticVariable>()
|
||||
val variables = mutableListOf<IRStStaticVariable>()
|
||||
text.lineSequence().forEach { line ->
|
||||
// examples:
|
||||
// uword main.start.qq2=0 zp=REQUIRE_ZP
|
||||
@@ -245,21 +247,21 @@ class IRFileReader {
|
||||
if(split.isBlank()) false else split.toBoolean()
|
||||
val align = if(alignment.isBlank()) 0u else alignment.toUInt()
|
||||
var initNumeric: Double? = null
|
||||
var initArray: StArray? = null
|
||||
var initArray: IRStArray? = null
|
||||
when {
|
||||
dt.isNumericOrBool -> initNumeric = parseIRValue(value)
|
||||
dt.isBoolArray -> {
|
||||
initArray = value.split(',').map {
|
||||
val boolean = parseIRValue(it) != 0.0
|
||||
StArrayElement(null, null, boolean)
|
||||
IRStArrayElement(boolean, null, null)
|
||||
}
|
||||
}
|
||||
dt.isArray -> {
|
||||
initArray = value.split(',').map {
|
||||
if (it.startsWith('@'))
|
||||
StArrayElement(null, it.drop(1), null)
|
||||
IRStArrayElement(null, null, it.drop(1))
|
||||
else
|
||||
StArrayElement(parseIRValue(it), null, null)
|
||||
IRStArrayElement(null, parseIRValue(it), null)
|
||||
}
|
||||
}
|
||||
dt.isString -> throw IRParseException("STR should have been converted to byte array")
|
||||
@@ -268,16 +270,14 @@ class IRFileReader {
|
||||
if(arraysize!=null && initArray!=null && initArray.all { it.number==0.0 }) {
|
||||
initArray=null // arrays with just zeros can be left uninitialized
|
||||
}
|
||||
val stVar = StStaticVariable(name, dt, null, initArray, arraysize, zp, align.toInt(), null)
|
||||
if(initNumeric!=null)
|
||||
stVar.setOnetimeInitNumeric(initNumeric)
|
||||
val stVar = IRStStaticVariable(name, dt, initNumeric, null, initArray, arraysize, zp, align.toInt())
|
||||
variables.add(stVar)
|
||||
}
|
||||
return variables
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseMemMapped(reader: XMLEventReader): List<StMemVar> {
|
||||
private fun parseMemMapped(reader: XMLEventReader): List<IRStMemVar> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="MEMORYMAPPEDVARIABLES") { "missing MEMORYMAPPEDVARIABLES" }
|
||||
@@ -287,7 +287,7 @@ class IRFileReader {
|
||||
return if(text.isBlank())
|
||||
emptyList()
|
||||
else {
|
||||
val memvars = mutableListOf<StMemVar>()
|
||||
val memvars = mutableListOf<IRStMemVar>()
|
||||
val mappedPattern = Regex("@(.+?)(\\[.+?\\])? (.+)=(.+)")
|
||||
text.lineSequence().forEach { line ->
|
||||
// examples:
|
||||
@@ -297,13 +297,13 @@ class IRFileReader {
|
||||
val (type, arrayspec, name, address) = match.destructured
|
||||
val arraysize = if(arrayspec.isNotBlank()) arrayspec.substring(1, arrayspec.length-1).toInt() else null
|
||||
val dt = parseDatatype(type, arraysize!=null)
|
||||
memvars.add(StMemVar(name, dt, parseIRValue(address).toUInt(), arraysize, null))
|
||||
memvars.add(IRStMemVar(name, dt, parseIRValue(address).toUInt(), arraysize))
|
||||
}
|
||||
memvars
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseSlabs(reader: XMLEventReader): List<StMemorySlab> {
|
||||
private fun parseSlabs(reader: XMLEventReader): List<IRStMemorySlab> {
|
||||
skipText(reader)
|
||||
val start = reader.nextEvent().asStartElement()
|
||||
require(start.name.localPart=="MEMORYSLABS") { "missing MEMORYSLABS" }
|
||||
@@ -313,13 +313,13 @@ class IRFileReader {
|
||||
return if(text.isBlank())
|
||||
emptyList()
|
||||
else {
|
||||
val slabs = mutableListOf<StMemorySlab>()
|
||||
val slabs = mutableListOf<IRStMemorySlab>()
|
||||
val slabPattern = Regex("(.+) (.+) (.+)")
|
||||
text.lineSequence().forEach { line ->
|
||||
// example: "slabname 4096 0"
|
||||
val match = slabPattern.matchEntire(line) ?: throw IRParseException("invalid slab $line")
|
||||
val (name, size, align) = match.destructured
|
||||
slabs.add(StMemorySlab(name, size.toUInt(), align.toUInt(), null))
|
||||
slabs.add(IRStMemorySlab(name, size.toUInt(), align.toUInt()))
|
||||
}
|
||||
slabs
|
||||
}
|
||||
|
||||
@@ -224,6 +224,7 @@ class IRFileWriter(private val irProgram: IRProgram, outfileOverride: Path?) {
|
||||
xml.writeCharacters("loadAddress=${irProgram.options.loadAddress.toHex()}\n")
|
||||
xml.writeCharacters("memtop=${irProgram.options.memtopAddress.toHex()}\n")
|
||||
xml.writeCharacters("optimize=${irProgram.options.optimize}\n")
|
||||
xml.writeCharacters("romable=${irProgram.options.romable}\n")
|
||||
xml.writeCharacters("outputDir=${irProgram.options.outputDir.absolute()}\n")
|
||||
// other options not yet useful here?
|
||||
xml.writeEndElement()
|
||||
|
||||
@@ -12,38 +12,6 @@ class IRSymbolTable {
|
||||
private val table = mutableMapOf<String, IRStNode>()
|
||||
private val asmSymbols = mutableMapOf<String, String>()
|
||||
|
||||
companion object {
|
||||
fun fromAstSymboltable(sourceSt: SymbolTable?): IRSymbolTable {
|
||||
val st = IRSymbolTable()
|
||||
if (sourceSt != null) {
|
||||
sourceSt.flat.forEach {
|
||||
when(it.value.type) {
|
||||
StNodeType.STATICVAR -> st.add(it.value as StStaticVariable)
|
||||
StNodeType.MEMVAR -> st.add(it.value as StMemVar)
|
||||
StNodeType.CONSTANT -> st.add(it.value as StConstant)
|
||||
StNodeType.MEMORYSLAB -> st.add(it.value as StMemorySlab)
|
||||
else -> { }
|
||||
}
|
||||
}
|
||||
|
||||
require(st.table.all { it.key == it.value.name })
|
||||
|
||||
st.allVariables().forEach { variable ->
|
||||
variable.onetimeInitializationArrayValue?.let {
|
||||
it.forEach { arrayElt ->
|
||||
if (arrayElt.addressOfSymbol != null) {
|
||||
require(arrayElt.addressOfSymbol.contains('.')) {
|
||||
"pointer var in array should be properly scoped: ${arrayElt.addressOfSymbol} in ${variable.name}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return st
|
||||
}
|
||||
}
|
||||
|
||||
fun allConstants(): Sequence<IRStConstant> =
|
||||
table.asSequence().map { it.value }.filterIsInstance<IRStConstant>()
|
||||
|
||||
@@ -58,92 +26,8 @@ class IRSymbolTable {
|
||||
|
||||
fun lookup(name: String) = table[name]
|
||||
|
||||
fun add(variable: StStaticVariable) {
|
||||
val scopedName: String
|
||||
val varToadd: IRStStaticVariable
|
||||
if('.' in variable.name) {
|
||||
scopedName = variable.name
|
||||
varToadd = IRStStaticVariable(variable.name,
|
||||
variable.dt,
|
||||
variable.initializationNumericValue,
|
||||
variable.initializationStringValue,
|
||||
variable.initializationArrayValue?.map { convertArrayElt(it) },
|
||||
variable.length,
|
||||
variable.zpwish,
|
||||
variable.align)
|
||||
} else {
|
||||
fun fixupAddressOfInArray(array: List<StArrayElement>?): List<IRStArrayElement>? {
|
||||
if(array==null)
|
||||
return null
|
||||
val newArray = mutableListOf<IRStArrayElement>()
|
||||
array.forEach {
|
||||
if(it.addressOfSymbol!=null) {
|
||||
val target = variable.lookup(it.addressOfSymbol!!) ?: throw NoSuchElementException("can't find variable ${it.addressOfSymbol}")
|
||||
newArray.add(IRStArrayElement(null, null, target.scopedName))
|
||||
} else {
|
||||
newArray.add(convertArrayElt(it))
|
||||
}
|
||||
}
|
||||
return newArray
|
||||
}
|
||||
scopedName = variable.scopedName
|
||||
varToadd = IRStStaticVariable(scopedName,
|
||||
variable.dt,
|
||||
variable.initializationNumericValue,
|
||||
variable.initializationStringValue,
|
||||
fixupAddressOfInArray(variable.initializationArrayValue),
|
||||
variable.length,
|
||||
variable.zpwish,
|
||||
variable.align
|
||||
)
|
||||
}
|
||||
table[scopedName] = varToadd
|
||||
}
|
||||
|
||||
fun add(variable: StMemVar) {
|
||||
val scopedName: String
|
||||
val varToadd: IRStMemVar
|
||||
if('.' in variable.name) {
|
||||
scopedName = variable.name
|
||||
varToadd = IRStMemVar(
|
||||
variable.name,
|
||||
variable.dt,
|
||||
variable.address,
|
||||
variable.length
|
||||
)
|
||||
} else {
|
||||
scopedName = try {
|
||||
variable.scopedName
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
variable.name
|
||||
}
|
||||
varToadd = IRStMemVar(scopedName, variable.dt, variable.address, variable.length)
|
||||
}
|
||||
table[scopedName] = varToadd
|
||||
}
|
||||
|
||||
fun add(variable: StMemorySlab) {
|
||||
val varToadd = if('.' in variable.name)
|
||||
IRStMemorySlab(variable.name, variable.size, variable.align)
|
||||
else {
|
||||
IRStMemorySlab("prog8_slabs.${variable.name}", variable.size, variable.align)
|
||||
}
|
||||
table[varToadd.name] = varToadd
|
||||
}
|
||||
|
||||
fun add(constant: StConstant) {
|
||||
val scopedName: String
|
||||
val dt = DataType.forDt(constant.dt)
|
||||
if('.' in constant.name) {
|
||||
scopedName = constant.name
|
||||
} else {
|
||||
scopedName = try {
|
||||
constant.scopedName
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
constant.name
|
||||
}
|
||||
}
|
||||
table[scopedName] = IRStConstant(scopedName, dt, constant.value)
|
||||
fun add(node: IRStNode) {
|
||||
table[node.name] = node
|
||||
}
|
||||
|
||||
fun addAsmSymbol(name: String, value: String) {
|
||||
@@ -157,17 +41,15 @@ class IRSymbolTable {
|
||||
val vars = table.filter { it.key.startsWith(prefix) }
|
||||
vars.forEach {
|
||||
// check if attempt is made to delete interned strings, if so, refuse that.
|
||||
if(!it.key.startsWith(internedStringsModuleName)) {
|
||||
if(!it.key.startsWith(INTERNED_STRINGS_MODULENAME)) {
|
||||
table.remove(it.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun convertArrayElt(elt: StArrayElement): IRStArrayElement = if(elt.boolean!=null)
|
||||
IRStArrayElement(elt.boolean, null, elt.addressOfSymbol)
|
||||
else
|
||||
IRStArrayElement(null, elt.number, elt.addressOfSymbol)
|
||||
fun validate() {
|
||||
require(table.all { it.key == it.value.name })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -178,10 +60,7 @@ enum class IRStNodeType {
|
||||
CONST
|
||||
}
|
||||
|
||||
open class IRStNode(val name: String,
|
||||
val type: IRStNodeType,
|
||||
val children: MutableMap<String, StNode> = mutableMapOf()
|
||||
)
|
||||
open class IRStNode(val name: String, val type: IRStNodeType)
|
||||
|
||||
class IRStMemVar(name: String,
|
||||
val dt: DataType,
|
||||
@@ -209,7 +88,7 @@ class IRStConstant(name: String, val dt: DataType, val value: Double) : IRStNode
|
||||
|
||||
class IRStStaticVariable(name: String,
|
||||
val dt: DataType,
|
||||
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
|
||||
val onetimeInitializationNumericValue: Double?, // TODO still needed? Or can go? regular (every-run-time) initialization is done via regular assignments
|
||||
val onetimeInitializationStringValue: IRStString?,
|
||||
val onetimeInitializationArrayValue: IRStArray?,
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package prog8.intermediate
|
||||
|
||||
import prog8.code.Either
|
||||
import prog8.code.core.*
|
||||
import prog8.code.left
|
||||
import prog8.code.right
|
||||
import prog8.Either
|
||||
import prog8.left
|
||||
import prog8.right
|
||||
|
||||
|
||||
fun DataType.irTypeString(length: Int?): String {
|
||||
|
||||
@@ -21,6 +21,7 @@ class TestIRFileInOut: FunSpec({
|
||||
CompilationOptions.AllZeropageAllowed,
|
||||
floats = false,
|
||||
noSysInit = true,
|
||||
romable = false,
|
||||
compTarget = target,
|
||||
loadAddress = target.PROGRAM_LOAD_ADDRESS,
|
||||
memtopAddress = 0xffffu,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
include(
|
||||
':parser',
|
||||
':codeCore',
|
||||
':simpleAst',
|
||||
':intermediate',
|
||||
':compilerAst',
|
||||
':codeOptimizers',
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":codeCore"))
|
||||
// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation("com.michael-bull.kotlin-result:kotlin-result-jvm:2.0.1")
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir("${project.projectDir}/src")
|
||||
}
|
||||
resources {
|
||||
srcDir("${project.projectDir}/res")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note: there are no unit tests yet in this module!
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
+5
-3
@@ -1,13 +1,15 @@
|
||||
package prog8.code.core
|
||||
package prog8.code
|
||||
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IErrorReporter
|
||||
|
||||
interface ICodeGeneratorBackend {
|
||||
fun generate(program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter): IAssemblyProgram?
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram?
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,6 @@ import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
const val internedStringsModuleName = "prog8_interned_strings"
|
||||
|
||||
|
||||
/**
|
||||
* Tree structure containing all symbol definitions in the program
|
||||
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
|
||||
@@ -260,7 +257,7 @@ class StMemorySlab(
|
||||
|
||||
|
||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returns: List<DataType>, astNode: PtNode) :
|
||||
StNode(name, StNodeType.SUBROUTINE, astNode)
|
||||
StNode(name, StNodeType.SUBROUTINE, astNode)
|
||||
|
||||
|
||||
class StExtSub(name: String,
|
||||
File renamed without changes.
@@ -96,12 +96,7 @@ class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Positi
|
||||
}
|
||||
|
||||
|
||||
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
|
||||
companion object {
|
||||
// all automatically generated labels everywhere need to have the same label name prefix:
|
||||
const val GENERATED_LABEL_PREFIX = "p8_label_gen_"
|
||||
}
|
||||
}
|
||||
class PtLabel(name: String, position: Position) : PtNamedNode(name, position)
|
||||
|
||||
|
||||
class PtBreakpoint(position: Position): PtNode(position)
|
||||
File renamed without changes.
File renamed without changes.
+35
@@ -6,6 +6,41 @@ import prog8.code.core.*
|
||||
sealed interface IPtSubroutine {
|
||||
val name: String
|
||||
val scopedName: String
|
||||
|
||||
fun returnsWhatWhere(): List<Pair<RegisterOrStatusflag, DataType>> {
|
||||
when(this) {
|
||||
is PtAsmSub -> {
|
||||
return returns
|
||||
}
|
||||
is PtSub -> {
|
||||
// for non-asm subroutines, determine the return registers based on the type of the return values
|
||||
|
||||
fun cpuRegisterFor(returntype: DataType): RegisterOrStatusflag = when {
|
||||
returntype.isByteOrBool -> RegisterOrStatusflag(RegisterOrPair.A, null)
|
||||
returntype.isWord -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||
returntype.isFloat -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
|
||||
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||
}
|
||||
|
||||
when(returns.size) {
|
||||
0 -> return emptyList()
|
||||
1 -> {
|
||||
val returntype = returns.single()
|
||||
val register = cpuRegisterFor(returntype)
|
||||
return listOf(Pair(register, returntype))
|
||||
}
|
||||
else -> {
|
||||
// for multi-value results, put the first one in A or AY cpu register(s) and the rest in the virtual registers starting from R15 and counting down
|
||||
val first = cpuRegisterFor(returns.first()) to returns.first()
|
||||
val others = returns.drop(1)
|
||||
.zip(Cx16VirtualRegisters.reversed())
|
||||
.map { (type, reg) -> RegisterOrStatusflag(reg, null) to type }
|
||||
return listOf(first) + others
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PtAsmSub(
|
||||
File renamed without changes.
Loaded 100 of 108 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user