mirror of
https://github.com/irmen/prog8.git
synced 2025-06-14 11:23:37 +00:00
Compare commits
135 Commits
Author | SHA1 | Date | |
---|---|---|---|
8acd94fc89 | |||
1436480eab | |||
448d176c24 | |||
fd269453a4 | |||
b3b380964c | |||
6e9025ebf2 | |||
3922691b3c | |||
0545b77cf4 | |||
6b3f39fa1a | |||
3114ab87dc | |||
00bc99cc7b | |||
540b3ae2f4 | |||
dbfe4140e1 | |||
d3675ec254 | |||
ded2483fc0 | |||
e62ea388e0 | |||
f20356e9be | |||
d282a2d846 | |||
4641ac46e7 | |||
ba9268a09e | |||
fb9902c536 | |||
5318ba6c6e | |||
fd5ebef488 | |||
d9e4f39ddc | |||
435b9d8973 | |||
0ea70ba656 | |||
92a07b87d2 | |||
c3c82282ba | |||
adc15c24ef | |||
dddf9a9396 | |||
9ca6860ffa | |||
f7dd388954 | |||
6012839f0e | |||
8e9cbab053 | |||
aaf375a57b | |||
3cce985f03 | |||
c59df6ec20 | |||
5c3f41f64d | |||
cf3523f49f | |||
db794752cb | |||
bceaebe856 | |||
3916de2921 | |||
9e0f8c1a97 | |||
0cbc56b82e | |||
b95608f68a | |||
b6e5dbd06c | |||
914f19be86 | |||
f09bcf3fcf | |||
d0b18dec8e | |||
75d486b124 | |||
4914609485 | |||
75bd66326a | |||
8f904f75bb | |||
549c598f51 | |||
ed68d604d6 | |||
f83752f43b | |||
86c22636eb | |||
30d20a453b | |||
fe29d8a23f | |||
694d088160 | |||
6aabbffc62 | |||
7b59bc8d12 | |||
79d0fb0b52 | |||
edf56d34f8 | |||
623329fb33 | |||
9f0074eef9 | |||
6733253826 | |||
f117805129 | |||
c75b1581d2 | |||
109e118aba | |||
201b77d5b6 | |||
a5ca08f33d | |||
86210c4513 | |||
988a3e4446 | |||
0f5cd22bb7 | |||
2f5bed36b3 | |||
5b6534bb28 | |||
e31e5b2477 | |||
07d5fafe2e | |||
e08da659e5 | |||
8a4979f44c | |||
e67464325f | |||
94c9b0d23b | |||
e9ec310d8a | |||
c78d1e3c39 | |||
e94319145f | |||
3f3b01b5f6 | |||
19a2791c65 | |||
4e8ccf0ef3 | |||
f1a7d5ecf7 | |||
8b05abb80d | |||
48c9349ce9 | |||
117d848466 | |||
9a2df072cc | |||
99c62aab36 | |||
224278e07a | |||
74b69e191e | |||
8cda8a727c | |||
a3c0c7c96f | |||
4403e4ed62 | |||
9b209823f6 | |||
b2cb125bd4 | |||
5e8f767642 | |||
6ee270d9d8 | |||
44fa309d20 | |||
58d88f3dd4 | |||
e980c23177 | |||
75224321bb | |||
801af05b20 | |||
7611dbbddc | |||
6d40ca15bc | |||
32c1c19224 | |||
bbf6357222 | |||
dc16629c24 | |||
3718b9d768 | |||
c25eb088ec | |||
3feb3e52f8 | |||
8e730ef93d | |||
e0913a39ab | |||
7a27fbc001 | |||
ee0dbdad35 | |||
9225f88f89 | |||
a04839dd6b | |||
002006517a | |||
f5b202d438 | |||
a7df094ff4 | |||
1e6fa77633 | |||
eb4cff202c | |||
7ee777f405 | |||
81bd5c784e | |||
b526e132a7 | |||
1860f66de5 | |||
ded9ada9bc | |||
d0e6a2eb8b | |||
4e103a1963 |
31
.github/workflows/all-ci.yml
vendored
Normal file
31
.github/workflows/all-ci.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
name: Build and Test the Prog8 compiler
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install 64tass
|
||||
run: sudo apt-get update -y && sudo apt-get install -y 64tass
|
||||
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 11
|
||||
distribution: adopt
|
||||
|
||||
- name: Build and test with Gradle
|
||||
run: ./gradlew build shadowJar --no-daemon
|
||||
|
||||
- name: Create compiler shadowJar artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: prog8-compiler-jar-zipped
|
||||
path: compiler/build/libs/*-all.jar
|
10
.idea/libraries/antlr_antlr4.xml
generated
10
.idea/libraries/antlr_antlr4.xml
generated
@ -1,17 +1,17 @@
|
||||
<component name="libraryTable">
|
||||
<library name="antlr.antlr4" type="repository">
|
||||
<properties maven-id="org.antlr:antlr4:4.10.1">
|
||||
<properties maven-id="org.antlr:antlr4:4.11.1">
|
||||
<exclude>
|
||||
<dependency maven-id="com.ibm.icu:icu4j" />
|
||||
</exclude>
|
||||
</properties>
|
||||
<CLASSES>
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.10.1/antlr4-4.10.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.10.1/antlr4-runtime-4.10.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4/4.11.1/antlr4-4.11.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr4-runtime/4.11.1/antlr4-runtime-4.11.1.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/antlr-runtime/3.5.3/antlr-runtime-3.5.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.3/ST4-4.3.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/antlr/ST4/4.3.4/ST4-4.3.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/abego/treelayout/org.abego.treelayout.core/1.0.3/org.abego.treelayout.core-1.0.3.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar!/" />
|
||||
<root url="jar://$MAVEN_REPOSITORY$/org/glassfish/javax.json/1.1.4/javax.json-1.1.4.jar!/" />
|
||||
</CLASSES>
|
||||
<JAVADOC />
|
||||
<SOURCES />
|
||||
|
2
.idea/misc.xml
generated
2
.idea/misc.xml
generated
@ -19,7 +19,7 @@
|
||||
<component name="FrameworkDetectionExcludesConfiguration">
|
||||
<type id="Python" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="openjdk-11" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
@ -1,35 +0,0 @@
|
||||
#### Just a few remarks upfront:
|
||||
* There is the (gradle/IDEA) module `parser`: that's the parser generated by ANTLR4, in Java. The only file to be edited here is the grammar, `prog8.g4`.
|
||||
* Then we have the module `compilerAst` - in Kotlin - which uses `parser` and adds AST nodes. Here we put our additions to the generated thing, *including any tests of the parsing stage*.
|
||||
- the name is a bit misleading, as this module isn't (or, resp. shouldn't be; see below) about *compiling*, only the parsing stage
|
||||
- also, the tree that comes out isn't much of an *abstraction*, but rather still more or less a parse tree (this might very well change).
|
||||
- **However, let's not *yet* rename the module.** We'll find a good name during refactoring.
|
||||
|
||||
#### Problems with `compilerAst`:
|
||||
* `ModuleImporter.kt`, doing (Prog8-) module resolution. That's not the parser's job.
|
||||
* `ParsingFailedError` (in `ModuleParsing.kt`): this exception (it is actually *not* a `java.lang.Error`...) is thrown in a number of places, where other exceptions would make more sense. For example: not finding a file should just yield a `NoSuchFileException`, not this one. The other problem with it is that it does not provide any additional information about the source of parsing error, in particular a `Position`.
|
||||
* During parsing, character literals are turned into UBYTEs (since there is no basic type e.g. CHAR). That's bad because it depends on a specific character encoding (`IStringEncoding` in `compilerAst/src/prog8/ast/AstToplevel.kt`) of/for some target platform. Note that *strings* are indeed encoded later, in the `compiler` module.
|
||||
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
|
||||
|
||||
#### Steps to take, in conceptual (!) order:
|
||||
|
||||
(note: all these steps have been implemented, rejected or otherwise solved now.)
|
||||
|
||||
1. introduce an abstraction `SourceCode` that encapsulates the origin and actual loading of Prog8 source code
|
||||
- from the local file system (use case: user programs)
|
||||
- from resources (prog8lib)
|
||||
- from plain strings (for testing)
|
||||
2. add subclass `ParseError : ParsingFailedError` which adds information about the *source of parsing error* (`SourceCode` and `Position`). We cannot just replace `ParsingFailedError` right away because it is so widely used (even in the `compiler` module). Therefore we'll just subclass for the time being, add more and more tests requiring the new one to be thrown (or, resp., NOT to be thrown), and gradually transition.
|
||||
3. introduce a minimal interface to the outside, input: `SourceCode`, output: a tree with a `Module` node as the root
|
||||
- this will be the Kotlin singleton `Prog8Parser` with the main method `parseModule`
|
||||
- plus, optionally, method's for registering/unregistering a listener with the parser
|
||||
- the *only* exception ever thrown / reported to listeners (TBD) will be `ParseError`
|
||||
- anything related to the lexer, error strategies, character/token streams is hidden from the outside
|
||||
- to make a clear distinction between the *generated* parser (and lexer) vs. `Prog8Parser`, and to discourage directly using the generated stuff, we'll rename the existing `prog8Parser`/`prog8Lexer` to `Prog8ANTLRParser` and `Prog8ANTLRLexer` and move them to package `prog8.parser.generated`
|
||||
4. introduce AST node `CharLiteral` and keep them until after identifier resolution and type checking; insert there an AST transformation step that turns them in UBYTE constants (literals)
|
||||
5. remove uses of `IStringEncoding` from module `compilerAst` - none should be necessary anymore
|
||||
6. move `IStringEncoding` to module `compiler`
|
||||
7. same with `ModuleImporter`, then rewrite that (addressing #46)
|
||||
8. refactor AST nodes and grammar: less generated parse tree nodes (`XyzContext`), less intermediary stuff (private classes in `Antrl2Kotlin.kt`), more compact code. Also: nicer names such as simply `StringLiteral` instead of `StringLiteralValue`
|
||||
9. re-think `IStringEncoding` to address #38
|
||||
|
@ -1,5 +1,7 @@
|
||||
package prog8.code
|
||||
|
||||
import prog8.code.ast.PtNode
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
@ -7,13 +9,14 @@ import prog8.code.core.*
|
||||
* Tree structure containing all symbol definitions in the program
|
||||
* (blocks, subroutines, variables (all types), memoryslabs, and labels).
|
||||
*/
|
||||
class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
||||
class SymbolTable(astProgram: PtProgram) : StNode(astProgram.name, StNodeType.GLOBAL, astProgram) {
|
||||
/**
|
||||
* The table as a flat mapping of scoped names to the StNode.
|
||||
* This gives the fastest lookup possible (no need to traverse tree nodes)
|
||||
*/
|
||||
val flat: Map<List<String>, StNode> by lazy {
|
||||
val result = mutableMapOf<List<String>, StNode>()
|
||||
|
||||
val flat: Map<String, StNode> by lazy {
|
||||
val result = mutableMapOf<String, StNode>()
|
||||
fun flatten(node: StNode) {
|
||||
result[node.scopedName] = node
|
||||
node.children.values.forEach { flatten(it) }
|
||||
@ -51,10 +54,20 @@ class SymbolTable : StNode("", StNodeType.GLOBAL, Position.DUMMY) {
|
||||
}
|
||||
|
||||
val allMemorySlabs: Collection<StMemorySlab> by lazy {
|
||||
children.mapNotNull { if (it.value.type == StNodeType.MEMORYSLAB) it.value as StMemorySlab else null }
|
||||
val vars = mutableListOf<StMemorySlab>()
|
||||
fun collect(node: StNode) {
|
||||
for(child in node.children) {
|
||||
if(child.value.type== StNodeType.MEMORYSLAB)
|
||||
vars.add(child.value as StMemorySlab)
|
||||
else
|
||||
collect(child.value)
|
||||
}
|
||||
}
|
||||
collect(this)
|
||||
vars
|
||||
}
|
||||
|
||||
override fun lookup(scopedName: List<String>) = flat[scopedName]
|
||||
override fun lookup(scopedName: String) = flat[scopedName]
|
||||
}
|
||||
|
||||
|
||||
@ -75,44 +88,24 @@ enum class StNodeType {
|
||||
|
||||
open class StNode(val name: String,
|
||||
val type: StNodeType,
|
||||
val position: Position,
|
||||
val astNode: PtNode,
|
||||
val children: MutableMap<String, StNode> = mutableMapOf()
|
||||
) {
|
||||
|
||||
lateinit var parent: StNode
|
||||
|
||||
val scopedName: List<String> by lazy {
|
||||
if(type== StNodeType.GLOBAL)
|
||||
emptyList()
|
||||
else
|
||||
parent.scopedName + name
|
||||
}
|
||||
val scopedName: String by lazy { scopedNameList.joinToString(".") }
|
||||
|
||||
fun lookup(name: String) =
|
||||
lookupUnqualified(name)
|
||||
open fun lookup(scopedName: List<String>) =
|
||||
if(scopedName.size>1) lookupQualified(scopedName) else lookupUnqualified(scopedName[0])
|
||||
fun lookupOrElse(name: String, default: () -> StNode) =
|
||||
lookupUnqualified(name) ?: default()
|
||||
fun lookupOrElse(scopedName: List<String>, default: () -> StNode) =
|
||||
lookup(scopedName) ?: default()
|
||||
open fun lookup(scopedName: String) =
|
||||
lookup(scopedName.split('.'))
|
||||
|
||||
private fun lookupQualified(scopedName: List<String>): StNode? {
|
||||
// a scoped name refers to a name in another namespace, and always stars from the root.
|
||||
var node = this
|
||||
while(node.type!= StNodeType.GLOBAL)
|
||||
node = node.parent
|
||||
fun lookupUnscopedOrElse(name: String, default: () -> StNode) =
|
||||
lookupUnscoped(name) ?: default()
|
||||
|
||||
for(name in scopedName) {
|
||||
if(name in node.children)
|
||||
node = node.children.getValue(name)
|
||||
else
|
||||
return null
|
||||
}
|
||||
return node
|
||||
}
|
||||
fun lookupOrElse(scopedName: String, default: () -> StNode): StNode =
|
||||
lookup(scopedName.split('.')) ?: default()
|
||||
|
||||
private fun lookupUnqualified(name: String): StNode? {
|
||||
fun lookupUnscoped(name: String): StNode? {
|
||||
// first consider the builtin functions
|
||||
var globalscope = this
|
||||
while(globalscope.type!= StNodeType.GLOBAL)
|
||||
@ -138,37 +131,57 @@ open class StNode(val name: String,
|
||||
children[child.name] = child
|
||||
child.parent = this
|
||||
}
|
||||
|
||||
private val scopedNameList: List<String> by lazy {
|
||||
if(type== StNodeType.GLOBAL)
|
||||
emptyList()
|
||||
else
|
||||
parent.scopedNameList + name
|
||||
}
|
||||
|
||||
private fun lookup(scopedName: List<String>): StNode? {
|
||||
// a scoped name refers to a name in another namespace, and always stars from the root.
|
||||
var node = this
|
||||
while(node.type!= StNodeType.GLOBAL)
|
||||
node = node.parent
|
||||
|
||||
for(name in scopedName) {
|
||||
if(name in node.children)
|
||||
node = node.children.getValue(name)
|
||||
else
|
||||
return null
|
||||
}
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
class StStaticVariable(name: String,
|
||||
val dt: DataType,
|
||||
val bss: Boolean,
|
||||
val onetimeInitializationNumericValue: Double?, // regular (every-run-time) initialization is done via regular assignments
|
||||
val onetimeInitializationStringValue: StString?,
|
||||
val onetimeInitializationArrayValue: StArray?,
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
val zpwish: ZeropageWish,
|
||||
position: Position) : StNode(name, StNodeType.STATICVAR, position) {
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
val zpwish: ZeropageWish, // used in the variable allocator
|
||||
astNode: PtNode) : StNode(name, StNodeType.STATICVAR, astNode) {
|
||||
|
||||
val uninitialized = onetimeInitializationArrayValue==null && onetimeInitializationStringValue==null && onetimeInitializationNumericValue==null
|
||||
|
||||
init {
|
||||
if(bss) {
|
||||
require(onetimeInitializationNumericValue==null)
|
||||
require(onetimeInitializationStringValue==null)
|
||||
require(onetimeInitializationArrayValue.isNullOrEmpty())
|
||||
} else {
|
||||
require(onetimeInitializationNumericValue!=null ||
|
||||
onetimeInitializationStringValue!=null ||
|
||||
onetimeInitializationArrayValue!=null)
|
||||
}
|
||||
if(length!=null) {
|
||||
require(onetimeInitializationNumericValue == null)
|
||||
if(onetimeInitializationArrayValue!=null)
|
||||
require(onetimeInitializationArrayValue.isEmpty() ||onetimeInitializationArrayValue.size==length)
|
||||
}
|
||||
if(onetimeInitializationNumericValue!=null)
|
||||
if(onetimeInitializationNumericValue!=null) {
|
||||
require(dt in NumericDatatypes)
|
||||
if(onetimeInitializationArrayValue!=null)
|
||||
require(onetimeInitializationNumericValue!=0.0) { "zero as init value should just remain uninitialized"}
|
||||
}
|
||||
if(onetimeInitializationArrayValue!=null) {
|
||||
require(dt in ArrayDatatypes)
|
||||
if(onetimeInitializationArrayValue.all { it.number!=null} ) {
|
||||
require(onetimeInitializationArrayValue.any { it.number != 0.0 }) { "array of all zerors as init value should just remain uninitialized" }
|
||||
}
|
||||
}
|
||||
if(onetimeInitializationStringValue!=null) {
|
||||
require(dt == DataType.STR)
|
||||
require(length == onetimeInitializationStringValue.first.length+1)
|
||||
@ -177,8 +190,8 @@ class StStaticVariable(name: String,
|
||||
}
|
||||
|
||||
|
||||
class StConstant(name: String, val dt: DataType, val value: Double, position: Position) :
|
||||
StNode(name, StNodeType.CONSTANT, position) {
|
||||
class StConstant(name: String, val dt: DataType, val value: Double, astNode: PtNode) :
|
||||
StNode(name, StNodeType.CONSTANT, astNode) {
|
||||
}
|
||||
|
||||
|
||||
@ -186,36 +199,36 @@ class StMemVar(name: String,
|
||||
val dt: DataType,
|
||||
val address: UInt,
|
||||
val length: Int?, // for arrays: the number of elements, for strings: number of characters *including* the terminating 0-byte
|
||||
position: Position) :
|
||||
StNode(name, StNodeType.MEMVAR, position) {
|
||||
astNode: PtNode) :
|
||||
StNode(name, StNodeType.MEMVAR, astNode) {
|
||||
}
|
||||
|
||||
class StMemorySlab(
|
||||
name: String,
|
||||
val size: UInt,
|
||||
val align: UInt,
|
||||
position: Position
|
||||
astNode: PtNode
|
||||
):
|
||||
StNode(name, StNodeType.MEMORYSLAB, position) {
|
||||
StNode(name, StNodeType.MEMORYSLAB, astNode) {
|
||||
}
|
||||
|
||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, position: Position) :
|
||||
StNode(name, StNodeType.SUBROUTINE, position) {
|
||||
class StSub(name: String, val parameters: List<StSubroutineParameter>, val returnType: DataType?, astNode: PtNode) :
|
||||
StNode(name, StNodeType.SUBROUTINE, astNode) {
|
||||
}
|
||||
|
||||
|
||||
class StRomSub(name: String,
|
||||
val address: UInt,
|
||||
val parameters: List<StRomSubParameter>,
|
||||
val returns: List<RegisterOrStatusflag>,
|
||||
position: Position) :
|
||||
StNode(name, StNodeType.ROMSUB, position) {
|
||||
}
|
||||
val returns: List<StRomSubParameter>,
|
||||
astNode: PtNode) :
|
||||
StNode(name, StNodeType.ROMSUB, astNode)
|
||||
|
||||
|
||||
|
||||
class StSubroutineParameter(val name: String, val type: DataType)
|
||||
class StRomSubParameter(val register: RegisterOrStatusflag, val type: DataType)
|
||||
class StArrayElement(val number: Double?, val addressOf: List<String>?)
|
||||
class StArrayElement(val number: Double?, val addressOfSymbol: String?)
|
||||
|
||||
typealias StString = Pair<String, Encoding>
|
||||
typealias StArray = List<StArrayElement>
|
||||
|
207
codeCore/src/prog8/code/SymbolTableMaker.kt
Normal file
207
codeCore/src/prog8/code/SymbolTableMaker.kt
Normal file
@ -0,0 +1,207 @@
|
||||
package prog8.code
|
||||
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import java.util.*
|
||||
|
||||
class SymbolTableMaker(private val program: PtProgram, private val options: CompilationOptions) {
|
||||
fun make(): SymbolTable {
|
||||
val st = SymbolTable(program)
|
||||
|
||||
BuiltinFunctions.forEach {
|
||||
st.add(StNode(it.key, StNodeType.BUILTINFUNC, PtIdentifier(it.key, it.value.returnType ?: DataType.UNDEFINED, Position.DUMMY)))
|
||||
}
|
||||
|
||||
val scopestack = Stack<StNode>()
|
||||
scopestack.push(st)
|
||||
program.children.forEach {
|
||||
addToSt(it, scopestack)
|
||||
}
|
||||
require(scopestack.size==1)
|
||||
|
||||
if(options.compTarget.name != VMTarget.NAME) {
|
||||
listOf(
|
||||
PtMemMapped("P8ZP_SCRATCH_B1", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_B1, null, Position.DUMMY),
|
||||
PtMemMapped("P8ZP_SCRATCH_REG", DataType.UBYTE, options.compTarget.machine.zeropage.SCRATCH_REG, null, Position.DUMMY),
|
||||
PtMemMapped("P8ZP_SCRATCH_W1", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W1, null, Position.DUMMY),
|
||||
PtMemMapped("P8ZP_SCRATCH_W2", DataType.UWORD, options.compTarget.machine.zeropage.SCRATCH_W2, null, Position.DUMMY),
|
||||
PtMemMapped("P8ESTACK_LO", DataType.ARRAY_UB, options.compTarget.machine.ESTACK_LO, 256u, Position.DUMMY),
|
||||
PtMemMapped("P8ESTACK_HI", DataType.ARRAY_UB, options.compTarget.machine.ESTACK_HI, 256u, Position.DUMMY)
|
||||
).forEach {
|
||||
it.parent = program
|
||||
st.add(StMemVar(it.name, it.type, it.address, null, it))
|
||||
}
|
||||
}
|
||||
|
||||
return st
|
||||
}
|
||||
|
||||
private fun addToSt(node: PtNode, scope: Stack<StNode>) {
|
||||
val stNode = when(node) {
|
||||
is PtAsmSub -> {
|
||||
if(node.address==null) {
|
||||
val params = node.parameters.map { StSubroutineParameter(it.second.name, it.second.type) }
|
||||
StSub(node.name, params, node.returns.singleOrNull()?.second, node)
|
||||
} else {
|
||||
val parameters = node.parameters.map { StRomSubParameter(it.first, it.second.type) }
|
||||
val returns = node.returns.map { StRomSubParameter(it.first, it.second) }
|
||||
StRomSub(node.name, node.address, parameters, returns, node)
|
||||
}
|
||||
}
|
||||
is PtBlock -> {
|
||||
StNode(node.name, StNodeType.BLOCK, node)
|
||||
}
|
||||
is PtConstant -> {
|
||||
StConstant(node.name, node.type, node.value, node)
|
||||
}
|
||||
is PtLabel -> {
|
||||
StNode(node.name, StNodeType.LABEL, node)
|
||||
}
|
||||
is PtMemMapped -> {
|
||||
StMemVar(node.name, node.type, node.address, node.arraySize?.toInt(), node)
|
||||
}
|
||||
is PtSub -> {
|
||||
val params = node.parameters.map {StSubroutineParameter(it.name, it.type) }
|
||||
StSub(node.name, params, node.returntype, node)
|
||||
}
|
||||
is PtVariable -> {
|
||||
val initialNumeric: Double?
|
||||
val initialString: StString?
|
||||
val initialArray: StArray?
|
||||
val numElements: Int?
|
||||
val value = node.value
|
||||
if(value!=null) {
|
||||
val number = (value as? PtNumber)?.number
|
||||
initialNumeric = if(number==0.0) null else number // 0 as init value -> just uninitialized
|
||||
when (value) {
|
||||
is PtString -> {
|
||||
initialString = StString(value.value, value.encoding)
|
||||
initialArray = null
|
||||
numElements = value.value.length + 1 // include the terminating 0-byte
|
||||
}
|
||||
is PtArray -> {
|
||||
val array = makeInitialArray(value)
|
||||
initialArray = if(array.all { it.number==0.0 }) null else array // all 0 as init value -> just uninitialized
|
||||
initialString = null
|
||||
numElements = array.size
|
||||
require(node.arraySize?.toInt()==numElements)
|
||||
}
|
||||
else -> {
|
||||
initialString = null
|
||||
initialArray = null
|
||||
numElements = node.arraySize?.toInt()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
initialNumeric = null
|
||||
initialArray = null
|
||||
initialString = null
|
||||
numElements = node.arraySize?.toInt()
|
||||
}
|
||||
StStaticVariable(node.name, node.type, initialNumeric, initialString, initialArray, numElements, node.zeropage, node)
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if(node.name=="memory") {
|
||||
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
|
||||
require(node.name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||
val slabname = (node.args[0] as PtString).value
|
||||
val size = (node.args[1] as PtNumber).number.toUInt()
|
||||
val align = (node.args[2] as PtNumber).number.toUInt()
|
||||
// don't add memory slabs in nested scope, just put them in the top level of the ST
|
||||
scope.firstElement().add(StMemorySlab("prog8_memoryslab_$slabname", size, align, node))
|
||||
}
|
||||
null
|
||||
}
|
||||
else -> null // node is not present in the ST
|
||||
}
|
||||
|
||||
if(stNode!=null) {
|
||||
scope.peek().add(stNode)
|
||||
scope.push(stNode)
|
||||
}
|
||||
node.children.forEach {
|
||||
addToSt(it, scope)
|
||||
}
|
||||
if(stNode!=null)
|
||||
scope.pop()
|
||||
}
|
||||
|
||||
private fun makeInitialArray(value: PtArray): List<StArrayElement> {
|
||||
return value.children.map {
|
||||
when(it) {
|
||||
is PtAddressOf -> StArrayElement(null, it.identifier.name)
|
||||
is PtIdentifier -> StArrayElement(null, it.name)
|
||||
is PtNumber -> StArrayElement(it.number, null)
|
||||
else -> throw AssemblyError("invalid array element $it")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// override fun visit(decl: VarDecl) {
|
||||
// val node =
|
||||
// when(decl.type) {
|
||||
// VarDeclType.VAR -> {
|
||||
// var initialNumeric = (decl.value as? NumericLiteral)?.number
|
||||
// if(initialNumeric==0.0)
|
||||
// initialNumeric=null // variable will go into BSS and this will be set to 0
|
||||
// val initialStringLit = decl.value as? StringLiteral
|
||||
// val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
||||
// val initialArrayLit = decl.value as? ArrayLiteral
|
||||
// val initialArray = makeInitialArray(initialArrayLit)
|
||||
// if(decl.isArray && decl.datatype !in ArrayDatatypes)
|
||||
// throw FatalAstException("array vardecl has mismatched dt ${decl.datatype}")
|
||||
// val numElements =
|
||||
// if(decl.isArray)
|
||||
// decl.arraysize!!.constIndex()
|
||||
// else if(initialStringLit!=null)
|
||||
// initialStringLit.value.length+1 // include the terminating 0-byte
|
||||
// else
|
||||
// null
|
||||
// val bss = if(decl.datatype==DataType.STR)
|
||||
// false
|
||||
// else if(decl.isArray)
|
||||
// initialArray.isNullOrEmpty()
|
||||
// else
|
||||
// initialNumeric == null
|
||||
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||
// StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, astNode, decl.position)
|
||||
// }
|
||||
// VarDeclType.CONST -> {
|
||||
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||
// StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, astNode, decl.position)
|
||||
// }
|
||||
// VarDeclType.MEMORY -> {
|
||||
// val numElements =
|
||||
// if(decl.datatype in ArrayDatatypes)
|
||||
// decl.arraysize!!.constIndex()
|
||||
// else null
|
||||
// val astNode = PtVariable(decl.name, decl.datatype, null, null, decl.position)
|
||||
// StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), numElements, astNode, decl.position)
|
||||
// }
|
||||
// }
|
||||
// scopestack.peek().add(node)
|
||||
// // st.origAstLinks[decl] = node
|
||||
// }
|
||||
//
|
||||
// private fun makeInitialArray(arrayLit: ArrayLiteral?): StArray? {
|
||||
// if(arrayLit==null)
|
||||
// return null
|
||||
// return arrayLit.value.map {
|
||||
// when(it){
|
||||
// is AddressOf -> {
|
||||
// val scopedName = it.identifier.targetNameAndType(program).first
|
||||
// StArrayElement(null, scopedName)
|
||||
// }
|
||||
// is IdentifierReference -> {
|
||||
// val scopedName = it.targetNameAndType(program).first
|
||||
// StArrayElement(null, scopedName)
|
||||
// }
|
||||
// is NumericLiteral -> StArrayElement(it.number, null)
|
||||
// else -> throw FatalAstException("weird element dt in array literal")
|
||||
// }
|
||||
// }.toList()
|
||||
// }
|
||||
//
|
@ -1,6 +1,9 @@
|
||||
package prog8.code.ast
|
||||
|
||||
import prog8.code.core.*
|
||||
import prog8.code.core.IMemSizer
|
||||
import prog8.code.core.IStringEncoding
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.SourceCode
|
||||
import java.nio.file.Path
|
||||
|
||||
// New simplified AST for the code generator.
|
||||
@ -11,16 +14,6 @@ sealed class PtNode(val position: Position) {
|
||||
val children = mutableListOf<PtNode>()
|
||||
lateinit var parent: PtNode
|
||||
|
||||
fun printIndented(indent: Int) {
|
||||
print(" ".repeat(indent))
|
||||
print("${this.javaClass.simpleName} ")
|
||||
printProperties()
|
||||
println()
|
||||
children.forEach { it.printIndented(indent+1) }
|
||||
}
|
||||
|
||||
abstract fun printProperties()
|
||||
|
||||
fun add(child: PtNode) {
|
||||
children.add(child)
|
||||
child.parent = this
|
||||
@ -34,23 +27,24 @@ sealed class PtNode(val position: Position) {
|
||||
fun definingBlock() = findParentNode<PtBlock>(this)
|
||||
fun definingSub() = findParentNode<PtSub>(this)
|
||||
fun definingAsmSub() = findParentNode<PtAsmSub>(this)
|
||||
fun definingISub() = findParentNode<IPtSubroutine>(this)
|
||||
}
|
||||
|
||||
|
||||
class PtNodeGroup : PtNode(Position.DUMMY) {
|
||||
override fun printProperties() {}
|
||||
}
|
||||
class PtNodeGroup : PtNode(Position.DUMMY)
|
||||
|
||||
|
||||
sealed class PtNamedNode(val name: String, position: Position): PtNode(position) {
|
||||
val scopedName: List<String> by lazy {
|
||||
sealed class PtNamedNode(var name: String, position: Position): PtNode(position) {
|
||||
// Note that as an exception, the 'name' is not read-only
|
||||
// but a var. This is to allow for cheap node renames.
|
||||
val scopedName: String by lazy {
|
||||
var namedParent: PtNode = this.parent
|
||||
if(namedParent is PtProgram)
|
||||
listOf(name)
|
||||
name
|
||||
else {
|
||||
while (namedParent !is PtNamedNode)
|
||||
namedParent = namedParent.parent
|
||||
namedParent.scopedName + name
|
||||
namedParent.scopedName + "." + name
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -61,10 +55,6 @@ class PtProgram(
|
||||
val memsizer: IMemSizer,
|
||||
val encoding: IStringEncoding
|
||||
) : PtNode(Position.DUMMY) {
|
||||
fun print() = printIndented(0)
|
||||
override fun printProperties() {
|
||||
print("'$name'")
|
||||
}
|
||||
|
||||
// fun allModuleDirectives(): Sequence<PtDirective> =
|
||||
// children.asSequence().flatMap { it.children }.filterIsInstance<PtDirective>().distinct()
|
||||
@ -82,12 +72,9 @@ class PtBlock(name: String,
|
||||
val library: Boolean,
|
||||
val forceOutput: Boolean,
|
||||
val alignment: BlockAlignment,
|
||||
val source: SourceCode, // taken from the module the block is defined in.
|
||||
position: Position
|
||||
) : PtNamedNode(name, position) {
|
||||
override fun printProperties() {
|
||||
print("$name addr=$address library=$library forceOutput=$forceOutput alignment=$alignment")
|
||||
}
|
||||
|
||||
enum class BlockAlignment {
|
||||
NONE,
|
||||
WORD,
|
||||
@ -97,8 +84,6 @@ class PtBlock(name: String,
|
||||
|
||||
|
||||
class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Position) : PtNode(position) {
|
||||
override fun printProperties() {}
|
||||
|
||||
init {
|
||||
require(!assembly.startsWith('\n') && !assembly.startsWith('\r')) { "inline assembly should be trimmed" }
|
||||
require(!assembly.endsWith('\n') && !assembly.endsWith('\r')) { "inline assembly should be trimmed" }
|
||||
@ -106,34 +91,16 @@ class PtInlineAssembly(val assembly: String, val isIR: Boolean, position: Positi
|
||||
}
|
||||
|
||||
|
||||
class PtLabel(name: String, position: Position) : PtNamedNode(name, position) {
|
||||
override fun printProperties() {
|
||||
print(name)
|
||||
}
|
||||
}
|
||||
class PtLabel(name: String, position: Position) : PtNamedNode(name, position)
|
||||
|
||||
|
||||
class PtBreakpoint(position: Position): PtNode(position) {
|
||||
override fun printProperties() {}
|
||||
}
|
||||
class PtBreakpoint(position: Position): PtNode(position)
|
||||
|
||||
|
||||
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position) {
|
||||
override fun printProperties() {
|
||||
print("filename=$file offset=$offset length=$length")
|
||||
}
|
||||
}
|
||||
class PtIncludeBinary(val file: Path, val offset: UInt?, val length: UInt?, position: Position) : PtNode(position)
|
||||
|
||||
|
||||
class PtNop(position: Position): PtNode(position) {
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
||||
|
||||
class PtScopeVarsDecls(position: Position): PtNode(position) {
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
||||
class PtNop(position: Position): PtNode(position)
|
||||
|
||||
|
||||
// find the parent node of a specific type or interface
|
||||
|
@ -1,9 +1,8 @@
|
||||
package prog8.code.ast
|
||||
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.*
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.round
|
||||
|
||||
|
||||
@ -23,17 +22,13 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
}
|
||||
}
|
||||
|
||||
override fun printProperties() {
|
||||
print(type)
|
||||
}
|
||||
|
||||
infix fun isSameAs(other: PtExpression): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> other is PtAddressOf && other.type==type && other.identifier isSameAs identifier
|
||||
is PtArrayIndexer -> other is PtArrayIndexer && other.type==type && other.variable isSameAs variable && other.index isSameAs index
|
||||
is PtBinaryExpression -> other is PtBinaryExpression && other.left isSameAs left && other.right isSameAs right
|
||||
is PtContainmentCheck -> other is PtContainmentCheck && other.type==type && other.element isSameAs element && other.iterable isSameAs iterable
|
||||
is PtIdentifier -> other is PtIdentifier && other.type==type && other.targetName==targetName
|
||||
is PtIdentifier -> other is PtIdentifier && other.type==type && other.name==name
|
||||
is PtMachineRegister -> other is PtMachineRegister && other.type==type && other.register==register
|
||||
is PtMemoryByte -> other is PtMemoryByte && other.address isSameAs address
|
||||
is PtNumber -> other is PtNumber && other.type==type && other.number==number
|
||||
@ -43,6 +38,69 @@ sealed class PtExpression(val type: DataType, position: Position) : PtNode(posit
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
infix fun isSameAs(target: PtAssignTarget): Boolean {
|
||||
return when {
|
||||
target.memory != null && this is PtMemoryByte-> {
|
||||
target.memory!!.address isSameAs this.address
|
||||
}
|
||||
target.identifier != null && this is PtIdentifier -> {
|
||||
this.name == target.identifier!!.name
|
||||
}
|
||||
target.array != null && this is PtArrayIndexer -> {
|
||||
this.variable.name == target.array!!.variable.name && this.index isSameAs target.array!!.index
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun asConstInteger(): Int? = (this as? PtNumber)?.number?.toInt()
|
||||
|
||||
fun isSimple(): Boolean {
|
||||
return when(this) {
|
||||
is PtAddressOf -> true
|
||||
is PtArray -> true
|
||||
is PtArrayIndexer -> index is PtNumber || index is PtIdentifier
|
||||
is PtBinaryExpression -> false
|
||||
is PtBuiltinFunctionCall -> name in arrayOf("msb", "lsb", "peek", "peekw", "mkword", "set_carry", "set_irqd", "clear_carry", "clear_irqd")
|
||||
is PtContainmentCheck -> false
|
||||
is PtFunctionCall -> false
|
||||
is PtIdentifier -> true
|
||||
is PtMachineRegister -> true
|
||||
is PtMemoryByte -> address is PtNumber || address is PtIdentifier
|
||||
is PtNumber -> true
|
||||
is PtPrefix -> value.isSimple()
|
||||
is PtRange -> true
|
||||
is PtString -> true
|
||||
is PtTypeCast -> value.isSimple()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
fun clone(): PtExpression {
|
||||
fun withClonedChildrenFrom(orig: PtExpression, clone: PtExpression): PtExpression {
|
||||
orig.children.forEach { clone.add((it as PtExpression).clone()) }
|
||||
return clone
|
||||
}
|
||||
when(this) {
|
||||
is PtAddressOf -> return withClonedChildrenFrom(this, PtAddressOf(position))
|
||||
is PtArray -> return withClonedChildrenFrom(this, PtArray(type, position))
|
||||
is PtArrayIndexer -> return withClonedChildrenFrom(this, PtArrayIndexer(type, position))
|
||||
is PtBinaryExpression -> return withClonedChildrenFrom(this, PtBinaryExpression(operator, type, position))
|
||||
is PtBuiltinFunctionCall -> return withClonedChildrenFrom(this, PtBuiltinFunctionCall(name, void, hasNoSideEffects, type, position))
|
||||
is PtContainmentCheck -> return withClonedChildrenFrom(this, PtContainmentCheck(position))
|
||||
is PtFunctionCall -> return withClonedChildrenFrom(this, PtFunctionCall(name, void, type, position))
|
||||
is PtIdentifier -> return withClonedChildrenFrom(this, PtIdentifier(name, type, position))
|
||||
is PtMachineRegister -> return withClonedChildrenFrom(this, PtMachineRegister(register, type, position))
|
||||
is PtMemoryByte -> return withClonedChildrenFrom(this, PtMemoryByte(position))
|
||||
is PtNumber -> return withClonedChildrenFrom(this, PtNumber(type, number, position))
|
||||
is PtPrefix -> return withClonedChildrenFrom(this, PtPrefix(operator, type, position))
|
||||
is PtRange -> return withClonedChildrenFrom(this, PtRange(type, position))
|
||||
is PtString -> return withClonedChildrenFrom(this, PtString(value, encoding, position))
|
||||
is PtTypeCast -> return withClonedChildrenFrom(this, PtTypeCast(type, position))
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
||||
@ -51,11 +109,15 @@ class PtAddressOf(position: Position) : PtExpression(DataType.UWORD, position) {
|
||||
}
|
||||
|
||||
|
||||
class PtArrayIndexer(type: DataType, position: Position): PtExpression(type, position) {
|
||||
class PtArrayIndexer(elementType: DataType, position: Position): PtExpression(elementType, position) {
|
||||
val variable: PtIdentifier
|
||||
get() = children[0] as PtIdentifier
|
||||
val index: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
|
||||
init {
|
||||
require(elementType in NumericDatatypes)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -66,6 +128,9 @@ class PtArray(type: DataType, position: Position): PtExpression(type, position)
|
||||
return false
|
||||
return type==other.type && children == other.children
|
||||
}
|
||||
|
||||
val size: Int
|
||||
get() = children.size
|
||||
}
|
||||
|
||||
|
||||
@ -81,9 +146,6 @@ class PtBuiltinFunctionCall(val name: String,
|
||||
|
||||
val args: List<PtExpression>
|
||||
get() = children.map { it as PtExpression }
|
||||
override fun printProperties() {
|
||||
print("$name void=$void noSideFx=$hasNoSideEffects")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -93,10 +155,6 @@ class PtBinaryExpression(val operator: String, type: DataType, position: Positio
|
||||
get() = children[0] as PtExpression
|
||||
val right: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
|
||||
override fun printProperties() {
|
||||
print("$operator -> $type")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -108,7 +166,7 @@ class PtContainmentCheck(position: Position): PtExpression(DataType.UBYTE, posit
|
||||
}
|
||||
|
||||
|
||||
class PtFunctionCall(val functionName: List<String>,
|
||||
class PtFunctionCall(val name: String,
|
||||
val void: Boolean,
|
||||
type: DataType,
|
||||
position: Position) : PtExpression(type, position) {
|
||||
@ -119,28 +177,25 @@ class PtFunctionCall(val functionName: List<String>,
|
||||
|
||||
val args: List<PtExpression>
|
||||
get() = children.map { it as PtExpression }
|
||||
override fun printProperties() {
|
||||
print("${functionName.joinToString(".")} void=$void")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtIdentifier(val ref: List<String>, val targetName: List<String>, type: DataType, position: Position) : PtExpression(type, position) {
|
||||
override fun printProperties() {
|
||||
print("$ref --> $targetName $type")
|
||||
}
|
||||
}
|
||||
class PtIdentifier(val name: String, type: DataType, position: Position) : PtExpression(type, position)
|
||||
|
||||
|
||||
class PtMemoryByte(position: Position) : PtExpression(DataType.UBYTE, position) {
|
||||
val address: PtExpression
|
||||
get() = children.single() as PtExpression
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
||||
|
||||
class PtNumber(type: DataType, val number: Double, position: Position) : PtExpression(type, position) {
|
||||
|
||||
companion object {
|
||||
fun fromBoolean(bool: Boolean, position: Position): PtNumber =
|
||||
PtNumber(DataType.UBYTE, if(bool) 1.0 else 0.0, position)
|
||||
}
|
||||
|
||||
init {
|
||||
if(type==DataType.BOOL)
|
||||
throw IllegalArgumentException("bool should have become ubyte @$position")
|
||||
@ -151,10 +206,6 @@ class PtNumber(type: DataType, val number: Double, position: Position) : PtExpre
|
||||
}
|
||||
}
|
||||
|
||||
override fun printProperties() {
|
||||
print("$number ($type)")
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(type, number)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@ -175,10 +226,6 @@ class PtPrefix(val operator: String, type: DataType, position: Position): PtExpr
|
||||
// note: the "not" operator may no longer occur in the ast; not x should have been replaced with x==0
|
||||
require(operator in setOf("+", "-", "~")) { "invalid prefix operator: $operator" }
|
||||
}
|
||||
|
||||
override fun printProperties() {
|
||||
print(operator)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -190,15 +237,36 @@ class PtRange(type: DataType, position: Position) : PtExpression(type, position)
|
||||
val step: PtNumber
|
||||
get() = children[2] as PtNumber
|
||||
|
||||
override fun printProperties() {}
|
||||
fun toConstantIntegerRange(): IntProgression? {
|
||||
fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
|
||||
return when {
|
||||
fromVal <= toVal -> when {
|
||||
stepVal <= 0 -> IntRange.EMPTY
|
||||
stepVal == 1 -> fromVal..toVal
|
||||
else -> fromVal..toVal step stepVal
|
||||
}
|
||||
else -> when {
|
||||
stepVal >= 0 -> IntRange.EMPTY
|
||||
stepVal == -1 -> fromVal downTo toVal
|
||||
else -> fromVal downTo toVal step abs(stepVal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val fromLv = from as? PtNumber
|
||||
val toLv = to as? PtNumber
|
||||
val stepLv = step as? PtNumber
|
||||
if(fromLv==null || toLv==null || stepLv==null)
|
||||
return null
|
||||
val fromVal = fromLv.number.toInt()
|
||||
val toVal = toLv.number.toInt()
|
||||
val stepVal = stepLv.number.toInt()
|
||||
return makeRange(fromVal, toVal, stepVal)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtString(val value: String, val encoding: Encoding, position: Position) : PtExpression(DataType.STR, position) {
|
||||
override fun printProperties() {
|
||||
print("$encoding:\"$value\"")
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(value, encoding)
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if(other==null || other !is PtString)
|
||||
@ -214,12 +282,8 @@ class PtTypeCast(type: DataType, position: Position) : PtExpression(type, positi
|
||||
}
|
||||
|
||||
|
||||
// special node that isn't created from compiling user code, but used internally
|
||||
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position) {
|
||||
override fun printProperties() {
|
||||
print("reg=$register $type")
|
||||
}
|
||||
}
|
||||
// special node that isn't created from compiling user code, but used internally in the Intermediate Code
|
||||
class PtMachineRegister(val register: Int, type: DataType, position: Position) : PtExpression(type, position)
|
||||
|
||||
|
||||
fun constValue(expr: PtExpression): Double? = if(expr is PtNumber) expr.number else null
|
||||
|
157
codeCore/src/prog8/code/ast/AstPrinter.kt
Normal file
157
codeCore/src/prog8/code/ast/AstPrinter.kt
Normal file
@ -0,0 +1,157 @@
|
||||
package prog8.code.ast
|
||||
|
||||
import prog8.code.core.*
|
||||
|
||||
/**
|
||||
* Produces readable text from a [PtNode] (AST node, usually starting with PtProgram as root),
|
||||
* passing it as a String to the specified receiver function.
|
||||
*/
|
||||
fun printAst(root: PtNode, output: (text: String) -> Unit) {
|
||||
fun type(dt: DataType) = "!${dt.name.lowercase()}!"
|
||||
fun txt(node: PtNode): String {
|
||||
return when(node) {
|
||||
is PtAssignTarget -> "<target>"
|
||||
is PtAssignment -> "<assign>"
|
||||
is PtAugmentedAssign -> "<inplace-assign> ${node.operator}"
|
||||
is PtBreakpoint -> "%breakpoint"
|
||||
is PtConditionalBranch -> "if_${node.condition.name.lowercase()}"
|
||||
is PtAddressOf -> "&"
|
||||
is PtArray -> "array len=${node.children.size} ${type(node.type)}"
|
||||
is PtArrayIndexer -> "<arrayindexer> ${type(node.type)}"
|
||||
is PtBinaryExpression -> "<expr> ${node.operator} ${type(node.type)}"
|
||||
is PtBuiltinFunctionCall -> {
|
||||
val str = if(node.void) "void " else ""
|
||||
str + node.name + "()"
|
||||
}
|
||||
is PtContainmentCheck -> "in"
|
||||
is PtFunctionCall -> {
|
||||
val str = if(node.void) "void " else ""
|
||||
str + node.name + "()"
|
||||
}
|
||||
is PtIdentifier -> "${node.name} ${type(node.type)}"
|
||||
is PtMachineRegister -> "VMREG#${node.register} ${type(node.type)}"
|
||||
is PtMemoryByte -> "@()"
|
||||
is PtNumber -> {
|
||||
val numstr = if(node.type == DataType.FLOAT) node.number.toString() else node.number.toHex()
|
||||
"$numstr ${type(node.type)}"
|
||||
}
|
||||
is PtPrefix -> node.operator
|
||||
is PtRange -> "<range>"
|
||||
is PtString -> "\"${node.value.escape()}\""
|
||||
is PtTypeCast -> "as ${node.type.name.lowercase()}"
|
||||
is PtForLoop -> "for"
|
||||
is PtIfElse -> "ifelse"
|
||||
is PtIncludeBinary -> "%incbin '${node.file}', ${node.offset}, ${node.length}"
|
||||
is PtInlineAssembly -> {
|
||||
if(node.isIR)
|
||||
"%ir {{ ...${node.assembly.length} characters... }}"
|
||||
else
|
||||
"%asm {{ ...${node.assembly.length} characters... }}"
|
||||
}
|
||||
is PtJump -> {
|
||||
if(node.identifier!=null)
|
||||
"goto ${node.identifier.name}"
|
||||
else if(node.address!=null)
|
||||
"goto ${node.address.toHex()}"
|
||||
else if(node.generatedLabel!=null)
|
||||
"goto ${node.generatedLabel}"
|
||||
else
|
||||
"???"
|
||||
}
|
||||
is PtAsmSub -> {
|
||||
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
|
||||
val clobbers = if (node.clobbers.isEmpty()) "" else "clobbers ${node.clobbers}"
|
||||
val returns = if (node.returns.isEmpty()) "" else (if (node.returns.size == 1) "-> ${node.returns[0].second.name.lowercase()}" else "-> ${node.returns.map { it.second.name.lowercase() }}")
|
||||
val str = if (node.inline) "inline " else ""
|
||||
if(node.address==null) {
|
||||
str + "asmsub ${node.name}($params) $clobbers $returns"
|
||||
} else {
|
||||
str + "romsub ${node.address.toHex()} = ${node.name}($params) $clobbers $returns"
|
||||
}
|
||||
}
|
||||
is PtBlock -> {
|
||||
val addr = if(node.address==null) "" else "@${node.address.toHex()}"
|
||||
val align = if(node.alignment==PtBlock.BlockAlignment.NONE) "" else "align=${node.alignment}"
|
||||
"\nblock '${node.name}' $addr $align"
|
||||
}
|
||||
is PtConstant -> {
|
||||
val value = if(node.type in IntegerDatatypes) node.value.toInt().toString() else node.value.toString()
|
||||
"const ${node.type.name.lowercase()} ${node.name} = $value"
|
||||
}
|
||||
is PtLabel -> "${node.name}:"
|
||||
is PtMemMapped -> {
|
||||
if(node.type in ArrayDatatypes) {
|
||||
val arraysize = if(node.arraySize==null) "" else node.arraySize.toString()
|
||||
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||
"&${eltType.name.lowercase()}[$arraysize] ${node.name} = ${node.address.toHex()}"
|
||||
} else {
|
||||
"&${node.type.name.lowercase()} ${node.name} = ${node.address.toHex()}"
|
||||
}
|
||||
}
|
||||
is PtSub -> {
|
||||
val params = if (node.parameters.isEmpty()) "" else "...TODO ${node.parameters.size} PARAMS..."
|
||||
var str = "sub ${node.name}($params) "
|
||||
if(node.returntype!=null)
|
||||
str += "-> ${node.returntype.name.lowercase()}"
|
||||
str
|
||||
}
|
||||
is PtVariable -> {
|
||||
val str = if(node.arraySize!=null) {
|
||||
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||
"${eltType.name.lowercase()}[${node.arraySize}] ${node.name}"
|
||||
}
|
||||
else if(node.type in ArrayDatatypes) {
|
||||
val eltType = ArrayToElementTypes.getValue(node.type)
|
||||
"${eltType.name.lowercase()}[] ${node.name}"
|
||||
}
|
||||
else
|
||||
"${node.type.name.lowercase()} ${node.name}"
|
||||
if(node.value!=null)
|
||||
str + " = " + txt(node.value)
|
||||
else
|
||||
str
|
||||
}
|
||||
is PtNodeGroup -> "<group>"
|
||||
is PtNop -> "nop"
|
||||
is PtPostIncrDecr -> "<post> ${node.operator}"
|
||||
is PtProgram -> "PROGRAM ${node.name}"
|
||||
is PtRepeatLoop -> "repeat"
|
||||
is PtReturn -> "return"
|
||||
is PtSubroutineParameter -> "${node.type.name.lowercase()} ${node.name}"
|
||||
is PtWhen -> "when"
|
||||
is PtWhenChoice -> {
|
||||
if(node.isElse)
|
||||
"else"
|
||||
else
|
||||
"->"
|
||||
}
|
||||
else -> throw InternalCompilerException("unrecognised ast node $node")
|
||||
}
|
||||
}
|
||||
|
||||
if(root is PtProgram) {
|
||||
output(txt(root))
|
||||
root.children.forEach {
|
||||
walkAst(it) { node, depth ->
|
||||
val txt = txt(node)
|
||||
if(txt.isNotEmpty())
|
||||
output(" ".repeat(depth) + txt(node))
|
||||
}
|
||||
}
|
||||
println()
|
||||
} else {
|
||||
walkAst(root) { node, depth ->
|
||||
val txt = txt(node)
|
||||
if(txt.isNotEmpty())
|
||||
output(" ".repeat(depth) + txt(node))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun walkAst(root: PtNode, act: (node: PtNode, depth: Int) -> Unit) {
|
||||
fun recurse(node: PtNode, depth: Int) {
|
||||
act(node, depth)
|
||||
node.children.forEach { recurse(it, depth+1) }
|
||||
}
|
||||
recurse(root, 0)
|
||||
}
|
@ -3,48 +3,39 @@ package prog8.code.ast
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
sealed interface IPtSubroutine {
|
||||
val name: String
|
||||
}
|
||||
|
||||
class PtAsmSub(
|
||||
name: String,
|
||||
val address: UInt?,
|
||||
val clobbers: Set<CpuRegister>,
|
||||
val parameters: List<Pair<PtSubroutineParameter, RegisterOrStatusflag>>,
|
||||
val returnTypes: List<DataType>, // TODO join with register as Pairs ?
|
||||
val retvalRegisters: List<RegisterOrStatusflag>,
|
||||
val parameters: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>,
|
||||
val returns: List<Pair<RegisterOrStatusflag, DataType>>,
|
||||
val inline: Boolean,
|
||||
position: Position
|
||||
) : PtNamedNode(name, position) {
|
||||
override fun printProperties() {
|
||||
print("$name inline=$inline")
|
||||
}
|
||||
}
|
||||
) : PtNamedNode(name, position), IPtSubroutine
|
||||
|
||||
|
||||
class PtSub(
|
||||
name: String,
|
||||
val parameters: List<PtSubroutineParameter>,
|
||||
val returntype: DataType?,
|
||||
val inline: Boolean,
|
||||
position: Position
|
||||
) : PtNamedNode(name, position) {
|
||||
override fun printProperties() {
|
||||
print(name)
|
||||
}
|
||||
|
||||
) : PtNamedNode(name, position), IPtSubroutine {
|
||||
init {
|
||||
// params and return value should not be str
|
||||
if(parameters.any{ it.type !in NumericDatatypes })
|
||||
throw AssemblyError("non-numeric parameter")
|
||||
if(returntype!=null && returntype !in NumericDatatypes)
|
||||
throw AssemblyError("non-numeric returntype $returntype")
|
||||
parameters.forEach { it.parent=this }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtSubroutineParameter(val name: String, val type: DataType, position: Position): PtNode(position) {
|
||||
override fun printProperties() {
|
||||
print("$type $name")
|
||||
}
|
||||
}
|
||||
class PtSubroutineParameter(name: String, val type: DataType, position: Position): PtNamedNode(name, position)
|
||||
|
||||
|
||||
class PtAssignment(position: Position) : PtNode(position) {
|
||||
@ -52,38 +43,22 @@ class PtAssignment(position: Position) : PtNode(position) {
|
||||
get() = children[0] as PtAssignTarget
|
||||
val value: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
}
|
||||
|
||||
override fun printProperties() { }
|
||||
|
||||
val isInplaceAssign: Boolean by lazy {
|
||||
val target = target.children.single() as PtExpression
|
||||
when(val source = value) {
|
||||
is PtArrayIndexer -> {
|
||||
if(target is PtArrayIndexer && source.type==target.type) {
|
||||
if(target.variable isSameAs source.variable) {
|
||||
target.index isSameAs source.index
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
is PtIdentifier -> target is PtIdentifier && target.type==source.type && target.targetName==source.targetName
|
||||
is PtMachineRegister -> target is PtMachineRegister && target.register==source.register
|
||||
is PtMemoryByte -> target is PtMemoryByte && target.address isSameAs source.address
|
||||
is PtNumber -> target is PtNumber && target.type == source.type && target.number==source.number
|
||||
is PtAddressOf -> target is PtAddressOf && target.identifier isSameAs source.identifier
|
||||
is PtPrefix -> {
|
||||
(target is PtPrefix && target.operator==source.operator && target.value isSameAs source.value)
|
||||
||
|
||||
(target is PtIdentifier && (source.value as? PtIdentifier)?.targetName==target.targetName)
|
||||
}
|
||||
is PtTypeCast -> target is PtTypeCast && target.type==source.type && target.value isSameAs source.value
|
||||
is PtBinaryExpression ->
|
||||
target isSameAs source.left
|
||||
else -> false
|
||||
class PtAugmentedAssign(val operator: String, position: Position) : PtNode(position) {
|
||||
val target: PtAssignTarget
|
||||
get() = children[0] as PtAssignTarget
|
||||
val value: PtExpression
|
||||
get() = children[1] as PtExpression
|
||||
init {
|
||||
require(operator.endsWith('=') || operator in PrefixOperators) {
|
||||
"invalid augmented assign operator $operator"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtAssignTarget(position: Position) : PtNode(position) {
|
||||
val identifier: PtIdentifier?
|
||||
get() = children.single() as? PtIdentifier
|
||||
@ -102,7 +77,7 @@ class PtAssignTarget(position: Position) : PtNode(position) {
|
||||
}
|
||||
}
|
||||
|
||||
override fun printProperties() {}
|
||||
infix fun isSameAs(expression: PtExpression): Boolean = expression.isSameAs(this)
|
||||
}
|
||||
|
||||
|
||||
@ -111,10 +86,6 @@ class PtConditionalBranch(val condition: BranchCondition, position: Position) :
|
||||
get() = children[0] as PtNodeGroup
|
||||
val falseScope: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
|
||||
override fun printProperties() {
|
||||
print(condition)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -125,8 +96,6 @@ class PtForLoop(position: Position) : PtNode(position) {
|
||||
get() = children[1] as PtExpression
|
||||
val statements: PtNodeGroup
|
||||
get() = children[2] as PtNodeGroup
|
||||
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
||||
|
||||
@ -137,8 +106,6 @@ class PtIfElse(position: Position) : PtNode(position) {
|
||||
get() = children[1] as PtNodeGroup
|
||||
val elseScope: PtNodeGroup
|
||||
get() = children[2] as PtNodeGroup
|
||||
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
||||
|
||||
@ -146,10 +113,8 @@ class PtJump(val identifier: PtIdentifier?,
|
||||
val address: UInt?,
|
||||
val generatedLabel: String?,
|
||||
position: Position) : PtNode(position) {
|
||||
override fun printProperties() {
|
||||
identifier?.printProperties()
|
||||
if(address!=null) print(address.toHex())
|
||||
if(generatedLabel!=null) print(generatedLabel)
|
||||
init {
|
||||
identifier?.let {it.parent = this }
|
||||
}
|
||||
}
|
||||
|
||||
@ -157,10 +122,6 @@ class PtJump(val identifier: PtIdentifier?,
|
||||
class PtPostIncrDecr(val operator: String, position: Position) : PtNode(position) {
|
||||
val target: PtAssignTarget
|
||||
get() = children.single() as PtAssignTarget
|
||||
|
||||
override fun printProperties() {
|
||||
print(operator)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -169,8 +130,6 @@ class PtRepeatLoop(position: Position) : PtNode(position) {
|
||||
get() = children[0] as PtExpression
|
||||
val statements: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
||||
|
||||
@ -183,30 +142,26 @@ class PtReturn(position: Position) : PtNode(position) {
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
||||
|
||||
class PtVariable(name: String, val type: DataType, var value: PtExpression?, var arraySize: UInt?, position: Position) : PtNamedNode(name, position) {
|
||||
override fun printProperties() {
|
||||
print("$type $name")
|
||||
sealed interface IPtVariable {
|
||||
val name: String
|
||||
val type: DataType
|
||||
}
|
||||
|
||||
|
||||
class PtVariable(name: String, override val type: DataType, val zeropage: ZeropageWish, val value: PtExpression?, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable {
|
||||
init {
|
||||
value?.let {it.parent=this}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PtConstant(name: String, val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position) {
|
||||
override fun printProperties() {
|
||||
print("$type $name = $value")
|
||||
}
|
||||
}
|
||||
class PtConstant(name: String, override val type: DataType, val value: Double, position: Position) : PtNamedNode(name, position), IPtVariable
|
||||
|
||||
|
||||
class PtMemMapped(name: String, val type: DataType, val address: UInt, position: Position) : PtNamedNode(name, position) {
|
||||
override fun printProperties() {
|
||||
print("&$type $name = ${address.toHex()}")
|
||||
}
|
||||
}
|
||||
class PtMemMapped(name: String, override val type: DataType, val address: UInt, val arraySize: UInt?, position: Position) : PtNamedNode(name, position), IPtVariable
|
||||
|
||||
|
||||
class PtWhen(position: Position) : PtNode(position) {
|
||||
@ -214,8 +169,6 @@ class PtWhen(position: Position) : PtNode(position) {
|
||||
get() = children[0] as PtExpression
|
||||
val choices: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
||||
|
||||
@ -224,5 +177,4 @@ class PtWhenChoice(val isElse: Boolean, position: Position) : PtNode(position) {
|
||||
get() = children[0] as PtNodeGroup
|
||||
val statements: PtNodeGroup
|
||||
get() = children[1] as PtNodeGroup
|
||||
override fun printProperties() {}
|
||||
}
|
||||
|
110
codeCore/src/prog8/code/core/BuiltinFunctions.kt
Normal file
110
codeCore/src/prog8/code/core/BuiltinFunctions.kt
Normal file
@ -0,0 +1,110 @@
|
||||
package prog8.code.core
|
||||
|
||||
class ReturnConvention(val dt: DataType?, val reg: RegisterOrPair?, val floatFac1: Boolean)
|
||||
class ParamConvention(val dt: DataType, val reg: RegisterOrPair?, val variable: Boolean)
|
||||
class CallConvention(val params: List<ParamConvention>, val returns: ReturnConvention) {
|
||||
override fun toString(): String {
|
||||
val paramConvs = params.mapIndexed { index, it ->
|
||||
when {
|
||||
it.reg!=null -> "$index:${it.reg}"
|
||||
it.variable -> "$index:variable"
|
||||
else -> "$index:???"
|
||||
}
|
||||
}
|
||||
val returnConv =
|
||||
when {
|
||||
returns.reg!=null -> returns.reg.toString()
|
||||
returns.floatFac1 -> "floatFAC1"
|
||||
else -> "<no returnvalue>"
|
||||
}
|
||||
return "CallConvention[" + paramConvs.joinToString() + " ; returns: $returnConv]"
|
||||
}
|
||||
}
|
||||
|
||||
class FParam(val name: String, val possibleDatatypes: Array<DataType>)
|
||||
|
||||
class FSignature(val pure: Boolean, // does it have side effects?
|
||||
val parameters: List<FParam>,
|
||||
val returnType: DataType?) {
|
||||
|
||||
fun callConvention(actualParamTypes: List<DataType>): CallConvention {
|
||||
val returns: ReturnConvention = when (returnType) {
|
||||
DataType.UBYTE, DataType.BYTE -> ReturnConvention(returnType, RegisterOrPair.A, false)
|
||||
DataType.UWORD, DataType.WORD -> ReturnConvention(returnType, RegisterOrPair.AY, false)
|
||||
DataType.FLOAT -> ReturnConvention(returnType, null, true)
|
||||
in PassByReferenceDatatypes -> ReturnConvention(returnType!!, RegisterOrPair.AY, false)
|
||||
null -> ReturnConvention(null, null, false)
|
||||
else -> {
|
||||
// return type depends on arg type
|
||||
when (val paramType = actualParamTypes.first()) {
|
||||
DataType.UBYTE, DataType.BYTE -> ReturnConvention(paramType, RegisterOrPair.A, false)
|
||||
DataType.UWORD, DataType.WORD -> ReturnConvention(paramType, RegisterOrPair.AY, false)
|
||||
DataType.FLOAT -> ReturnConvention(paramType, null, true)
|
||||
in PassByReferenceDatatypes -> ReturnConvention(paramType, RegisterOrPair.AY, false)
|
||||
else -> ReturnConvention(paramType, null, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return when {
|
||||
actualParamTypes.isEmpty() -> CallConvention(emptyList(), returns)
|
||||
actualParamTypes.size==1 -> {
|
||||
// one parameter goes via register/registerpair
|
||||
val paramConv = when(val paramType = actualParamTypes[0]) {
|
||||
DataType.UBYTE, DataType.BYTE -> ParamConvention(paramType, RegisterOrPair.A, false)
|
||||
DataType.UWORD, DataType.WORD -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
DataType.FLOAT -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
in PassByReferenceDatatypes -> ParamConvention(paramType, RegisterOrPair.AY, false)
|
||||
else -> ParamConvention(paramType, null, false)
|
||||
}
|
||||
CallConvention(listOf(paramConv), returns)
|
||||
}
|
||||
else -> {
|
||||
// multiple parameters go via variables
|
||||
val paramConvs = actualParamTypes.map { ParamConvention(it, null, true) }
|
||||
CallConvention(paramConvs, returns)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val BuiltinFunctions: Map<String, FSignature> = mapOf(
|
||||
// this set of function have no return value and operate in-place:
|
||||
"rol" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
|
||||
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
|
||||
// cmp returns a status in the carry flag, but not a proper return value
|
||||
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypesNoBool), FParam("value2", NumericDatatypesNoBool)), null),
|
||||
"abs" to FSignature(true, listOf(FParam("value", IntegerDatatypesNoBool)), DataType.UWORD),
|
||||
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), DataType.UWORD),
|
||||
// normal functions follow:
|
||||
"sizeof" to FSignature(true, listOf(FParam("object", DataType.values())), DataType.UBYTE),
|
||||
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypesNoBool)), DataType.BYTE),
|
||||
"sqrt16" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
|
||||
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE),
|
||||
"lsb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||
"msb" to FSignature(true, listOf(FParam("value", arrayOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE),
|
||||
"mkword" to FSignature(true, listOf(FParam("msb", arrayOf(DataType.UBYTE)), FParam("lsb", arrayOf(DataType.UBYTE))), DataType.UWORD),
|
||||
"peek" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UBYTE),
|
||||
"peekw" to FSignature(true, listOf(FParam("address", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"poke" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
"pokemon" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UBYTE))), null),
|
||||
"pokew" to FSignature(false, listOf(FParam("address", arrayOf(DataType.UWORD)), FParam("value", arrayOf(DataType.UWORD))), null),
|
||||
"pop" to FSignature(false, listOf(FParam("target", ByteDatatypes)), null),
|
||||
"popw" to FSignature(false, listOf(FParam("target", WordDatatypes)), null),
|
||||
"push" to FSignature(false, listOf(FParam("value", ByteDatatypes)), null),
|
||||
"pushw" to FSignature(false, listOf(FParam("value", WordDatatypes)), null),
|
||||
"rsave" to FSignature(false, emptyList(), null),
|
||||
"rsavex" to FSignature(false, emptyList(), null),
|
||||
"rrestore" to FSignature(false, emptyList(), null),
|
||||
"rrestorex" to FSignature(false, emptyList(), null),
|
||||
"memory" to FSignature(true, listOf(FParam("name", arrayOf(DataType.STR)), FParam("size", arrayOf(DataType.UWORD)), FParam("alignment", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
"callfar" to FSignature(false, listOf(FParam("bank", arrayOf(DataType.UBYTE)), FParam("address", arrayOf(DataType.UWORD)), FParam("arg", arrayOf(DataType.UWORD))), DataType.UWORD),
|
||||
)
|
||||
|
||||
val InplaceModifyingBuiltinFunctions = setOf("rol", "ror", "rol2", "ror2", "sort", "reverse")
|
@ -16,11 +16,15 @@ class CompilationOptions(val output: OutputType,
|
||||
var slowCodegenWarnings: Boolean = false,
|
||||
var optimize: Boolean = false,
|
||||
var optimizeFloatExpressions: Boolean = false,
|
||||
var dontReinitGlobals: Boolean = false,
|
||||
var asmQuiet: Boolean = false,
|
||||
var asmListfile: Boolean = false,
|
||||
var experimentalCodegen: Boolean = false,
|
||||
var varsHigh: Boolean = false,
|
||||
var evalStackBaseAddress: UInt? = null,
|
||||
var outputDir: Path = Path(""),
|
||||
var symbolDefs: Map<String, String> = emptyMap()
|
||||
)
|
||||
) {
|
||||
init {
|
||||
compTarget.machine.initializeMemoryAreas(this)
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +0,0 @@
|
||||
package prog8.code.core
|
||||
|
||||
interface IAssemblyGenerator {
|
||||
fun compileToAssembly(): IAssemblyProgram?
|
||||
}
|
||||
|
||||
interface IAssemblyProgram {
|
||||
val name: String
|
||||
fun assemble(options: CompilationOptions): Boolean
|
||||
}
|
||||
|
||||
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
19
codeCore/src/prog8/code/core/ICodeGeneratorBackend.kt
Normal file
19
codeCore/src/prog8/code/core/ICodeGeneratorBackend.kt
Normal file
@ -0,0 +1,19 @@
|
||||
package prog8.code.core
|
||||
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
|
||||
interface ICodeGeneratorBackend {
|
||||
fun generate(program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter): IAssemblyProgram?
|
||||
}
|
||||
|
||||
|
||||
interface IAssemblyProgram {
|
||||
val name: String
|
||||
fun assemble(options: CompilationOptions): Boolean
|
||||
}
|
||||
|
||||
fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list"
|
@ -7,5 +7,5 @@ interface ICompilationTarget: IStringEncoding, IMemSizer {
|
||||
val defaultEncoding: Encoding
|
||||
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
|
||||
}
|
||||
|
@ -16,6 +16,8 @@ interface IMachineDefinition {
|
||||
var ESTACK_LO: UInt
|
||||
var ESTACK_HI: UInt
|
||||
val PROGRAM_LOAD_ADDRESS : UInt
|
||||
val BSSHIGHRAM_START: UInt
|
||||
val BSSHIGHRAM_END: UInt
|
||||
|
||||
val cpu: CpuType
|
||||
var zeropage: Zeropage
|
||||
|
@ -10,5 +10,5 @@ enum class Encoding(val prefix: String) {
|
||||
|
||||
interface IStringEncoding {
|
||||
fun encodeString(str: String, encoding: Encoding): List<UByte>
|
||||
fun decodeString(bytes: List<UByte>, encoding: Encoding): String
|
||||
fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ class MemAllocationError(message: String) : Exception(message)
|
||||
abstract class MemoryAllocator(protected val options: CompilationOptions) {
|
||||
data class VarAllocation(val address: UInt, val dt: DataType, val size: Int)
|
||||
|
||||
abstract fun allocate(name: List<String>,
|
||||
abstract fun allocate(name: String,
|
||||
datatype: DataType,
|
||||
numElements: Int?,
|
||||
position: Position?,
|
||||
@ -29,7 +29,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
|
||||
// the variables allocated into Zeropage.
|
||||
// name (scoped) ==> pair of address to (Datatype + bytesize)
|
||||
val allocatedVariables = mutableMapOf<List<String>, VarAllocation>()
|
||||
val allocatedVariables = mutableMapOf<String, VarAllocation>()
|
||||
|
||||
val free = mutableListOf<UInt>() // subclasses must set this to the appropriate free locations.
|
||||
|
||||
@ -51,7 +51,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
return free.windowed(2).any { it[0] == it[1] - 1u }
|
||||
}
|
||||
|
||||
override fun allocate(name: List<String>,
|
||||
override fun allocate(name: String,
|
||||
datatype: DataType,
|
||||
numElements: Int?,
|
||||
position: Position?,
|
||||
@ -107,7 +107,7 @@ abstract class Zeropage(options: CompilationOptions): MemoryAllocator(options) {
|
||||
|
||||
private fun reserve(range: UIntRange) = free.removeAll(range)
|
||||
|
||||
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: List<String>): UInt {
|
||||
private fun makeAllocation(address: UInt, size: Int, datatype: DataType, name: String): UInt {
|
||||
require(size>=0)
|
||||
free.removeAll(address until address+size.toUInt())
|
||||
if(name.isNotEmpty()) {
|
||||
@ -135,7 +135,7 @@ class GoldenRam(options: CompilationOptions, val region: UIntRange): MemoryAlloc
|
||||
private var nextLocation: UInt = region.first
|
||||
|
||||
override fun allocate(
|
||||
name: List<String>,
|
||||
name: String,
|
||||
datatype: DataType,
|
||||
numElements: Int?,
|
||||
position: Position?,
|
||||
|
@ -5,6 +5,7 @@ val ComparisonOperators = setOf("==", "!=", "<", ">", "<=", ">=")
|
||||
val LogicalOperators = setOf("and", "or", "xor", "not")
|
||||
val AugmentAssignmentOperators = setOf("+", "-", "/", "*", "&", "|", "^", "<<", ">>", "%", "and", "or", "xor")
|
||||
val BitwiseOperators = setOf("&", "|", "^", "~")
|
||||
val PrefixOperators = setOf("+", "-", "~", "not")
|
||||
// val InvalidOperatorsForBoolean = setOf("+", "-", "*", "/", "%", "<<", ">>") + BitwiseOperators
|
||||
|
||||
fun invertedComparisonOperator(operator: String) =
|
||||
|
@ -23,7 +23,7 @@ object Encoder: IStringEncoding {
|
||||
success = { it }
|
||||
)
|
||||
}
|
||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||
val decoded = when(encoding) {
|
||||
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
|
||||
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
|
||||
|
@ -13,9 +13,12 @@ class AtariMachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MEM_SIZE = 6
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x2000u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive // TODO
|
||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive // TODO
|
||||
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x1b00u // $1b00-$1b7f inclusive // TODO
|
||||
override var ESTACK_HI = 0x1b80u // $1b80-$1bff inclusive // TODO
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // TODO
|
||||
override val BSSHIGHRAM_END = 0u // TODO
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
@ -14,9 +14,13 @@ class C128MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x1c01u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x1a00u // $1a00-$1aff inclusive
|
||||
override var ESTACK_HI = 0x1b00u // $1b00-$1bff inclusive
|
||||
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x1b00u // $1b00-$1b7f inclusive
|
||||
override var ESTACK_HI = 0x1b80u // $1b80-$1bff inclusive
|
||||
|
||||
override val BSSHIGHRAM_START = 0u // TODO
|
||||
override val BSSHIGHRAM_END = 0u // TODO
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
|
@ -15,9 +15,13 @@ class C64MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0xce00u // $ce00-$ceff inclusive
|
||||
override var ESTACK_HI = 0xcf00u // $ce00-$ceff inclusive
|
||||
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0xcf00u // $cf00-$cf7f inclusive
|
||||
override var ESTACK_HI = 0xcf80u // $cf80-$cfff inclusive
|
||||
|
||||
override val BSSHIGHRAM_START = 0xc000u
|
||||
override val BSSHIGHRAM_END = ESTACK_LO
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
|
@ -83,12 +83,12 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||
// The base addres is $04. Unfortunately it cannot be the same as on the Commander X16 ($02).
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
allocatedVariables["cx16.r${reg}"] = VarAllocation((4+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables["cx16.r${reg}s"] = VarAllocation((4+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables["cx16.r${reg}L"] = VarAllocation((4+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables["cx16.r${reg}H"] = VarAllocation((5+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((4+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((5+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
free.remove((4+reg*2).toUInt())
|
||||
free.remove((5+reg*2).toUInt())
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ object AtasciiEncoding {
|
||||
return Ok(mapped)
|
||||
}
|
||||
|
||||
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
|
||||
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
|
||||
return Ok(bytes.map { decodeTable[it.toInt()] }.joinToString(""))
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ object IsoEncoding {
|
||||
}
|
||||
}
|
||||
|
||||
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
|
||||
fun decode(bytes: Iterable<UByte>): Result<String, CharConversionException> {
|
||||
return try {
|
||||
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
|
||||
} catch (ce: CharConversionException) {
|
||||
|
@ -14,9 +14,13 @@ class CX16MachineDefinition: IMachineDefinition {
|
||||
override val FLOAT_MEM_SIZE = Mflpt5.FLOAT_MEM_SIZE
|
||||
override val PROGRAM_LOAD_ADDRESS = 0x0801u
|
||||
|
||||
// the 2*256 byte evaluation stack (on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x0400u // $0400-$04ff inclusive
|
||||
override var ESTACK_HI = 0x0500u // $0500-$05ff inclusive
|
||||
// the 2*128 byte evaluation stack (1 page, on which bytes, words, and even floats are stored during calculations)
|
||||
override var ESTACK_LO = 0x0700u // $0700-$077f inclusive
|
||||
override var ESTACK_HI = 0x0780u // $0780-$07ff inclusive
|
||||
|
||||
override val BSSHIGHRAM_START = 0xa000u // hiram bank 1, 8Kb, assumed to be active
|
||||
override val BSSHIGHRAM_END = 0xc000u // rom starts here.
|
||||
|
||||
override lateinit var zeropage: Zeropage
|
||||
override lateinit var golden: GoldenRam
|
||||
|
||||
|
@ -58,12 +58,12 @@ class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
// However, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
|
||||
// This is important because the compiler sometimes treats ZP variables more efficiently (for example if it's a pointer)
|
||||
for(reg in 0..15) {
|
||||
allocatedVariables[listOf("cx16", "r${reg}")] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables[listOf("cx16", "r${reg}s")] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables[listOf("cx16", "r${reg}L")] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables[listOf("cx16", "r${reg}H")] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables[listOf("cx16", "r${reg}sL")] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables[listOf("cx16", "r${reg}sH")] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
allocatedVariables["cx16.r${reg}"] = VarAllocation((2+reg*2).toUInt(), DataType.UWORD, 2) // cx16.r0 .. cx16.r15
|
||||
allocatedVariables["cx16.r${reg}s"] = VarAllocation((2+reg*2).toUInt(), DataType.WORD, 2) // cx16.r0s .. cx16.r15s
|
||||
allocatedVariables["cx16.r${reg}L"] = VarAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0L .. cx16.r15L
|
||||
allocatedVariables["cx16.r${reg}H"] = VarAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1) // cx16.r0H .. cx16.r15H
|
||||
allocatedVariables["cx16.r${reg}sL"] = VarAllocation((2+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sL .. cx16.r15sL
|
||||
allocatedVariables["cx16.r${reg}sH"] = VarAllocation((3+reg*2).toUInt(), DataType.BYTE, 1) // cx16.r0sH .. cx16.r15sH
|
||||
}
|
||||
}
|
||||
}
|
@ -17,10 +17,18 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
|
||||
override var ESTACK_LO = 0u // not actually used
|
||||
override var ESTACK_HI = 0u // not actually used
|
||||
override val BSSHIGHRAM_START = 0u // not actually used
|
||||
override val BSSHIGHRAM_END = 0u // not actually used
|
||||
override lateinit var zeropage: Zeropage // not actually used
|
||||
override lateinit var golden: GoldenRam // not actually used
|
||||
|
||||
override fun getFloatAsmBytes(num: Number) = TODO("float asm bytes from number")
|
||||
override fun getFloatAsmBytes(num: Number): String {
|
||||
// little endian binary representation
|
||||
val bits = num.toFloat().toBits().toUInt()
|
||||
val hexStr = bits.toString(16).padStart(8, '0')
|
||||
val parts = hexStr.chunked(2).map { "\$" + it }
|
||||
return parts.joinToString(", ")
|
||||
}
|
||||
|
||||
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
|
||||
return listOf("syslib")
|
||||
@ -44,9 +52,24 @@ class VirtualMachineDefinition: IMachineDefinition {
|
||||
|
||||
override fun isIOAddress(address: UInt): Boolean = false
|
||||
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {}
|
||||
override fun initializeMemoryAreas(compilerOptions: CompilationOptions) {
|
||||
zeropage = VirtualZeropage(compilerOptions)
|
||||
}
|
||||
}
|
||||
|
||||
interface IVirtualMachineRunner {
|
||||
fun runProgram(irSource: String)
|
||||
}
|
||||
|
||||
private class VirtualZeropage(options: CompilationOptions): Zeropage(options) {
|
||||
override val SCRATCH_B1: UInt
|
||||
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||
override val SCRATCH_REG: UInt
|
||||
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||
override val SCRATCH_W1: UInt
|
||||
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||
override val SCRATCH_W2: UInt
|
||||
get() = throw IllegalStateException("virtual shouldn't use this zeropage variable")
|
||||
|
||||
override fun allocateCx16VirtualRegisters() { /* there is no actual zero page in this target to allocate thing in */ }
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ plugins {
|
||||
id 'java'
|
||||
id 'application'
|
||||
id "org.jetbrains.kotlin.jvm"
|
||||
id "io.kotest" version "0.3.9"
|
||||
}
|
||||
|
||||
java {
|
||||
@ -25,11 +26,11 @@ compileTestKotlin {
|
||||
|
||||
dependencies {
|
||||
implementation project(':codeCore')
|
||||
implementation project(':compilerAst')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation "com.michael-bull.kotlin-result:kotlin-result-jvm:1.1.16"
|
||||
|
||||
testImplementation 'io.kotest:kotest-runner-junit5-jvm:5.3.2'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@ -41,6 +42,22 @@ sourceSets {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDir "${project.projectDir}/test"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note: there are no unit tests in this module!
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
@ -4,13 +4,15 @@
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="module" module-name="codeCore" />
|
||||
<orderEntry type="module" module-name="compilerAst" />
|
||||
<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" />
|
||||
</component>
|
||||
</module>
|
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,15 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.statements.VarDeclType
|
||||
import prog8.code.StConstant
|
||||
import prog8.code.StMemVar
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.core.IMachineDefinition
|
||||
|
||||
|
||||
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
|
||||
|
||||
|
||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
|
||||
internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, symbolTable: SymbolTable): Int {
|
||||
|
||||
var numberOfOptimizations = 0
|
||||
|
||||
@ -37,7 +36,7 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeStoreLoadSame(linesByFour, machine, program)
|
||||
mods = optimizeStoreLoadSame(linesByFour, machine, symbolTable)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFour = getLinesBy(lines, 4)
|
||||
@ -52,14 +51,14 @@ internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefin
|
||||
}
|
||||
|
||||
var linesByFourteen = getLinesBy(lines, 14)
|
||||
mods = optimizeSameAssignments(linesByFourteen, machine, program)
|
||||
mods = optimizeSameAssignments(linesByFourteen, machine, symbolTable)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFourteen = getLinesBy(lines, 14)
|
||||
numberOfOptimizations++
|
||||
}
|
||||
|
||||
mods = optimizeSamePointerIndexing(linesByFourteen, machine, program)
|
||||
mods = optimizeSamePointerIndexing(linesByFourteen)
|
||||
if(mods.isNotEmpty()) {
|
||||
apply(mods, lines)
|
||||
linesByFourteen = getLinesBy(lines, 14)
|
||||
@ -129,7 +128,11 @@ private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<S
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||
private fun optimizeSameAssignments(
|
||||
linesByFourteen: List<List<IndexedValue<String>>>,
|
||||
machine: IMachineDefinition,
|
||||
symbolTable: SymbolTable
|
||||
): List<Modification> {
|
||||
|
||||
// Optimize sequential assignments of the same value to various targets (bytes, words, floats)
|
||||
// the float one is the one that requires 2*7=14 lines of code to check...
|
||||
@ -154,8 +157,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val fourthvalue = sixth.substring(4)
|
||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||
val address1 = getAddressArg(first, program)
|
||||
val address2 = getAddressArg(second, program)
|
||||
val address1 = getAddressArg(first, symbolTable)
|
||||
val address2 = getAddressArg(second, symbolTable)
|
||||
if(address1==null || address2==null || (!machine.isIOAddress(address1) && !machine.isIOAddress(address2))) {
|
||||
mods.add(Modification(lines[4].index, true, null))
|
||||
mods.add(Modification(lines[5].index, true, null))
|
||||
@ -168,7 +171,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val secondvalue = third.substring(4)
|
||||
if(firstvalue==secondvalue) {
|
||||
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line)
|
||||
val address = getAddressArg(first, program)
|
||||
val address = getAddressArg(first, symbolTable)
|
||||
if(address==null || !machine.isIOAddress(address))
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
}
|
||||
@ -251,7 +254,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val thirdvalue = third.substring(4)
|
||||
val fourthvalue = fourth.substring(4)
|
||||
if(firstvalue==thirdvalue && secondvalue == fourthvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
val address = getAddressArg(first, symbolTable)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
@ -275,7 +278,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val firstvalue = first.substring(4)
|
||||
val thirdvalue = third.substring(4)
|
||||
if(firstvalue==thirdvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
val address = getAddressArg(first, symbolTable)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
mods.add(Modification(lines[2].index, true, null))
|
||||
@ -295,7 +298,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val secondvalue = second.substring(4)
|
||||
val thirdvalue = third.substring(4)
|
||||
if(firstvalue==secondvalue && firstvalue==thirdvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
val address = getAddressArg(first, symbolTable)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
val reg2 = second[2]
|
||||
@ -314,7 +317,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = second.substring(4)
|
||||
if(firstvalue==secondvalue) {
|
||||
val address = getAddressArg(first, program)
|
||||
val address = getAddressArg(first, symbolTable)
|
||||
if(address==null || !machine.isIOAddress(address)) {
|
||||
overlappingMods = true
|
||||
mods.add(Modification(lines[0].index, true, null))
|
||||
@ -327,7 +330,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||
private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
|
||||
|
||||
// Optimize same pointer indexing where for instance we load and store to the same ptr index in Y
|
||||
// if Y isn't modified in between we can omit the second LDY:
|
||||
@ -369,7 +372,11 @@ private fun optimizeSamePointerIndexing(linesByFourteen: List<List<IndexedValue<
|
||||
return mods
|
||||
}
|
||||
|
||||
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>, machine: IMachineDefinition, program: Program): List<Modification> {
|
||||
private fun optimizeStoreLoadSame(
|
||||
linesByFour: List<List<IndexedValue<String>>>,
|
||||
machine: IMachineDefinition,
|
||||
symbolTable: SymbolTable
|
||||
): List<Modification> {
|
||||
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can OFTEN be eliminated
|
||||
val mods = mutableListOf<Modification>()
|
||||
for (lines in linesByFour) {
|
||||
@ -397,7 +404,7 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
|
||||
}
|
||||
else {
|
||||
// no branch instruction follows, we can remove the load instruction
|
||||
val address = getAddressArg(lines[2].value, program)
|
||||
val address = getAddressArg(lines[2].value, symbolTable)
|
||||
address==null || !machine.isIOAddress(address)
|
||||
}
|
||||
|
||||
@ -439,7 +446,8 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
|
||||
|
||||
private val identifierRegex = Regex("""^([a-zA-Z_$][a-zA-Z\d_\.$]*)""")
|
||||
|
||||
private fun getAddressArg(line: String, program: Program): UInt? {
|
||||
private fun getAddressArg(line: String, symbolTable: SymbolTable): UInt? {
|
||||
// try to get the constant value address, could return null if it's a symbol instead
|
||||
val loadArg = line.trimStart().substring(3).trim()
|
||||
return when {
|
||||
loadArg.startsWith('$') -> loadArg.substring(1).toUIntOrNull(16)
|
||||
@ -450,15 +458,11 @@ private fun getAddressArg(line: String, program: Program): UInt? {
|
||||
val identMatch = identifierRegex.find(loadArg)
|
||||
if(identMatch!=null) {
|
||||
val identifier = identMatch.value
|
||||
val decl = program.toplevelModule.lookup(identifier.split(".")) as? VarDecl
|
||||
if(decl!=null) {
|
||||
when(decl.type){
|
||||
VarDeclType.VAR -> null
|
||||
VarDeclType.CONST,
|
||||
VarDeclType.MEMORY -> (decl.value as NumericLiteral).number.toUInt()
|
||||
}
|
||||
when (val symbol = symbolTable.flat[identifier]) {
|
||||
is StConstant -> symbol.value.toUInt()
|
||||
is StMemVar -> symbol.address
|
||||
else -> null
|
||||
}
|
||||
else null
|
||||
} else null
|
||||
}
|
||||
else -> loadArg.substring(1).toUIntOrNull()
|
||||
|
@ -1,15 +1,12 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.expressions.ArrayIndexedExpression
|
||||
import prog8.ast.expressions.BuiltinFunctionCall
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.Cx16VirtualRegisters
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.core.RegisterOrStatusflag
|
||||
|
||||
|
||||
fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
|
||||
fun asmsub6502ArgsEvalOrder(sub: PtAsmSub): List<Int> {
|
||||
val order = mutableListOf<Int>()
|
||||
// order is:
|
||||
// 1) cx16 virtual word registers,
|
||||
@ -17,43 +14,45 @@ fun asmsub6502ArgsEvalOrder(sub: Subroutine): List<Int> {
|
||||
// 3) single CPU registers (X last), except A,
|
||||
// 4) CPU Carry status flag
|
||||
// 5) the A register itself last (so everything before it can use the accumulator without having to save its value)
|
||||
val args = sub.parameters.zip(sub.asmParameterRegisters).withIndex()
|
||||
val (cx16regs, args2) = args.partition { it.value.second.registerOrPair in Cx16VirtualRegisters }
|
||||
val args = sub.parameters.withIndex()
|
||||
val (cx16regs, args2) = args.partition { it.value.first.registerOrPair in Cx16VirtualRegisters }
|
||||
val pairedRegisters = arrayOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
val (pairedRegs , args3) = args2.partition { it.value.second.registerOrPair in pairedRegisters }
|
||||
val (regsWithoutA, args4) = args3.partition { it.value.second.registerOrPair != RegisterOrPair.A }
|
||||
val (regA, rest) = args4.partition { it.value.second.registerOrPair != null }
|
||||
val (pairedRegs , args3) = args2.partition { it.value.first.registerOrPair in pairedRegisters }
|
||||
val (regsWithoutA, args4) = args3.partition { it.value.first.registerOrPair != RegisterOrPair.A }
|
||||
val (regA, rest) = args4.partition { it.value.first.registerOrPair != null }
|
||||
|
||||
cx16regs.forEach { order += it.index }
|
||||
pairedRegs.forEach { order += it.index }
|
||||
regsWithoutA.forEach {
|
||||
if(it.value.second.registerOrPair != RegisterOrPair.X)
|
||||
if(it.value.first.registerOrPair != RegisterOrPair.X)
|
||||
order += it.index
|
||||
}
|
||||
regsWithoutA.firstOrNull { it.value.second.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
|
||||
regsWithoutA.firstOrNull { it.value.first.registerOrPair==RegisterOrPair.X } ?.let { order += it.index}
|
||||
rest.forEach { order += it.index }
|
||||
regA.forEach { order += it.index }
|
||||
require(order.size==sub.parameters.size)
|
||||
return order
|
||||
}
|
||||
|
||||
fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
|
||||
paramRegisters: List<RegisterOrStatusflag>): Boolean {
|
||||
fun isClobberRisk(expr: Expression): Boolean {
|
||||
fun asmsub6502ArgsHaveRegisterClobberRisk(
|
||||
args: List<PtExpression>,
|
||||
params: List<Pair<RegisterOrStatusflag, PtSubroutineParameter>>
|
||||
): Boolean {
|
||||
fun isClobberRisk(expr: PtExpression): Boolean {
|
||||
when (expr) {
|
||||
is ArrayIndexedExpression -> {
|
||||
return paramRegisters.any {
|
||||
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
is PtArrayIndexer -> {
|
||||
return params.any {
|
||||
it.first.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
|
||||
}
|
||||
}
|
||||
is BuiltinFunctionCall -> {
|
||||
is PtBuiltinFunctionCall -> {
|
||||
if (expr.name == "lsb" || expr.name == "msb")
|
||||
return isClobberRisk(expr.args[0])
|
||||
if (expr.name == "mkword")
|
||||
return isClobberRisk(expr.args[0]) && isClobberRisk(expr.args[1])
|
||||
return !expr.isSimple
|
||||
return !expr.isSimple()
|
||||
}
|
||||
else -> return !expr.isSimple
|
||||
else -> return !expr.isSimple()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ package prog8.codegen.cpu6502
|
||||
import com.github.michaelbull.result.Ok
|
||||
import com.github.michaelbull.result.Result
|
||||
import com.github.michaelbull.result.mapError
|
||||
import prog8.ast.generatedLabelPrefix
|
||||
import prog8.code.core.*
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
@ -63,7 +62,7 @@ internal class AssemblyProgram(
|
||||
"atari" -> {
|
||||
// Atari800XL .xex generation.
|
||||
|
||||
// TODO are these options okay?
|
||||
// TODO are these options okay for atari?
|
||||
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
|
||||
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
|
||||
"--no-monitor"
|
||||
@ -104,7 +103,7 @@ internal class AssemblyProgram(
|
||||
}
|
||||
|
||||
private fun removeGeneratedLabelsFromMonlist() {
|
||||
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
|
||||
val pattern = Regex("""al (\w+) \S+prog8_label_.+?""")
|
||||
val lines = viceMonListFile.toFile().readLines()
|
||||
viceMonListFile.toFile().outputStream().bufferedWriter().use {
|
||||
for (line in lines) {
|
||||
@ -124,7 +123,7 @@ internal class AssemblyProgram(
|
||||
breakpoints.add("break \$" + match.groupValues[1])
|
||||
}
|
||||
val num = breakpoints.size
|
||||
breakpoints.add(0, "; vice monitor breakpoint list now follows")
|
||||
breakpoints.add(0, "; breakpoint list now follows")
|
||||
breakpoints.add(1, "; $num breakpoints have been defined")
|
||||
breakpoints.add(2, "del")
|
||||
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
|
||||
|
@ -1,49 +1,39 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.ArrayIndex
|
||||
import prog8.ast.statements.BuiltinFunctionCallStatement
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.assignment.*
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
import prog8.compiler.FSignature
|
||||
|
||||
|
||||
internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val assignAsmGen: AssignmentAsmGen) {
|
||||
|
||||
internal fun translateFunctioncallExpression(fcall: BuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
val func = BuiltinFunctions.getValue(fcall.target.nameInSource.single())
|
||||
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
|
||||
internal fun translateFunctioncallExpression(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?): DataType? {
|
||||
return translateFunctioncall(fcall, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
|
||||
}
|
||||
|
||||
internal fun translateFunctioncallStatement(fcall: BuiltinFunctionCallStatement) {
|
||||
val func = BuiltinFunctions.getValue(fcall.name)
|
||||
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
|
||||
internal fun translateFunctioncallStatement(fcall: PtBuiltinFunctionCall) {
|
||||
translateFunctioncall(fcall, discardResult = true, resultToStack = false, resultRegister = null)
|
||||
}
|
||||
|
||||
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if (discardResult && func.pure)
|
||||
return // can just ignore the whole function call altogether
|
||||
private fun translateFunctioncall(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?): DataType? {
|
||||
if (discardResult && fcall.hasNoSideEffects)
|
||||
return null // can just ignore the whole function call altogether
|
||||
|
||||
if(discardResult && resultToStack)
|
||||
throw AssemblyError("cannot both discard the result AND put it onto stack")
|
||||
|
||||
val sscope = (fcall as Node).definingSubroutine
|
||||
val sscope = fcall.definingISub()
|
||||
|
||||
when (func.name) {
|
||||
when (fcall.name) {
|
||||
"msb" -> funcMsb(fcall, resultToStack, resultRegister)
|
||||
"lsb" -> funcLsb(fcall, resultToStack, resultRegister)
|
||||
"mkword" -> funcMkword(fcall, resultToStack, resultRegister)
|
||||
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
|
||||
"abs" -> funcAbs(fcall, resultToStack, resultRegister, sscope)
|
||||
"any", "all" -> funcAnyAll(fcall, resultToStack, resultRegister, sscope)
|
||||
"sgn" -> funcSgn(fcall, resultToStack, resultRegister, sscope)
|
||||
"sqrt16" -> funcSqrt16(fcall, resultToStack, resultRegister, sscope)
|
||||
"rol" -> funcRol(fcall)
|
||||
"rol2" -> funcRol2(fcall)
|
||||
"ror" -> funcRor(fcall)
|
||||
@ -59,16 +49,20 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
"push" -> asmgen.pushCpuStack(DataType.UBYTE, fcall.args[0])
|
||||
"pushw" -> asmgen.pushCpuStack(DataType.UWORD, fcall.args[0])
|
||||
"pop" -> {
|
||||
require(fcall.args[0] is IdentifierReference) {
|
||||
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
|
||||
require(fcall.args[0] is PtIdentifier) {
|
||||
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${fcall.position}"
|
||||
}
|
||||
asmgen.popCpuStack(DataType.UBYTE, (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!, (fcall as Node).definingSubroutine)
|
||||
val symbol = asmgen.symbolTable.lookup((fcall.args[0] as PtIdentifier).name)
|
||||
val target = symbol!!.astNode as IPtVariable
|
||||
asmgen.popCpuStack(DataType.UBYTE, target, fcall.definingISub())
|
||||
}
|
||||
"popw" -> {
|
||||
require(fcall.args[0] is IdentifierReference) {
|
||||
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${(fcall as Node).position}"
|
||||
require(fcall.args[0] is PtIdentifier) {
|
||||
"attempt to pop a value into a differently typed variable, or in something else that isn't supported ${fcall.position}"
|
||||
}
|
||||
asmgen.popCpuStack(DataType.UWORD, (fcall.args[0] as IdentifierReference).targetVarDecl(program)!!, (fcall as Node).definingSubroutine)
|
||||
val symbol = asmgen.symbolTable.lookup((fcall.args[0] as PtIdentifier).name)
|
||||
val target = symbol!!.astNode as IPtVariable
|
||||
asmgen.popCpuStack(DataType.UWORD, target, fcall.definingISub())
|
||||
}
|
||||
"rsave" -> funcRsave()
|
||||
"rsavex" -> funcRsaveX()
|
||||
@ -76,9 +70,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
"rrestorex" -> funcRrestoreX()
|
||||
"cmp" -> funcCmp(fcall)
|
||||
"callfar" -> funcCallFar(fcall)
|
||||
"callrom" -> funcCallRom(fcall)
|
||||
else -> throw AssemblyError("missing asmgen for builtin func ${func.name}")
|
||||
else -> throw AssemblyError("missing asmgen for builtin func ${fcall.name}")
|
||||
}
|
||||
|
||||
return BuiltinFunctions.getValue(fcall.name).returnType
|
||||
}
|
||||
|
||||
private fun funcRsave() {
|
||||
@ -132,147 +127,60 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
asmgen.out(" sta P8ZP_SCRATCH_B1 | pla | tax | lda P8ZP_SCRATCH_B1")
|
||||
}
|
||||
|
||||
private fun funcCallFar(fcall: IFunctionCall) {
|
||||
private fun funcCallFar(fcall: PtBuiltinFunctionCall) {
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callfar only works on cx16 target at this time")
|
||||
|
||||
val bank = fcall.args[0].constValue(program)?.number?.toInt()
|
||||
val address = fcall.args[1].constValue(program)?.number?.toInt() ?: 0
|
||||
val argAddrArg = fcall.args[2]
|
||||
if(bank==null)
|
||||
throw AssemblyError("callfar (jsrfar) bank has to be a constant")
|
||||
if(fcall.args[1].constValue(program) == null) {
|
||||
assignAsmGen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY, false)
|
||||
asmgen.out(" sta (+)+0 | sty (+)+1 ; store jsrfar address word")
|
||||
}
|
||||
|
||||
if(argAddrArg.constValue(program)?.number == 0.0) {
|
||||
asmgen.out("""
|
||||
jsr cx16.jsrfar
|
||||
+ .word ${address.toHex()}
|
||||
.byte ${bank.toHex()}""")
|
||||
} else {
|
||||
when(argAddrArg) {
|
||||
is AddressOf -> {
|
||||
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
|
||||
throw AssemblyError("callfar done with 'arg' pointer to variable that's not UBYTE")
|
||||
asmgen.out("""
|
||||
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
|
||||
jsr cx16.jsrfar
|
||||
+ .word ${address.toHex()}
|
||||
.byte ${bank.toHex()}
|
||||
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
|
||||
}
|
||||
is NumericLiteral -> {
|
||||
asmgen.out("""
|
||||
lda ${argAddrArg.number.toHex()}
|
||||
jsr cx16.jsrfar
|
||||
+ .word ${address.toHex()}
|
||||
.byte ${bank.toHex()}
|
||||
sta ${argAddrArg.number.toHex()}""")
|
||||
}
|
||||
else -> throw AssemblyError("callfar only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
|
||||
}
|
||||
}
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // bank
|
||||
asmgen.out(" sta (++)+0")
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) // jump address
|
||||
asmgen.out(" sta (+)+0 | sty (+)+1")
|
||||
asmgen.assignExpressionToRegister(fcall.args[2], RegisterOrPair.AY) // uword argument
|
||||
asmgen.out("""
|
||||
jsr cx16.jsrfar
|
||||
+ .word 0
|
||||
+ .byte 0""")
|
||||
// note that by convention the values in A+Y registers are now the return value of the call.
|
||||
}
|
||||
|
||||
private fun funcCallRom(fcall: IFunctionCall) {
|
||||
if(asmgen.options.compTarget.name != "cx16")
|
||||
throw AssemblyError("callrom only works on cx16 target at this time")
|
||||
|
||||
val bank = fcall.args[0].constValue(program)?.number?.toInt()
|
||||
val address = fcall.args[1].constValue(program)?.number?.toInt()
|
||||
if(bank==null || address==null)
|
||||
throw AssemblyError("callrom requires constant arguments")
|
||||
|
||||
if(address !in 0xc000..0xffff)
|
||||
throw AssemblyError("callrom done on address outside of cx16 banked rom")
|
||||
if(bank>=32)
|
||||
throw AssemblyError("callrom bank must be <32")
|
||||
|
||||
val argAddrArg = fcall.args[2]
|
||||
if(argAddrArg.constValue(program)?.number == 0.0) {
|
||||
asmgen.out("""
|
||||
lda $01
|
||||
pha
|
||||
lda #${bank}
|
||||
sta $01
|
||||
jsr ${address.toHex()}
|
||||
pla
|
||||
sta $01""")
|
||||
} else {
|
||||
when(argAddrArg) {
|
||||
is AddressOf -> {
|
||||
if(argAddrArg.identifier.targetVarDecl(program)?.datatype != DataType.UBYTE)
|
||||
throw AssemblyError("callrom done with 'arg' pointer to variable that's not UBYTE")
|
||||
asmgen.out("""
|
||||
lda $01
|
||||
pha
|
||||
lda #${bank}
|
||||
sta $01
|
||||
lda ${asmgen.asmVariableName(argAddrArg.identifier)}
|
||||
jsr ${address.toHex()}
|
||||
sta ${asmgen.asmVariableName(argAddrArg.identifier)}
|
||||
pla
|
||||
sta $01""")
|
||||
}
|
||||
is NumericLiteral -> {
|
||||
asmgen.out("""
|
||||
lda $01
|
||||
pha
|
||||
lda #${bank}
|
||||
sta $01
|
||||
lda ${argAddrArg.number.toHex()}
|
||||
jsr ${address.toHex()}
|
||||
sta ${argAddrArg.number.toHex()}
|
||||
pla
|
||||
sta $01""")
|
||||
}
|
||||
else -> throw AssemblyError("callrom only accepts pointer-of a (ubyte) variable or constant memory address for the 'arg' parameter")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcCmp(fcall: IFunctionCall) {
|
||||
private fun funcCmp(fcall: PtBuiltinFunctionCall) {
|
||||
val arg1 = fcall.args[0]
|
||||
val arg2 = fcall.args[1]
|
||||
val dt1 = arg1.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
val dt2 = arg2.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(dt1 in ByteDatatypes) {
|
||||
if(dt2 in ByteDatatypes) {
|
||||
if(arg1.type in ByteDatatypes) {
|
||||
if(arg2.type in ByteDatatypes) {
|
||||
when (arg2) {
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
|
||||
}
|
||||
is NumericLiteral -> {
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp #${arg2.number.toInt()}")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if(arg2.addressExpression is NumericLiteral) {
|
||||
is PtMemoryByte -> {
|
||||
if(arg2.address is PtNumber) {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
|
||||
asmgen.out(" cmp ${arg2.address.asConstInteger()!!.toHex()}")
|
||||
} else {
|
||||
if(arg1.isSimple) {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
if(arg1.isSimple()) {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
} else {
|
||||
asmgen.pushCpuStack(DataType.UBYTE, arg1)
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
|
||||
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if(arg1.isSimple) {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
if(arg1.isSimple()) {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
|
||||
asmgen.out(" cmp P8ZP_SCRATCH_B1")
|
||||
} else {
|
||||
asmgen.pushCpuStack(DataType.UBYTE, arg1)
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, fcall.definingISub())
|
||||
asmgen.out(" pla | cmp P8ZP_SCRATCH_B1")
|
||||
}
|
||||
}
|
||||
@ -280,10 +188,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
} else
|
||||
throw AssemblyError("args for cmp() should have same dt")
|
||||
} else {
|
||||
// dt1 is a word
|
||||
if(dt2 in WordDatatypes) {
|
||||
// arg1 is a word
|
||||
if(arg2.type in WordDatatypes) {
|
||||
when (arg2) {
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy ${asmgen.asmVariableName(arg2)}+1
|
||||
@ -291,7 +199,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
cmp ${asmgen.asmVariableName(arg2)}
|
||||
+""")
|
||||
}
|
||||
is NumericLiteral -> {
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy #>${arg2.number.toInt()}
|
||||
@ -300,8 +208,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
+""")
|
||||
}
|
||||
else -> {
|
||||
if(arg1.isSimple) {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
||||
if(arg1.isSimple()) {
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, fcall.definingISub())
|
||||
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
cpy P8ZP_SCRATCH_W1+1
|
||||
@ -310,7 +218,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
+""")
|
||||
} else {
|
||||
asmgen.pushCpuStack(DataType.UWORD, arg1)
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine)
|
||||
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, fcall.definingISub())
|
||||
asmgen.restoreRegisterStack(CpuRegister.Y, false)
|
||||
asmgen.restoreRegisterStack(CpuRegister.A, false)
|
||||
asmgen.out("""
|
||||
@ -326,25 +234,27 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if(discardResult || fcall !is BuiltinFunctionCall)
|
||||
private fun funcMemory(fcall: PtBuiltinFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if(discardResult)
|
||||
throw AssemblyError("should not discard result of memory allocation at $fcall")
|
||||
val name = (fcall.args[0] as StringLiteral).value
|
||||
val name = (fcall.args[0] as PtString).value
|
||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name ${fcall.position}"}
|
||||
val slabname = IdentifierReference(listOf("prog8_slabs", "prog8_memoryslab_$name"), fcall.position)
|
||||
slabname.linkParents(fcall)
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = AddressOf(slabname, fcall.position))
|
||||
val slabname = PtIdentifier("prog8_slabs.prog8_memoryslab_$name", DataType.UWORD, fcall.position)
|
||||
val addressOf = PtAddressOf(fcall.position)
|
||||
addressOf.add(slabname)
|
||||
addressOf.parent = fcall
|
||||
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UWORD, expression = addressOf)
|
||||
val target =
|
||||
if(resultToStack)
|
||||
AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, null)
|
||||
else
|
||||
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, asmgen)
|
||||
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
|
||||
val assign = AsmAssignment(src, target, program.memsizer, fcall.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
}
|
||||
|
||||
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
private fun funcSqrt16(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
translateArguments(fcall, scope)
|
||||
if(resultToStack)
|
||||
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
|
||||
else {
|
||||
@ -353,13 +263,18 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcReverse(fcall: IFunctionCall) {
|
||||
private fun funcReverse(fcall: PtBuiltinFunctionCall) {
|
||||
val variable = fcall.args.single()
|
||||
if (variable is IdentifierReference) {
|
||||
val decl = variable.targetVarDecl(program)!!
|
||||
if (variable is PtIdentifier) {
|
||||
val symbol = asmgen.symbolTable.lookup(variable.name)
|
||||
val decl = symbol!!.astNode as IPtVariable
|
||||
val numElements = when(decl) {
|
||||
is PtConstant -> throw AssemblyError("cannot reverse a constant")
|
||||
is PtMemMapped -> decl.arraySize
|
||||
is PtVariable -> decl.arraySize
|
||||
}
|
||||
val varName = asmgen.asmVariableName(variable)
|
||||
val numElements = decl.arraysize!!.constIndex()
|
||||
when (decl.datatype) {
|
||||
when (decl.type) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
@ -392,13 +307,18 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcSort(fcall: IFunctionCall) {
|
||||
private fun funcSort(fcall: PtBuiltinFunctionCall) {
|
||||
val variable = fcall.args.single()
|
||||
if (variable is IdentifierReference) {
|
||||
val decl = variable.targetVarDecl(program)!!
|
||||
if (variable is PtIdentifier) {
|
||||
val symbol = asmgen.symbolTable.lookup(variable.name)
|
||||
val decl = symbol!!.astNode as IPtVariable
|
||||
val varName = asmgen.asmVariableName(variable)
|
||||
val numElements = decl.arraysize!!.constIndex()
|
||||
when (decl.datatype) {
|
||||
val numElements = when(decl) {
|
||||
is PtConstant -> throw AssemblyError("cannot sort a constant")
|
||||
is PtMemMapped -> decl.arraySize
|
||||
is PtVariable -> decl.arraySize
|
||||
}
|
||||
when (decl.type) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
asmgen.out("""
|
||||
lda #<$varName
|
||||
@ -406,7 +326,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements""")
|
||||
asmgen.out(if (decl.datatype == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
|
||||
asmgen.out(if (decl.type == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
asmgen.out("""
|
||||
@ -415,7 +335,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$numElements""")
|
||||
asmgen.out(if (decl.datatype == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
|
||||
asmgen.out(if (decl.type == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
|
||||
}
|
||||
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
|
||||
else -> throw AssemblyError("weird type")
|
||||
@ -424,26 +344,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
throw AssemblyError("weird type")
|
||||
}
|
||||
|
||||
private fun funcRor2(fcall: IFunctionCall) {
|
||||
private fun funcRor2(fcall: PtBuiltinFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
val dt = what.inferType(program)
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
when (what.type) {
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror2", 'b')
|
||||
is PtArrayIndexer -> {
|
||||
translateRolRorArrayArgs(what.variable, what, "ror2", 'b')
|
||||
asmgen.out(" jsr prog8_lib.ror2_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteral) {
|
||||
val number = (what.addressExpression as NumericLiteral).number
|
||||
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()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
|
||||
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
|
||||
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
|
||||
}
|
||||
@ -452,11 +371,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror2", 'w')
|
||||
is PtArrayIndexer -> {
|
||||
translateRolRorArrayArgs(what.variable, what, "ror2", 'w')
|
||||
asmgen.out(" jsr prog8_lib.ror2_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
|
||||
}
|
||||
@ -467,26 +386,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcRor(fcall: IFunctionCall) {
|
||||
private fun funcRor(fcall: PtBuiltinFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
val dt = what.inferType(program)
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
when (what.type) {
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror", 'b')
|
||||
is PtArrayIndexer -> {
|
||||
translateRolRorArrayArgs(what.variable, what, "ror", 'b')
|
||||
asmgen.out(" jsr prog8_lib.ror_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteral) {
|
||||
val number = (what.addressExpression as NumericLiteral).number
|
||||
is PtMemoryByte -> {
|
||||
if (what.address is PtNumber) {
|
||||
val number = (what.address as PtNumber).number
|
||||
asmgen.out(" ror ${number.toHex()}")
|
||||
} else {
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
|
||||
if(ptrAndIndex!=null) {
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
asmgen.out("""
|
||||
@ -495,7 +413,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
+ ror ${'$'}ffff,x ; modified""")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
|
||||
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sta (+) + 1
|
||||
sty (+) + 2
|
||||
@ -503,7 +421,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" ror $variable")
|
||||
}
|
||||
@ -512,11 +430,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "ror", 'w')
|
||||
is PtArrayIndexer -> {
|
||||
translateRolRorArrayArgs(what.variable, what, "ror", 'w')
|
||||
asmgen.out(" jsr prog8_lib.ror_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" ror $variable+1 | ror $variable")
|
||||
}
|
||||
@ -527,26 +445,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcRol2(fcall: IFunctionCall) {
|
||||
private fun funcRol2(fcall: PtBuiltinFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
val dt = what.inferType(program)
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
when (what.type) {
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol2", 'b')
|
||||
is PtArrayIndexer -> {
|
||||
translateRolRorArrayArgs(what.variable, what, "rol2", 'b')
|
||||
asmgen.out(" jsr prog8_lib.rol2_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteral) {
|
||||
val number = (what.addressExpression as NumericLiteral).number
|
||||
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()}")
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
|
||||
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
|
||||
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
|
||||
}
|
||||
@ -555,11 +472,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol2", 'w')
|
||||
is PtArrayIndexer -> {
|
||||
translateRolRorArrayArgs(what.variable, what, "rol2", 'w')
|
||||
asmgen.out(" jsr prog8_lib.rol2_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
|
||||
}
|
||||
@ -570,26 +487,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcRol(fcall: IFunctionCall) {
|
||||
private fun funcRol(fcall: PtBuiltinFunctionCall) {
|
||||
val what = fcall.args.single()
|
||||
val dt = what.inferType(program)
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
when (what.type) {
|
||||
DataType.UBYTE -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol", 'b')
|
||||
is PtArrayIndexer -> {
|
||||
translateRolRorArrayArgs(what.variable, what, "rol", 'b')
|
||||
asmgen.out(" jsr prog8_lib.rol_array_ub")
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
if (what.addressExpression is NumericLiteral) {
|
||||
val number = (what.addressExpression as NumericLiteral).number
|
||||
is PtMemoryByte -> {
|
||||
if (what.address is PtNumber) {
|
||||
val number = (what.address as PtNumber).number
|
||||
asmgen.out(" rol ${number.toHex()}")
|
||||
} else {
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
|
||||
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.address)
|
||||
if(ptrAndIndex!=null) {
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.X)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(ptrAndIndex.first, RegisterOrPair.AY)
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
asmgen.out("""
|
||||
@ -598,7 +514,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
+ rol ${'$'}ffff,x ; modified""")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
} else {
|
||||
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
|
||||
asmgen.assignExpressionToRegister(what.address, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sta (+) + 1
|
||||
sty (+) + 2
|
||||
@ -606,7 +522,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" rol $variable")
|
||||
}
|
||||
@ -615,11 +531,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when (what) {
|
||||
is ArrayIndexedExpression -> {
|
||||
translateRolRorArrayArgs(what.arrayvar, what.indexer, "rol", 'w')
|
||||
is PtArrayIndexer -> {
|
||||
translateRolRorArrayArgs(what.variable, what, "rol", 'w')
|
||||
asmgen.out(" jsr prog8_lib.rol_array_uw")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val variable = asmgen.asmVariableName(what)
|
||||
asmgen.out(" rol $variable | rol $variable+1")
|
||||
}
|
||||
@ -630,22 +546,25 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateRolRorArrayArgs(arrayvar: IdentifierReference, indexer: ArrayIndex, operation: String, dt: Char) {
|
||||
if(arrayvar.targetVarDecl(program)!!.datatype==DataType.UWORD) {
|
||||
private fun translateRolRorArrayArgs(arrayvar: PtIdentifier, indexer: PtArrayIndexer, operation: String, dt: Char) {
|
||||
if(arrayvar.type==DataType.UWORD) {
|
||||
if(dt!='b')
|
||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||
asmgen.assignExpressionToVariable(arrayvar, "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
|
||||
} else {
|
||||
asmgen.assignExpressionToVariable(AddressOf(arrayvar, arrayvar.position), "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
|
||||
val addressOf = PtAddressOf(arrayvar.position)
|
||||
addressOf.add(arrayvar)
|
||||
addressOf.parent = arrayvar.parent.parent
|
||||
asmgen.assignExpressionToVariable(addressOf, "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
|
||||
}
|
||||
asmgen.assignExpressionToVariable(indexer.indexExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
|
||||
asmgen.assignExpressionToVariable(indexer.index, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
|
||||
}
|
||||
|
||||
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
private fun funcSgn(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
translateArguments(fcall, scope)
|
||||
val dt = fcall.args.single().type
|
||||
if(resultToStack) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_stack")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_stack")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_stack")
|
||||
@ -654,7 +573,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.func_sign_ub_into_A")
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.func_sign_b_into_A")
|
||||
DataType.UWORD -> asmgen.out(" jsr prog8_lib.func_sign_uw_into_A")
|
||||
@ -666,30 +585,30 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcAnyAll(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
private fun funcAnyAll(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
outputAddressAndLenghtOfArray(fcall.args[0])
|
||||
val dt = fcall.args.single().inferType(program)
|
||||
val dt = fcall.args.single().type
|
||||
if(resultToStack) {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_stack")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_stack")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_stack")
|
||||
when (dt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_stack")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_stack")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_stack")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
} else {
|
||||
when (dt.getOr(DataType.UNDEFINED)) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A | ldy #0")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_A | ldy #0")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A | ldy #0")
|
||||
when (dt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_b_into_A | ldy #0")
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${fcall.name}_w_into_A | ldy #0")
|
||||
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${fcall.name}_f_into_A | ldy #0")
|
||||
else -> throw AssemblyError("weird type $dt")
|
||||
}
|
||||
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, false, scope, asmgen), CpuRegister.A)
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
|
||||
translateArguments(fcall.args, func, scope)
|
||||
val dt = fcall.args.single().inferType(program).getOr(DataType.UNDEFINED)
|
||||
private fun funcAbs(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: IPtSubroutine?) {
|
||||
translateArguments(fcall, scope)
|
||||
val dt = fcall.args.single().type
|
||||
if(resultToStack) {
|
||||
when (dt) {
|
||||
DataType.UBYTE -> asmgen.out(" ldy #0")
|
||||
@ -710,19 +629,19 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcPokeW(fcall: IFunctionCall) {
|
||||
private fun funcPokeW(fcall: PtBuiltinFunctionCall) {
|
||||
when(val addrExpr = fcall.args[0]) {
|
||||
is NumericLiteral -> {
|
||||
is PtNumber -> {
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
|
||||
val addr = addrExpr.number.toHex()
|
||||
asmgen.out(" sta $addr | sty ${addr}+1")
|
||||
return
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(addrExpr)
|
||||
if(asmgen.isZpVar(addrExpr)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
|
||||
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
|
||||
asmgen.out("""
|
||||
@ -742,14 +661,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
return
|
||||
}
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) {
|
||||
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
|
||||
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
|
||||
is PtBinaryExpression -> {
|
||||
if(addrExpr.operator=="+" && addrExpr.left is PtIdentifier && addrExpr.right is PtNumber) {
|
||||
val varname = asmgen.asmVariableName(addrExpr.left as PtIdentifier)
|
||||
if(asmgen.isZpVar(addrExpr.left as PtIdentifier)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, fcall.definingISub()!!)
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
|
||||
val index = (addrExpr.right as NumericLiteral).number.toHex()
|
||||
val index = (addrExpr.right as PtNumber).number.toHex()
|
||||
asmgen.out("""
|
||||
ldy #$index
|
||||
sta ($varname),y
|
||||
@ -769,13 +688,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
asmgen.out(" jsr prog8_lib.func_pokew")
|
||||
}
|
||||
|
||||
private fun funcPeekW(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
private fun funcPeekW(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
when(val addrExpr = fcall.args[0]) {
|
||||
is NumericLiteral -> {
|
||||
is PtNumber -> {
|
||||
val addr = addrExpr.number.toHex()
|
||||
asmgen.out(" lda $addr | ldy ${addr}+1")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val varname = asmgen.asmVariableName(addrExpr)
|
||||
if(asmgen.isZpVar(addrExpr)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
@ -800,12 +719,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
asmgen.out(" jsr prog8_lib.func_peekw")
|
||||
}
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) {
|
||||
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
|
||||
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
|
||||
is PtBinaryExpression -> {
|
||||
if(addrExpr.operator=="+" && addrExpr.left is PtIdentifier && addrExpr.right is PtNumber) {
|
||||
val varname = asmgen.asmVariableName(addrExpr.left as PtIdentifier)
|
||||
if(asmgen.isZpVar(addrExpr.left as PtIdentifier)) {
|
||||
// pointervar is already in the zero page, no need to copy
|
||||
val index = (addrExpr.right as NumericLiteral).number.toHex()
|
||||
val index = (addrExpr.right as PtNumber).number.toHex()
|
||||
asmgen.out("""
|
||||
ldy #$index
|
||||
lda ($varname),y
|
||||
@ -845,7 +764,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMkword(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
private fun funcMkword(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
if(resultToStack) {
|
||||
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
|
||||
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
|
||||
@ -854,12 +773,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
val reg = resultRegister ?: RegisterOrPair.AY
|
||||
var needAsave = asmgen.needAsaveForExpr(fcall.args[0])
|
||||
if(!needAsave) {
|
||||
val mr0 = fcall.args[0] as? DirectMemoryRead
|
||||
val mr1 = fcall.args[1] as? DirectMemoryRead
|
||||
val mr0 = fcall.args[0] as? PtMemoryByte
|
||||
val mr1 = fcall.args[1] as? PtMemoryByte
|
||||
if (mr0 != null)
|
||||
needAsave = mr0.addressExpression !is NumericLiteral
|
||||
needAsave = mr0.address !is PtNumber
|
||||
if (mr1 != null)
|
||||
needAsave = needAsave or (mr1.addressExpression !is NumericLiteral)
|
||||
needAsave = needAsave or (mr1.address !is PtNumber)
|
||||
}
|
||||
when(reg) {
|
||||
RegisterOrPair.AX -> {
|
||||
@ -898,13 +817,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
private fun funcMsb(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.inferType(program).isWords)
|
||||
if (arg.type !in WordDatatypes)
|
||||
throw AssemblyError("msb required word argument")
|
||||
if (arg is NumericLiteral)
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("msb(const) should have been const-folded away")
|
||||
if (arg is IdentifierReference) {
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
if(resultToStack) {
|
||||
asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
|
||||
@ -952,14 +871,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
private fun funcLsb(fcall: PtBuiltinFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
|
||||
val arg = fcall.args.single()
|
||||
if (!arg.inferType(program).isWords)
|
||||
if (arg.type !in WordDatatypes)
|
||||
throw AssemblyError("lsb required word argument")
|
||||
if (arg is NumericLiteral)
|
||||
if (arg is PtNumber)
|
||||
throw AssemblyError("lsb(const) should have been const-folded away")
|
||||
|
||||
if (arg is IdentifierReference) {
|
||||
if (arg is PtIdentifier) {
|
||||
val sourceName = asmgen.asmVariableName(arg)
|
||||
if(resultToStack) {
|
||||
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
|
||||
@ -1013,35 +932,39 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun outputAddressAndLenghtOfArray(arg: Expression) {
|
||||
private fun outputAddressAndLenghtOfArray(arg: PtExpression) {
|
||||
// address in P8ZP_SCRATCH_W1, number of elements in A
|
||||
arg as IdentifierReference
|
||||
val arrayVar = arg.targetVarDecl(program)!!
|
||||
if(!arrayVar.isArray)
|
||||
throw AssemblyError("length of non-array requested")
|
||||
val size = arrayVar.arraysize!!.constIndex()!!
|
||||
arg as PtIdentifier
|
||||
val symbol = asmgen.symbolTable.lookup(arg.name)
|
||||
val arrayVar = symbol!!.astNode as IPtVariable
|
||||
val numElements = when(arrayVar) {
|
||||
is PtConstant -> null
|
||||
is PtMemMapped -> arrayVar.arraySize
|
||||
is PtVariable -> arrayVar.arraySize
|
||||
} ?: throw AssemblyError("length of non-array requested")
|
||||
val identifierName = asmgen.asmVariableName(arg)
|
||||
asmgen.out("""
|
||||
lda #<$identifierName
|
||||
ldy #>$identifierName
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
lda #$size
|
||||
lda #$numElements
|
||||
""")
|
||||
}
|
||||
|
||||
private fun translateArguments(args: MutableList<Expression>, signature: FSignature, scope: Subroutine?) {
|
||||
val callConv = signature.callConvention(args.map {
|
||||
it.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
})
|
||||
private fun translateArguments(call: PtBuiltinFunctionCall, scope: IPtSubroutine?) {
|
||||
val signature = BuiltinFunctions.getValue(call.name)
|
||||
val callConv = signature.callConvention(call.args.map { it.type})
|
||||
|
||||
fun getSourceForFloat(value: Expression): AsmAssignSource {
|
||||
fun getSourceForFloat(value: PtExpression): AsmAssignSource {
|
||||
return when (value) {
|
||||
is IdentifierReference -> {
|
||||
val addr = AddressOf(value, value.position)
|
||||
is PtIdentifier -> {
|
||||
val addr = PtAddressOf(value.position)
|
||||
addr.add(value)
|
||||
addr.parent = call
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
}
|
||||
is NumericLiteral -> {
|
||||
is PtNumber -> {
|
||||
throw AssemblyError("float literals should have been converted into autovar")
|
||||
}
|
||||
else -> {
|
||||
@ -1049,27 +972,30 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
throw AssemblyError("cannot use float arguments outside of a subroutine scope")
|
||||
|
||||
asmgen.subroutineExtra(scope).usedFloatEvalResultVar2 = true
|
||||
val variable = IdentifierReference(listOf(subroutineFloatEvalResultVar2), value.position)
|
||||
val addr = AddressOf(variable, value.position)
|
||||
addr.linkParents(value)
|
||||
val variable = PtIdentifier(subroutineFloatEvalResultVar2, DataType.FLOAT, value.position)
|
||||
val addr = PtAddressOf(value.position)
|
||||
addr.add(variable)
|
||||
addr.parent = call
|
||||
asmgen.assignExpressionToVariable(value, asmgen.asmVariableName(variable), DataType.FLOAT, scope)
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
args.zip(callConv.params).zip(signature.parameters).forEach {
|
||||
call.args.zip(callConv.params).zip(signature.parameters).forEach {
|
||||
val paramName = it.second.name
|
||||
val conv = it.first.second
|
||||
val value = it.first.first
|
||||
when {
|
||||
conv.variable -> {
|
||||
val varname = "prog8_lib.func_${signature.name}._arg_${paramName}"
|
||||
val varname = "prog8_lib.func_${call.name}._arg_${paramName}"
|
||||
val src = when (conv.dt) {
|
||||
DataType.FLOAT -> getSourceForFloat(value)
|
||||
in PassByReferenceDatatypes -> {
|
||||
// put the address of the argument in AY
|
||||
val addr = AddressOf(value as IdentifierReference, value.position)
|
||||
val addr = PtAddressOf(value.position)
|
||||
addr.add(value)
|
||||
addr.parent = call
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
}
|
||||
else -> {
|
||||
@ -1077,7 +1003,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, conv.dt, null, variableAsmName = varname)
|
||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
|
||||
val assign = AsmAssignment(src, tgt, program.memsizer, value.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
}
|
||||
conv.reg != null -> {
|
||||
@ -1085,7 +1011,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
DataType.FLOAT -> getSourceForFloat(value)
|
||||
in PassByReferenceDatatypes -> {
|
||||
// put the address of the argument in AY
|
||||
val addr = AddressOf(value as IdentifierReference, value.position)
|
||||
val addr = PtAddressOf(value.position)
|
||||
addr.add(value)
|
||||
addr.parent = call
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen)
|
||||
}
|
||||
else -> {
|
||||
@ -1093,7 +1021,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
val tgt = AsmAssignTarget.fromRegisters(conv.reg!!, false, null, asmgen)
|
||||
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
|
||||
val assign = AsmAssignment(src, tgt, program.memsizer, value.position)
|
||||
asmgen.translateNormalAssignment(assign)
|
||||
}
|
||||
else -> throw AssemblyError("callconv")
|
||||
|
@ -1,18 +1,17 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ExpressionsAsmGen(private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
internal class ExpressionsAsmGen(private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val allocator: VariableAllocator) {
|
||||
|
||||
@Deprecated("avoid calling this as it generates slow evalstack based code")
|
||||
internal fun translateExpression(expression:Expression) {
|
||||
internal fun translateExpression(expression: PtExpression) {
|
||||
if (this.asmgen.options.slowCodegenWarnings) {
|
||||
asmgen.errors.warn("slow stack evaluation used for expression $expression", expression.position)
|
||||
asmgen.errors.warn("slow stack evaluation used for expression", expression.position)
|
||||
}
|
||||
translateExpressionInternal(expression)
|
||||
}
|
||||
@ -21,41 +20,42 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
// the rest of the methods are all PRIVATE
|
||||
|
||||
|
||||
private fun translateExpressionInternal(expression: Expression) {
|
||||
private fun translateExpressionInternal(expression: PtExpression) {
|
||||
|
||||
when(expression) {
|
||||
is PrefixExpression -> translateExpression(expression)
|
||||
is BinaryExpression -> translateExpression(expression)
|
||||
is ArrayIndexedExpression -> translateExpression(expression)
|
||||
is TypecastExpression -> translateExpression(expression)
|
||||
is AddressOf -> translateExpression(expression)
|
||||
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
|
||||
is NumericLiteral -> translateExpression(expression)
|
||||
is IdentifierReference -> translateExpression(expression)
|
||||
is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
|
||||
is BuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
|
||||
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
|
||||
is ArrayLiteral, is StringLiteral -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||
is RangeExpression -> throw AssemblyError("range expression should have been changed into array values")
|
||||
is CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
|
||||
is PtPrefix -> translateExpression(expression)
|
||||
is PtBinaryExpression -> translateExpression(expression)
|
||||
is PtArrayIndexer -> translateExpression(expression)
|
||||
is PtTypeCast -> translateExpression(expression)
|
||||
is PtAddressOf -> translateExpression(expression)
|
||||
is PtMemoryByte -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
|
||||
is PtNumber -> translateExpression(expression)
|
||||
is PtIdentifier -> translateExpression(expression)
|
||||
is PtFunctionCall -> translateFunctionCallResultOntoStack(expression)
|
||||
is PtBuiltinFunctionCall -> asmgen.translateBuiltinFunctionCallExpression(expression, true, null)
|
||||
is PtContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
|
||||
is PtArray, is PtString -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
|
||||
is PtRange -> throw AssemblyError("range expression should have been changed into array values")
|
||||
is PtMachineRegister -> throw AssemblyError("machine register ast node should not occur in 6502 codegen it is for IR code")
|
||||
else -> TODO("missing expression asmgen for $expression")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpression) {
|
||||
private fun translateFunctionCallResultOntoStack(call: PtFunctionCall) {
|
||||
// only for use in nested expression evaluation
|
||||
|
||||
val sub = call.target.targetSubroutine(program)!!
|
||||
val symbol = asmgen.symbolTable.lookup(call.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
asmgen.saveXbeforeCall(call)
|
||||
asmgen.translateFunctionCall(call, true)
|
||||
asmgen.translateFunctionCall(call)
|
||||
if(sub.regXasResult()) {
|
||||
// store the return value in X somewhere that we can access again below
|
||||
asmgen.out(" stx P8ZP_SCRATCH_REG")
|
||||
}
|
||||
asmgen.restoreXafterCall(call)
|
||||
|
||||
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
|
||||
for ((_, reg) in returns) {
|
||||
val returns: List<Pair<RegisterOrStatusflag, DataType>> = sub.returnsWhatWhere()
|
||||
for ((reg, _) in returns) {
|
||||
// result value is in cpu or status registers, put it on the stack instead (as we're evaluating an expression tree)
|
||||
if (reg.registerOrPair != null) {
|
||||
when (reg.registerOrPair!!) {
|
||||
@ -133,9 +133,9 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(typecast: TypecastExpression) {
|
||||
translateExpressionInternal(typecast.expression)
|
||||
when(typecast.expression.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
private fun translateExpression(typecast: PtTypeCast) {
|
||||
translateExpressionInternal(typecast.value)
|
||||
when(typecast.value.type) {
|
||||
DataType.UBYTE, DataType.BOOL -> {
|
||||
when(typecast.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {}
|
||||
@ -197,12 +197,12 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: AddressOf) {
|
||||
private fun translateExpression(expr: PtAddressOf) {
|
||||
val name = asmgen.asmVariableName(expr.identifier)
|
||||
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: NumericLiteral) {
|
||||
private fun translateExpression(expr: PtNumber) {
|
||||
when(expr.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
|
||||
DataType.UWORD, DataType.WORD -> asmgen.out("""
|
||||
@ -220,9 +220,9 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: IdentifierReference) {
|
||||
private fun translateExpression(expr: PtIdentifier) {
|
||||
val varname = asmgen.asmVariableName(expr)
|
||||
when(expr.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
when(expr.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
asmgen.out(" lda $varname | sta P8ESTACK_LO,x | dex")
|
||||
}
|
||||
@ -239,22 +239,16 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: BinaryExpression) {
|
||||
// Uses evalstack to evaluate the given expression.
|
||||
// TODO we're slowly reducing the number of places where this is called and instead replace that by more efficient assignment-form code (using temp var or register for instance).
|
||||
val leftIDt = expr.left.inferType(program)
|
||||
val rightIDt = expr.right.inferType(program)
|
||||
if(!leftIDt.isKnown || !rightIDt.isKnown)
|
||||
throw AssemblyError("can't infer type of both expression operands")
|
||||
|
||||
val leftDt = leftIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
val rightDt = rightIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
// see if we can apply some optimized routines
|
||||
private fun translateExpression(expr: PtBinaryExpression) {
|
||||
// Uses evalstack to evaluate the given expression. THIS IS SLOW AND SHOULD BE AVOIDED!
|
||||
val leftDt = expr.left.type
|
||||
val rightDt = expr.right.type
|
||||
// see if we can apply some optimized routines still
|
||||
when(expr.operator) {
|
||||
"+" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val leftVal = expr.left.constValue(program)?.number?.toInt()
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
val leftVal = expr.left.asConstInteger()
|
||||
val rightVal = expr.right.asConstInteger()
|
||||
if (leftVal!=null && leftVal in -4..4) {
|
||||
translateExpressionInternal(expr.right)
|
||||
if(rightDt in ByteDatatypes) {
|
||||
@ -318,7 +312,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
"-" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
val rightVal = expr.right.asConstInteger()
|
||||
if (rightVal!=null && rightVal in -4..4)
|
||||
{
|
||||
translateExpressionInternal(expr.left)
|
||||
@ -352,7 +346,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
">>" -> {
|
||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||
val amount = expr.right.asConstInteger()
|
||||
if(amount!=null) {
|
||||
translateExpressionInternal(expr.left)
|
||||
when (leftDt) {
|
||||
@ -423,7 +417,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
"<<" -> {
|
||||
val amount = expr.right.constValue(program)?.number?.toInt()
|
||||
val amount = expr.right.asConstInteger()
|
||||
if(amount!=null) {
|
||||
translateExpressionInternal(expr.left)
|
||||
if (leftDt in ByteDatatypes) {
|
||||
@ -450,13 +444,13 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
"*" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val leftVar = expr.left as? IdentifierReference
|
||||
val rightVar = expr.right as? IdentifierReference
|
||||
val leftVar = expr.left as? PtIdentifier
|
||||
val rightVar = expr.right as? PtIdentifier
|
||||
if(leftVar!=null && rightVar!=null && leftVar==rightVar)
|
||||
return translateSquared(leftVar, leftDt)
|
||||
}
|
||||
|
||||
val value = expr.right.constValue(program)
|
||||
val value = expr.right as? PtNumber
|
||||
if(value!=null) {
|
||||
if(rightDt in IntegerDatatypes) {
|
||||
val amount = value.number.toInt()
|
||||
@ -516,7 +510,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
"/" -> {
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number?.toInt()
|
||||
val rightVal = expr.right.asConstInteger()
|
||||
if(rightVal!=null && rightVal==2) {
|
||||
translateExpressionInternal(expr.left)
|
||||
when (leftDt) {
|
||||
@ -557,8 +551,8 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
in ComparisonOperators -> {
|
||||
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
|
||||
val rightVal = expr.right.constValue(program)?.number
|
||||
if(rightVal==0.0)
|
||||
val rightVal = expr.right.asConstInteger()
|
||||
if(rightVal==0)
|
||||
return translateComparisonWithZero(expr.left, leftDt, expr.operator)
|
||||
}
|
||||
}
|
||||
@ -572,7 +566,8 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
translateCompareStrings(expr.left, expr.operator, expr.right)
|
||||
}
|
||||
else {
|
||||
// the general, non-optimized cases TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
|
||||
// the general, non-optimized cases
|
||||
// TODO optimize more cases.... (or one day just don't use the evalstack at all anymore)
|
||||
translateExpressionInternal(expr.left)
|
||||
translateExpressionInternal(expr.right)
|
||||
when (leftDt) {
|
||||
@ -584,8 +579,8 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateComparisonWithZero(expr: Expression, dt: DataType, operator: String) {
|
||||
if(expr.isSimple) {
|
||||
private fun translateComparisonWithZero(expr: PtExpression, dt: DataType, operator: String) {
|
||||
if(expr.isSimple()) {
|
||||
if(operator=="!=") {
|
||||
when (dt) {
|
||||
in ByteDatatypes -> {
|
||||
@ -641,7 +636,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
"<" -> {
|
||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
||||
return translateExpressionInternal(NumericLiteral.fromBoolean(false, expr.position))
|
||||
return translateExpressionInternal(PtNumber.fromBoolean(false, expr.position))
|
||||
when(dt) {
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
|
||||
@ -671,7 +666,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
">=" -> {
|
||||
if(dt==DataType.UBYTE || dt==DataType.UWORD)
|
||||
return translateExpressionInternal(NumericLiteral.fromBoolean(true, expr.position))
|
||||
return translateExpressionInternal(PtNumber.fromBoolean(true, expr.position))
|
||||
when(dt) {
|
||||
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
|
||||
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
|
||||
@ -683,7 +678,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateSquared(variable: IdentifierReference, dt: DataType) {
|
||||
private fun translateSquared(variable: PtIdentifier, dt: DataType) {
|
||||
val asmVar = asmgen.asmVariableName(variable)
|
||||
when(dt) {
|
||||
DataType.BYTE, DataType.UBYTE -> {
|
||||
@ -699,14 +694,12 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
|
||||
}
|
||||
|
||||
private fun translateExpression(expr: PrefixExpression) {
|
||||
translateExpressionInternal(expr.expression)
|
||||
val itype = expr.inferType(program)
|
||||
val type = itype.getOrElse { throw AssemblyError("unknown dt") }
|
||||
private fun translateExpression(expr: PtPrefix) {
|
||||
translateExpressionInternal(expr.value)
|
||||
when(expr.operator) {
|
||||
"+" -> {}
|
||||
"-" -> {
|
||||
when(type) {
|
||||
when(expr.type) {
|
||||
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
|
||||
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
|
||||
DataType.FLOAT -> asmgen.out(" jsr floats.neg_f")
|
||||
@ -714,7 +707,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
"~" -> {
|
||||
when(type) {
|
||||
when(expr.type) {
|
||||
in ByteDatatypes ->
|
||||
asmgen.out("""
|
||||
lda P8ESTACK_LO+1,x
|
||||
@ -729,22 +722,18 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateExpression(arrayExpr: ArrayIndexedExpression) {
|
||||
val elementIDt = arrayExpr.inferType(program)
|
||||
if(!elementIDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
val elementDt = elementIDt.getOr(DataType.UNDEFINED)
|
||||
val arrayVarName = asmgen.asmVariableName(arrayExpr.arrayvar)
|
||||
private fun translateExpression(arrayExpr: PtArrayIndexer) {
|
||||
val elementDt = arrayExpr.type
|
||||
val arrayVarName = asmgen.asmVariableName(arrayExpr.variable)
|
||||
|
||||
val arrayVarDecl = arrayExpr.arrayvar.targetVarDecl(program)!!
|
||||
if(arrayVarDecl.datatype==DataType.UWORD) {
|
||||
if(arrayExpr.variable.type==DataType.UWORD) {
|
||||
// indexing a pointer var instead of a real array or string
|
||||
if(elementDt !in ByteDatatypes)
|
||||
throw AssemblyError("non-array var indexing requires bytes dt")
|
||||
if(arrayExpr.inferType(program) isnot DataType.UBYTE)
|
||||
if(arrayExpr.index.type != DataType.UBYTE)
|
||||
throw AssemblyError("non-array var indexing requires bytes index")
|
||||
asmgen.loadScaledArrayIndexIntoRegister(arrayExpr, elementDt, CpuRegister.Y)
|
||||
if(asmgen.isZpVar(arrayExpr.arrayvar)) {
|
||||
if(asmgen.isZpVar(arrayExpr.variable)) {
|
||||
asmgen.out(" lda ($arrayVarName),y")
|
||||
} else {
|
||||
asmgen.out(" lda $arrayVarName | sta P8ZP_SCRATCH_W1 | lda $arrayVarName+1 | sta P8ZP_SCRATCH_W1+1")
|
||||
@ -754,7 +743,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
return
|
||||
}
|
||||
|
||||
val constIndexNum = arrayExpr.indexer.constIndex()
|
||||
val constIndexNum = arrayExpr.index.asConstInteger()
|
||||
if(constIndexNum!=null) {
|
||||
val indexValue = constIndexNum * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
@ -881,7 +870,7 @@ internal class ExpressionsAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateCompareStrings(s1: Expression, operator: String, s2: Expression) {
|
||||
private fun translateCompareStrings(s1: PtExpression, operator: String, s2: PtExpression) {
|
||||
asmgen.assignExpressionToVariable(s1, "prog8_lib.strcmp_expression._arg_s1", DataType.UWORD, null)
|
||||
asmgen.assignExpressionToVariable(s2, "prog8_lib.strcmp_expression._arg_s2", DataType.UWORD, null)
|
||||
asmgen.out(" jsr prog8_lib.strcmp_expression") // result of compare is in A
|
||||
|
60
codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt
Normal file
60
codeGenCpu6502/src/prog8/codegen/cpu6502/Extensions.kt
Normal file
@ -0,0 +1,60 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.code.ast.IPtSubroutine
|
||||
import prog8.code.ast.PtAsmSub
|
||||
import prog8.code.ast.PtSub
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal fun IPtSubroutine.regXasResult(): Boolean =
|
||||
(this is PtAsmSub) && this.returns.any { it.first.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
|
||||
internal fun IPtSubroutine.shouldSaveX(): Boolean =
|
||||
this.regXasResult() || (this is PtAsmSub && (CpuRegister.X in this.clobbers || regXasParam()))
|
||||
|
||||
internal fun PtAsmSub.regXasParam(): Boolean =
|
||||
parameters.any { it.first.registerOrPair in arrayOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
|
||||
|
||||
internal class KeepAresult(val saveOnEntry: Boolean, val saveOnReturn: Boolean)
|
||||
|
||||
internal fun PtAsmSub.shouldKeepA(): KeepAresult {
|
||||
// determine if A's value should be kept when preparing for calling the subroutine, and when returning from it
|
||||
|
||||
// it seems that we never have to save A when calling? will be loaded correctly after setup.
|
||||
// but on return it depends on wether the routine returns something in A.
|
||||
val saveAonReturn = returns.any { it.first.registerOrPair==RegisterOrPair.A || it.first.registerOrPair==RegisterOrPair.AY || it.first.registerOrPair==RegisterOrPair.AX }
|
||||
return KeepAresult(false, saveAonReturn)
|
||||
}
|
||||
|
||||
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 value
|
||||
return if(returntype==null)
|
||||
emptyList()
|
||||
else {
|
||||
val register = when (returntype!!) {
|
||||
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
|
||||
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
|
||||
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||
}
|
||||
listOf(Pair(register, returntype!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal fun PtSub.returnRegister(): RegisterOrStatusflag? {
|
||||
return when(returntype) {
|
||||
in ByteDatatypes -> RegisterOrStatusflag(RegisterOrPair.A, null)
|
||||
in WordDatatypes -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||
DataType.FLOAT -> RegisterOrStatusflag(RegisterOrPair.FAC1, null)
|
||||
null -> null
|
||||
else -> RegisterOrStatusflag(RegisterOrPair.AY, null)
|
||||
}
|
||||
}
|
@ -1,46 +1,43 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import com.github.michaelbull.result.fold
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.RangeExpression
|
||||
import prog8.ast.statements.ForLoop
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen, private val zeropage: Zeropage) {
|
||||
internal class ForLoopsAsmGen(private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val zeropage: Zeropage) {
|
||||
|
||||
internal fun translate(stmt: ForLoop) {
|
||||
val iterableDt = stmt.iterable.inferType(program)
|
||||
if(!iterableDt.isKnown)
|
||||
throw AssemblyError("unknown dt")
|
||||
internal fun translate(stmt: PtForLoop) {
|
||||
val iterableDt = stmt.iterable.type
|
||||
when(stmt.iterable) {
|
||||
is RangeExpression -> {
|
||||
val range = (stmt.iterable as RangeExpression).toConstantIntegerRange()
|
||||
is PtRange -> {
|
||||
val range = (stmt.iterable as PtRange).toConstantIntegerRange()
|
||||
if(range==null) {
|
||||
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpression)
|
||||
translateForOverNonconstRange(stmt, iterableDt, stmt.iterable as PtRange)
|
||||
} else {
|
||||
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
|
||||
translateForOverConstRange(stmt, iterableDt, range)
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
translateForOverIterableVar(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as IdentifierReference)
|
||||
is PtIdentifier -> {
|
||||
translateForOverIterableVar(stmt, iterableDt, stmt.iterable as PtIdentifier)
|
||||
}
|
||||
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpression) {
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
val modifiedLabel = program.makeLabel("for_modified")
|
||||
val modifiedLabel2 = program.makeLabel("for_modifiedb")
|
||||
private fun translateForOverNonconstRange(stmt: PtForLoop, iterableDt: DataType, range: PtRange) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
val modifiedLabel = asmgen.makeLabel("for_modified")
|
||||
val modifiedLabel2 = asmgen.makeLabel("for_modifiedb")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val stepsize=range.step.constValue(program)!!.number.toInt()
|
||||
val stepsize=range.step.asConstInteger()!!
|
||||
|
||||
if(stepsize < -1) {
|
||||
val limit = range.to.constValue(program)?.number
|
||||
if(limit==0.0)
|
||||
val limit = range.to.asConstInteger()
|
||||
if(limit==0)
|
||||
throw AssemblyError("for unsigned loop variable it's not possible to count down with step != -1 from a non-const value to exactly zero due to value wrapping")
|
||||
}
|
||||
|
||||
@ -52,11 +49,11 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
|
||||
|
||||
val incdec = if(stepsize==1) "inc" else "dec"
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
|
||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
@ -70,11 +67,11 @@ $modifiedLabel cmp #0 ; modified
|
||||
// bytes, step >= 2 or <= -2
|
||||
|
||||
// loop over byte range via loopvar
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.assignExpressionToVariable(range.from, varname, ArrayToElementTypes.getValue(iterableDt), null)
|
||||
asmgen.assignExpressionToVariable(range.to, "$modifiedLabel+1", ArrayToElementTypes.getValue(iterableDt), null)
|
||||
asmgen.out(loopLabel)
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
if(stepsize>0) {
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
@ -102,14 +99,14 @@ $modifiedLabel cmp #0 ; modified
|
||||
// words, step 1 or -1
|
||||
|
||||
stepsize == 1 || stepsize == -1 -> {
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
assignLoopvar(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname+1
|
||||
$modifiedLabel cmp #0 ; modified
|
||||
@ -136,14 +133,14 @@ $modifiedLabel2 cmp #0 ; modified
|
||||
stepsize > 0 -> {
|
||||
|
||||
// (u)words, step >= 2
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
assignLoopvar(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
|
||||
if (iterableDt == DataType.ARRAY_UW) {
|
||||
asmgen.out("""
|
||||
@ -184,14 +181,14 @@ $endLabel""")
|
||||
else -> {
|
||||
|
||||
// (u)words, step <= -2
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
assignLoopvar(stmt, range)
|
||||
asmgen.assignExpressionToRegister(range.to, RegisterOrPair.AY)
|
||||
asmgen.out("""
|
||||
sty $modifiedLabel+1
|
||||
sta $modifiedLabel2+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
|
||||
if(iterableDt==DataType.ARRAY_UW) {
|
||||
asmgen.out("""
|
||||
@ -237,12 +234,18 @@ $endLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
private fun translateForOverIterableVar(stmt: PtForLoop, iterableDt: DataType, ident: PtIdentifier) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val iterableName = asmgen.asmVariableName(ident)
|
||||
val decl = ident.targetVarDecl(program)!!
|
||||
val symbol = asmgen.symbolTable.lookup(ident.name)
|
||||
val decl = symbol!!.astNode as IPtVariable
|
||||
val numElements = when(decl) {
|
||||
is PtConstant -> throw AssemblyError("length of non-array requested")
|
||||
is PtMemMapped -> decl.arraySize
|
||||
is PtVariable -> decl.arraySize
|
||||
}
|
||||
when(iterableDt) {
|
||||
DataType.STR -> {
|
||||
asmgen.out("""
|
||||
@ -252,8 +255,8 @@ $endLabel""")
|
||||
sty $loopLabel+2
|
||||
$loopLabel lda ${65535.toHex()} ; modified
|
||||
beq $endLabel
|
||||
sta ${asmgen.asmVariableName(stmt.loopVar)}""")
|
||||
asmgen.translate(stmt.body)
|
||||
sta ${asmgen.asmVariableName(stmt.variable)}""")
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
inc $loopLabel+1
|
||||
bne $loopLabel
|
||||
@ -262,19 +265,18 @@ $loopLabel lda ${65535.toHex()} ; modified
|
||||
$endLabel""")
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B -> {
|
||||
val length = decl.arraysize!!.constIndex()!!
|
||||
val indexVar = program.makeLabel("for_index")
|
||||
val indexVar = asmgen.makeLabel("for_index")
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
$loopLabel sty $indexVar
|
||||
lda $iterableName,y
|
||||
sta ${asmgen.asmVariableName(stmt.loopVar)}""")
|
||||
asmgen.translate(stmt.body)
|
||||
if(length<=255) {
|
||||
sta ${asmgen.asmVariableName(stmt.variable)}""")
|
||||
asmgen.translate(stmt.statements)
|
||||
if(numElements!!<=255u) {
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
cpy #$length
|
||||
cpy #$numElements
|
||||
beq $endLabel
|
||||
bne $loopLabel""")
|
||||
} else {
|
||||
@ -285,9 +287,9 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(length>=16) {
|
||||
if(numElements>=16u) {
|
||||
// allocate index var on ZP if possible
|
||||
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
@ -298,9 +300,9 @@ $loopLabel sty $indexVar
|
||||
asmgen.out(endLabel)
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
val length = decl.arraysize!!.constIndex()!! * 2
|
||||
val indexVar = program.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.loopVar)
|
||||
val length = numElements!! * 2u
|
||||
val indexVar = asmgen.makeLabel("for_index")
|
||||
val loopvarName = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
$loopLabel sty $indexVar
|
||||
@ -308,8 +310,8 @@ $loopLabel sty $indexVar
|
||||
sta $loopvarName
|
||||
lda $iterableName+1,y
|
||||
sta $loopvarName+1""")
|
||||
asmgen.translate(stmt.body)
|
||||
if(length<=127) {
|
||||
asmgen.translate(stmt.statements)
|
||||
if(length<=127u) {
|
||||
asmgen.out("""
|
||||
ldy $indexVar
|
||||
iny
|
||||
@ -326,9 +328,9 @@ $loopLabel sty $indexVar
|
||||
bne $loopLabel
|
||||
beq $endLabel""")
|
||||
}
|
||||
if(length>=16) {
|
||||
if(length>=16u) {
|
||||
// allocate index var on ZP if possible
|
||||
val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
val result = zeropage.allocate(indexVar, DataType.UBYTE, null, stmt.position, asmgen.errors)
|
||||
result.fold(
|
||||
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
|
||||
failure = { asmgen.out("$indexVar .byte 0") }
|
||||
@ -346,7 +348,7 @@ $loopLabel sty $indexVar
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
|
||||
private fun translateForOverConstRange(stmt: PtForLoop, iterableDt: DataType, range: IntProgression) {
|
||||
if (range.isEmpty() || range.step==0)
|
||||
throw AssemblyError("empty range or step 0")
|
||||
if(iterableDt==DataType.ARRAY_B || iterableDt==DataType.ARRAY_UB) {
|
||||
@ -359,18 +361,18 @@ $loopLabel sty $indexVar
|
||||
}
|
||||
|
||||
// not one of the easy cases, generate more complex code...
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
when(iterableDt) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> {
|
||||
// loop over byte range via loopvar, step >= 2 or <= -2
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #${range.first}
|
||||
sta $varname
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
when (range.step) {
|
||||
0, 1, -1 -> {
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||
@ -430,7 +432,7 @@ $loopLabel""")
|
||||
}
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> {
|
||||
// loop over word range via loopvar, step >= 2 or <= -2
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
when (range.step) {
|
||||
0, 1, -1 -> {
|
||||
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
|
||||
@ -444,7 +446,7 @@ $loopLabel""")
|
||||
sta $varname
|
||||
sty $varname+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<${range.last}
|
||||
@ -470,16 +472,16 @@ $loopLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun translateForSimpleByteRangeAsc(stmt: ForLoop, range: IntProgression) {
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
private fun translateForSimpleByteRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #${range.first}
|
||||
sta $varname
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
if (range.last == 255) {
|
||||
asmgen.out("""
|
||||
inc $varname
|
||||
@ -496,16 +498,16 @@ $endLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun translateForSimpleByteRangeDesc(stmt: ForLoop, range: IntProgression) {
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
private fun translateForSimpleByteRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #${range.first}
|
||||
sta $varname
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
when (range.last) {
|
||||
0 -> {
|
||||
asmgen.out("""
|
||||
@ -533,18 +535,18 @@ $endLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun translateForSimpleWordRangeAsc(stmt: ForLoop, range: IntProgression) {
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
private fun translateForSimpleWordRangeAsc(stmt: PtForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #<${range.first}
|
||||
ldy #>${range.first}
|
||||
sta $varname
|
||||
sty $varname+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<${range.last}
|
||||
@ -560,18 +562,18 @@ $loopLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun translateForSimpleWordRangeDesc(stmt: ForLoop, range: IntProgression) {
|
||||
val loopLabel = program.makeLabel("for_loop")
|
||||
val endLabel = program.makeLabel("for_end")
|
||||
private fun translateForSimpleWordRangeDesc(stmt: PtForLoop, range: IntProgression) {
|
||||
val loopLabel = asmgen.makeLabel("for_loop")
|
||||
val endLabel = asmgen.makeLabel("for_end")
|
||||
asmgen.loopEndLabels.push(endLabel)
|
||||
val varname = asmgen.asmVariableName(stmt.loopVar)
|
||||
val varname = asmgen.asmVariableName(stmt.variable)
|
||||
asmgen.out("""
|
||||
lda #<${range.first}
|
||||
ldy #>${range.first}
|
||||
sta $varname
|
||||
sty $varname+1
|
||||
$loopLabel""")
|
||||
asmgen.translate(stmt.body)
|
||||
asmgen.translate(stmt.statements)
|
||||
asmgen.out("""
|
||||
lda $varname
|
||||
cmp #<${range.last}
|
||||
@ -588,10 +590,10 @@ $loopLabel""")
|
||||
asmgen.loopEndLabels.pop()
|
||||
}
|
||||
|
||||
private fun assignLoopvar(stmt: ForLoop, range: RangeExpression) =
|
||||
private fun assignLoopvar(stmt: PtForLoop, range: PtRange) =
|
||||
asmgen.assignExpressionToVariable(
|
||||
range.from,
|
||||
asmgen.asmVariableName(stmt.loopVar),
|
||||
stmt.loopVarDt(program).getOrElse { throw AssemblyError("unknown dt") },
|
||||
stmt.definingSubroutine)
|
||||
asmgen.asmVariableName(stmt.variable),
|
||||
stmt.variable.type,
|
||||
stmt.definingISub())
|
||||
}
|
||||
|
@ -1,16 +1,6 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.IFunctionCall
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.AddressOf
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.FunctionCallStatement
|
||||
import prog8.ast.statements.InlineAssembly
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignSource
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
||||
@ -18,51 +8,60 @@ import prog8.codegen.cpu6502.assignment.AsmAssignment
|
||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||
|
||||
|
||||
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
internal class FunctionCallAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||
|
||||
internal fun translateFunctionCallStatement(stmt: FunctionCallStatement) {
|
||||
internal fun translateFunctionCallStatement(stmt: PtFunctionCall) {
|
||||
saveXbeforeCall(stmt)
|
||||
translateFunctionCall(stmt, false)
|
||||
translateFunctionCall(stmt)
|
||||
restoreXafterCall(stmt)
|
||||
// just ignore any result values from the function call.
|
||||
}
|
||||
|
||||
internal fun saveXbeforeCall(stmt: IFunctionCall) {
|
||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
internal fun saveXbeforeCall(stmt: PtFunctionCall) {
|
||||
val symbol = asmgen.symbolTable.lookup(stmt.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
if(sub.shouldSaveX()) {
|
||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
if(regSaveOnStack)
|
||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
||||
else
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, (stmt as Node).definingSubroutine!!)
|
||||
if(sub is PtAsmSub) {
|
||||
val regSaveOnStack = sub.address == null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
if (regSaveOnStack)
|
||||
asmgen.saveRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnEntry)
|
||||
else
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, stmt.definingISub()!!)
|
||||
} else
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, stmt.definingISub()!!)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun restoreXafterCall(stmt: IFunctionCall) {
|
||||
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
|
||||
internal fun restoreXafterCall(stmt: PtFunctionCall) {
|
||||
val symbol = asmgen.symbolTable.lookup(stmt.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
if(sub.shouldSaveX()) {
|
||||
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
if(regSaveOnStack)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
||||
else
|
||||
if(sub is PtAsmSub) {
|
||||
val regSaveOnStack = sub.address == null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
|
||||
if (regSaveOnStack)
|
||||
asmgen.restoreRegisterStack(CpuRegister.X, sub.shouldKeepA().saveOnReturn)
|
||||
else
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
} else
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun optimizeIntArgsViaRegisters(sub: Subroutine) =
|
||||
internal fun optimizeIntArgsViaRegisters(sub: PtSub) =
|
||||
(sub.parameters.size==1 && sub.parameters[0].type in IntegerDatatypes)
|
||||
|| (sub.parameters.size==2 && sub.parameters[0].type in ByteDatatypes && sub.parameters[1].type in ByteDatatypes)
|
||||
|
||||
internal fun translateFunctionCall(call: IFunctionCall, isExpression: Boolean) { // TODO remove isExpression unused parameter
|
||||
internal fun translateFunctionCall(call: PtFunctionCall) {
|
||||
// Output only the code to set up the parameters and perform the actual call
|
||||
// NOTE: does NOT output the code to deal with the result values!
|
||||
// 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 sub = call.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${call.target}")
|
||||
val subAsmName = asmgen.asmSymbolName(call.target)
|
||||
val symbol = asmgen.symbolTable.lookup(call.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
val subAsmName = asmgen.asmSymbolName(call.name)
|
||||
|
||||
if(sub.isAsmSubroutine) {
|
||||
if(sub is PtAsmSub) {
|
||||
argumentsViaRegisters(sub, call)
|
||||
if (sub.inline && asmgen.options.optimize) {
|
||||
// inline the subroutine.
|
||||
@ -70,16 +69,13 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
// NOTE: *if* there is a return statement, it will be the only one, and the very last statement of the subroutine
|
||||
// (this condition has been enforced by an ast check earlier)
|
||||
asmgen.out(" \t; inlined routine follows: ${sub.name}")
|
||||
sub.statements.forEach { asmgen.translate(it as InlineAssembly) }
|
||||
sub.children.forEach { asmgen.translate(it as PtInlineAssembly) }
|
||||
asmgen.out(" \t; inlined routine end: ${sub.name}")
|
||||
} else {
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(sub.inline)
|
||||
throw AssemblyError("can only reliably inline asmsub routines at this time")
|
||||
|
||||
else if(sub is PtSub) {
|
||||
if(optimizeIntArgsViaRegisters(sub)) {
|
||||
if(sub.parameters.size==1) {
|
||||
val register = if (sub.parameters[0].type in ByteDatatypes) RegisterOrPair.A else RegisterOrPair.AY
|
||||
@ -100,84 +96,82 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
asmgen.out(" jsr $subAsmName")
|
||||
}
|
||||
else throw AssemblyError("invalid sub type")
|
||||
|
||||
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
|
||||
}
|
||||
|
||||
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
|
||||
private fun argumentsViaRegisters(sub: PtAsmSub, call: PtFunctionCall) {
|
||||
if(sub.parameters.size==1) {
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), call.args[0])
|
||||
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single().second), call.args[0])
|
||||
} else {
|
||||
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.asmParameterRegisters)) {
|
||||
if(asmsub6502ArgsHaveRegisterClobberRisk(call.args, sub.parameters)) {
|
||||
registerArgsViaCpuStackEvaluation(call, sub)
|
||||
} else {
|
||||
asmsub6502ArgsEvalOrder(sub).forEach {
|
||||
val param = sub.parameters[it]
|
||||
val arg = call.args[it]
|
||||
argumentViaRegister(sub, IndexedValue(it, param), arg)
|
||||
argumentViaRegister(sub, IndexedValue(it, param.second), arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun registerArgsViaCpuStackEvaluation(call: IFunctionCall, callee: Subroutine) {
|
||||
private fun registerArgsViaCpuStackEvaluation(call: PtFunctionCall, callee: PtAsmSub) {
|
||||
// this is called when one or more of the arguments are 'complex' and
|
||||
// cannot be assigned to a register easily or risk clobbering other registers.
|
||||
|
||||
require(callee.isAsmSubroutine) { "register args only for asm subroutine ${callee.position}" }
|
||||
if(callee.parameters.isEmpty())
|
||||
return
|
||||
|
||||
// use the cpu hardware stack as intermediate storage for the arguments.
|
||||
val argOrder = asmsub6502ArgsEvalOrder(callee)
|
||||
argOrder.reversed().forEach {
|
||||
asmgen.pushCpuStack(callee.parameters[it].type, call.args[it])
|
||||
asmgen.pushCpuStack(callee.parameters[it].second.type, call.args[it])
|
||||
}
|
||||
argOrder.forEach {
|
||||
val param = callee.parameters[it]
|
||||
val targetVar = callee.searchParameter(param.name)!!
|
||||
asmgen.popCpuStack(param.type, targetVar, (call as Node).definingSubroutine)
|
||||
asmgen.popCpuStack(callee, param.second, param.first)
|
||||
}
|
||||
}
|
||||
|
||||
private fun argumentViaVariable(sub: Subroutine, parameter: SubroutineParameter, value: Expression) {
|
||||
private fun argumentViaVariable(sub: PtSub, parameter: PtSubroutineParameter, value: PtExpression) {
|
||||
// pass parameter via a regular variable (not via registers)
|
||||
val valueIDt = value.inferType(program)
|
||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.type))
|
||||
if(!isArgumentTypeCompatible(value.type, parameter.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + parameter.name)
|
||||
val varName = asmgen.asmVariableName(sub.scopedName + "." + parameter.name)
|
||||
asmgen.assignExpressionToVariable(value, varName, parameter.type, sub)
|
||||
}
|
||||
|
||||
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
|
||||
private fun argumentViaRegister(sub: IPtSubroutine, parameter: IndexedValue<PtSubroutineParameter>, value: PtExpression, registerOverride: RegisterOrPair? = null) {
|
||||
// pass argument via a register parameter
|
||||
val valueIDt = value.inferType(program)
|
||||
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
if(!isArgumentTypeCompatible(valueDt, parameter.value.type))
|
||||
if(!isArgumentTypeCompatible(value.type, parameter.value.type))
|
||||
throw AssemblyError("argument type incompatible")
|
||||
|
||||
val paramRegister = if(registerOverride==null) sub.asmParameterRegisters[parameter.index] else RegisterOrStatusflag(registerOverride, null)
|
||||
val paramRegister: RegisterOrStatusflag = when(sub) {
|
||||
is PtAsmSub -> if(registerOverride==null) sub.parameters[parameter.index].first else RegisterOrStatusflag(registerOverride, null)
|
||||
is PtSub -> RegisterOrStatusflag(registerOverride!!, null)
|
||||
}
|
||||
val statusflag = paramRegister.statusflag
|
||||
val register = paramRegister.registerOrPair
|
||||
val requiredDt = parameter.value.type
|
||||
if(requiredDt!=valueDt) {
|
||||
if(valueDt largerThan requiredDt)
|
||||
if(requiredDt!=value.type) {
|
||||
if(value.type largerThan requiredDt)
|
||||
throw AssemblyError("can only convert byte values to word param types")
|
||||
}
|
||||
if (statusflag!=null) {
|
||||
if(requiredDt!=valueDt)
|
||||
if(requiredDt!=value.type)
|
||||
throw AssemblyError("for statusflag, byte value is required")
|
||||
if (statusflag == Statusflag.Pc) {
|
||||
// this param needs to be set last, right before the jsr
|
||||
// for now, this is already enforced on the subroutine definition by the Ast Checker
|
||||
when(value) {
|
||||
is NumericLiteral -> {
|
||||
is PtNumber -> {
|
||||
val carrySet = value.number.toInt() != 0
|
||||
asmgen.out(if(carrySet) " sec" else " clc")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val sourceName = asmgen.asmVariableName(value)
|
||||
asmgen.out("""
|
||||
pha
|
||||
@ -202,10 +196,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
else {
|
||||
// via register or register pair
|
||||
register!!
|
||||
if(requiredDt largerThan valueDt) {
|
||||
if(requiredDt largerThan value.type) {
|
||||
// we need to sign extend the source, do this via temporary word variable
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_W1", DataType.UBYTE, sub)
|
||||
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", valueDt)
|
||||
asmgen.signExtendVariableLsb("P8ZP_SCRATCH_W1", value.type)
|
||||
asmgen.assignVariableToRegister("P8ZP_SCRATCH_W1", register)
|
||||
} else {
|
||||
val target: AsmAssignTarget =
|
||||
@ -215,9 +209,11 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
val signed = parameter.value.type == DataType.BYTE || parameter.value.type == DataType.WORD
|
||||
AsmAssignTarget.fromRegisters(register, signed, sub, asmgen)
|
||||
}
|
||||
val src = if(valueDt in PassByReferenceDatatypes) {
|
||||
if(value is IdentifierReference) {
|
||||
val addr = AddressOf(value, Position.DUMMY)
|
||||
val src = if(value.type in PassByReferenceDatatypes) {
|
||||
if(value is PtIdentifier) {
|
||||
val addr = PtAddressOf(Position.DUMMY)
|
||||
addr.add(value)
|
||||
addr.parent = sub as PtNode
|
||||
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||
@ -225,7 +221,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
|
||||
} else {
|
||||
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
|
||||
}
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY))
|
||||
asmgen.translateNormalAssignment(AsmAssignment(src, target, program.memsizer, Position.DUMMY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,23 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.PostIncrDecr
|
||||
import prog8.code.ast.PtIdentifier
|
||||
import prog8.code.ast.PtNumber
|
||||
import prog8.code.ast.PtPostIncrDecr
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
|
||||
internal fun translate(stmt: PostIncrDecr) {
|
||||
internal class PostIncrDecrAsmGen(private val program: PtProgram, private val asmgen: AsmGen6502Internal) {
|
||||
internal fun translate(stmt: PtPostIncrDecr) {
|
||||
val incr = stmt.operator=="++"
|
||||
val targetIdent = stmt.target.identifier
|
||||
val targetMemory = stmt.target.memoryAddress
|
||||
val targetArrayIdx = stmt.target.arrayindexed
|
||||
val scope = stmt.definingSubroutine
|
||||
val targetMemory = stmt.target.memory
|
||||
val targetArrayIdx = stmt.target.array
|
||||
val scope = stmt.definingISub()
|
||||
when {
|
||||
targetIdent!=null -> {
|
||||
val what = asmgen.asmVariableName(targetIdent)
|
||||
when (stmt.target.inferType(program).getOr(DataType.UNDEFINED)) {
|
||||
when (stmt.target.type) {
|
||||
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
|
||||
in WordDatatypes -> {
|
||||
if(incr)
|
||||
@ -38,12 +38,12 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
targetMemory!=null -> {
|
||||
when (val addressExpr = targetMemory.addressExpression) {
|
||||
is NumericLiteral -> {
|
||||
when (val addressExpr = targetMemory.address) {
|
||||
is PtNumber -> {
|
||||
val what = addressExpr.number.toHex()
|
||||
asmgen.out(if(incr) " inc $what" else " dec $what")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
is PtIdentifier -> {
|
||||
val what = asmgen.asmVariableName(addressExpr)
|
||||
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
|
||||
if(incr)
|
||||
@ -62,9 +62,9 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
|
||||
}
|
||||
}
|
||||
targetArrayIdx!=null -> {
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
|
||||
val elementDt = targetArrayIdx.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val constIndex = targetArrayIdx.indexer.constIndex()
|
||||
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.variable)
|
||||
val elementDt = targetArrayIdx.type
|
||||
val constIndex = targetArrayIdx.index.asConstInteger()
|
||||
if(constIndex!=null) {
|
||||
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
|
||||
when(elementDt) {
|
||||
|
@ -1,12 +1,10 @@
|
||||
package prog8.codegen.cpu6502
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
|
||||
import prog8.codegen.cpu6502.assignment.TargetStorageKind
|
||||
import prog8.compiler.CallGraph
|
||||
import java.time.LocalDate
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.math.absoluteValue
|
||||
@ -20,30 +18,26 @@ import kotlin.math.absoluteValue
|
||||
* - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
|
||||
*/
|
||||
internal class ProgramAndVarsGen(
|
||||
val program: Program,
|
||||
val program: PtProgram,
|
||||
val options: CompilationOptions,
|
||||
val errors: IErrorReporter,
|
||||
private val symboltable: SymbolTable,
|
||||
private val functioncallAsmGen: FunctionCallAsmGen,
|
||||
private val asmgen: AsmGen,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val allocator: VariableAllocator,
|
||||
private val zeropage: Zeropage
|
||||
) {
|
||||
private val compTarget = options.compTarget
|
||||
private val callGraph = CallGraph(program, true)
|
||||
private val blockVariableInitializers = program.allBlocks.associateWith { it.statements.filterIsInstance<Assignment>() }
|
||||
private val blockVariableInitializers = program.allBlocks().associateWith { it.children.filterIsInstance<PtAssignment>() }
|
||||
|
||||
internal fun generate() {
|
||||
val allInitializers = blockVariableInitializers.asSequence().flatMap { it.value }
|
||||
require(allInitializers.all { it.origin==AssignmentOrigin.VARINIT }) {"all block-level assignments must be a variable initializer"}
|
||||
|
||||
header()
|
||||
val allBlocks = program.allBlocks
|
||||
val allBlocks = program.allBlocks()
|
||||
if(allBlocks.first().name != "main")
|
||||
throw AssemblyError("first block should be 'main'")
|
||||
|
||||
if(errors.noErrors()) {
|
||||
program.allBlocks.forEach { block2asm(it) }
|
||||
program.allBlocks().forEach { block2asm(it) }
|
||||
|
||||
// the global list of all floating point constants for the whole program
|
||||
asmgen.out("; global float constants")
|
||||
@ -71,7 +65,7 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out("; assembler syntax is for the 64tasm cross-assembler")
|
||||
asmgen.out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
|
||||
asmgen.out("")
|
||||
asmgen.out(".cpu '$cpu'\n.enc 'none'\n")
|
||||
asmgen.out(".cpu '$cpu'\n.enc 'none'")
|
||||
|
||||
// the global prog8 variables needed
|
||||
val zp = zeropage
|
||||
@ -94,13 +88,13 @@ internal class ProgramAndVarsGen(
|
||||
when(options.output) {
|
||||
OutputType.RAW -> {
|
||||
asmgen.out("; ---- raw assembler program ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}\n")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
}
|
||||
OutputType.PRG -> {
|
||||
when(options.launcher) {
|
||||
CbmPrgLauncherType.BASIC -> {
|
||||
if (options.loadAddress != options.compTarget.machine.PROGRAM_LOAD_ADDRESS) {
|
||||
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.toplevelModule.position)
|
||||
errors.err("BASIC output must have load address ${options.compTarget.machine.PROGRAM_LOAD_ADDRESS.toHex()}", program.position)
|
||||
}
|
||||
asmgen.out("; ---- basic program with sys call ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
@ -108,14 +102,14 @@ internal class ProgramAndVarsGen(
|
||||
asmgen.out(" .word (+), $year")
|
||||
asmgen.out(" .null $9e, format(' %d ', prog8_entrypoint), $3a, $8f, ' prog8'")
|
||||
asmgen.out("+\t.word 0")
|
||||
asmgen.out("prog8_entrypoint\t; assembly code starts here\n")
|
||||
asmgen.out("prog8_entrypoint\t; assembly code starts here")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
}
|
||||
CbmPrgLauncherType.NONE -> {
|
||||
asmgen.out("; ---- program without basic sys call ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}\n")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
@ -124,7 +118,7 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
OutputType.XEX -> {
|
||||
asmgen.out("; ---- atari xex program ----")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}\n")
|
||||
asmgen.out("* = ${options.loadAddress.toHex()}")
|
||||
if(!options.noSysInit)
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system")
|
||||
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
|
||||
@ -168,62 +162,89 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun memorySlabs() {
|
||||
asmgen.out("; memory slabs")
|
||||
asmgen.out("prog8_slabs\t.block")
|
||||
for(slab in symboltable.allMemorySlabs) {
|
||||
if(slab.align>1u)
|
||||
asmgen.out("\t.align ${slab.align.toHex()}")
|
||||
asmgen.out("${slab.name}\t.fill ${slab.size}")
|
||||
if(symboltable.allMemorySlabs.isNotEmpty()) {
|
||||
asmgen.out("; memory slabs\n .section slabs_BSS")
|
||||
asmgen.out("prog8_slabs\t.block")
|
||||
for (slab in symboltable.allMemorySlabs) {
|
||||
if (slab.align > 1u)
|
||||
asmgen.out("\t.align ${slab.align.toHex()}")
|
||||
asmgen.out("${slab.name}\t.fill ${slab.size}")
|
||||
}
|
||||
asmgen.out("\t.bend\n .send slabs_BSS")
|
||||
}
|
||||
asmgen.out("\t.bend")
|
||||
}
|
||||
|
||||
private fun footer() {
|
||||
// program end
|
||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||
asmgen.out("; bss sections")
|
||||
if(options.varsHigh) {
|
||||
if(options.compTarget.machine.BSSHIGHRAM_START == 0u || options.compTarget.machine.BSSHIGHRAM_END==0u) {
|
||||
throw AssemblyError("current compilation target hasn't got the high ram area properly defined")
|
||||
}
|
||||
// BSS vars in high ram area, memory() slabs just concatenated at the end of the program.
|
||||
if(symboltable.allMemorySlabs.isNotEmpty()) {
|
||||
asmgen.out(" .dsection slabs_BSS")
|
||||
}
|
||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||
asmgen.out(" * = ${options.compTarget.machine.BSSHIGHRAM_START.toHex()}")
|
||||
asmgen.out("prog8_bss_section_start")
|
||||
asmgen.out(" .dsection BSS")
|
||||
asmgen.out(" .cerror * >= ${options.compTarget.machine.BSSHIGHRAM_END.toHex()}, \"too many variables for BSS section\"")
|
||||
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
|
||||
} else {
|
||||
// BSS vars followed by memory() slabs, concatenated at the end of the program.
|
||||
asmgen.out("prog8_bss_section_start")
|
||||
asmgen.out(" .dsection BSS")
|
||||
asmgen.out("prog8_bss_section_size = * - prog8_bss_section_start")
|
||||
if(symboltable.allMemorySlabs.isNotEmpty()) {
|
||||
asmgen.out(" .dsection slabs_BSS")
|
||||
}
|
||||
asmgen.out("prog8_program_end\t; end of program label for progend()")
|
||||
}
|
||||
}
|
||||
|
||||
private fun block2asm(block: Block) {
|
||||
private fun block2asm(block: PtBlock) {
|
||||
asmgen.out("")
|
||||
asmgen.out("; ---- block: '${block.name}' ----")
|
||||
if(block.address!=null)
|
||||
asmgen.out("* = ${block.address!!.toHex()}")
|
||||
else {
|
||||
if("align_word" in block.options())
|
||||
if(block.alignment==PtBlock.BlockAlignment.WORD)
|
||||
asmgen.out("\t.align 2")
|
||||
else if("align_page" in block.options())
|
||||
else if(block.alignment==PtBlock.BlockAlignment.PAGE)
|
||||
asmgen.out("\t.align $100")
|
||||
}
|
||||
|
||||
asmgen.out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
|
||||
asmgen.out("${block.name}\t" + (if(block.forceOutput) ".block" else ".proc"))
|
||||
asmgen.outputSourceLine(block)
|
||||
|
||||
createBlockVariables(block)
|
||||
asmsubs2asm(block.statements)
|
||||
asmsubs2asm(block.children)
|
||||
|
||||
asmgen.out("")
|
||||
|
||||
val initializers = blockVariableInitializers.getValue(block)
|
||||
val notInitializers = block.statements.filterNot { it in initializers }
|
||||
val notInitializers = block.children.filterNot { it in initializers }
|
||||
notInitializers.forEach { asmgen.translate(it) }
|
||||
|
||||
if(!options.dontReinitGlobals) {
|
||||
// generate subroutine to initialize block-level (global) variables
|
||||
if (initializers.isNotEmpty()) {
|
||||
asmgen.out("prog8_init_vars\t.block\n")
|
||||
initializers.forEach { assign -> asmgen.translate(assign) }
|
||||
asmgen.out(" rts\n .bend")
|
||||
// generate subroutine to initialize block-level (global) variables
|
||||
if (initializers.isNotEmpty()) {
|
||||
asmgen.out("prog8_init_vars\t.block")
|
||||
initializers.forEach { assign ->
|
||||
if((assign.value as? PtNumber)?.number != 0.0 || allocator.isZpVar(assign.target.identifier!!.name))
|
||||
asmgen.translate(assign)
|
||||
// the other variables that should be set to zero are done so as part of the BSS section.
|
||||
}
|
||||
asmgen.out(" rts\n .bend")
|
||||
}
|
||||
|
||||
asmgen.out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
|
||||
asmgen.out(if(block.forceOutput) "\n\t.bend" else "\n\t.pend")
|
||||
}
|
||||
|
||||
private fun getVars(scope: StNode): Map<String, StNode> =
|
||||
scope.children.filter { it.value.type in arrayOf(StNodeType.STATICVAR, StNodeType.CONSTANT, StNodeType.MEMVAR) }
|
||||
|
||||
private fun createBlockVariables(block: Block) {
|
||||
val scope = symboltable.lookupOrElse(block.name) { throw AssemblyError("lookup") }
|
||||
private fun createBlockVariables(block: PtBlock) {
|
||||
val scope = symboltable.lookupUnscopedOrElse(block.name) { throw AssemblyError("lookup") }
|
||||
require(scope.type==StNodeType.BLOCK)
|
||||
val varsInBlock = getVars(scope)
|
||||
|
||||
@ -247,18 +268,10 @@ internal class ProgramAndVarsGen(
|
||||
nonZpVariables2asm(variables)
|
||||
}
|
||||
|
||||
internal fun translateSubroutine(sub: Subroutine) {
|
||||
var onlyVariables = false
|
||||
|
||||
internal fun translateAsmSubroutine(sub: PtAsmSub) {
|
||||
if(sub.inline) {
|
||||
if(options.optimize) {
|
||||
if(sub.isAsmSubroutine || callGraph.unused(sub))
|
||||
return
|
||||
|
||||
// from an inlined subroutine only the local variables are generated,
|
||||
// all other code statements are omitted in the subroutine itself
|
||||
// (they've been inlined at the call site, remember?)
|
||||
onlyVariables = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,7 +279,7 @@ internal class ProgramAndVarsGen(
|
||||
|
||||
val asmStartScope: String
|
||||
val asmEndScope: String
|
||||
if(sub.definingBlock.options().contains("force_output")) {
|
||||
if(sub.definingBlock()!!.forceOutput) {
|
||||
asmStartScope = ".block"
|
||||
asmEndScope = ".bend"
|
||||
} else {
|
||||
@ -274,105 +287,127 @@ internal class ProgramAndVarsGen(
|
||||
asmEndScope = ".pend"
|
||||
}
|
||||
|
||||
if(sub.isAsmSubroutine) {
|
||||
if(sub.asmAddress!=null)
|
||||
return // already done at the memvars section
|
||||
if(sub.address!=null)
|
||||
return // already done at the memvars section
|
||||
|
||||
// asmsub with most likely just an inline asm in it
|
||||
asmgen.out("${sub.name}\t$asmStartScope")
|
||||
sub.statements.forEach { asmgen.translate(it) }
|
||||
asmgen.out(" $asmEndScope\n")
|
||||
// asmsub with most likely just an inline asm in it
|
||||
asmgen.out("${sub.name}\t$asmStartScope")
|
||||
sub.children.forEach { asmgen.translate(it) }
|
||||
asmgen.out(" $asmEndScope")
|
||||
}
|
||||
|
||||
|
||||
internal fun translateSubroutine(sub: PtSub) {
|
||||
asmgen.out("")
|
||||
|
||||
val asmStartScope: String
|
||||
val asmEndScope: String
|
||||
if(sub.definingBlock()!!.forceOutput) {
|
||||
asmStartScope = ".block"
|
||||
asmEndScope = ".bend"
|
||||
} else {
|
||||
// regular subroutine
|
||||
asmgen.out("${sub.name}\t$asmStartScope")
|
||||
|
||||
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
|
||||
require(scope.type==StNodeType.SUBROUTINE)
|
||||
val varsInSubroutine = getVars(scope)
|
||||
|
||||
// Zeropage Variables
|
||||
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
|
||||
zeropagevars2asm(varnames)
|
||||
|
||||
// MemDefs and Consts
|
||||
val mvs = varsInSubroutine
|
||||
.filter { it.value.type==StNodeType.MEMVAR }
|
||||
.map { it.value as StMemVar }
|
||||
val consts = varsInSubroutine
|
||||
.filter { it.value.type==StNodeType.CONSTANT }
|
||||
.map { it.value as StConstant }
|
||||
memdefsAndConsts2asm(mvs, consts)
|
||||
|
||||
asmsubs2asm(sub.statements)
|
||||
|
||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
||||
if(sub.name=="start" && sub.definingBlock.name=="main")
|
||||
entrypointInitialization()
|
||||
|
||||
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
|
||||
asmgen.out("; simple int arg(s) passed via register(s)")
|
||||
if(sub.parameters.size==1) {
|
||||
val dt = sub.parameters[0].type
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
|
||||
if(dt in ByteDatatypes)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
else
|
||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||
} else {
|
||||
require(sub.parameters.size==2)
|
||||
// 2 simple byte args, first in A, second in Y
|
||||
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
|
||||
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target1)
|
||||
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
||||
}
|
||||
}
|
||||
|
||||
if(!onlyVariables) {
|
||||
asmgen.out("; statements")
|
||||
sub.statements.forEach { asmgen.translate(it) }
|
||||
}
|
||||
|
||||
asmgen.out("; variables")
|
||||
val asmGenInfo = asmgen.subroutineExtra(sub)
|
||||
for((dt, name, addr) in asmGenInfo.extraVars) {
|
||||
if(addr!=null)
|
||||
asmgen.out("$name = $addr")
|
||||
else when(dt) {
|
||||
DataType.UBYTE -> asmgen.out("$name .byte 0")
|
||||
DataType.UWORD -> asmgen.out("$name .word 0")
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
if(asmGenInfo.usedRegsaveA) // will probably never occur
|
||||
asmgen.out("prog8_regsaveA .byte 0")
|
||||
if(asmGenInfo.usedRegsaveX)
|
||||
asmgen.out("prog8_regsaveX .byte 0")
|
||||
if(asmGenInfo.usedRegsaveY)
|
||||
asmgen.out("prog8_regsaveY .byte 0")
|
||||
if(asmGenInfo.usedFloatEvalResultVar1)
|
||||
asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
||||
if(asmGenInfo.usedFloatEvalResultVar2)
|
||||
asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
|
||||
|
||||
// normal statically allocated variables
|
||||
val variables = varsInSubroutine
|
||||
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
|
||||
.map { it.value as StStaticVariable }
|
||||
nonZpVariables2asm(variables)
|
||||
|
||||
asmgen.out(" $asmEndScope\n")
|
||||
asmStartScope = ".proc"
|
||||
asmEndScope = ".pend"
|
||||
}
|
||||
|
||||
asmgen.out("${sub.name}\t$asmStartScope")
|
||||
|
||||
val scope = symboltable.lookupOrElse(sub.scopedName) { throw AssemblyError("lookup") }
|
||||
require(scope.type==StNodeType.SUBROUTINE)
|
||||
val varsInSubroutine = getVars(scope)
|
||||
|
||||
// Zeropage Variables
|
||||
val varnames = varsInSubroutine.filter { it.value.type==StNodeType.STATICVAR }.map { it.value.scopedName }.toSet()
|
||||
zeropagevars2asm(varnames)
|
||||
|
||||
// MemDefs and Consts
|
||||
val mvs = varsInSubroutine
|
||||
.filter { it.value.type==StNodeType.MEMVAR }
|
||||
.map { it.value as StMemVar }
|
||||
val consts = varsInSubroutine
|
||||
.filter { it.value.type==StNodeType.CONSTANT }
|
||||
.map { it.value as StConstant }
|
||||
memdefsAndConsts2asm(mvs, consts)
|
||||
|
||||
asmsubs2asm(sub.children)
|
||||
|
||||
// the main.start subroutine is the program's entrypoint and should perform some initialization logic
|
||||
if(sub.name=="start" && sub.definingBlock()!!.name=="main")
|
||||
entrypointInitialization()
|
||||
|
||||
if(functioncallAsmGen.optimizeIntArgsViaRegisters(sub)) {
|
||||
asmgen.out("; simple int arg(s) passed via register(s)")
|
||||
if(sub.parameters.size==1) {
|
||||
val dt = sub.parameters[0].type
|
||||
val target = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, sub, variableAsmName = sub.parameters[0].name)
|
||||
if(dt in ByteDatatypes)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target)
|
||||
else
|
||||
asmgen.assignRegister(RegisterOrPair.AY, target)
|
||||
} else {
|
||||
require(sub.parameters.size==2)
|
||||
// 2 simple byte args, first in A, second in Y
|
||||
val target1 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
|
||||
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, sub.parameters[1].type, sub, variableAsmName = sub.parameters[1].name)
|
||||
asmgen.assignRegister(RegisterOrPair.A, target1)
|
||||
asmgen.assignRegister(RegisterOrPair.Y, target2)
|
||||
}
|
||||
}
|
||||
|
||||
asmgen.out("; statements")
|
||||
sub.children.forEach { asmgen.translate(it) }
|
||||
|
||||
asmgen.out("; variables")
|
||||
val asmGenInfo = asmgen.subroutineExtra(sub)
|
||||
for((dt, name, addr) in asmGenInfo.extraVars) {
|
||||
if(addr!=null)
|
||||
asmgen.out("$name = $addr")
|
||||
else when(dt) {
|
||||
DataType.UBYTE -> asmgen.out("$name .byte 0")
|
||||
DataType.UWORD -> asmgen.out("$name .word 0")
|
||||
else -> throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
if(asmGenInfo.usedRegsaveA) // will probably never occur
|
||||
asmgen.out("prog8_regsaveA .byte 0")
|
||||
if(asmGenInfo.usedRegsaveX)
|
||||
asmgen.out("prog8_regsaveX .byte 0")
|
||||
if(asmGenInfo.usedRegsaveY)
|
||||
asmgen.out("prog8_regsaveY .byte 0")
|
||||
if(asmGenInfo.usedFloatEvalResultVar1)
|
||||
asmgen.out("$subroutineFloatEvalResultVar1 .byte 0,0,0,0,0")
|
||||
if(asmGenInfo.usedFloatEvalResultVar2)
|
||||
asmgen.out("$subroutineFloatEvalResultVar2 .byte 0,0,0,0,0")
|
||||
|
||||
// normal statically allocated variables
|
||||
val variables = varsInSubroutine
|
||||
.filter { it.value.type==StNodeType.STATICVAR && !allocator.isZpVar(it.value.scopedName) }
|
||||
.map { it.value as StStaticVariable }
|
||||
nonZpVariables2asm(variables)
|
||||
|
||||
asmgen.out(" $asmEndScope")
|
||||
}
|
||||
|
||||
private fun entrypointInitialization() {
|
||||
asmgen.out("; program startup initialization")
|
||||
asmgen.out(" cld")
|
||||
if(!options.dontReinitGlobals) {
|
||||
blockVariableInitializers.forEach {
|
||||
if (it.value.isNotEmpty())
|
||||
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
|
||||
}
|
||||
asmgen.out(" cld | tsx | stx prog8_lib.orig_stackpointer ; required for sys.exit()")
|
||||
// set full BSS area to zero
|
||||
asmgen.out("""
|
||||
.if prog8_bss_section_size>0
|
||||
; reset all variables in BSS section to zero
|
||||
lda #<prog8_bss_section_start
|
||||
ldy #>prog8_bss_section_start
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
ldx #<prog8_bss_section_size
|
||||
ldy #>prog8_bss_section_size
|
||||
lda #0
|
||||
jsr prog8_lib.memset
|
||||
.endif""")
|
||||
|
||||
blockVariableInitializers.forEach {
|
||||
if (it.value.isNotEmpty())
|
||||
asmgen.out(" jsr ${it.key.name}.prog8_init_vars")
|
||||
}
|
||||
|
||||
// string and array variables in zeropage that have initializer value, should be initialized
|
||||
@ -420,21 +455,20 @@ internal class ProgramAndVarsGen(
|
||||
arrayVariable2asm(varname, it.alloc.dt, it.value, null)
|
||||
}
|
||||
|
||||
asmgen.out("""+ tsx
|
||||
stx prog8_lib.orig_stackpointer ; required for sys.exit()
|
||||
ldx #255 ; init estack ptr
|
||||
asmgen.out("""+
|
||||
ldx #127 ; init estack ptr (half page)
|
||||
clv
|
||||
clc""")
|
||||
}
|
||||
|
||||
private class ZpStringWithInitial(
|
||||
val name: List<String>,
|
||||
val name: String,
|
||||
val alloc: MemoryAllocator.VarAllocation,
|
||||
val value: Pair<String, Encoding>
|
||||
)
|
||||
|
||||
private class ZpArrayWithInitial(
|
||||
val name: List<String>,
|
||||
val name: String,
|
||||
val alloc: MemoryAllocator.VarAllocation,
|
||||
val value: StArray
|
||||
)
|
||||
@ -443,9 +477,10 @@ internal class ProgramAndVarsGen(
|
||||
val result = mutableListOf<ZpStringWithInitial>()
|
||||
val vars = allocator.zeropageVars.filter { it.value.dt==DataType.STR }
|
||||
for (variable in vars) {
|
||||
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
|
||||
val scopedName = variable.key
|
||||
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||
if(svar.onetimeInitializationStringValue!=null)
|
||||
result.add(ZpStringWithInitial(variable.key, variable.value, svar.onetimeInitializationStringValue!!))
|
||||
result.add(ZpStringWithInitial(scopedName, variable.value, svar.onetimeInitializationStringValue!!))
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -454,36 +489,69 @@ internal class ProgramAndVarsGen(
|
||||
val result = mutableListOf<ZpArrayWithInitial>()
|
||||
val vars = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes }
|
||||
for (variable in vars) {
|
||||
val svar = symboltable.flat.getValue(variable.key) as StStaticVariable
|
||||
val scopedName = variable.key
|
||||
val svar = symboltable.flat.getValue(scopedName) as StStaticVariable
|
||||
if(svar.onetimeInitializationArrayValue!=null)
|
||||
result.add(ZpArrayWithInitial(variable.key, variable.value, svar.onetimeInitializationArrayValue!!))
|
||||
result.add(ZpArrayWithInitial(scopedName, variable.value, svar.onetimeInitializationArrayValue!!))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun zeropagevars2asm(varNames: Set<List<String>>) {
|
||||
val zpVariables = allocator.zeropageVars.filter { it.key in varNames }
|
||||
private fun zeropagevars2asm(varNames: Set<String>) {
|
||||
val zpVariables = allocator.zeropageVars.filter { it.key in varNames }.toList().sortedBy { it.second.address }
|
||||
for ((scopedName, zpvar) in zpVariables) {
|
||||
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit())
|
||||
if (scopedName.startsWith("cx16.r"))
|
||||
continue // The 16 virtual registers of the cx16 are not actual variables in zp, they're memory mapped
|
||||
asmgen.out("${scopedName.last()} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
||||
asmgen.out("${scopedName.substringAfterLast('.')} \t= ${zpvar.address} \t; zp ${zpvar.dt}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun nonZpVariables2asm(variables: List<StStaticVariable>) {
|
||||
asmgen.out("")
|
||||
asmgen.out("; non-zeropage variables")
|
||||
val (stringvars, othervars) = variables.partition { it.dt==DataType.STR }
|
||||
stringvars.forEach {
|
||||
outputStringvar(it.name, it.onetimeInitializationStringValue!!.second, it.onetimeInitializationStringValue!!.first)
|
||||
val (varsNoInit, varsWithInit) = variables.partition { it.uninitialized }
|
||||
if(varsNoInit.isNotEmpty()) {
|
||||
asmgen.out("; non-zeropage variables without initialization value")
|
||||
asmgen.out(" .section BSS")
|
||||
varsNoInit.sortedWith(compareBy<StStaticVariable> { it.name }.thenBy { it.dt }).forEach {
|
||||
uninitializedVariable2asm(it)
|
||||
}
|
||||
asmgen.out(" .send BSS")
|
||||
}
|
||||
othervars.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
|
||||
if(varsWithInit.isNotEmpty()) {
|
||||
asmgen.out("; non-zeropage variables")
|
||||
val (stringvars, othervars) = varsWithInit.sortedBy { it.name }.partition { it.dt == DataType.STR }
|
||||
stringvars.forEach {
|
||||
outputStringvar(
|
||||
it.name,
|
||||
it.onetimeInitializationStringValue!!.second,
|
||||
it.onetimeInitializationStringValue!!.first
|
||||
)
|
||||
}
|
||||
othervars.sortedBy { it.type }.forEach {
|
||||
staticVariable2asm(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun uninitializedVariable2asm(variable: StStaticVariable) {
|
||||
when (variable.dt) {
|
||||
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ?")
|
||||
DataType.BYTE -> asmgen.out("${variable.name}\t.char ?")
|
||||
DataType.UWORD -> asmgen.out("${variable.name}\t.word ?")
|
||||
DataType.WORD -> asmgen.out("${variable.name}\t.sint ?")
|
||||
DataType.FLOAT -> asmgen.out("${variable.name}\t.fill ${compTarget.machine.FLOAT_MEM_SIZE}")
|
||||
in ArrayDatatypes -> {
|
||||
val numbytes = compTarget.memorySize(variable.dt, variable.length!!)
|
||||
asmgen.out("${variable.name}\t.fill $numbytes")
|
||||
}
|
||||
else -> {
|
||||
throw AssemblyError("weird dt")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun staticVariable2asm(variable: StStaticVariable) {
|
||||
val name = variable.name
|
||||
val initialValue: Number =
|
||||
if(variable.onetimeInitializationNumericValue!=null) {
|
||||
if(variable.dt== DataType.FLOAT)
|
||||
@ -493,22 +561,22 @@ internal class ProgramAndVarsGen(
|
||||
} else 0
|
||||
|
||||
when (variable.dt) {
|
||||
DataType.UBYTE -> asmgen.out("$name\t.byte ${initialValue.toHex()}")
|
||||
DataType.BYTE -> asmgen.out("$name\t.char $initialValue")
|
||||
DataType.UWORD -> asmgen.out("$name\t.word ${initialValue.toHex()}")
|
||||
DataType.WORD -> asmgen.out("$name\t.sint $initialValue")
|
||||
DataType.UBYTE -> asmgen.out("${variable.name}\t.byte ${initialValue.toHex()}")
|
||||
DataType.BYTE -> asmgen.out("${variable.name}\t.char $initialValue")
|
||||
DataType.UWORD -> asmgen.out("${variable.name}\t.word ${initialValue.toHex()}")
|
||||
DataType.WORD -> asmgen.out("${variable.name}\t.sint $initialValue")
|
||||
DataType.FLOAT -> {
|
||||
if(initialValue==0) {
|
||||
asmgen.out("$name\t.byte 0,0,0,0,0 ; float")
|
||||
asmgen.out("${variable.name}\t.byte 0,0,0,0,0 ; float")
|
||||
} else {
|
||||
val floatFill = compTarget.machine.getFloatAsmBytes(initialValue)
|
||||
asmgen.out("$name\t.byte $floatFill ; float $initialValue")
|
||||
asmgen.out("${variable.name}\t.byte $floatFill ; float $initialValue")
|
||||
}
|
||||
}
|
||||
DataType.STR -> {
|
||||
throw AssemblyError("all string vars should have been interned into prog")
|
||||
}
|
||||
in ArrayDatatypes -> arrayVariable2asm(name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
|
||||
in ArrayDatatypes -> arrayVariable2asm(variable.name, variable.dt, variable.onetimeInitializationArrayValue, variable.length)
|
||||
else -> {
|
||||
throw AssemblyError("weird dt")
|
||||
}
|
||||
@ -579,10 +647,10 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
|
||||
private fun memdefsAndConsts2asm(memvars: Collection<StMemVar>, consts: Collection<StConstant>) {
|
||||
memvars.forEach {
|
||||
memvars.sortedBy { it.address }.forEach {
|
||||
asmgen.out(" ${it.name} = ${it.address.toHex()}")
|
||||
}
|
||||
consts.forEach {
|
||||
consts.sortedBy { it.name }.forEach {
|
||||
if(it.dt==DataType.FLOAT)
|
||||
asmgen.out(" ${it.name} = ${it.value}")
|
||||
else
|
||||
@ -590,12 +658,12 @@ internal class ProgramAndVarsGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun asmsubs2asm(statements: List<Statement>) {
|
||||
private fun asmsubs2asm(statements: List<PtNode>) {
|
||||
statements
|
||||
.filter { it is Subroutine && it.isAsmSubroutine && it.asmAddress!=null }
|
||||
.filter { it is PtAsmSub && it.address!=null }
|
||||
.forEach { asmsub ->
|
||||
asmsub as Subroutine
|
||||
asmgen.out(" ${asmsub.name} = ${asmsub.asmAddress!!.toHex()}")
|
||||
asmsub as PtAsmSub
|
||||
asmgen.out(" ${asmsub.name} = ${asmsub.address!!.toHex()}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -620,8 +688,8 @@ internal class ProgramAndVarsGen(
|
||||
if(it.number!=null) {
|
||||
"$" + it.number!!.toInt().toString(16).padStart(4, '0')
|
||||
}
|
||||
else if(it.addressOf!=null) {
|
||||
asmgen.asmSymbolName(it.addressOf!!)
|
||||
else if(it.addressOfSymbol!=null) {
|
||||
asmgen.asmSymbolName(it.addressOfSymbol!!)
|
||||
}
|
||||
else
|
||||
throw AssemblyError("weird array elt")
|
||||
|
@ -16,13 +16,14 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
|
||||
private val zeropage = options.compTarget.machine.zeropage
|
||||
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
|
||||
internal val zeropageVars: Map<List<String>, MemoryAllocator.VarAllocation> = zeropage.allocatedVariables
|
||||
internal val zeropageVars: Map<String, MemoryAllocator.VarAllocation>
|
||||
|
||||
init {
|
||||
allocateZeropageVariables()
|
||||
zeropageVars = zeropage.allocatedVariables
|
||||
}
|
||||
|
||||
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables
|
||||
internal fun isZpVar(scopedName: String) = scopedName in zeropageVars
|
||||
|
||||
internal fun getFloatAsmConst(number: Double): String {
|
||||
val asmName = globalFloatConsts[number]
|
||||
@ -59,7 +60,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
variable.scopedName,
|
||||
variable.dt,
|
||||
variable.length,
|
||||
variable.position,
|
||||
variable.astNode.position,
|
||||
errors
|
||||
)
|
||||
result.fold(
|
||||
@ -67,7 +68,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
numVariablesAllocatedInZP++
|
||||
},
|
||||
failure = {
|
||||
errors.err(it.message!!, variable.position)
|
||||
errors.err(it.message!!, variable.astNode.position)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -78,7 +79,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
variable.scopedName,
|
||||
variable.dt,
|
||||
variable.length,
|
||||
variable.position,
|
||||
variable.astNode.position,
|
||||
errors
|
||||
)
|
||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||
@ -88,7 +89,8 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
// try to allocate any other interger variables into the zeropage until it is full.
|
||||
// TODO some form of intelligent priorization? most often used variables first? loopcounter vars first? ...?
|
||||
if(errors.noErrors()) {
|
||||
for (variable in varsDontCare.sortedBy { it.scopedName.size }) {
|
||||
val sortedList = varsDontCare.sortedByDescending { it.scopedName }
|
||||
for (variable in sortedList) {
|
||||
if(variable.dt in IntegerDatatypes) {
|
||||
if(zeropage.free.isEmpty()) {
|
||||
break
|
||||
@ -97,7 +99,7 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
variable.scopedName,
|
||||
variable.dt,
|
||||
variable.length,
|
||||
variable.position,
|
||||
variable.astNode.position,
|
||||
errors
|
||||
)
|
||||
result.onSuccess { numVariablesAllocatedInZP++ }
|
||||
@ -124,6 +126,6 @@ internal class VariableAllocator(private val symboltable: SymbolTable,
|
||||
}
|
||||
}
|
||||
collect(st)
|
||||
return vars
|
||||
return vars.sortedBy { it.dt }
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,9 @@
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.DirectMemoryWrite
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.AsmGen
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
import prog8.codegen.cpu6502.returnsWhatWhere
|
||||
|
||||
|
||||
internal enum class TargetStorageKind {
|
||||
@ -29,25 +25,25 @@ internal enum class SourceStorageKind {
|
||||
}
|
||||
|
||||
internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
private val asmgen: AsmGen,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
val datatype: DataType,
|
||||
val scope: Subroutine?,
|
||||
val scope: IPtSubroutine?,
|
||||
private val variableAsmName: String? = null,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryWrite? = null,
|
||||
val array: PtArrayIndexer? = null,
|
||||
val memory: PtMemoryByte? = null,
|
||||
val register: RegisterOrPair? = null,
|
||||
val origAstTarget: AssignTarget? = null
|
||||
val origAstTarget: PtAssignTarget? = null
|
||||
)
|
||||
{
|
||||
val constArrayIndexValue by lazy { array?.indexer?.constIndex()?.toUInt() }
|
||||
val constArrayIndexValue by lazy { array?.index?.asConstInteger()?.toUInt() }
|
||||
val asmVarname: String by lazy {
|
||||
if (array == null)
|
||||
variableAsmName!!
|
||||
else
|
||||
asmgen.asmVariableName(array.arrayvar)
|
||||
asmgen.asmVariableName(array.variable)
|
||||
}
|
||||
|
||||
lateinit var origAssign: AsmAssignment
|
||||
lateinit var origAssign: AsmAssignmentBase
|
||||
|
||||
init {
|
||||
if(register!=null && datatype !in NumericDatatypes)
|
||||
@ -55,33 +51,31 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromAstAssignment(assign: Assignment, program: Program, asmgen: AsmGen): AsmAssignTarget {
|
||||
with(assign.target) {
|
||||
val idt = inferType(program)
|
||||
val dt = idt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
fun fromAstAssignment(target: PtAssignTarget, definingSub: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget {
|
||||
with(target) {
|
||||
when {
|
||||
identifier != null -> {
|
||||
val parameter = identifier!!.targetVarDecl(program)?.subroutineParameter
|
||||
val parameter = asmgen.findSubroutineParameter(identifier!!.name, asmgen)
|
||||
if (parameter!=null) {
|
||||
val sub = parameter.definingSubroutine!!
|
||||
if (sub.isAsmSubroutine) {
|
||||
val reg = sub.asmParameterRegisters[sub.parameters.indexOf(parameter)]
|
||||
val sub = parameter.definingAsmSub()
|
||||
if (sub!=null) {
|
||||
val reg = sub.parameters.single { it.second===parameter }.first
|
||||
if(reg.statusflag!=null)
|
||||
throw AssemblyError("can't assign value to processor statusflag directly")
|
||||
else
|
||||
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, dt, assign.definingSubroutine, register=reg.registerOrPair, origAstTarget = this)
|
||||
return AsmAssignTarget(TargetStorageKind.REGISTER, asmgen, type, definingSub, register=reg.registerOrPair, origAstTarget = this)
|
||||
}
|
||||
}
|
||||
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, dt, assign.definingSubroutine, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
return AsmAssignTarget(TargetStorageKind.VARIABLE, asmgen, type, definingSub, variableAsmName = asmgen.asmVariableName(identifier!!), origAstTarget = this)
|
||||
}
|
||||
arrayindexed != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, dt, assign.definingSubroutine, array = arrayindexed, origAstTarget = this)
|
||||
memoryAddress != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, dt, assign.definingSubroutine, memory = memoryAddress, origAstTarget = this)
|
||||
array != null -> return AsmAssignTarget(TargetStorageKind.ARRAY, asmgen, type, definingSub, array = array, origAstTarget = this)
|
||||
memory != null -> return AsmAssignTarget(TargetStorageKind.MEMORY, asmgen, type, definingSub, memory = memory, origAstTarget = this)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: Subroutine?, asmgen: AsmGen): AsmAssignTarget =
|
||||
fun fromRegisters(registers: RegisterOrPair, signed: Boolean, scope: IPtSubroutine?, asmgen: AsmGen6502Internal): AsmAssignTarget =
|
||||
when(registers) {
|
||||
RegisterOrPair.A,
|
||||
RegisterOrPair.X,
|
||||
@ -112,69 +106,66 @@ internal class AsmAssignTarget(val kind: TargetStorageKind,
|
||||
}
|
||||
|
||||
internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
private val program: Program,
|
||||
private val asmgen: AsmGen,
|
||||
private val program: PtProgram,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
val datatype: DataType,
|
||||
private val variableAsmName: String? = null,
|
||||
val array: ArrayIndexedExpression? = null,
|
||||
val memory: DirectMemoryRead? = null,
|
||||
val array: PtArrayIndexer? = null,
|
||||
val memory: PtMemoryByte? = null,
|
||||
val register: RegisterOrPair? = null,
|
||||
val number: NumericLiteral? = null,
|
||||
val expression: Expression? = null
|
||||
val number: PtNumber? = null,
|
||||
val expression: PtExpression? = null
|
||||
)
|
||||
{
|
||||
val asmVarname: String
|
||||
get() = if(array==null)
|
||||
variableAsmName!!
|
||||
else
|
||||
asmgen.asmVariableName(array.arrayvar)
|
||||
asmgen.asmVariableName(array.variable)
|
||||
|
||||
companion object {
|
||||
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
|
||||
val cv = value.constValue(program)
|
||||
fun fromAstSource(value: PtExpression, program: PtProgram, asmgen: AsmGen6502Internal): AsmAssignSource {
|
||||
val cv = value as? PtNumber
|
||||
if(cv!=null)
|
||||
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
|
||||
|
||||
return when(value) {
|
||||
is NumericLiteral -> throw AssemblyError("should have been constant value")
|
||||
is StringLiteral -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is ArrayLiteral -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is IdentifierReference -> {
|
||||
val parameter = value.targetVarDecl(program)?.subroutineParameter
|
||||
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
|
||||
// checked above: is PtNumber -> throw AssemblyError("should have been constant value")
|
||||
is PtString -> throw AssemblyError("string literal value should not occur anymore for asm generation")
|
||||
is PtArray -> throw AssemblyError("array literal value should not occur anymore for asm generation")
|
||||
is PtIdentifier -> {
|
||||
val parameter = asmgen.findSubroutineParameter(value.name, asmgen)
|
||||
if(parameter?.definingAsmSub() != null)
|
||||
throw AssemblyError("can't assign from a asmsub register parameter $value ${value.position}")
|
||||
val dt = value.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val varName=asmgen.asmVariableName(value)
|
||||
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
|
||||
if(dt == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
|
||||
if(value.type == DataType.UWORD && varName.lowercase().startsWith("cx16.r")) {
|
||||
val regStr = varName.lowercase().substring(5)
|
||||
val reg = RegisterOrPair.valueOf(regStr.uppercase())
|
||||
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
|
||||
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, value.type, register = reg)
|
||||
} else {
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
|
||||
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, value.type, variableAsmName = varName)
|
||||
}
|
||||
}
|
||||
is DirectMemoryRead -> {
|
||||
is PtMemoryByte -> {
|
||||
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
|
||||
}
|
||||
is ArrayIndexedExpression -> {
|
||||
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
|
||||
is PtArrayIndexer -> {
|
||||
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, value.type, array = value)
|
||||
}
|
||||
is BuiltinFunctionCall -> {
|
||||
val returnType = value.inferType(program)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
|
||||
is PtBuiltinFunctionCall -> {
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||
}
|
||||
is FunctionCallExpression -> {
|
||||
val sub = value.target.targetSubroutine(program)!!
|
||||
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
|
||||
is PtFunctionCall -> {
|
||||
val symbol = asmgen.symbolTable.lookup(value.name)
|
||||
val sub = symbol!!.astNode as IPtSubroutine
|
||||
val returnType = sub.returnsWhatWhere().firstOrNull { rr -> rr.first.registerOrPair != null || rr.first.statusflag!=null }?.second
|
||||
?: throw AssemblyError("can't translate zero return values in assignment")
|
||||
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType, expression = value)
|
||||
}
|
||||
else -> {
|
||||
val returnType = value.inferType(program)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, returnType.getOrElse { throw AssemblyError("unknown dt") }, expression = value)
|
||||
AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, value.type, expression = value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -199,17 +190,27 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
|
||||
}
|
||||
|
||||
|
||||
internal class AsmAssignment(val source: AsmAssignSource,
|
||||
val target: AsmAssignTarget,
|
||||
val isAugmentable: Boolean,
|
||||
memsizer: IMemSizer,
|
||||
val position: Position) {
|
||||
|
||||
internal sealed class AsmAssignmentBase(val source: AsmAssignSource,
|
||||
val target: AsmAssignTarget,
|
||||
val memsizer: IMemSizer,
|
||||
val position: Position) {
|
||||
init {
|
||||
if(target.register !in arrayOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
|
||||
require(source.datatype != DataType.UNDEFINED) { "must not be placeholder/undefined datatype at $position" }
|
||||
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
|
||||
"source dt size must be less or equal to target dt size at $position"
|
||||
"source dt size must be less or equal to target dt size at $position srcdt=${source.datatype} targetdt=${target.datatype}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class AsmAssignment(source: AsmAssignSource,
|
||||
target: AsmAssignTarget,
|
||||
memsizer: IMemSizer,
|
||||
position: Position): AsmAssignmentBase(source, target, memsizer, position)
|
||||
|
||||
internal class AsmAugmentedAssignment(source: AsmAssignSource,
|
||||
val operator: String,
|
||||
target: AsmAssignTarget,
|
||||
memsizer: IMemSizer,
|
||||
position: Position): AsmAssignmentBase(source, target, memsizer, position)
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,166 +1,71 @@
|
||||
package prog8.codegen.cpu6502.assignment
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.codegen.cpu6502.AsmGen
|
||||
import prog8.codegen.cpu6502.AsmGen6502Internal
|
||||
import prog8.codegen.cpu6502.VariableAllocator
|
||||
|
||||
|
||||
internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
internal class AugmentableAssignmentAsmGen(private val program: PtProgram,
|
||||
private val assignmentAsmGen: AssignmentAsmGen,
|
||||
private val asmgen: AsmGen,
|
||||
private val asmgen: AsmGen6502Internal,
|
||||
private val allocator: VariableAllocator
|
||||
) {
|
||||
fun translate(assign: AsmAssignment) {
|
||||
require(assign.isAugmentable)
|
||||
require(assign.source.kind == SourceStorageKind.EXPRESSION) {
|
||||
"non-expression assign value should be handled elsewhere ${assign.position}"
|
||||
}
|
||||
fun translate(assign: AsmAugmentedAssignment) {
|
||||
|
||||
when (val value = assign.source.expression!!) {
|
||||
is PrefixExpression -> {
|
||||
// A = -A , A = +A, A = ~A, A = not A
|
||||
when (value.operator) {
|
||||
"+" -> {}
|
||||
"-" -> inplaceNegate(assign, false)
|
||||
"~" -> inplaceInvert(assign)
|
||||
else -> throw AssemblyError("invalid prefix operator")
|
||||
}
|
||||
when(assign.operator) {
|
||||
"-" -> {
|
||||
val a2 = AsmAssignment(assign.source, assign.target, assign.memsizer, assign.position)
|
||||
assignmentAsmGen.inplaceNegate(a2, false)
|
||||
}
|
||||
"~" -> {
|
||||
val a2 = AsmAssignment(assign.source, assign.target, assign.memsizer, assign.position)
|
||||
assignmentAsmGen.inplaceInvert(a2)
|
||||
}
|
||||
"+" -> { /* is a nop */ }
|
||||
else -> {
|
||||
if(assign.operator.endsWith('='))
|
||||
augmentedAssignExpr(assign)
|
||||
else
|
||||
throw AssemblyError("invalid augmented assign operator ${assign.operator}")
|
||||
}
|
||||
is TypecastExpression -> inplaceCast(assign.target, value, assign.position)
|
||||
is BinaryExpression -> inplaceBinary(assign.target, value)
|
||||
else -> throw AssemblyError("invalid aug assign value type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceBinary(target: AsmAssignTarget, binExpr: BinaryExpression) {
|
||||
val astTarget = target.origAstTarget!!
|
||||
if (binExpr.left isSameAs astTarget) {
|
||||
// A = A <operator> Something
|
||||
return inplaceModification(target, binExpr.operator, binExpr.right)
|
||||
private fun augmentedAssignExpr(assign: AsmAugmentedAssignment) {
|
||||
val srcValue = assign.source.toAstExpression(assign.target.scope as PtNamedNode)
|
||||
when (assign.operator) {
|
||||
"+=" -> inplaceModification(assign.target, "+", srcValue)
|
||||
"-=" -> inplaceModification(assign.target, "-", srcValue)
|
||||
"*=" -> inplaceModification(assign.target, "*", srcValue)
|
||||
"/=" -> inplaceModification(assign.target, "/", srcValue)
|
||||
"|=" -> inplaceModification(assign.target, "|", srcValue)
|
||||
"&=" -> inplaceModification(assign.target, "&", srcValue)
|
||||
"^=" -> inplaceModification(assign.target, "^", srcValue)
|
||||
"<<=" -> inplaceModification(assign.target, "<<", srcValue)
|
||||
">>=" -> inplaceModification(assign.target, ">>", srcValue)
|
||||
else -> throw AssemblyError("invalid augmented assign operator ${assign.operator}")
|
||||
}
|
||||
|
||||
if (binExpr.operator in AssociativeOperators) {
|
||||
if (binExpr.right isSameAs astTarget) {
|
||||
// A = 5 <operator> A
|
||||
return inplaceModification(target, binExpr.operator, binExpr.left)
|
||||
}
|
||||
|
||||
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||
if (leftBinExpr?.operator == binExpr.operator) {
|
||||
// TODO better optimize the chained asm to avoid intermediate stores/loads?
|
||||
when {
|
||||
binExpr.right isSameAs astTarget -> {
|
||||
// A = (x <associative-operator> y) <same-operator> A
|
||||
inplaceModification(target, binExpr.operator, leftBinExpr.left)
|
||||
inplaceModification(target, binExpr.operator, leftBinExpr.right)
|
||||
return
|
||||
}
|
||||
leftBinExpr.left isSameAs astTarget -> {
|
||||
// A = (A <associative-operator> x) <same-operator> y
|
||||
inplaceModification(target, binExpr.operator, leftBinExpr.right)
|
||||
inplaceModification(target, binExpr.operator, binExpr.right)
|
||||
return
|
||||
}
|
||||
leftBinExpr.right isSameAs astTarget -> {
|
||||
// A = (x <associative-operator> A) <same-operator> y
|
||||
inplaceModification(target, binExpr.operator, leftBinExpr.left)
|
||||
inplaceModification(target, binExpr.operator, binExpr.right)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||
if (rightBinExpr?.operator == binExpr.operator) {
|
||||
when {
|
||||
binExpr.left isSameAs astTarget -> {
|
||||
// A = A <associative-operator> (x <same-operator> y)
|
||||
inplaceModification(target, binExpr.operator, rightBinExpr.left)
|
||||
inplaceModification(target, binExpr.operator, rightBinExpr.right)
|
||||
return
|
||||
}
|
||||
rightBinExpr.left isSameAs astTarget -> {
|
||||
// A = y <associative-operator> (A <same-operator> x)
|
||||
inplaceModification(target, binExpr.operator, binExpr.left)
|
||||
inplaceModification(target, binExpr.operator, rightBinExpr.right)
|
||||
return
|
||||
}
|
||||
rightBinExpr.right isSameAs astTarget -> {
|
||||
// A = y <associative-operator> (x <same-operator> y)
|
||||
inplaceModification(target, binExpr.operator, binExpr.left)
|
||||
inplaceModification(target, binExpr.operator, rightBinExpr.left)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val leftBinExpr = binExpr.left as? BinaryExpression
|
||||
val rightBinExpr = binExpr.right as? BinaryExpression
|
||||
if(leftBinExpr!=null && rightBinExpr==null) {
|
||||
if(leftBinExpr.left isSameAs astTarget) {
|
||||
// X = (X <oper> Right) <oper> Something
|
||||
inplaceModification(target, leftBinExpr.operator, leftBinExpr.right)
|
||||
inplaceModification(target, binExpr.operator, binExpr.right)
|
||||
return
|
||||
}
|
||||
if(leftBinExpr.right isSameAs astTarget) {
|
||||
// X = (Left <oper> X) <oper> Something
|
||||
if(leftBinExpr.operator in AssociativeOperators) {
|
||||
inplaceModification(target, leftBinExpr.operator, leftBinExpr.left)
|
||||
inplaceModification(target, binExpr.operator, binExpr.right)
|
||||
return
|
||||
} else {
|
||||
throw AssemblyError("operands in wrong order for non-associative operator")
|
||||
}
|
||||
}
|
||||
}
|
||||
if(leftBinExpr==null && rightBinExpr!=null) {
|
||||
if(rightBinExpr.left isSameAs astTarget) {
|
||||
// X = Something <oper> (X <oper> Right)
|
||||
if(binExpr.operator in AssociativeOperators) {
|
||||
inplaceModification(target, rightBinExpr.operator, rightBinExpr.right)
|
||||
inplaceModification(target, binExpr.operator, binExpr.left)
|
||||
return
|
||||
} else {
|
||||
throw AssemblyError("operands in wrong order for non-associative operator")
|
||||
}
|
||||
}
|
||||
if(rightBinExpr.right isSameAs astTarget) {
|
||||
// X = Something <oper> (Left <oper> X)
|
||||
if(binExpr.operator in AssociativeOperators && rightBinExpr.operator in AssociativeOperators) {
|
||||
inplaceModification(target, rightBinExpr.operator, rightBinExpr.left)
|
||||
inplaceModification(target, binExpr.operator, binExpr.left)
|
||||
return
|
||||
} else {
|
||||
throw AssemblyError("operands in wrong order for non-associative operator")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw FatalAstException("assignment should follow augmentable rules $binExpr")
|
||||
}
|
||||
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: Expression) {
|
||||
private fun inplaceModification(target: AsmAssignTarget, operator: String, origValue: PtExpression) {
|
||||
|
||||
// the asm-gen code can deal with situations where you want to assign a byte into a word.
|
||||
// it will create the most optimized code to do this (so it type-extends for us).
|
||||
// But we can't deal with writing a word into a byte - explicit typeconversion is required
|
||||
val value = if(program.memsizer.memorySize(origValue.inferType(program).getOrElse { throw AssemblyError("unknown dt") }) > program.memsizer.memorySize(target.datatype)) {
|
||||
val typecast = TypecastExpression(origValue, target.datatype, true, origValue.position)
|
||||
typecast.linkParents(origValue.parent)
|
||||
val value = if(program.memsizer.memorySize(origValue.type) > program.memsizer.memorySize(target.datatype)) {
|
||||
val typecast = PtTypeCast(target.datatype, origValue.position)
|
||||
typecast.add(origValue)
|
||||
require(typecast.type!=origValue.type)
|
||||
typecast
|
||||
}
|
||||
else {
|
||||
origValue
|
||||
}
|
||||
|
||||
val valueLv = (value as? NumericLiteral)?.number
|
||||
val ident = value as? IdentifierReference
|
||||
val memread = value as? DirectMemoryRead
|
||||
val valueLv = (value as? PtNumber)?.number
|
||||
val ident = value as? PtIdentifier
|
||||
val memread = value as? PtMemoryByte
|
||||
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
@ -170,7 +75,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
|
||||
memread != null -> inplaceModification_byte_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
value is PtTypeCast -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
|
||||
}
|
||||
@ -182,7 +87,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
valueLv != null -> inplaceModification_word_litval_to_variable(target.asmVarname, target.datatype, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_word_variable_to_variable(target.asmVarname, target.datatype, operator, ident)
|
||||
memread != null -> inplaceModification_word_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
value is PtTypeCast -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator))
|
||||
return
|
||||
inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
|
||||
@ -194,7 +99,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
when {
|
||||
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble(), target.scope!!)
|
||||
ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident, target.scope!!)
|
||||
value is TypecastExpression -> {
|
||||
value is PtTypeCast -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
|
||||
}
|
||||
@ -206,27 +111,27 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
val memory = target.memory!!
|
||||
when (memory.addressExpression) {
|
||||
is NumericLiteral -> {
|
||||
val addr = (memory.addressExpression as NumericLiteral).number.toInt()
|
||||
when (memory.address) {
|
||||
is PtNumber -> {
|
||||
val addr = (memory.address as PtNumber).number.toInt()
|
||||
// re-use code to assign a variable, instead this time, use a direct memory address
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable(addr.toHex(), DataType.UBYTE, operator, ident)
|
||||
memread != null -> inplaceModification_byte_memread_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
||||
value is TypecastExpression -> {
|
||||
value is PtTypeCast -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
||||
}
|
||||
else -> inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
|
||||
}
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val pointer = memory.addressExpression as IdentifierReference
|
||||
is PtIdentifier -> {
|
||||
val pointer = memory.address as PtIdentifier
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_pointer(pointer, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_pointer(pointer, operator, ident)
|
||||
value is TypecastExpression -> {
|
||||
value is PtTypeCast -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_pointer(pointer, operator, value)
|
||||
}
|
||||
@ -235,13 +140,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
else -> {
|
||||
// TODO use some other evaluation here; don't use the estack to transfer the address to read/write from
|
||||
asmgen.assignExpressionTo(memory.addressExpression, AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, memory.definingSubroutine))
|
||||
asmgen.assignExpressionTo(memory.address, AsmAssignTarget(TargetStorageKind.STACK, asmgen, DataType.UWORD, memory.definingISub()))
|
||||
asmgen.out(" jsr prog8_lib.read_byte_from_address_on_stack | sta P8ZP_SCRATCH_B1")
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident)
|
||||
memread != null -> inplaceModification_byte_memread_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
value is PtTypeCast -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
|
||||
}
|
||||
@ -252,91 +157,89 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
with(target.array!!.indexer) {
|
||||
val indexNum = indexExpr as? NumericLiteral
|
||||
val indexVar = indexExpr as? IdentifierReference
|
||||
when {
|
||||
indexNum!=null -> {
|
||||
val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable(targetVarName, target.datatype, operator, ident)
|
||||
memread != null -> inplaceModification_byte_memread_to_variable(targetVarName, target.datatype, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
}
|
||||
else -> inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
val indexNum = target.array!!.index as? PtNumber
|
||||
val indexVar = target.array.index as? PtIdentifier
|
||||
when {
|
||||
indexNum!=null -> {
|
||||
val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
when {
|
||||
valueLv != null -> inplaceModification_byte_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_byte_variable_to_variable(targetVarName, target.datatype, operator, ident)
|
||||
memread != null -> inplaceModification_byte_memread_to_variable(targetVarName, target.datatype, operator, memread)
|
||||
value is PtTypeCast -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
}
|
||||
else -> inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
when {
|
||||
valueLv != null -> inplaceModification_word_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_word_variable_to_variable(targetVarName, target.datatype, operator, ident)
|
||||
memread != null -> inplaceModification_word_memread_to_variable(targetVarName, target.datatype, operator, memread)
|
||||
value is TypecastExpression -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
}
|
||||
else -> inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when {
|
||||
valueLv != null -> inplaceModification_float_litval_to_variable(targetVarName, operator, valueLv.toDouble(), target.scope!!)
|
||||
ident != null -> inplaceModification_float_variable_to_variable(targetVarName, operator, ident, target.scope!!)
|
||||
value is TypecastExpression -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
|
||||
}
|
||||
else -> inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
}
|
||||
}
|
||||
indexVar!=null -> {
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
val tgt =
|
||||
AsmAssignTarget.fromRegisters(
|
||||
RegisterOrPair.A,
|
||||
target.datatype == DataType.BYTE, null,
|
||||
asmgen
|
||||
)
|
||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
|
||||
in WordDatatypes -> {
|
||||
when {
|
||||
valueLv != null -> inplaceModification_word_litval_to_variable(targetVarName, target.datatype, operator, valueLv.toInt())
|
||||
ident != null -> inplaceModification_word_variable_to_variable(targetVarName, target.datatype, operator, ident)
|
||||
memread != null -> inplaceModification_word_memread_to_variable(targetVarName, target.datatype, operator, memread)
|
||||
value is PtTypeCast -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
}
|
||||
else -> inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
val tgt =
|
||||
AsmAssignTarget.fromRegisters(
|
||||
RegisterOrPair.AY,
|
||||
target.datatype == DataType.WORD, null,
|
||||
asmgen
|
||||
)
|
||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
val tgt =
|
||||
AsmAssignTarget.fromRegisters(
|
||||
RegisterOrPair.FAC1,
|
||||
true, null,
|
||||
asmgen
|
||||
)
|
||||
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
assignmentAsmGen.assignFAC1float(target)
|
||||
}
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when {
|
||||
valueLv != null -> inplaceModification_float_litval_to_variable(targetVarName, operator, valueLv.toDouble(), target.scope!!)
|
||||
ident != null -> inplaceModification_float_variable_to_variable(targetVarName, operator, ident, target.scope!!)
|
||||
value is PtTypeCast -> {
|
||||
if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
|
||||
inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
|
||||
}
|
||||
else -> inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
}
|
||||
else -> throw AssemblyError("indexer expression should have been replaced by auto indexer var")
|
||||
}
|
||||
indexVar!=null -> {
|
||||
when (target.datatype) {
|
||||
in ByteDatatypes -> {
|
||||
val tgt =
|
||||
AsmAssignTarget.fromRegisters(
|
||||
RegisterOrPair.A,
|
||||
target.datatype == DataType.BYTE, null,
|
||||
asmgen
|
||||
)
|
||||
val assign = AsmAssignment(target.origAssign.source, tgt, program.memsizer, value.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
|
||||
}
|
||||
in WordDatatypes -> {
|
||||
val tgt =
|
||||
AsmAssignTarget.fromRegisters(
|
||||
RegisterOrPair.AY,
|
||||
target.datatype == DataType.WORD, null,
|
||||
asmgen
|
||||
)
|
||||
val assign = AsmAssignment(target.origAssign.source, tgt, program.memsizer, value.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
val tgt =
|
||||
AsmAssignTarget.fromRegisters(
|
||||
RegisterOrPair.FAC1,
|
||||
true, null,
|
||||
asmgen
|
||||
)
|
||||
val assign = AsmAssignment(target.origAssign.source, tgt, program.memsizer, value.position)
|
||||
assignmentAsmGen.translateNormalAssignment(assign)
|
||||
assignmentAsmGen.assignFAC1float(target)
|
||||
}
|
||||
else -> throw AssemblyError("weird type to do in-place modification on ${target.datatype}")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("indexer expression should have been replaced by auto indexer var")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> throw AssemblyError("no asm gen for reg in-place modification")
|
||||
@ -344,21 +247,20 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryInplaceModifyWithRemovedRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
|
||||
private fun tryInplaceModifyWithRemovedRedundantCast(value: PtTypeCast, target: AsmAssignTarget, operator: String): Boolean {
|
||||
if (target.datatype == value.type) {
|
||||
val childIDt = value.expression.inferType(program)
|
||||
val childDt = childIDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
val childDt = value.value.type
|
||||
if (value.type!=DataType.FLOAT && (value.type.equalsSize(childDt) || value.type.largerThan(childDt))) {
|
||||
// this typecast is redundant here; the rest of the code knows how to deal with the uncasted value.
|
||||
// (works for integer types, not for float.)
|
||||
inplaceModification(target, operator, value.expression)
|
||||
inplaceModification(target, operator, value.value)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_value_to_pointer(pointervar: IdentifierReference, operator: String, value: Expression) {
|
||||
private fun inplaceModification_byte_value_to_pointer(pointervar: PtIdentifier, operator: String, value: PtExpression) {
|
||||
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", DataType.UBYTE, null)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
when (operator) {
|
||||
@ -394,7 +296,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_variable_to_pointer(pointervar: IdentifierReference, operator: String, value: IdentifierReference) {
|
||||
private fun inplaceModification_byte_variable_to_pointer(pointervar: PtIdentifier, operator: String, value: PtIdentifier) {
|
||||
val otherName = asmgen.asmVariableName(value)
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(pointervar)
|
||||
|
||||
@ -431,7 +333,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.storeAIntoZpPointerVar(sourceName)
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_litval_to_pointer(pointervar: IdentifierReference, operator: String, value: Int) {
|
||||
private fun inplaceModification_byte_litval_to_pointer(pointervar: PtIdentifier, operator: String, value: Int) {
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
"+" -> {
|
||||
@ -499,7 +401,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) {
|
||||
private fun inplaceModification_byte_value_to_variable(name: String, dt: DataType, operator: String, value: PtExpression) {
|
||||
// this should be the last resort for code generation for this,
|
||||
// because the value is evaluated onto the eval stack (=slow).
|
||||
when (operator) {
|
||||
@ -594,7 +496,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) {
|
||||
private fun inplaceModification_byte_variable_to_variable(name: String, dt: DataType, operator: String, ident: PtIdentifier) {
|
||||
val otherName = asmgen.asmVariableName(ident)
|
||||
when (operator) {
|
||||
// note: ** (power) operator requires floats.
|
||||
@ -762,7 +664,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||
private fun inplaceModification_byte_memread_to_variable(name: String, dt: DataType, operator: String, memread: PtMemoryByte) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
@ -799,7 +701,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: DirectMemoryRead) {
|
||||
private fun inplaceModification_word_memread_to_variable(name: String, dt: DataType, operator: String, memread: PtMemoryByte) {
|
||||
when (operator) {
|
||||
"+" -> {
|
||||
asmgen.translateDirectMemReadExpressionToRegAorStack(memread, false)
|
||||
@ -1111,9 +1013,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) {
|
||||
private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: PtIdentifier) {
|
||||
val otherName = asmgen.asmVariableName(ident)
|
||||
when (val valueDt = ident.targetVarDecl(program)!!.datatype) {
|
||||
when (val valueDt = ident.type) {
|
||||
in ByteDatatypes -> {
|
||||
// the other variable is a BYTE type so optimize for that
|
||||
when (operator) {
|
||||
@ -1356,12 +1258,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification_word_value_to_variable(name: String, dt: DataType, operator: String, value: Expression) {
|
||||
private fun inplaceModification_word_value_to_variable(name: String, dt: DataType, operator: String, value: PtExpression) {
|
||||
// this should be the last resort for code generation for this,
|
||||
// because the value is evaluated onto the eval stack (=slow).
|
||||
|
||||
val valueiDt = value.inferType(program)
|
||||
val valueDt = valueiDt.getOrElse { throw AssemblyError("unknown dt") }
|
||||
|
||||
fun multiplyVarByWordInAY() {
|
||||
asmgen.out("""
|
||||
@ -1410,7 +1310,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
""")
|
||||
}
|
||||
|
||||
when (valueDt) {
|
||||
when (val valueDt = value.type) {
|
||||
in ByteDatatypes -> {
|
||||
// the other variable is a BYTE type so optimize for that
|
||||
when (operator) {
|
||||
@ -1581,7 +1481,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: Expression, scope: Subroutine) {
|
||||
private fun inplaceModification_float_value_to_variable(name: String, operator: String, value: PtExpression, scope: IPtSubroutine) {
|
||||
asmgen.assignExpressionToRegister(value, RegisterOrPair.FAC1)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope)
|
||||
when (operator) {
|
||||
@ -1623,8 +1523,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
|
||||
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference, scope: Subroutine) {
|
||||
val valueDt = ident.targetVarDecl(program)!!.datatype
|
||||
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: PtIdentifier, scope: IPtSubroutine) {
|
||||
val valueDt = ident.type
|
||||
if(valueDt != DataType.FLOAT)
|
||||
throw AssemblyError("float variable expected")
|
||||
|
||||
@ -1682,7 +1582,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
|
||||
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: Subroutine) {
|
||||
private fun inplaceModification_float_litval_to_variable(name: String, operator: String, value: Double, scope: IPtSubroutine) {
|
||||
val constValueName = allocator.getFloatAsmConst(value)
|
||||
asmgen.saveRegisterLocal(CpuRegister.X, scope)
|
||||
when (operator) {
|
||||
@ -1743,259 +1643,50 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
|
||||
""")
|
||||
asmgen.restoreRegisterLocal(CpuRegister.X)
|
||||
}
|
||||
|
||||
private fun inplaceCast(target: AsmAssignTarget, cast: TypecastExpression, position: Position) {
|
||||
val outerCastDt = cast.type
|
||||
val innerCastDt = (cast.expression as? TypecastExpression)?.type
|
||||
if (innerCastDt == null) {
|
||||
// simple typecast where the value is the target
|
||||
when (target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> { /* byte target can't be typecasted to anything else at all */ }
|
||||
DataType.UWORD, DataType.WORD -> {
|
||||
when (outerCastDt) {
|
||||
DataType.UBYTE, DataType.BYTE -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz ${target.asmVarname}+1")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta ${target.asmVarname}+1")
|
||||
}
|
||||
TargetStorageKind.ARRAY -> {
|
||||
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, target.datatype, CpuRegister.Y, true)
|
||||
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
|
||||
}
|
||||
TargetStorageKind.STACK -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" stz P8ESTACK_HI+1,x")
|
||||
else
|
||||
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
|
||||
}
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
DataType.UWORD, DataType.WORD, in IterableDatatypes -> {}
|
||||
DataType.FLOAT -> throw AssemblyError("can't cast float in-place")
|
||||
else -> throw AssemblyError("weird cast type")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if (outerCastDt != DataType.FLOAT)
|
||||
throw AssemblyError("in-place cast of a float makes no sense")
|
||||
}
|
||||
else -> throw AssemblyError("invalid cast target type")
|
||||
}
|
||||
} else {
|
||||
// typecast with nested typecast, that has the target as a value
|
||||
// calculate singular cast that is required
|
||||
val castDt = if (outerCastDt largerThan innerCastDt) innerCastDt else outerCastDt
|
||||
val value = (cast.expression as TypecastExpression).expression
|
||||
val resultingCast = TypecastExpression(value, castDt, false, position)
|
||||
inplaceCast(target, resultingCast, position)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceInvert(assign: AsmAssignment) {
|
||||
val target = assign.target
|
||||
when (assign.target.datatype) {
|
||||
DataType.UBYTE -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda ${target.asmVarname}
|
||||
eor #255
|
||||
sta ${target.asmVarname}""")
|
||||
}
|
||||
TargetStorageKind.MEMORY -> {
|
||||
val memory = target.memory!!
|
||||
when (memory.addressExpression) {
|
||||
is NumericLiteral -> {
|
||||
val addr = (memory.addressExpression as NumericLiteral).number.toHex()
|
||||
asmgen.out("""
|
||||
lda $addr
|
||||
eor #255
|
||||
sta $addr""")
|
||||
}
|
||||
is IdentifierReference -> {
|
||||
val sourceName = asmgen.loadByteFromPointerIntoA(memory.addressExpression as IdentifierReference)
|
||||
asmgen.out(" eor #255")
|
||||
asmgen.out(" sta ($sourceName),y")
|
||||
}
|
||||
else -> {
|
||||
asmgen.assignExpressionToVariable(memory.addressExpression, "P8ZP_SCRATCH_W2", DataType.UWORD, target.scope)
|
||||
asmgen.out("""
|
||||
ldy #0
|
||||
lda (P8ZP_SCRATCH_W2),y
|
||||
eor #255""")
|
||||
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
|
||||
}
|
||||
}
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.A -> asmgen.out(" eor #255")
|
||||
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay")
|
||||
else -> throw AssemblyError("invalid reg dt for byte invert")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack invert")
|
||||
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda ${target.asmVarname}
|
||||
eor #255
|
||||
sta ${target.asmVarname}
|
||||
lda ${target.asmVarname}+1
|
||||
eor #255
|
||||
sta ${target.asmVarname}+1""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.AX -> asmgen.out(" pha | txa | eor #255 | tax | pla | eor #255")
|
||||
RegisterOrPair.AY -> asmgen.out(" pha | tya | eor #255 | tay | pla | eor #255")
|
||||
RegisterOrPair.XY -> asmgen.out(" txa | eor #255 | tax | tya | eor #255 | tay")
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
else -> throw AssemblyError("invalid reg dt for word invert")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack invert")
|
||||
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invert of invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
internal fun inplaceNegate(assign: AsmAssignment, ignoreDatatype: Boolean) {
|
||||
val target = assign.target
|
||||
val datatype = if(ignoreDatatype) {
|
||||
when(target.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> DataType.BYTE
|
||||
DataType.UWORD, DataType.WORD -> DataType.WORD
|
||||
else -> target.datatype
|
||||
}
|
||||
} else target.datatype
|
||||
when (datatype) {
|
||||
DataType.BYTE -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sec
|
||||
sbc ${target.asmVarname}
|
||||
sta ${target.asmVarname}""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.A -> {
|
||||
if(asmgen.isTargetCpu(CpuType.CPU65c02))
|
||||
asmgen.out(" eor #255 | ina")
|
||||
else
|
||||
asmgen.out(" eor #255 | clc | adc #1")
|
||||
|
||||
}
|
||||
RegisterOrPair.X -> asmgen.out(" txa | eor #255 | tax | inx")
|
||||
RegisterOrPair.Y -> asmgen.out(" tya | eor #255 | tay | iny")
|
||||
else -> throw AssemblyError("invalid reg dt for byte negate")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for byte stack negate")
|
||||
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
asmgen.out("""
|
||||
lda #0
|
||||
sec
|
||||
sbc ${target.asmVarname}
|
||||
sta ${target.asmVarname}
|
||||
lda #0
|
||||
sbc ${target.asmVarname}+1
|
||||
sta ${target.asmVarname}+1""")
|
||||
}
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) { //P8ZP_SCRATCH_REG
|
||||
RegisterOrPair.AX -> {
|
||||
asmgen.out("""
|
||||
sec
|
||||
eor #255
|
||||
adc #0
|
||||
pha
|
||||
txa
|
||||
eor #255
|
||||
adc #0
|
||||
tax
|
||||
pla""")
|
||||
}
|
||||
RegisterOrPair.AY -> {
|
||||
asmgen.out("""
|
||||
sec
|
||||
eor #255
|
||||
adc #0
|
||||
pha
|
||||
tya
|
||||
eor #255
|
||||
adc #0
|
||||
tay
|
||||
pla""")
|
||||
}
|
||||
RegisterOrPair.XY -> {
|
||||
asmgen.out("""
|
||||
sec
|
||||
txa
|
||||
eor #255
|
||||
adc #0
|
||||
tax
|
||||
tya
|
||||
eor #255
|
||||
adc #0
|
||||
tay""")
|
||||
}
|
||||
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")
|
||||
else -> throw AssemblyError("invalid reg dt for word neg")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.MEMORY -> throw AssemblyError("memory is ubyte, can't negate that")
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for word stack negate")
|
||||
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||
else -> throw AssemblyError("weird target")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
when (target.kind) {
|
||||
TargetStorageKind.REGISTER -> {
|
||||
when(target.register!!) {
|
||||
RegisterOrPair.FAC1 -> asmgen.out(" jsr floats.NEGOP")
|
||||
RegisterOrPair.FAC2 -> asmgen.out(" jsr floats.MOVFA | jsr floats.NEGOP | jsr floats.MOVEF")
|
||||
else -> throw AssemblyError("invalid float register")
|
||||
}
|
||||
}
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
// simply flip the sign bit in the float
|
||||
asmgen.out("""
|
||||
lda ${target.asmVarname}+1
|
||||
eor #$80
|
||||
sta ${target.asmVarname}+1
|
||||
""")
|
||||
}
|
||||
TargetStorageKind.STACK -> TODO("no asm gen for float stack negate")
|
||||
TargetStorageKind.ARRAY -> assignmentAsmGen.assignPrefixedExpressionToArrayElt(assign)
|
||||
else -> throw AssemblyError("weird target for in-place float negation")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("negate of invalid type")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun AsmAssignSource.toAstExpression(scope: PtNamedNode): PtExpression {
|
||||
return when(kind) {
|
||||
SourceStorageKind.LITERALNUMBER -> this.number!!
|
||||
SourceStorageKind.VARIABLE -> {
|
||||
val ident = PtIdentifier(scope.scopedName + '.' + asmVarname, datatype, Position.DUMMY)
|
||||
ident.parent = scope
|
||||
ident
|
||||
}
|
||||
SourceStorageKind.ARRAY -> this.array!!
|
||||
SourceStorageKind.MEMORY -> this.memory!!
|
||||
SourceStorageKind.EXPRESSION -> this.expression!!
|
||||
SourceStorageKind.REGISTER -> {
|
||||
if(register in Cx16VirtualRegisters) {
|
||||
val ident = PtIdentifier("cx16.${register!!.name.lowercase()}", DataType.UWORD, position = scope.position)
|
||||
ident.parent = scope
|
||||
ident
|
||||
} else {
|
||||
throw AssemblyError("no ast expr possible for source register $register")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid assign source kind $kind")
|
||||
}
|
||||
}
|
||||
|
||||
private fun AsmAssignTarget.toAstExpression(): PtExpression {
|
||||
return when(kind) {
|
||||
TargetStorageKind.VARIABLE -> {
|
||||
val ident = PtIdentifier((this.scope as PtNamedNode).scopedName + '.' + asmVarname, datatype, origAstTarget?.position ?: Position.DUMMY)
|
||||
ident.parent = this.scope
|
||||
ident
|
||||
}
|
||||
TargetStorageKind.ARRAY -> this.array!!
|
||||
TargetStorageKind.MEMORY -> this.memory!!
|
||||
TargetStorageKind.REGISTER -> {
|
||||
if(register in Cx16VirtualRegisters) {
|
||||
val ident = PtIdentifier("cx16.${register!!.name.lowercase()}", DataType.UWORD, position = this.origAssign.position)
|
||||
ident.parent = (this.scope as? PtNamedNode) ?: this.origAstTarget!!
|
||||
ident
|
||||
} else {
|
||||
throw AssemblyError("no ast expr possible for target register $register")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid assign target kind $kind")
|
||||
}
|
||||
}
|
||||
|
60
codeGenCpu6502/test/Dummies.kt
Normal file
60
codeGenCpu6502/test/Dummies.kt
Normal file
@ -0,0 +1,60 @@
|
||||
package prog8tests.codegencpu6502
|
||||
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal object DummyMemsizer : IMemSizer {
|
||||
override fun memorySize(dt: DataType) = when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
DataType.FLOAT -> 5
|
||||
else -> 2
|
||||
}
|
||||
override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) {
|
||||
DataType.ARRAY_UW -> numElements*2
|
||||
DataType.ARRAY_W -> numElements*2
|
||||
DataType.ARRAY_F -> numElements*5
|
||||
else -> numElements
|
||||
}
|
||||
}
|
||||
|
||||
internal object DummyStringEncoder : IStringEncoding {
|
||||
override fun encodeString(str: String, encoding: Encoding): List<UByte> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false):
|
||||
IErrorReporter {
|
||||
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
errors.add("${position.toClickableStr()} $msg")
|
||||
}
|
||||
|
||||
override fun warn(msg: String, position: Position) {
|
||||
warnings.add("${position.toClickableStr()} $msg")
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
|
||||
override fun report() {
|
||||
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||
if(throwExceptionAtReportIfErrors)
|
||||
finalizeNumErrors(errors.size, warnings.size)
|
||||
if(!keepMessagesAfterReporting) {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
errors.clear()
|
||||
warnings.clear()
|
||||
}
|
||||
}
|
106
codeGenCpu6502/test/TestCodegen.kt
Normal file
106
codeGenCpu6502/test/TestCodegen.kt
Normal file
@ -0,0 +1,106 @@
|
||||
package prog8tests.codegencpu6502
|
||||
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import prog8.code.SymbolTableMaker
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.C64Target
|
||||
import prog8.codegen.cpu6502.AsmGen6502
|
||||
import java.nio.file.Files
|
||||
import kotlin.io.path.Path
|
||||
|
||||
class TestCodegen: FunSpec({
|
||||
|
||||
fun getTestOptions(): CompilationOptions {
|
||||
val target = C64Target()
|
||||
return CompilationOptions(
|
||||
OutputType.RAW,
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
zpReserved = emptyList(),
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = target,
|
||||
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||
)
|
||||
}
|
||||
|
||||
test("augmented assign on arrays") {
|
||||
//main {
|
||||
// sub start() {
|
||||
// ubyte[] particleX = [1,2,3]
|
||||
// ubyte[] particleDX = [1,2,3]
|
||||
// particleX[2] += particleDX[2]
|
||||
//
|
||||
// word @shared xx = 1
|
||||
// xx = -xx
|
||||
// xx += 42
|
||||
// xx += cx16.r0
|
||||
// }
|
||||
//}
|
||||
val codegen = AsmGen6502()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
|
||||
|
||||
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val target = PtAssignTarget(Position.DUMMY).also {
|
||||
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
|
||||
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
|
||||
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||
}
|
||||
it.add(targetIdx)
|
||||
}
|
||||
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
|
||||
value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY))
|
||||
value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||
assign.add(target)
|
||||
assign.add(value)
|
||||
sub.add(assign)
|
||||
|
||||
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
|
||||
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
prefixAssign.add(prefixTarget)
|
||||
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
sub.add(prefixAssign)
|
||||
|
||||
val numberAssign = PtAugmentedAssign("-=", Position.DUMMY)
|
||||
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
numberAssign.add(numberAssignTarget)
|
||||
numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY))
|
||||
sub.add(numberAssign)
|
||||
|
||||
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
cxregAssign.add(cxregAssignTarget)
|
||||
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
|
||||
sub.add(cxregAssign)
|
||||
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
// define the "cx16.r0" virtual register
|
||||
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
|
||||
program.add(cx16block)
|
||||
|
||||
val options = getTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = codegen.generate(program, st, options, errors)!!
|
||||
result.name shouldBe "test"
|
||||
Files.deleteIfExists(Path("${result.name}.asm"))
|
||||
}
|
||||
})
|
||||
|
@ -3,19 +3,19 @@ package prog8.codegen.experimental
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IAssemblyGenerator
|
||||
import prog8.code.core.IAssemblyProgram
|
||||
import prog8.code.core.ICodeGeneratorBackend
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
|
||||
class CodeGen(private val program: PtProgram,
|
||||
private val symbolTable: SymbolTable,
|
||||
private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter
|
||||
): IAssemblyGenerator {
|
||||
override fun compileToAssembly(): IAssemblyProgram? {
|
||||
|
||||
class ExperiCodeGen: ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram? {
|
||||
// you could write a code generator directly on the PtProgram AST,
|
||||
// but you can also use the Intermediate Representation to build a codegen on:
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
@ -3,6 +3,7 @@ package prog8.codegen.intermediate
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.AssemblyError
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.PrefixOperators
|
||||
import prog8.code.core.SignedDatatypes
|
||||
import prog8.intermediate.*
|
||||
|
||||
@ -12,22 +13,26 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
if(assignment.target.children.single() is PtMachineRegister)
|
||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||
|
||||
return if (assignment.isInplaceAssign)
|
||||
translateInplaceAssign(assignment)
|
||||
else
|
||||
translateRegularAssign(assignment)
|
||||
return translateRegularAssign(assignment)
|
||||
}
|
||||
|
||||
private fun translateInplaceAssign(assignment: PtAssignment): IRCodeChunks {
|
||||
internal fun translate(augmentedAssign: PtAugmentedAssign): IRCodeChunks {
|
||||
if(augmentedAssign.target.children.single() is PtMachineRegister)
|
||||
throw AssemblyError("assigning to a register should be done by just evaluating the expression into resultregister")
|
||||
|
||||
return translateInplaceAssign(augmentedAssign)
|
||||
}
|
||||
|
||||
private fun translateInplaceAssign(assignment: PtAugmentedAssign): IRCodeChunks {
|
||||
val ident = assignment.target.identifier
|
||||
val memory = assignment.target.memory
|
||||
val array = assignment.target.array
|
||||
|
||||
return if(ident!=null) {
|
||||
assignSelfInMemory(ident.targetName.joinToString("."), assignment.value, assignment)
|
||||
assignVarAugmented(ident.name, assignment)
|
||||
} else if(memory != null) {
|
||||
if(memory.address is PtNumber)
|
||||
assignSelfInMemoryKnownAddress((memory.address as PtNumber).number.toInt(), assignment.value, assignment)
|
||||
assignMemoryAugmented((memory.address as PtNumber).number.toInt(), assignment)
|
||||
else
|
||||
fallbackAssign(assignment)
|
||||
} else if(array!=null) {
|
||||
@ -40,120 +45,83 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignSelfInMemoryKnownAddress(
|
||||
private fun assignMemoryAugmented(
|
||||
address: Int,
|
||||
value: PtExpression,
|
||||
origAssign: PtAssignment
|
||||
assignment: PtAugmentedAssign
|
||||
): IRCodeChunks {
|
||||
val value = assignment.value
|
||||
val vmDt = codeGen.irType(value.type)
|
||||
when(value) {
|
||||
is PtIdentifier -> return emptyList() // do nothing, x=x null assignment.
|
||||
is PtMachineRegister -> return emptyList() // do nothing, reg=reg null assignment
|
||||
is PtPrefix -> return inplacePrefix(value.operator, vmDt, address, null)
|
||||
is PtBinaryExpression -> return inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, address, null, origAssign)
|
||||
is PtMemoryByte -> {
|
||||
return if (!codeGen.options.compTarget.machine.isIOAddress(address.toUInt()))
|
||||
emptyList() // do nothing, mem=mem null assignment.
|
||||
else {
|
||||
// read and write a (i/o) memory location to itself.
|
||||
val tempReg = codeGen.registers.nextFree()
|
||||
val code = IRCodeChunk(null, null)
|
||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, value = address)
|
||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, value = address)
|
||||
listOf(code)
|
||||
}
|
||||
}
|
||||
else -> return fallbackAssign(origAssign)
|
||||
return when(assignment.operator) {
|
||||
"+" -> expressionEval.operatorPlusInplace(address, null, vmDt, value)
|
||||
"-" -> expressionEval.operatorMinusInplace(address, null, vmDt, value)
|
||||
"*" -> expressionEval.operatorMultiplyInplace(address, null, vmDt, value)
|
||||
"/" -> expressionEval.operatorDivideInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||
"|" -> expressionEval.operatorOrInplace(address, null, vmDt, value)
|
||||
"&" -> expressionEval.operatorAndInplace(address, null, vmDt, value)
|
||||
"^" -> expressionEval.operatorXorInplace(address, null, vmDt, value)
|
||||
"<<" -> expressionEval.operatorShiftLeftInplace(address, null, vmDt, value)
|
||||
">>" -> expressionEval.operatorShiftRightInplace(address, null, vmDt, value.type in SignedDatatypes, value)
|
||||
in PrefixOperators -> inplacePrefix(assignment.operator, vmDt, address, null)
|
||||
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assignSelfInMemory(
|
||||
symbol: String,
|
||||
value: PtExpression,
|
||||
origAssign: PtAssignment
|
||||
): IRCodeChunks {
|
||||
val vmDt = codeGen.irType(value.type)
|
||||
return when(value) {
|
||||
is PtIdentifier -> emptyList() // do nothing, x=x null assignment.
|
||||
is PtMachineRegister -> emptyList() // do nothing, reg=reg null assignment
|
||||
is PtPrefix -> inplacePrefix(value.operator, vmDt, null, symbol)
|
||||
is PtBinaryExpression -> inplaceBinexpr(value.operator, value.right, vmDt, value.type in SignedDatatypes, null, symbol, origAssign)
|
||||
is PtMemoryByte -> {
|
||||
val code = IRCodeChunk(null, null)
|
||||
val tempReg = codeGen.registers.nextFree()
|
||||
code += IRInstruction(Opcode.LOADM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
||||
code += IRInstruction(Opcode.STOREM, vmDt, reg1 = tempReg, labelSymbol = symbol)
|
||||
listOf(code)
|
||||
}
|
||||
|
||||
else -> fallbackAssign(origAssign)
|
||||
private fun assignVarAugmented(symbol: String, assignment: PtAugmentedAssign): IRCodeChunks {
|
||||
val value = assignment.value
|
||||
val valueVmDt = codeGen.irType(value.type)
|
||||
return when (assignment.operator) {
|
||||
"+=" -> expressionEval.operatorPlusInplace(null, symbol, valueVmDt, value)
|
||||
"-=" -> expressionEval.operatorMinusInplace(null, symbol, valueVmDt, value)
|
||||
"*=" -> expressionEval.operatorMultiplyInplace(null, symbol, valueVmDt, value)
|
||||
"/=" -> expressionEval.operatorDivideInplace(null, symbol, valueVmDt, value.type in SignedDatatypes, value)
|
||||
"|=" -> expressionEval.operatorOrInplace(null, symbol, valueVmDt, value)
|
||||
"&=" -> expressionEval.operatorAndInplace(null, symbol, valueVmDt, value)
|
||||
"^=" -> expressionEval.operatorXorInplace(null, symbol, valueVmDt, value)
|
||||
"<<=" -> expressionEval.operatorShiftLeftInplace(null, symbol, valueVmDt, value)
|
||||
">>=" -> expressionEval.operatorShiftRightInplace(null, symbol, valueVmDt, value.type in SignedDatatypes, value)
|
||||
in PrefixOperators -> inplacePrefix(assignment.operator, valueVmDt, null, symbol)
|
||||
else -> throw AssemblyError("invalid augmented assign operator ${assignment.operator}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun fallbackAssign(origAssign: PtAssignment): IRCodeChunks {
|
||||
private fun fallbackAssign(origAssign: PtAugmentedAssign): IRCodeChunks {
|
||||
if (codeGen.options.slowCodegenWarnings)
|
||||
codeGen.errors.warn("indirect code for in-place assignment", origAssign.position)
|
||||
return translateRegularAssign(origAssign)
|
||||
}
|
||||
|
||||
private fun inplaceBinexpr(
|
||||
operator: String,
|
||||
operand: PtExpression,
|
||||
vmDt: IRDataType,
|
||||
signed: Boolean,
|
||||
knownAddress: Int?,
|
||||
symbol: String?,
|
||||
origAssign: PtAssignment
|
||||
): IRCodeChunks {
|
||||
if(knownAddress!=null) {
|
||||
when (operator) {
|
||||
"+" -> return expressionEval.operatorPlusInplace(knownAddress, null, vmDt, operand)
|
||||
"-" -> return expressionEval.operatorMinusInplace(knownAddress, null, vmDt, operand)
|
||||
"*" -> return expressionEval.operatorMultiplyInplace(knownAddress, null, vmDt, operand)
|
||||
"/" -> return expressionEval.operatorDivideInplace(knownAddress, null, vmDt, signed, operand)
|
||||
"|" -> return expressionEval.operatorOrInplace(knownAddress, null, vmDt, operand)
|
||||
"&" -> return expressionEval.operatorAndInplace(knownAddress, null, vmDt, operand)
|
||||
"^" -> return expressionEval.operatorXorInplace(knownAddress, null, vmDt, operand)
|
||||
"<<" -> return expressionEval.operatorShiftLeftInplace(knownAddress, null, vmDt, operand)
|
||||
">>" -> return expressionEval.operatorShiftRightInplace(knownAddress, null, vmDt, signed, operand)
|
||||
else -> {}
|
||||
}
|
||||
val normalAssign = PtAssignment(origAssign.position)
|
||||
normalAssign.add(origAssign.target)
|
||||
val value: PtExpression
|
||||
if(origAssign.operator in PrefixOperators) {
|
||||
value = PtPrefix(origAssign.operator, origAssign.value.type, origAssign.value.position)
|
||||
value.add(origAssign.value)
|
||||
} else {
|
||||
symbol!!
|
||||
when (operator) {
|
||||
"+" -> return expressionEval.operatorPlusInplace(null, symbol, vmDt, operand)
|
||||
"-" -> return expressionEval.operatorMinusInplace(null, symbol, vmDt, operand)
|
||||
"*" -> return expressionEval.operatorMultiplyInplace(null, symbol, vmDt, operand)
|
||||
"/" -> return expressionEval.operatorDivideInplace(null, symbol, vmDt, signed, operand)
|
||||
"|" -> return expressionEval.operatorOrInplace(null, symbol, vmDt, operand)
|
||||
"&" -> return expressionEval.operatorAndInplace(null, symbol, vmDt, operand)
|
||||
"^" -> return expressionEval.operatorXorInplace(null, symbol, vmDt, operand)
|
||||
"<<" -> return expressionEval.operatorShiftLeftInplace(null, symbol, vmDt, operand)
|
||||
">>" -> return expressionEval.operatorShiftRightInplace(null, symbol, vmDt, signed, operand)
|
||||
else -> {}
|
||||
}
|
||||
require(origAssign.operator.endsWith('='))
|
||||
value = PtBinaryExpression(origAssign.operator.dropLast(1), origAssign.value.type, origAssign.value.position)
|
||||
val left: PtExpression = origAssign.target.children.single() as PtExpression
|
||||
value.add(left)
|
||||
value.add(origAssign.value)
|
||||
}
|
||||
return fallbackAssign(origAssign)
|
||||
normalAssign.add(value)
|
||||
return translateRegularAssign(normalAssign)
|
||||
}
|
||||
|
||||
private fun inplacePrefix(operator: String, vmDt: IRDataType, knownAddress: Int?, addressSymbol: String?): IRCodeChunks {
|
||||
private fun inplacePrefix(operator: String, vmDt: IRDataType, address: Int?, symbol: String?): IRCodeChunks {
|
||||
val code= IRCodeChunk(null, null)
|
||||
when(operator) {
|
||||
"+" -> { }
|
||||
"-" -> {
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.NEGM, vmDt, value = knownAddress)
|
||||
code += if(address!=null)
|
||||
IRInstruction(Opcode.NEGM, vmDt, value = address)
|
||||
else
|
||||
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = addressSymbol)
|
||||
IRInstruction(Opcode.NEGM, vmDt, labelSymbol = symbol)
|
||||
}
|
||||
"~" -> {
|
||||
val regMask = codeGen.registers.nextFree()
|
||||
val mask = if(vmDt==IRDataType.BYTE) 0x00ff else 0xffff
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=regMask, value = mask)
|
||||
code += if(knownAddress!=null)
|
||||
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = knownAddress)
|
||||
code += if(address!=null)
|
||||
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, value = address)
|
||||
else
|
||||
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = addressSymbol)
|
||||
IRInstruction(Opcode.XORM, vmDt, reg1=regMask, labelSymbol = symbol)
|
||||
}
|
||||
else -> throw AssemblyError("weird prefix operator")
|
||||
}
|
||||
@ -186,20 +154,19 @@ internal class AssignmentGen(private val codeGen: IRCodeGen, private val express
|
||||
}
|
||||
}
|
||||
if(ident!=null) {
|
||||
val symbol = ident.targetName.joinToString(".")
|
||||
val instruction = if(zero) {
|
||||
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = symbol)
|
||||
IRInstruction(Opcode.STOREZM, vmDt, labelSymbol = ident.name)
|
||||
} else {
|
||||
if (vmDt == IRDataType.FLOAT)
|
||||
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
|
||||
IRInstruction(Opcode.STOREM, vmDt, fpReg1 = resultFpRegister, labelSymbol = ident.name)
|
||||
else
|
||||
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
|
||||
IRInstruction(Opcode.STOREM, vmDt, reg1 = resultRegister, labelSymbol = ident.name)
|
||||
}
|
||||
result += IRCodeChunk(null, null).also { it += instruction }
|
||||
return result
|
||||
}
|
||||
else if(array!=null) {
|
||||
val variable = array.variable.targetName.joinToString(".")
|
||||
val variable = array.variable.name
|
||||
val itemsize = codeGen.program.memsizer.memorySize(array.type)
|
||||
|
||||
if(array.variable.type==DataType.UWORD) {
|
||||
|
@ -26,7 +26,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
"rrestore",
|
||||
"rrestorex" -> emptyList() // vm doesn't have registers to save/restore
|
||||
"callfar" -> throw AssemblyError("callfar() is for cx16 target only")
|
||||
"callrom" -> throw AssemblyError("callrom() is for cx16 target only")
|
||||
"msb" -> funcMsb(call, resultRegister)
|
||||
"lsb" -> funcLsb(call, resultRegister)
|
||||
"memory" -> funcMemory(call, resultRegister)
|
||||
@ -60,7 +59,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcAny(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||
val syscall =
|
||||
when (array.dt) {
|
||||
DataType.ARRAY_UB,
|
||||
@ -83,7 +82,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcAll(call: PtBuiltinFunctionCall, resultRegister: Int): IRCodeChunks {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||
val syscall =
|
||||
when(array.dt) {
|
||||
DataType.ARRAY_UB,
|
||||
@ -204,7 +203,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcReverse(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||
val syscall =
|
||||
when(array.dt) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
|
||||
@ -223,7 +222,7 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
|
||||
|
||||
private fun funcSort(call: PtBuiltinFunctionCall): IRCodeChunks {
|
||||
val arrayName = call.args[0] as PtIdentifier
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.targetName) as StStaticVariable
|
||||
val array = codeGen.symbolTable.flat.getValue(arrayName.name) as StStaticVariable
|
||||
val syscall =
|
||||
when(array.dt) {
|
||||
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
|
||||
|
@ -39,22 +39,21 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val vmDt = codeGen.irType(expr.type)
|
||||
val symbol = expr.targetName.joinToString(".")
|
||||
val code = IRCodeChunk(null, null)
|
||||
code += if (expr.type in PassByValueDatatypes) {
|
||||
if(vmDt==IRDataType.FLOAT)
|
||||
IRInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, labelSymbol = symbol)
|
||||
IRInstruction(Opcode.LOADM, vmDt, fpReg1 = resultFpRegister, labelSymbol = expr.name)
|
||||
else
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = symbol)
|
||||
IRInstruction(Opcode.LOADM, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
|
||||
} else {
|
||||
// for strings and arrays etc., load the *address* of the value instead
|
||||
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = symbol)
|
||||
IRInstruction(Opcode.LOAD, vmDt, reg1 = resultRegister, labelSymbol = expr.name)
|
||||
}
|
||||
listOf(code)
|
||||
}
|
||||
is PtAddressOf -> {
|
||||
val vmDt = codeGen.irType(expr.type)
|
||||
val symbol = expr.identifier.targetName.joinToString(".")
|
||||
val symbol = expr.identifier.name
|
||||
// note: LOAD <symbol> gets you the address of the symbol, whereas LOADM <symbol> would get you the value stored at that location
|
||||
val code = IRCodeChunk(null, null)
|
||||
code += IRInstruction(Opcode.LOAD, vmDt, reg1=resultRegister, labelSymbol = symbol)
|
||||
@ -72,27 +71,13 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
result
|
||||
}
|
||||
is PtTypeCast -> {
|
||||
translate(expr, resultRegister, resultFpRegister)
|
||||
}
|
||||
is PtPrefix -> {
|
||||
translate(expr, resultRegister, resultFpRegister)
|
||||
}
|
||||
is PtArrayIndexer -> {
|
||||
translate(expr, resultRegister, resultFpRegister)
|
||||
}
|
||||
is PtBinaryExpression -> {
|
||||
translate(expr, resultRegister, resultFpRegister)
|
||||
}
|
||||
is PtBuiltinFunctionCall -> {
|
||||
codeGen.translateBuiltinFunc(expr, resultRegister)
|
||||
}
|
||||
is PtFunctionCall -> {
|
||||
translate(expr, resultRegister, resultFpRegister)
|
||||
}
|
||||
is PtContainmentCheck -> {
|
||||
translate(expr, resultRegister, resultFpRegister)
|
||||
}
|
||||
is PtTypeCast -> translate(expr, resultRegister, resultFpRegister)
|
||||
is PtPrefix -> translate(expr, resultRegister, resultFpRegister)
|
||||
is PtArrayIndexer -> translate(expr, resultRegister, resultFpRegister)
|
||||
is PtBinaryExpression -> translate(expr, resultRegister, resultFpRegister)
|
||||
is PtBuiltinFunctionCall -> codeGen.translateBuiltinFunc(expr, resultRegister)
|
||||
is PtFunctionCall -> translate(expr, resultRegister, resultFpRegister)
|
||||
is PtContainmentCheck -> translate(expr, resultRegister, resultFpRegister)
|
||||
is PtRange,
|
||||
is PtArray,
|
||||
is PtString -> throw AssemblyError("range/arrayliteral/string should no longer occur as expression")
|
||||
@ -103,7 +88,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
private fun translate(check: PtContainmentCheck, resultRegister: Int, resultFpRegister: Int): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
result += translateExpression(check.element, resultRegister, -1) // load the element to check in resultRegister
|
||||
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.targetName) as StStaticVariable
|
||||
val iterable = codeGen.symbolTable.flat.getValue(check.iterable.name) as StStaticVariable
|
||||
when(iterable.dt) {
|
||||
DataType.STR -> {
|
||||
result += translateExpression(check.element, SyscallRegisterBase, -1)
|
||||
@ -135,7 +120,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> throw AssemblyError("containment check in float-array not supported")
|
||||
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.targetName}")
|
||||
else -> throw AssemblyError("weird iterable dt ${iterable.dt} for ${check.iterable.name}")
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -145,7 +130,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
val vmDt = codeGen.irType(arrayIx.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val idxReg = codeGen.registers.nextFree()
|
||||
val arrayVarSymbol = arrayIx.variable.targetName.joinToString(".")
|
||||
val arrayVarSymbol = arrayIx.variable.name
|
||||
|
||||
if(arrayIx.variable.type==DataType.UWORD) {
|
||||
// indexing a pointer var instead of a real array or string
|
||||
@ -430,11 +415,17 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
it += IRInstruction(Opcode.AND, vmDt, reg1 = resultRegister, value = 1)
|
||||
}
|
||||
} else {
|
||||
val rightResultReg = codeGen.registers.nextFree()
|
||||
result += translateExpression(binExpr.left, resultRegister, -1)
|
||||
result += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ
|
||||
addInstr(result, IRInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg), null)
|
||||
if(constValue(binExpr.right)==0.0) {
|
||||
result += translateExpression(binExpr.left, resultRegister, -1)
|
||||
val opcode = if (notEquals) Opcode.SNZ else Opcode.SZ
|
||||
addInstr(result, IRInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = resultRegister), null)
|
||||
} else {
|
||||
val rightResultReg = codeGen.registers.nextFree()
|
||||
result += translateExpression(binExpr.left, resultRegister, -1)
|
||||
result += translateExpression(binExpr.right, rightResultReg, -1)
|
||||
val opcode = if (notEquals) Opcode.SNE else Opcode.SEQ
|
||||
addInstr(result, IRInstruction(opcode, vmDt, reg1 = resultRegister, reg2 = rightResultReg), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
@ -940,12 +931,12 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
|
||||
fun translate(fcall: PtFunctionCall, resultRegister: Int, resultFpRegister: Int): IRCodeChunks {
|
||||
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.functionName)) {
|
||||
when (val callTarget = codeGen.symbolTable.flat.getValue(fcall.name)) {
|
||||
is StSub -> {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
for ((arg, parameter) in fcall.args.zip(callTarget.parameters)) {
|
||||
val paramDt = codeGen.irType(parameter.type)
|
||||
val symbol = (fcall.functionName + parameter.name).joinToString(".")
|
||||
val symbol = "${fcall.name}.${parameter.name}"
|
||||
if(codeGen.isZero(arg)) {
|
||||
addInstr(result, IRInstruction(Opcode.STOREZM, paramDt, labelSymbol = symbol), null)
|
||||
} else {
|
||||
@ -960,7 +951,7 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
}
|
||||
}
|
||||
}
|
||||
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol=fcall.functionName.joinToString(".")), null)
|
||||
addInstr(result, IRInstruction(Opcode.CALL, labelSymbol=fcall.name), null)
|
||||
if(fcall.type==DataType.FLOAT) {
|
||||
if (!fcall.void && resultFpRegister != 0) {
|
||||
// Call convention: result value is in fr0, so put it in the required register instead.
|
||||
@ -997,20 +988,20 @@ internal class ExpressionGen(private val codeGen: IRCodeGen) {
|
||||
if(fcall.type==DataType.FLOAT)
|
||||
throw AssemblyError("doesn't support float register result in asm romsub")
|
||||
val returns = callTarget.returns.single()
|
||||
val regStr = if(returns.registerOrPair!=null) returns.registerOrPair.toString() else returns.statusflag.toString()
|
||||
val regStr = if(returns.register.registerOrPair!=null) returns.register.registerOrPair.toString() else returns.register.statusflag.toString()
|
||||
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null)
|
||||
}
|
||||
else -> {
|
||||
val returnRegister = callTarget.returns.singleOrNull{ it.registerOrPair!=null }
|
||||
val returnRegister = callTarget.returns.singleOrNull{ it.register.registerOrPair!=null }
|
||||
if(returnRegister!=null) {
|
||||
// we skip the other values returned in the status flags.
|
||||
val regStr = returnRegister.registerOrPair.toString()
|
||||
val regStr = returnRegister.register.registerOrPair.toString()
|
||||
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null)
|
||||
} else {
|
||||
val firstReturnRegister = callTarget.returns.firstOrNull{ it.registerOrPair!=null }
|
||||
val firstReturnRegister = callTarget.returns.firstOrNull{ it.register.registerOrPair!=null }
|
||||
if(firstReturnRegister!=null) {
|
||||
// we just take the first register return value and ignore the rest.
|
||||
val regStr = firstReturnRegister.registerOrPair.toString()
|
||||
val regStr = firstReturnRegister.register.registerOrPair.toString()
|
||||
addInstr(result, IRInstruction(Opcode.LOADCPU, codeGen.irType(fcall.type), reg1=resultRegister, labelSymbol = regStr), null)
|
||||
} else {
|
||||
throw AssemblyError("invalid number of return values from call")
|
||||
|
@ -24,23 +24,22 @@ class IRCodeGen(
|
||||
internal val registers = RegisterPool()
|
||||
|
||||
fun generate(): IRProgram {
|
||||
flattenLabelNames()
|
||||
flattenNestedSubroutines()
|
||||
makeAllNodenamesScoped()
|
||||
moveAllNestedSubroutinesToBlockScope()
|
||||
verifyNameScoping(program, symbolTable)
|
||||
|
||||
val irProg = IRProgram(program.name, IRSymbolTable(symbolTable), options, program.encoding)
|
||||
|
||||
if(options.evalStackBaseAddress!=null)
|
||||
throw AssemblyError("IR doesn't use eval-stack")
|
||||
|
||||
if(!options.dontReinitGlobals) {
|
||||
// collect global variables initializers
|
||||
program.allBlocks().forEach {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
it.children.filterIsInstance<PtAssignment>().forEach { assign -> result += assignmentGen.translate(assign) }
|
||||
result.forEach { chunk ->
|
||||
if (chunk is IRCodeChunk) irProg.addGlobalInits(chunk)
|
||||
else throw AssemblyError("only expect code chunk for global inits")
|
||||
}
|
||||
// collect global variables initializers
|
||||
program.allBlocks().forEach {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
it.children.filterIsInstance<PtAssignment>().forEach { assign -> result += assignmentGen.translate(assign) }
|
||||
result.forEach { chunk ->
|
||||
if (chunk is IRCodeChunk) irProg.addGlobalInits(chunk)
|
||||
else throw AssemblyError("only expect code chunk for global inits")
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,6 +70,39 @@ class IRCodeGen(
|
||||
return irProg
|
||||
}
|
||||
|
||||
private fun verifyNameScoping(program: PtProgram, symbolTable: SymbolTable) {
|
||||
fun verifyPtNode(node: PtNode) {
|
||||
when (node) {
|
||||
is PtBuiltinFunctionCall -> require('.' !in node.name) { "builtin function call name should not be scoped: ${node.name}" }
|
||||
is PtFunctionCall -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtIdentifier -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtAsmSub -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtBlock -> require('.' !in node.name) { "block name should not be scoped: ${node.name}" }
|
||||
is PtConstant -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtLabel -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtMemMapped -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtSub -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtVariable -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
is PtProgram -> require('.' !in node.name) { "program name should not be scoped: ${node.name}" }
|
||||
is PtSubroutineParameter -> require('.' in node.name) { "node $node name is not scoped: ${node.name}" }
|
||||
else -> { /* node has no name */
|
||||
}
|
||||
}
|
||||
node.children.forEach { verifyPtNode(it) }
|
||||
}
|
||||
|
||||
fun verifyStNode(node: StNode) {
|
||||
require('.' !in node.name) { "st node name should not be scoped: ${node.name}"}
|
||||
node.children.forEach {
|
||||
require(it.key==it.value.name)
|
||||
verifyStNode(it.value)
|
||||
}
|
||||
}
|
||||
|
||||
verifyPtNode(program)
|
||||
verifyStNode(symbolTable)
|
||||
}
|
||||
|
||||
private fun ensureFirstChunkLabels(irProg: IRProgram) {
|
||||
// make sure that first chunks in Blocks and Subroutines share the name of the block/sub as label.
|
||||
|
||||
@ -131,7 +163,7 @@ class IRCodeGen(
|
||||
symbol = symbolExpr
|
||||
index = 0u
|
||||
}
|
||||
val target = symbolTable.flat[symbol.split('.')]
|
||||
val target = symbolTable.flat[symbol]
|
||||
if (target is StMemVar) {
|
||||
replacements.add(Triple(chunk, idx, target.address+index))
|
||||
}
|
||||
@ -155,112 +187,56 @@ class IRCodeGen(
|
||||
}
|
||||
}
|
||||
|
||||
private fun flattenLabelNames() {
|
||||
val renameLabels = mutableListOf<Pair<PtNode, PtLabel>>()
|
||||
|
||||
fun flattenRecurse(node: PtNode) {
|
||||
private fun makeAllNodenamesScoped() {
|
||||
val renames = mutableListOf<Pair<PtNamedNode, String>>()
|
||||
fun recurse(node: PtNode) {
|
||||
node.children.forEach {
|
||||
if (it is PtLabel)
|
||||
renameLabels += Pair(it.parent, it)
|
||||
else
|
||||
flattenRecurse(it)
|
||||
if(it is PtNamedNode)
|
||||
renames.add(it to it.scopedName)
|
||||
recurse(it)
|
||||
}
|
||||
}
|
||||
|
||||
flattenRecurse(program)
|
||||
|
||||
renameLabels.forEach { (parent, label) ->
|
||||
val renamedLabel = PtLabel(label.scopedName.joinToString("."), label.position)
|
||||
val idx = parent.children.indexOf(label)
|
||||
parent.children.removeAt(idx)
|
||||
parent.children.add(idx, renamedLabel)
|
||||
}
|
||||
recurse(program)
|
||||
renames.forEach { it.first.name = it.second }
|
||||
}
|
||||
|
||||
private fun flattenNestedSubroutines() {
|
||||
// this moves all nested subroutines up to the block scope.
|
||||
// also changes the name to be the fully scoped one, so it becomes unique at the top level.
|
||||
val flattenedSubs = mutableListOf<Pair<PtBlock, PtSub>>()
|
||||
val flattenedAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
|
||||
val removalsSubs = mutableListOf<Pair<PtSub, PtSub>>()
|
||||
val removalsAsmSubs = mutableListOf<Pair<PtSub, PtAsmSub>>()
|
||||
val renameSubs = mutableListOf<Pair<PtBlock, PtSub>>()
|
||||
val renameAsmSubs = mutableListOf<Pair<PtBlock, PtAsmSub>>()
|
||||
private fun moveAllNestedSubroutinesToBlockScope() {
|
||||
val movedSubs = mutableListOf<Pair<PtBlock, PtSub>>()
|
||||
val removedSubs = mutableListOf<Pair<PtSub, PtSub>>()
|
||||
|
||||
fun flattenNestedAsmSub(block: PtBlock, parentSub: PtSub, asmsub: PtAsmSub) {
|
||||
val flattened = PtAsmSub(asmsub.scopedName.joinToString("."),
|
||||
asmsub.address,
|
||||
asmsub.clobbers,
|
||||
asmsub.parameters,
|
||||
asmsub.returnTypes,
|
||||
asmsub.retvalRegisters,
|
||||
asmsub.inline,
|
||||
asmsub.position)
|
||||
asmsub.children.forEach { flattened.add(it) }
|
||||
flattenedAsmSubs += Pair(block, flattened)
|
||||
removalsAsmSubs += Pair(parentSub, asmsub)
|
||||
fun moveToBlock(block: PtBlock, parent: PtSub, asmsub: PtAsmSub) {
|
||||
block.add(asmsub)
|
||||
parent.children.remove(asmsub)
|
||||
}
|
||||
|
||||
fun flattenNestedSub(block: PtBlock, parentSub: PtSub, sub: PtSub) {
|
||||
sub.children.filterIsInstance<PtSub>().forEach { subsub->flattenNestedSub(block, sub, subsub) }
|
||||
sub.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub->flattenNestedAsmSub(block, sub, asmsubsub) }
|
||||
val flattened = PtSub(sub.scopedName.joinToString("."),
|
||||
sub.parameters,
|
||||
sub.returntype,
|
||||
sub.inline,
|
||||
sub.position)
|
||||
sub.children.forEach { if(it !is PtSub) flattened.add(it) }
|
||||
flattenedSubs += Pair(block, flattened)
|
||||
removalsSubs += Pair(parentSub, sub)
|
||||
fun moveToBlock(block: PtBlock, parent: PtSub, sub: PtSub) {
|
||||
sub.children.filterIsInstance<PtSub>().forEach { subsub -> moveToBlock(block, sub, subsub) }
|
||||
sub.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub -> moveToBlock(block, sub, asmsubsub) }
|
||||
movedSubs += Pair(block, sub)
|
||||
removedSubs += Pair(parent, sub)
|
||||
}
|
||||
|
||||
program.allBlocks().forEach { block ->
|
||||
block.children.forEach {
|
||||
if(it is PtSub) {
|
||||
if (it is PtSub) {
|
||||
// Only regular subroutines can have nested subroutines.
|
||||
it.children.filterIsInstance<PtSub>().forEach { subsub->flattenNestedSub(block, it, subsub)}
|
||||
it.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub->flattenNestedAsmSub(block, it, asmsubsub)}
|
||||
renameSubs += Pair(block, it)
|
||||
it.children.filterIsInstance<PtSub>().forEach { subsub -> moveToBlock(block, it, subsub) }
|
||||
it.children.filterIsInstance<PtAsmSub>().forEach { asmsubsub -> moveToBlock(block, it, asmsubsub) }
|
||||
}
|
||||
if(it is PtAsmSub)
|
||||
renameAsmSubs += Pair(block, it)
|
||||
}
|
||||
}
|
||||
|
||||
removalsSubs.forEach { (parent, sub) -> parent.children.remove(sub) }
|
||||
removalsAsmSubs.forEach { (parent, asmsub) -> parent.children.remove(asmsub) }
|
||||
flattenedSubs.forEach { (block, sub) -> block.add(sub) }
|
||||
flattenedAsmSubs.forEach { (block, asmsub) -> block.add(asmsub) }
|
||||
renameSubs.forEach { (parent, sub) ->
|
||||
val renamedSub = PtSub(sub.scopedName.joinToString("."), sub.parameters, sub.returntype, sub.inline, sub.position)
|
||||
sub.children.forEach { renamedSub.add(it) }
|
||||
val subindex = parent.children.indexOf(sub)
|
||||
parent.children[subindex] = renamedSub // keep the order of nodes the same
|
||||
}
|
||||
renameAsmSubs.forEach { (parent, sub) ->
|
||||
val renamedSub = PtAsmSub(sub.scopedName.joinToString("."),
|
||||
sub.address,
|
||||
sub.clobbers,
|
||||
sub.parameters,
|
||||
sub.returnTypes,
|
||||
sub.retvalRegisters,
|
||||
sub.inline,
|
||||
sub.position)
|
||||
|
||||
if(sub.children.isNotEmpty())
|
||||
renamedSub.add(sub.children.single())
|
||||
val subindex = parent.children.indexOf(sub)
|
||||
parent.children[subindex] = renamedSub // keep the order of nodes the same
|
||||
}
|
||||
removedSubs.forEach { (parent, sub) -> parent.children.remove(sub) }
|
||||
movedSubs.forEach { (block, sub) -> block.add(sub) }
|
||||
}
|
||||
|
||||
internal fun translateNode(node: PtNode): IRCodeChunks {
|
||||
val chunks = when(node) {
|
||||
is PtScopeVarsDecls -> emptyList() // vars should be looked up via symbol table
|
||||
is PtVariable -> emptyList() // var should be looked up via symbol table
|
||||
is PtMemMapped -> emptyList() // memmapped var should be looked up via symbol table
|
||||
is PtConstant -> emptyList() // constants have all been folded into the code
|
||||
is PtAssignment -> assignmentGen.translate(node)
|
||||
is PtAugmentedAssign -> assignmentGen.translate(node)
|
||||
is PtNodeGroup -> translateGroup(node.children)
|
||||
is PtBuiltinFunctionCall -> translateBuiltinFunc(node, 0)
|
||||
is PtFunctionCall -> expressionEval.translate(node, 0, 0)
|
||||
@ -338,7 +314,7 @@ class IRCodeGen(
|
||||
}
|
||||
addInstr(result, branchIns, null)
|
||||
} else {
|
||||
val label = if(goto.generatedLabel!=null) goto.generatedLabel else goto.identifier!!.targetName.joinToString(".")
|
||||
val label = if(goto.generatedLabel!=null) goto.generatedLabel else goto.identifier!!.name
|
||||
val branchIns = when(branch.condition) {
|
||||
BranchCondition.CS -> IRInstruction(Opcode.BSTCS, labelSymbol = label)
|
||||
BranchCondition.CC -> IRInstruction(Opcode.BSTCC, labelSymbol = label)
|
||||
@ -454,7 +430,7 @@ class IRCodeGen(
|
||||
}
|
||||
|
||||
private fun translate(forLoop: PtForLoop): IRCodeChunks {
|
||||
val loopvar = symbolTable.lookup(forLoop.variable.targetName)!!
|
||||
val loopvar = symbolTable.lookup(forLoop.variable.name)!!
|
||||
val iterable = forLoop.iterable
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
when(iterable) {
|
||||
@ -465,9 +441,9 @@ class IRCodeGen(
|
||||
result += translateForInNonConstantRange(forLoop, loopvar)
|
||||
}
|
||||
is PtIdentifier -> {
|
||||
val symbol = iterable.targetName.joinToString(".")
|
||||
val iterableVar = symbolTable.lookup(iterable.targetName) as StStaticVariable
|
||||
val loopvarSymbol = loopvar.scopedName.joinToString(".")
|
||||
val iterableVar = symbolTable.lookup(iterable.name) as StStaticVariable
|
||||
require(forLoop.variable.name == loopvar.scopedName)
|
||||
val loopvarSymbol = forLoop.variable.name
|
||||
val indexReg = registers.nextFree()
|
||||
val tmpReg = registers.nextFree()
|
||||
val loopLabel = createLabelName()
|
||||
@ -476,7 +452,7 @@ class IRCodeGen(
|
||||
// iterate over a zero-terminated string
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, value=0), null)
|
||||
val chunk = IRCodeChunk(loopLabel, null)
|
||||
chunk += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpReg, reg2=indexReg, labelSymbol = symbol)
|
||||
chunk += IRInstruction(Opcode.LOADX, IRDataType.BYTE, reg1=tmpReg, reg2=indexReg, labelSymbol = iterable.name)
|
||||
chunk += IRInstruction(Opcode.BZ, IRDataType.BYTE, reg1=tmpReg, labelSymbol = endLabel)
|
||||
chunk += IRInstruction(Opcode.STOREM, IRDataType.BYTE, reg1=tmpReg, labelSymbol = loopvarSymbol)
|
||||
result += chunk
|
||||
@ -498,7 +474,7 @@ class IRCodeGen(
|
||||
chunk += IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=lengthReg, value=lengthBytes)
|
||||
result += chunk
|
||||
val chunk2 = IRCodeChunk(loopLabel, null)
|
||||
chunk2 += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=symbol)
|
||||
chunk2 += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name)
|
||||
chunk2 += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=tmpReg, labelSymbol = loopvarSymbol)
|
||||
result += chunk2
|
||||
result += translateNode(forLoop.statements)
|
||||
@ -507,7 +483,7 @@ class IRCodeGen(
|
||||
} else if(lengthBytes==256) {
|
||||
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1=indexReg, value=0), null)
|
||||
val chunk = IRCodeChunk(loopLabel, null)
|
||||
chunk += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=symbol)
|
||||
chunk += IRInstruction(Opcode.LOADX, irType(elementDt), reg1=tmpReg, reg2=indexReg, labelSymbol=iterable.name)
|
||||
chunk += IRInstruction(Opcode.STOREM, irType(elementDt), reg1=tmpReg, labelSymbol = loopvarSymbol)
|
||||
result += chunk
|
||||
result += translateNode(forLoop.statements)
|
||||
@ -530,7 +506,8 @@ class IRCodeGen(
|
||||
throw AssemblyError("step 0")
|
||||
val indexReg = registers.nextFree()
|
||||
val endvalueReg = registers.nextFree()
|
||||
val loopvarSymbol = loopvar.scopedName.joinToString(".")
|
||||
require(forLoop.variable.name == loopvar.scopedName)
|
||||
val loopvarSymbol = forLoop.variable.name
|
||||
val loopvarDt = when(loopvar) {
|
||||
is StMemVar -> loopvar.dt
|
||||
is StStaticVariable -> loopvar.dt
|
||||
@ -542,18 +519,27 @@ class IRCodeGen(
|
||||
|
||||
result += expressionEval.translateExpression(iterable.to, endvalueReg, -1)
|
||||
result += expressionEval.translateExpression(iterable.from, indexReg, -1)
|
||||
|
||||
val labelAfterFor = createLabelName()
|
||||
val greaterOpcode = if(loopvarDt in SignedDatatypes) Opcode.BGTS else Opcode.BGT
|
||||
addInstr(result, IRInstruction(greaterOpcode, loopvarDtIr, indexReg, endvalueReg, labelSymbol=labelAfterFor), null)
|
||||
|
||||
addInstr(result, IRInstruction(Opcode.STOREM, loopvarDtIr, reg1=indexReg, labelSymbol=loopvarSymbol), null)
|
||||
result += labelFirstChunk(translateNode(forLoop.statements), loopLabel)
|
||||
result += addConstMem(loopvarDtIr, null, loopvarSymbol, step)
|
||||
addInstr(result, IRInstruction(Opcode.LOADM, loopvarDtIr, reg1 = indexReg, labelSymbol = loopvarSymbol), null)
|
||||
val branchOpcode = if(loopvarDt in SignedDatatypes) Opcode.BLES else Opcode.BLE
|
||||
addInstr(result, IRInstruction(branchOpcode, loopvarDtIr, reg1=indexReg, reg2=endvalueReg, labelSymbol=loopLabel), null)
|
||||
// if endvalue >= index, iterate loop
|
||||
val branchOpcode = if(loopvarDt in SignedDatatypes) Opcode.BGES else Opcode.BGE
|
||||
addInstr(result, IRInstruction(branchOpcode, loopvarDtIr, reg1=endvalueReg, reg2=indexReg, labelSymbol=loopLabel), null)
|
||||
|
||||
result += IRCodeChunk(labelAfterFor, null)
|
||||
return result
|
||||
}
|
||||
|
||||
private fun translateForInConstantRange(forLoop: PtForLoop, loopvar: StNode): IRCodeChunks {
|
||||
val loopLabel = createLabelName()
|
||||
val loopvarSymbol = loopvar.scopedName.joinToString(".")
|
||||
require(forLoop.variable.name == loopvar.scopedName)
|
||||
val loopvarSymbol = forLoop.variable.name
|
||||
val indexReg = registers.nextFree()
|
||||
val loopvarDt = when(loopvar) {
|
||||
is StMemVar -> loopvar.dt
|
||||
@ -900,56 +886,117 @@ class IRCodeGen(
|
||||
if(ifElse.condition.operator !in ComparisonOperators)
|
||||
throw AssemblyError("if condition should only be a binary comparison expression")
|
||||
|
||||
val signed = ifElse.condition.left.type in arrayOf(DataType.BYTE, DataType.WORD, DataType.FLOAT)
|
||||
val signed = ifElse.condition.left.type in SignedDatatypes
|
||||
val irDt = irType(ifElse.condition.left.type)
|
||||
|
||||
val goto = ifElse.ifScope.children.firstOrNull() as? PtJump
|
||||
if(goto!=null && ifElse.elseScope.children.isEmpty()) {
|
||||
// special case the form: if <condition> goto <place>
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val leftReg = registers.nextFree()
|
||||
val rightReg = registers.nextFree()
|
||||
result += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
|
||||
result += expressionEval.translateExpression(ifElse.condition.right, rightReg, -1)
|
||||
val opcode = when(ifElse.condition.operator) {
|
||||
"==" -> Opcode.BEQ
|
||||
"!=" -> Opcode.BNE
|
||||
"<" -> Opcode.BLT
|
||||
">" -> Opcode.BGT
|
||||
"<=" -> Opcode.BLE
|
||||
">=" -> Opcode.BGE
|
||||
val leftRegNum = registers.nextFree()
|
||||
val rightRegNum = registers.nextFree()
|
||||
result += expressionEval.translateExpression(ifElse.condition.left, leftRegNum, -1)
|
||||
result += expressionEval.translateExpression(ifElse.condition.right, rightRegNum, -1)
|
||||
val opcode: Opcode
|
||||
val firstReg: Int
|
||||
val secondReg: Int
|
||||
when(ifElse.condition.operator) {
|
||||
"==" -> {
|
||||
opcode = Opcode.BEQ
|
||||
firstReg = leftRegNum
|
||||
secondReg = rightRegNum
|
||||
}
|
||||
"!=" -> {
|
||||
opcode = Opcode.BNE
|
||||
firstReg = leftRegNum
|
||||
secondReg = rightRegNum
|
||||
}
|
||||
"<" -> {
|
||||
// swapped '>'
|
||||
opcode = if(signed) Opcode.BGTS else Opcode.BGT
|
||||
firstReg = rightRegNum
|
||||
secondReg = leftRegNum
|
||||
}
|
||||
">" -> {
|
||||
opcode = if(signed) Opcode.BGTS else Opcode.BGT
|
||||
firstReg = leftRegNum
|
||||
secondReg = rightRegNum
|
||||
}
|
||||
"<=" -> {
|
||||
// swapped '>='
|
||||
opcode = if(signed) Opcode.BGES else Opcode.BGE
|
||||
firstReg = rightRegNum
|
||||
secondReg = leftRegNum
|
||||
}
|
||||
">=" -> {
|
||||
opcode = if(signed) Opcode.BGES else Opcode.BGE
|
||||
firstReg = leftRegNum
|
||||
secondReg = rightRegNum
|
||||
}
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
if(goto.address!=null)
|
||||
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, value = goto.address?.toInt()), null)
|
||||
addInstr(result, IRInstruction(opcode, irDt, reg1=firstReg, reg2=secondReg, value = goto.address?.toInt()), null)
|
||||
else if(goto.generatedLabel!=null)
|
||||
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = goto.generatedLabel), null)
|
||||
addInstr(result, IRInstruction(opcode, irDt, reg1=firstReg, reg2=secondReg, labelSymbol = goto.generatedLabel), null)
|
||||
else
|
||||
addInstr(result, IRInstruction(opcode, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = goto.identifier!!.targetName.joinToString(".")), null)
|
||||
addInstr(result, IRInstruction(opcode, irDt, reg1=firstReg, reg2=secondReg, labelSymbol = goto.identifier!!.name), null)
|
||||
return result
|
||||
}
|
||||
|
||||
fun translateNonZeroComparison(): IRCodeChunks {
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
val elseBranch = when(ifElse.condition.operator) {
|
||||
"==" -> Opcode.BNE
|
||||
"!=" -> Opcode.BEQ
|
||||
"<" -> if(signed) Opcode.BGES else Opcode.BGE
|
||||
">" -> if(signed) Opcode.BLES else Opcode.BLE
|
||||
"<=" -> if(signed) Opcode.BGTS else Opcode.BGT
|
||||
">=" -> if(signed) Opcode.BLTS else Opcode.BLT
|
||||
val leftRegNum = registers.nextFree()
|
||||
val rightRegNum = registers.nextFree()
|
||||
result += expressionEval.translateExpression(ifElse.condition.left, leftRegNum, -1)
|
||||
result += expressionEval.translateExpression(ifElse.condition.right, rightRegNum, -1)
|
||||
|
||||
val elseBranchOpcode: Opcode
|
||||
val elseBranchFirstReg: Int
|
||||
val elseBranchSecondReg: Int
|
||||
when(ifElse.condition.operator) {
|
||||
"==" -> {
|
||||
elseBranchOpcode = Opcode.BNE
|
||||
elseBranchFirstReg = leftRegNum
|
||||
elseBranchSecondReg = rightRegNum
|
||||
}
|
||||
"!=" -> {
|
||||
elseBranchOpcode = Opcode.BEQ
|
||||
elseBranchFirstReg = leftRegNum
|
||||
elseBranchSecondReg = rightRegNum
|
||||
}
|
||||
"<" -> {
|
||||
// else part when left >= right
|
||||
elseBranchOpcode = if(signed) Opcode.BGES else Opcode.BGE
|
||||
elseBranchFirstReg = leftRegNum
|
||||
elseBranchSecondReg = rightRegNum
|
||||
}
|
||||
">" -> {
|
||||
// else part when left <= right --> right >= left
|
||||
elseBranchOpcode = if(signed) Opcode.BGES else Opcode.BGE
|
||||
elseBranchFirstReg = rightRegNum
|
||||
elseBranchSecondReg = leftRegNum
|
||||
}
|
||||
"<=" -> {
|
||||
// else part when left > right
|
||||
elseBranchOpcode = if(signed) Opcode.BGTS else Opcode.BGT
|
||||
elseBranchFirstReg = leftRegNum
|
||||
elseBranchSecondReg = rightRegNum
|
||||
}
|
||||
">=" -> {
|
||||
// else part when left < right --> right > left
|
||||
elseBranchOpcode = if(signed) Opcode.BGTS else Opcode.BGT
|
||||
elseBranchFirstReg = rightRegNum
|
||||
elseBranchSecondReg = leftRegNum
|
||||
}
|
||||
else -> throw AssemblyError("invalid comparison operator")
|
||||
}
|
||||
|
||||
val leftReg = registers.nextFree()
|
||||
val rightReg = registers.nextFree()
|
||||
result += expressionEval.translateExpression(ifElse.condition.left, leftReg, -1)
|
||||
result += expressionEval.translateExpression(ifElse.condition.right, rightReg, -1)
|
||||
if(ifElse.elseScope.children.isNotEmpty()) {
|
||||
// if and else parts
|
||||
val elseLabel = createLabelName()
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(result, IRInstruction(elseBranch, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = elseLabel), null)
|
||||
addInstr(result, IRInstruction(elseBranchOpcode, irDt, reg1=elseBranchFirstReg, reg2=elseBranchSecondReg, labelSymbol = elseLabel), null)
|
||||
result += translateNode(ifElse.ifScope)
|
||||
addInstr(result, IRInstruction(Opcode.JUMP, labelSymbol = afterIfLabel), null)
|
||||
result += labelFirstChunk(translateNode(ifElse.elseScope), elseLabel)
|
||||
@ -957,7 +1004,7 @@ class IRCodeGen(
|
||||
} else {
|
||||
// only if part
|
||||
val afterIfLabel = createLabelName()
|
||||
addInstr(result, IRInstruction(elseBranch, irDt, reg1=leftReg, reg2=rightReg, labelSymbol = afterIfLabel), null)
|
||||
addInstr(result, IRInstruction(elseBranchOpcode, irDt, reg1=elseBranchFirstReg, reg2=elseBranchSecondReg, labelSymbol = afterIfLabel), null)
|
||||
result += translateNode(ifElse.ifScope)
|
||||
result += IRCodeChunk(afterIfLabel, null)
|
||||
}
|
||||
@ -989,18 +1036,13 @@ class IRCodeGen(
|
||||
}
|
||||
|
||||
return when (ifElse.condition.operator) {
|
||||
"==" -> {
|
||||
// if X==0 ... so we just branch on left expr is Not-zero.
|
||||
equalOrNotEqualZero(Opcode.BNZ)
|
||||
}
|
||||
"!=" -> {
|
||||
// if X!=0 ... so we just branch on left expr is Zero.
|
||||
equalOrNotEqualZero(Opcode.BZ)
|
||||
}
|
||||
else -> {
|
||||
// another comparison against 0, just use regular codegen for this.
|
||||
translateNonZeroComparison()
|
||||
}
|
||||
"==" -> equalOrNotEqualZero(Opcode.BNZ)
|
||||
"!=" -> equalOrNotEqualZero(Opcode.BZ)
|
||||
"<" -> if(signed) equalOrNotEqualZero(Opcode.BGEZS) else throw AssemblyError("unsigned < 0 shouldn't occur in codegen")
|
||||
">" -> if(signed) equalOrNotEqualZero(Opcode.BLEZS) else throw AssemblyError("unsigned > 0 shouldn't occur in codegen")
|
||||
"<=" -> if(signed) equalOrNotEqualZero(Opcode.BGZS) else throw AssemblyError("unsigned <= 0 shouldn't occur in codegen")
|
||||
">=" -> if(signed) equalOrNotEqualZero(Opcode.BLZS) else throw AssemblyError("unsigned >= 0 shouldn't occur in codegen")
|
||||
else -> throw AssemblyError("weird operator")
|
||||
}
|
||||
}
|
||||
|
||||
@ -1030,7 +1072,7 @@ class IRCodeGen(
|
||||
val irDt = irType(postIncrDecr.target.type)
|
||||
val result = mutableListOf<IRCodeChunkBase>()
|
||||
if(ident!=null) {
|
||||
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = ident.targetName.joinToString(".")), null)
|
||||
addInstr(result, IRInstruction(operationMem, irDt, labelSymbol = ident.name), null)
|
||||
} else if(memory!=null) {
|
||||
if(memory.address is PtNumber) {
|
||||
val address = (memory.address as PtNumber).number.toInt()
|
||||
@ -1046,7 +1088,7 @@ class IRCodeGen(
|
||||
result += chunk
|
||||
}
|
||||
} else if (array!=null) {
|
||||
val variable = array.variable.targetName.joinToString(".")
|
||||
val variable = array.variable.name
|
||||
val itemsize = program.memsizer.memorySize(array.type)
|
||||
val fixedIndex = constIntValue(array.index)
|
||||
if(fixedIndex!=null) {
|
||||
@ -1102,7 +1144,7 @@ class IRCodeGen(
|
||||
if (jump.generatedLabel != null)
|
||||
IRInstruction(Opcode.JUMP, labelSymbol = jump.generatedLabel!!)
|
||||
else if (jump.identifier != null)
|
||||
IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.targetName.joinToString("."))
|
||||
IRInstruction(Opcode.JUMP, labelSymbol = jump.identifier!!.name)
|
||||
else
|
||||
throw AssemblyError("weird jump")
|
||||
}
|
||||
@ -1135,8 +1177,8 @@ class IRCodeGen(
|
||||
for (child in block.children) {
|
||||
when(child) {
|
||||
is PtNop -> { /* nothing */ }
|
||||
is PtAssignment -> { /* global variable initialization is done elsewhere */ }
|
||||
is PtScopeVarsDecls -> { /* vars should be looked up via symbol table */ }
|
||||
is PtAssignment, is PtAugmentedAssign -> { /* global variable initialization is done elsewhere */ }
|
||||
is PtVariable, is PtConstant, is PtMemMapped -> { /* vars should be looked up via symbol table */ }
|
||||
is PtSub -> {
|
||||
val sub = IRSubroutine(child.name, translate(child.parameters), child.returntype, child.position)
|
||||
for (subchild in child.children) {
|
||||
@ -1160,8 +1202,8 @@ class IRCodeGen(
|
||||
child.name,
|
||||
child.address,
|
||||
child.clobbers,
|
||||
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.second, it.first.type) }, // note: the name of the asmsub param is not used anymore.
|
||||
child.returnTypes.zip(child.retvalRegisters).map { IRAsmSubroutine.IRAsmParam(it.second, it.first) },
|
||||
child.parameters.map { IRAsmSubroutine.IRAsmParam(it.first, it.second.type) }, // note: the name of the asmsub param is not used here anymore
|
||||
child.returns.map { IRAsmSubroutine.IRAsmParam(it.first, it.second)},
|
||||
asmChunk,
|
||||
child.position
|
||||
)
|
||||
@ -1184,9 +1226,9 @@ class IRCodeGen(
|
||||
|
||||
private fun translate(parameters: List<PtSubroutineParameter>) =
|
||||
parameters.map {
|
||||
val flattenedName = (it.definingSub()!!.scopedName + it.name)
|
||||
val flattenedName = it.definingSub()!!.name + "." + it.name
|
||||
val orig = symbolTable.flat.getValue(flattenedName) as StStaticVariable
|
||||
IRSubroutine.IRParam(flattenedName.joinToString("."), orig.dt)
|
||||
IRSubroutine.IRParam(flattenedName, orig.dt)
|
||||
}
|
||||
|
||||
private fun translate(alignment: PtBlock.BlockAlignment): IRBlock.BlockAlignment {
|
||||
|
@ -3,29 +3,28 @@ package prog8.codegen.vm
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.IAssemblyGenerator
|
||||
import prog8.code.core.IAssemblyProgram
|
||||
import prog8.code.core.ICodeGeneratorBackend
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.codegen.intermediate.IRCodeGen
|
||||
import prog8.intermediate.IRFileWriter
|
||||
import prog8.intermediate.IRProgram
|
||||
|
||||
class VmCodeGen(private val program: PtProgram,
|
||||
private val symbolTable: SymbolTable,
|
||||
private val options: CompilationOptions,
|
||||
private val errors: IErrorReporter
|
||||
): IAssemblyGenerator {
|
||||
override fun compileToAssembly(): IAssemblyProgram? {
|
||||
|
||||
class VmCodeGen: ICodeGeneratorBackend {
|
||||
override fun generate(
|
||||
program: PtProgram,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions,
|
||||
errors: IErrorReporter
|
||||
): IAssemblyProgram? {
|
||||
val irCodeGen = IRCodeGen(program, symbolTable, options, errors)
|
||||
val irProgram = irCodeGen.generate()
|
||||
|
||||
return VmAssemblyProgram(irProgram.name, irProgram)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class VmAssemblyProgram(override val name: String, private val irProgram: IRProgram): IAssemblyProgram {
|
||||
internal class VmAssemblyProgram(override val name: String, internal val irProgram: IRProgram): IAssemblyProgram {
|
||||
|
||||
override fun assemble(options: CompilationOptions): Boolean {
|
||||
// the VM reads the IR file from disk.
|
||||
|
@ -1,12 +1,18 @@
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Encoding
|
||||
import prog8.code.core.IMemSizer
|
||||
import prog8.code.core.IStringEncoding
|
||||
import prog8.code.core.*
|
||||
|
||||
|
||||
internal object DummyMemsizer : IMemSizer {
|
||||
override fun memorySize(dt: DataType) = 0
|
||||
override fun memorySize(arrayDt: DataType, numElements: Int) = 0
|
||||
override fun memorySize(dt: DataType) = when(dt) {
|
||||
in ByteDatatypes -> 1
|
||||
DataType.FLOAT -> 5
|
||||
else -> 2
|
||||
}
|
||||
override fun memorySize(arrayDt: DataType, numElements: Int) = when(arrayDt) {
|
||||
DataType.ARRAY_UW -> numElements*2
|
||||
DataType.ARRAY_W -> numElements*2
|
||||
DataType.ARRAY_F -> numElements*5
|
||||
else -> numElements
|
||||
}
|
||||
}
|
||||
|
||||
internal object DummyStringEncoder : IStringEncoding {
|
||||
@ -14,7 +20,39 @@ internal object DummyStringEncoder : IStringEncoding {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String {
|
||||
override fun decodeString(bytes: Iterable<UByte>, encoding: Encoding): String {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
internal class ErrorReporterForTests(private val throwExceptionAtReportIfErrors: Boolean=true, private val keepMessagesAfterReporting: Boolean=false):
|
||||
IErrorReporter {
|
||||
|
||||
val errors = mutableListOf<String>()
|
||||
val warnings = mutableListOf<String>()
|
||||
|
||||
override fun err(msg: String, position: Position) {
|
||||
errors.add("${position.toClickableStr()} $msg")
|
||||
}
|
||||
|
||||
override fun warn(msg: String, position: Position) {
|
||||
warnings.add("${position.toClickableStr()} $msg")
|
||||
}
|
||||
|
||||
override fun noErrors(): Boolean = errors.isEmpty()
|
||||
|
||||
override fun report() {
|
||||
warnings.forEach { println("UNITTEST COMPILATION REPORT: WARNING: $it") }
|
||||
errors.forEach { println("UNITTEST COMPILATION REPORT: ERROR: $it") }
|
||||
if(throwExceptionAtReportIfErrors)
|
||||
finalizeNumErrors(errors.size, warnings.size)
|
||||
if(!keepMessagesAfterReporting) {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
errors.clear()
|
||||
warnings.clear()
|
||||
}
|
||||
}
|
||||
|
103
codeGenIntermediate/test/TestVmCodeGen.kt
Normal file
103
codeGenIntermediate/test/TestVmCodeGen.kt
Normal file
@ -0,0 +1,103 @@
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.ints.shouldBeGreaterThan
|
||||
import prog8.code.SymbolTableMaker
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.codegen.vm.VmAssemblyProgram
|
||||
import prog8.codegen.vm.VmCodeGen
|
||||
import prog8.intermediate.IRSubroutine
|
||||
|
||||
class TestVmCodeGen: FunSpec({
|
||||
|
||||
fun getTestOptions(): CompilationOptions {
|
||||
val target = VMTarget()
|
||||
return CompilationOptions(
|
||||
OutputType.RAW,
|
||||
CbmPrgLauncherType.NONE,
|
||||
ZeropageType.DONTUSE,
|
||||
zpReserved = emptyList(),
|
||||
floats = true,
|
||||
noSysInit = false,
|
||||
compTarget = target,
|
||||
loadAddress = target.machine.PROGRAM_LOAD_ADDRESS
|
||||
)
|
||||
}
|
||||
|
||||
test("augmented assigns") {
|
||||
//main {
|
||||
// sub start() {
|
||||
// ubyte[] particleX = [1,2,3]
|
||||
// ubyte[] particleDX = [1,2,3]
|
||||
// particleX[2] += particleDX[2]
|
||||
//
|
||||
// word @shared xx = 1
|
||||
// xx = -xx
|
||||
// xx += 42
|
||||
// xx += cx16.r0
|
||||
// }
|
||||
//}
|
||||
val codegen = VmCodeGen()
|
||||
val program = PtProgram("test", DummyMemsizer, DummyStringEncoder)
|
||||
val block = PtBlock("main", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
val sub = PtSub("start", emptyList(), null, Position.DUMMY)
|
||||
sub.add(PtVariable("pi", DataType.UBYTE, ZeropageWish.DONTCARE, PtNumber(DataType.UBYTE, 0.0, Position.DUMMY), null, Position.DUMMY))
|
||||
sub.add(PtVariable("particleX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
sub.add(PtVariable("particleDX", DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, 3u, Position.DUMMY))
|
||||
sub.add(PtVariable("xx", DataType.WORD, ZeropageWish.DONTCARE, PtNumber(DataType.WORD, 1.0, Position.DUMMY), null, Position.DUMMY))
|
||||
|
||||
val assign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val target = PtAssignTarget(Position.DUMMY).also {
|
||||
val targetIdx = PtArrayIndexer(DataType.UBYTE, Position.DUMMY).also { idx ->
|
||||
idx.add(PtIdentifier("main.start.particleX", DataType.ARRAY_UB, Position.DUMMY))
|
||||
idx.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||
}
|
||||
it.add(targetIdx)
|
||||
}
|
||||
val value = PtArrayIndexer(DataType.UBYTE, Position.DUMMY)
|
||||
value.add(PtIdentifier("main.start.particleDX", DataType.ARRAY_UB, Position.DUMMY))
|
||||
value.add(PtNumber(DataType.UBYTE, 2.0, Position.DUMMY))
|
||||
assign.add(target)
|
||||
assign.add(value)
|
||||
sub.add(assign)
|
||||
|
||||
val prefixAssign = PtAugmentedAssign("-", Position.DUMMY)
|
||||
val prefixTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
prefixAssign.add(prefixTarget)
|
||||
prefixAssign.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
sub.add(prefixAssign)
|
||||
|
||||
val numberAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val numberAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
numberAssign.add(numberAssignTarget)
|
||||
numberAssign.add(PtNumber(DataType.WORD, 42.0, Position.DUMMY))
|
||||
sub.add(numberAssign)
|
||||
|
||||
val cxregAssign = PtAugmentedAssign("+=", Position.DUMMY)
|
||||
val cxregAssignTarget = PtAssignTarget(Position.DUMMY).also {
|
||||
it.add(PtIdentifier("main.start.xx", DataType.WORD, Position.DUMMY))
|
||||
}
|
||||
cxregAssign.add(cxregAssignTarget)
|
||||
cxregAssign.add(PtIdentifier("cx16.r0", DataType.UWORD, Position.DUMMY))
|
||||
sub.add(cxregAssign)
|
||||
|
||||
block.add(sub)
|
||||
program.add(block)
|
||||
|
||||
// define the "cx16.r0" virtual register
|
||||
val cx16block = PtBlock("cx16", null, false, false, PtBlock.BlockAlignment.NONE, SourceCode.Generated("test"), Position.DUMMY)
|
||||
cx16block.add(PtMemMapped("r0", DataType.UWORD, 100u, null, Position.DUMMY))
|
||||
program.add(cx16block)
|
||||
|
||||
val options = getTestOptions()
|
||||
val st = SymbolTableMaker(program, options).make()
|
||||
val errors = ErrorReporterForTests()
|
||||
val result = codegen.generate(program, st, options, errors) as VmAssemblyProgram
|
||||
val irChunks = (result.irProgram.blocks.first().children.single() as IRSubroutine).chunks
|
||||
irChunks.size shouldBeGreaterThan 4
|
||||
}
|
||||
})
|
8
codeGenVirtual/codeGenVirtual.iml
Normal file
8
codeGenVirtual/codeGenVirtual.iml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -4,9 +4,6 @@ import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.expressions.BinaryExpression
|
||||
import prog8.ast.expressions.IdentifierReference
|
||||
import prog8.ast.expressions.TypecastExpression
|
||||
import prog8.ast.getTempVar
|
||||
import prog8.ast.statements.AssignTarget
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.AssignmentOrigin
|
||||
@ -31,23 +28,6 @@ class BinExprSplitter(private val program: Program, private val options: Compila
|
||||
val binExpr = assignment.value as? BinaryExpression
|
||||
if (binExpr != null) {
|
||||
|
||||
/*
|
||||
|
||||
Reduce the complexity of a (binary) expression that has to be evaluated on the eval stack,
|
||||
by attempting to splitting it up into individual simple steps.
|
||||
We only consider a binary expression *one* level deep (so the operands must not be a combined expression)
|
||||
|
||||
|
||||
X = BinExpr X = LeftExpr
|
||||
<operator> followed by
|
||||
/ \ IF 'X' not used X = BinExpr
|
||||
/ \ IN expression ==> <operator>
|
||||
/ \ / \
|
||||
LeftExpr. RightExpr. / \
|
||||
X RightExpr.
|
||||
|
||||
|
||||
*/
|
||||
if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) {
|
||||
if(assignment.target isSameAs binExpr.right)
|
||||
return noModifications
|
||||
@ -60,20 +40,6 @@ X = BinExpr X = LeftExpr
|
||||
val rightBx = binExpr.right as? BinaryExpression
|
||||
if(rightBx!=null && (!rightBx.left.isSimple || !rightBx.right.isSimple))
|
||||
return noModifications
|
||||
|
||||
// TODO below attempts to remove stack-based evaluated expressions, but often the resulting code is BIGGER, and SLOWER.
|
||||
// val dt = assignment.target.inferType(program)
|
||||
// if(!dt.isInteger)
|
||||
// return noModifications
|
||||
// val tempVar = IdentifierReference(getTempVarName(dt), binExpr.right.position)
|
||||
// val assignTempVar = Assignment(
|
||||
// AssignTarget(tempVar, null, null, binExpr.right.position),
|
||||
// binExpr.right, binExpr.right.position
|
||||
// )
|
||||
// return listOf(
|
||||
// IAstModification.InsertBefore(assignment, assignTempVar, assignment.parent as IStatementContainer),
|
||||
// IAstModification.ReplaceNode(binExpr.right, tempVar.copy(), binExpr)
|
||||
// )
|
||||
}
|
||||
|
||||
if(binExpr.right.isSimple) {
|
||||
@ -87,30 +53,10 @@ X = BinExpr X = LeftExpr
|
||||
}
|
||||
}
|
||||
|
||||
// TODO further unraveling of binary expression trees into flat statements.
|
||||
// however this should probably be done in a more generic way to also work on
|
||||
// the expressiontrees that are not used in an assignment statement...
|
||||
}
|
||||
|
||||
val typecast = assignment.value as? TypecastExpression
|
||||
if(typecast!=null) {
|
||||
val origExpr = typecast.expression as? BinaryExpression
|
||||
if(origExpr!=null && options.compTarget.name!=VMTarget.NAME) {
|
||||
// it's a typecast of a binary expression.
|
||||
// we can see if we can unwrap the binary expression by working on a new temporary variable
|
||||
// (that has the type of the expression), and then finally doing the typecast.
|
||||
// Once it's outside the typecast, the regular splitting can commence.
|
||||
val tempvarDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)
|
||||
val (tempVarName, _) = program.getTempVar(tempvarDt)
|
||||
val assignTempVar = Assignment(
|
||||
AssignTarget(IdentifierReference(tempVarName, typecast.position), null, null, typecast.position),
|
||||
typecast.expression, AssignmentOrigin.OPTIMIZER, typecast.position
|
||||
)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(typecast.expression, IdentifierReference(tempVarName, typecast.position), typecast)
|
||||
)
|
||||
}
|
||||
// Further unraveling of binary expressions is really complicated here and
|
||||
// often results in much bigger code, thereby defeating the purpose a bit.
|
||||
// All in all this should probably be fixed in a better code generation backend
|
||||
// that doesn't require this at all.
|
||||
}
|
||||
|
||||
return noModifications
|
||||
|
@ -354,9 +354,11 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
if(decl.type== VarDeclType.CONST && numval!=null) {
|
||||
val valueDt = numval.inferType(program)
|
||||
if(valueDt isnot decl.datatype) {
|
||||
val cast = numval.cast(decl.datatype)
|
||||
if(cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||
if(decl.datatype!=DataType.BOOL || valueDt.isnot(DataType.UBYTE)) {
|
||||
val cast = numval.cast(decl.datatype)
|
||||
if (cast.isValid)
|
||||
return listOf(IAstModification.ReplaceNode(numval, cast.valueOrZero(), decl))
|
||||
}
|
||||
}
|
||||
}
|
||||
return noModifications
|
||||
@ -386,9 +388,9 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
|
||||
subleftIsConst: Boolean,
|
||||
subrightIsConst: Boolean): IAstModification?
|
||||
{
|
||||
// NOTE: THIS IS ONLY VALID ON FLOATING POINT CONSTANTS
|
||||
// NOTE: THESE REORDERINGS ARE ONLY VALID FOR FLOATING POINT CONSTANTS
|
||||
// TODO: this implements only a small set of possible reorderings at this time, we could perhaps add more
|
||||
|
||||
// TODO: this implements only a small set of possible reorderings at this time, we could think of more
|
||||
if(expr.operator==subExpr.operator) {
|
||||
// both operators are the same.
|
||||
|
||||
|
@ -18,7 +18,7 @@ import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.pow
|
||||
|
||||
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
|
||||
// TODO add more peephole expression optimizations? Investigate what optimizations binaryen has
|
||||
|
||||
class ExpressionSimplifier(private val program: Program,
|
||||
private val errors: IErrorReporter,
|
||||
@ -187,12 +187,37 @@ class ExpressionSimplifier(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
// X <= Y-1 ---> X<Y , X >= Y+1 ---> X>Y
|
||||
if(leftDt in IntegerDatatypes && rightDt in IntegerDatatypes) {
|
||||
val rightExpr = expr.right as? BinaryExpression
|
||||
if(rightExpr!=null && rightExpr.right.constValue(program)?.number==1.0) {
|
||||
if (expr.operator == "<=" && rightExpr.operator == "-") {
|
||||
expr.operator = "<"
|
||||
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
|
||||
} else if (expr.operator == ">=" && rightExpr.operator == "+") {
|
||||
expr.operator = ">"
|
||||
return listOf(IAstModification.ReplaceNode(rightExpr, rightExpr.left, expr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
|
||||
// for integers: x >= 1 --> x > 0
|
||||
expr.operator = ">"
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
|
||||
}
|
||||
|
||||
// for signed integers: X <= -1 => X<0 , X > -1 => X>=0
|
||||
if(leftDt in SignedDatatypes && leftDt!=DataType.FLOAT && rightVal?.number==-1.0) {
|
||||
if(expr.operator=="<=") {
|
||||
expr.operator = "<"
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt, 0.0, expr.right.position), expr))
|
||||
} else if(expr.operator==">") {
|
||||
expr.operator = ">="
|
||||
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral(rightDt, 0.0, expr.right.position), expr))
|
||||
}
|
||||
}
|
||||
|
||||
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
|
||||
if(expr.operator == ">=" && rightVal?.number == 0.0) {
|
||||
// unsigned >= 0 --> true
|
||||
@ -231,6 +256,18 @@ class ExpressionSimplifier(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
if(leftDt==DataType.BOOL) {
|
||||
// optimize boolean constant comparisons
|
||||
// if(expr.operator=="==" && rightVal?.number==0.0)
|
||||
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
|
||||
// if(expr.operator=="!=" && rightVal?.number==1.0)
|
||||
// return listOf(IAstModification.ReplaceNode(expr, PrefixExpression("not", expr.left, expr.position), parent))
|
||||
if(expr.operator=="==" && rightVal?.number==1.0)
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
if(expr.operator=="!=" && rightVal?.number==0.0)
|
||||
return listOf(IAstModification.ReplaceNode(expr, expr.left, parent))
|
||||
}
|
||||
|
||||
// simplify when a term is constant and directly determines the outcome
|
||||
val constFalse = NumericLiteral.fromBoolean(false, expr.position)
|
||||
val newExpr: Expression? = when (expr.operator) {
|
||||
|
@ -118,9 +118,11 @@ class Inliner(val program: Program): AstWalker() {
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(identifier: IdentifierReference) {
|
||||
val scoped = (identifier.targetStatement(program)!! as INamedStatement).scopedName
|
||||
val scopedIdent = IdentifierReference(scoped, identifier.position)
|
||||
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
|
||||
identifier.targetStatement(program)?.let { target ->
|
||||
val scoped = (target as INamedStatement).scopedName
|
||||
val scopedIdent = IdentifierReference(scoped, identifier.position)
|
||||
modifications += IAstModification.ReplaceNode(identifier, scopedIdent, identifier.parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: BuiltinFunctionCallStatement) {
|
||||
@ -130,27 +132,30 @@ class Inliner(val program: Program): AstWalker() {
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: FunctionCallStatement) {
|
||||
val sub = call.target.targetSubroutine(program)!!
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
call.target.targetSubroutine(program)?.let { sub ->
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = FunctionCallStatement(scopedName, scopedArgs.toMutableList(), call.void, call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: BuiltinFunctionCall) {
|
||||
val sub = call.target.targetSubroutine(program)!!
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
call.target.targetSubroutine(program)?.let { sub ->
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = BuiltinFunctionCall(scopedName, scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeFullyScoped(call: FunctionCallExpression) {
|
||||
val sub = call.target.targetSubroutine(program)!!
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
call.target.targetSubroutine(program)?.let { sub ->
|
||||
val scopedName = IdentifierReference(sub.scopedName, call.target.position)
|
||||
val scopedArgs = makeScopedArgs(call.args)
|
||||
val scopedCall = FunctionCallExpression(scopedName, scopedArgs.toMutableList(), call.position)
|
||||
modifications += IAstModification.ReplaceNode(call, scopedCall, call.parent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeScopedArgs(args: List<Expression>): List<Expression> {
|
||||
|
@ -120,12 +120,26 @@ class UnusedCodeRemover(private val program: Program,
|
||||
if(singleUse is AssignTarget) {
|
||||
val assignment = singleUse.parent as Assignment
|
||||
if(assignment.origin==AssignmentOrigin.VARINIT) {
|
||||
if(!decl.definingModule.isLibrary)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(
|
||||
IAstModification.Remove(decl, parent as IStatementContainer),
|
||||
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
|
||||
)
|
||||
if(assignment.value.isSimple) {
|
||||
// remove the vardecl
|
||||
if(!decl.definingModule.isLibrary)
|
||||
errors.warn("removing unused variable '${decl.name}'", decl.position)
|
||||
return listOf(
|
||||
IAstModification.Remove(decl, parent as IStatementContainer),
|
||||
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
|
||||
)
|
||||
} else if(assignment.value is IFunctionCall) {
|
||||
// replace the unused variable's initializer function call by a void
|
||||
errors.warn("replaced unused variable '${decl.name}' with void call, maybe this can be removed altogether", decl.position)
|
||||
val fcall = assignment.value as IFunctionCall
|
||||
val voidCall = FunctionCallStatement(fcall.target, fcall.args, true, fcall.position)
|
||||
return listOf(
|
||||
IAstModification.ReplaceNode(decl, voidCall, parent),
|
||||
IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
|
||||
)
|
||||
} else {
|
||||
errors.warn("variable '${decl.name}' is unused but has non-trivial initialization assignment. Leaving this in but maybe it can be removed altogether", decl.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ dependencies {
|
||||
implementation project(':codeGenIntermediate')
|
||||
implementation project(':codeGenExperimental')
|
||||
implementation project(':virtualmachine')
|
||||
implementation 'org.antlr:antlr4-runtime:4.10.1'
|
||||
implementation 'org.antlr:antlr4-runtime:4.11.1'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
|
||||
// implementation "org.jetbrains.kotlin:kotlin-reflect"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.4'
|
||||
|
@ -120,17 +120,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to signed word in A/Y
|
||||
%asm {{
|
||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to unsigned word in A/Y
|
||||
%asm {{
|
||||
|
@ -561,15 +561,32 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||
asmsub wait(uword jiffies @AY) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
||||
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||
repeat jiffies {
|
||||
ubyte jiff = lsb(c64.RDTIM16())
|
||||
while jiff==lsb(c64.RDTIM16()) {
|
||||
; wait until 1 jiffy has passed
|
||||
}
|
||||
}
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
|
||||
_loop lda P8ZP_SCRATCH_W1
|
||||
ora P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
rts
|
||||
|
||||
+ lda c64.TIME_LO
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda c64.TIME_LO
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq -
|
||||
|
||||
lda P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
dec P8ZP_SCRATCH_W1+1
|
||||
+ dec P8ZP_SCRATCH_W1
|
||||
jmp _loop
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitvsync() clobbers(A) {
|
||||
|
@ -28,10 +28,6 @@ romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
|
||||
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
|
||||
|
||||
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
|
||||
; (tip: use floats.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
|
||||
|
||||
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
|
||||
; (tip: use floats.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
|
||||
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
|
||||
@ -147,17 +143,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to signed word in A/Y
|
||||
%asm {{
|
||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||
sta P8ZP_SCRATCH_REG
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_REG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to unsigned word in A/Y
|
||||
%asm {{
|
||||
|
@ -527,15 +527,32 @@ sys {
|
||||
}}
|
||||
}
|
||||
|
||||
sub wait(uword jiffies) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds)
|
||||
asmsub wait(uword jiffies @AY) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
||||
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||
repeat jiffies {
|
||||
ubyte jiff = lsb(c64.RDTIM16())
|
||||
while jiff==lsb(c64.RDTIM16()) {
|
||||
; wait until 1 jiffy has passed
|
||||
}
|
||||
}
|
||||
%asm {{
|
||||
stx P8ZP_SCRATCH_B1
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
|
||||
_loop lda P8ZP_SCRATCH_W1
|
||||
ora P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
ldx P8ZP_SCRATCH_B1
|
||||
rts
|
||||
|
||||
+ lda c64.TIME_LO
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- lda c64.TIME_LO
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq -
|
||||
|
||||
lda P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
dec P8ZP_SCRATCH_W1+1
|
||||
+ dec P8ZP_SCRATCH_W1
|
||||
jmp _loop
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub waitvsync() clobbers(A) {
|
||||
|
@ -521,7 +521,7 @@ asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
|
||||
|
||||
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
|
||||
;By Omegamatrix Further optimizations by tepples
|
||||
; routine from https://forums.nesdev.org/viewtopic.php?f=2&t=11341&start=15
|
||||
; routine from https://forums.nesdev.org/viewtopic.php?p=130363&sid=1944ba8bac4d6afa9c02e3cc42304e6b#p130363
|
||||
|
||||
;HexToDec99
|
||||
; start in A
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
cx16diskio {
|
||||
|
||||
; Same as diskio.load() but with additional bank parameter to select the Ram bank to load into.
|
||||
; Use kernal LOAD routine to load the given program file in memory.
|
||||
; This is similar to Basic's LOAD "filename",drive / LOAD "filename",drive,1
|
||||
; If you don't give an address_override, the location in memory is taken from the 2-byte file header.
|
||||
@ -14,9 +15,10 @@ cx16diskio {
|
||||
; You can use the load_size() function to calcuate the size of the file that was loaded.
|
||||
sub load(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
|
||||
cx16.rambank(bank)
|
||||
return diskio.load(drivenumber, filenameptr, address_override)
|
||||
return diskio.internal_load_routine(drivenumber, filenameptr, address_override, false)
|
||||
}
|
||||
|
||||
; Same as diskio.load_raw() but with additional bank parameter to select the Ram bank to load into.
|
||||
; Use kernal LOAD routine to load the given file in memory.
|
||||
; INCLUDING the first 2 bytes in the file: no program header is assumed in the file.
|
||||
; The load address is mandatory. Returns the number of bytes loaded.
|
||||
@ -24,9 +26,9 @@ cx16diskio {
|
||||
; or alternatively make sure to reset the correct ram bank yourself after the load!
|
||||
; Returns the end load address+1 if successful or 0 if a load error occurred.
|
||||
; You can use the load_size() function to calcuate the size of the file that was loaded.
|
||||
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address) -> uword {
|
||||
sub load_raw(ubyte drivenumber, uword filenameptr, ubyte bank, uword address_override) -> uword {
|
||||
cx16.rambank(bank)
|
||||
return diskio.load_headerless_cx16(drivenumber, filenameptr, address, true)
|
||||
return diskio.internal_load_routine(drivenumber, filenameptr, address_override, true)
|
||||
}
|
||||
|
||||
; For use directly after a load or load_raw call (don't mess with the ram bank yet):
|
||||
@ -87,8 +89,9 @@ internal_vload:
|
||||
}}
|
||||
}
|
||||
|
||||
; replacement function that makes use of fast block read capability of the X16
|
||||
; use this in place of regular diskio.f_read()
|
||||
; Replacement function that makes use of fast block read capability of the X16,
|
||||
; and can wrap over multiple ram banks while reading.
|
||||
; Use this in place of regular diskio.f_read() on X16.
|
||||
sub f_read(uword bufferpointer, uword num_bytes) -> uword {
|
||||
; -- read from the currently open file, up to the given number of bytes.
|
||||
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
||||
@ -147,7 +150,7 @@ m_in_buffer sta $ffff
|
||||
}
|
||||
|
||||
; replacement function that makes use of fast block read capability of the X16
|
||||
; use this in place of regular diskio.f_read_all()
|
||||
; use this in place of regular diskio.f_read_all() on X16
|
||||
sub f_read_all(uword bufferpointer) -> uword {
|
||||
; -- read the full contents of the file, returns number of bytes read.
|
||||
if not diskio.iteration_in_progress
|
||||
|
@ -119,17 +119,6 @@ asmsub GIVAYFAY (uword value @ AY) clobbers(A,X,Y) {
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub FTOSWRDAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to signed word in A/Y
|
||||
%asm {{
|
||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||
sta P8ZP_SCRATCH_B1
|
||||
tya
|
||||
ldy P8ZP_SCRATCH_B1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub GETADRAY () clobbers(X) -> uword @ AY {
|
||||
; ---- fac1 to unsigned word in A/Y
|
||||
%asm {{
|
||||
|
@ -454,19 +454,28 @@ inline asmsub getrombank() -> ubyte @A {
|
||||
}
|
||||
|
||||
inline asmsub getrambank() -> ubyte @A {
|
||||
; -- get the current ram bank
|
||||
; -- get the current RAM bank
|
||||
%asm {{
|
||||
lda $00
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub numbanks() -> ubyte @A {
|
||||
; -- uses MEMTOP's cx16 extension to query the number of available RAM banks. (each is 8 Kb)
|
||||
asmsub numbanks() -> uword @AY {
|
||||
; -- Returns the number of available RAM banks according to the kernal (each bank is 8 Kb).
|
||||
; Note that the number of such banks can be 256 so a word is returned.
|
||||
; But just looking at the A register (the LSB of the result word) could suffice if you know that A=0 means 256 banks:
|
||||
; The maximum number of RAM banks in the X16 is currently 256 (2 Megabytes of banked RAM).
|
||||
; Kernal's MEMTOP routine reports 0 in this case but that doesn't mean 'zero banks', instead it means 256 banks,
|
||||
; as there is no X16 without at least 1 page of banked RAM. So this routine returns 256 instead of 0.
|
||||
%asm {{
|
||||
phx
|
||||
sec
|
||||
jsr c64.MEMTOP
|
||||
plx
|
||||
ldy #0
|
||||
cmp #0
|
||||
bne +
|
||||
iny
|
||||
+ plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
@ -624,13 +633,17 @@ asmsub init_system() {
|
||||
; Called automatically by the loader program logic.
|
||||
%asm {{
|
||||
sei
|
||||
lda #0
|
||||
tax
|
||||
tay
|
||||
jsr cx16.mouse_config ; disable mouse
|
||||
cld
|
||||
lda VERA_DC_VIDEO
|
||||
and #%00000111 ; retain chroma + output mode
|
||||
sta P8ZP_SCRATCH_REG
|
||||
lda #$80
|
||||
sta VERA_CTRL ; reset vera
|
||||
stz $01 ; select rom bank 0 (enable kernal)
|
||||
stz $01 ; rom bank 0 (kernal)
|
||||
jsr c64.IOINIT
|
||||
jsr c64.RESTOR
|
||||
jsr c64.CINT
|
||||
@ -638,13 +651,11 @@ asmsub init_system() {
|
||||
and #%11111000
|
||||
ora P8ZP_SCRATCH_REG
|
||||
sta VERA_DC_VIDEO ; keep old output mode
|
||||
ldy #0
|
||||
clc
|
||||
jsr c64.PLOT ; force a call to PLOT to avoid autostart black square issue, also see textio.fix_autostart_square()
|
||||
lda #$90 ; black
|
||||
jsr c64.CHROUT
|
||||
lda #1 ; swap fg/bg
|
||||
jsr c64.CHROUT
|
||||
lda #1
|
||||
sta $00 ; select ram bank 1
|
||||
jsr c64.CHROUT ; swap fg/bg
|
||||
lda #$9e ; yellow
|
||||
jsr c64.CHROUT
|
||||
lda #147 ; clear screen
|
||||
@ -677,7 +688,7 @@ asmsub cleanup_at_exit() {
|
||||
lda #1
|
||||
sta $00 ; ram bank 1
|
||||
lda #4
|
||||
sta $01 ; rom bank 4 (kernal)
|
||||
sta $01 ; rom bank 4 (basic)
|
||||
stz $2d ; hack to reset machine code monitor bank to 0
|
||||
rts
|
||||
}}
|
||||
@ -707,8 +718,7 @@ _modified jsr $ffff ; modified
|
||||
lda _use_kernal
|
||||
bne +
|
||||
; end irq processing - don't use kernal's irq handling
|
||||
lda cx16.VERA_ISR
|
||||
ora #1
|
||||
lda #1
|
||||
sta cx16.VERA_ISR ; clear Vera Vsync irq status
|
||||
ply
|
||||
plx
|
||||
@ -911,17 +921,29 @@ sys {
|
||||
|
||||
asmsub wait(uword jiffies @AY) {
|
||||
; --- wait approximately the given number of jiffies (1/60th seconds) (N or N+1)
|
||||
; note: regular system vsync irq handler must be running, and no nother irqs
|
||||
; note: the system irq handler has to be active for this to work as it depends on the system jiffy clock
|
||||
%asm {{
|
||||
- wai ; wait for irq (assume it was vsync)
|
||||
cmp #0
|
||||
phx
|
||||
sta P8ZP_SCRATCH_W1
|
||||
sty P8ZP_SCRATCH_W1+1
|
||||
|
||||
_loop lda P8ZP_SCRATCH_W1
|
||||
ora P8ZP_SCRATCH_W1+1
|
||||
bne +
|
||||
dey
|
||||
+ dec a
|
||||
bne -
|
||||
cpy #0
|
||||
bne -
|
||||
plx
|
||||
rts
|
||||
|
||||
+ jsr c64.RDTIM
|
||||
sta P8ZP_SCRATCH_B1
|
||||
- jsr c64.RDTIM
|
||||
cmp P8ZP_SCRATCH_B1
|
||||
beq -
|
||||
|
||||
lda P8ZP_SCRATCH_W1
|
||||
bne +
|
||||
dec P8ZP_SCRATCH_W1+1
|
||||
+ dec P8ZP_SCRATCH_W1
|
||||
bra _loop
|
||||
}}
|
||||
}
|
||||
|
||||
|
@ -21,26 +21,6 @@ sub home() {
|
||||
txt.chrout(19)
|
||||
}
|
||||
|
||||
asmsub fix_autostart_square() {
|
||||
; Here's a possible work around for weird issue that prints a black character after first call to c64.PLOT()
|
||||
; if you're also using c64.CINT() yourself. The default prog8 program initialization (which calls CINT) already performs this workaround.
|
||||
; The problem occurs when a program is autostarded in the emulator with -run -prg test.prg,
|
||||
; or when the program is saved as AUTOBOOT.X16 and loaded on boot like that.
|
||||
%asm {{
|
||||
phx
|
||||
sec
|
||||
jsr c64.PLOT
|
||||
clc
|
||||
jsr c64.PLOT
|
||||
lda #' '
|
||||
jsr c64.CHROUT ; overwrite the black square
|
||||
clc
|
||||
jsr c64.PLOT ; cursor back to original position
|
||||
plx
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
sub nl() {
|
||||
txt.chrout('\n')
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ diskio {
|
||||
void c64.CHRIN() ; skip the 4 prologue bytes
|
||||
}
|
||||
|
||||
; while not key pressed / EOF encountered, read data.
|
||||
; while not stop key pressed / EOF encountered, read data.
|
||||
status = c64.READST()
|
||||
if status!=0 {
|
||||
status = 1
|
||||
@ -84,7 +84,6 @@ io_error:
|
||||
if c64.READST()!=0
|
||||
goto io_error
|
||||
|
||||
; while not key pressed / EOF encountered, read data.
|
||||
cx16.r0 = &list_filename
|
||||
repeat {
|
||||
@(cx16.r0) = c64.CHRIN()
|
||||
@ -283,6 +282,7 @@ close_end:
|
||||
; returns the actual number of bytes read. (checks for End-of-file and error conditions)
|
||||
; NOTE: on systems with banked ram (such as Commander X16) this routine DOES NOT
|
||||
; automatically load into subsequent banks if it reaches a bank boundary!
|
||||
; Consider using cx16diskio.f_read() on X16.
|
||||
if not iteration_in_progress or not num_bytes
|
||||
return 0
|
||||
|
||||
@ -317,6 +317,7 @@ m_in_buffer sta $ffff
|
||||
|
||||
sub f_read_all(uword bufferpointer) -> uword {
|
||||
; -- read the full contents of the file, returns number of bytes read.
|
||||
; Note: Consider using cx16diskio.f_read_all() on X16!
|
||||
if not iteration_in_progress
|
||||
return 0
|
||||
|
||||
@ -476,9 +477,9 @@ io_error:
|
||||
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
|
||||
; (which is possible on the Commander X16), the returned size is not correct,
|
||||
; because it doesn't take the number of ram banks into account.
|
||||
; Consider using cx16diskio.load() instead.
|
||||
; Also consider using cx16diskio.load() instead on the Commander X16.
|
||||
sub load(ubyte drivenumber, uword filenameptr, uword address_override) -> uword {
|
||||
return load_headerless_cx16(drivenumber, filenameptr, address_override, false)
|
||||
return internal_load_routine(drivenumber, filenameptr, address_override, false)
|
||||
}
|
||||
|
||||
; Use kernal LOAD routine to load the given file in memory.
|
||||
@ -489,10 +490,10 @@ io_error:
|
||||
; NOTE: when the load is larger than 64Kb and/or spans multiple RAM banks
|
||||
; (which is possible on the Commander X16), the returned size is not correct,
|
||||
; because it doesn't take the number of ram banks into account.
|
||||
; Consider using cx16diskio.load_raw() instead on the Commander X16.
|
||||
; Also consider using cx16diskio.load_raw() instead on the Commander X16.
|
||||
sub load_raw(ubyte drivenumber, uword filenameptr, uword address) -> uword {
|
||||
if sys.target==16 ; are we on commander X16?
|
||||
return load_headerless_cx16(drivenumber, filenameptr, address, true)
|
||||
return internal_load_routine(drivenumber, filenameptr, address, true)
|
||||
; fallback to reading the 2 header bytes separately
|
||||
if not f_open(drivenumber, filenameptr)
|
||||
return 0
|
||||
@ -507,7 +508,8 @@ io_error:
|
||||
|
||||
; Internal routine, only to be used on Commander X16 platform if headerless=true,
|
||||
; because this routine uses kernal support for that to load headerless files.
|
||||
sub load_headerless_cx16(ubyte drivenumber, uword filenameptr, uword address_override, bool headerless) -> uword {
|
||||
; On C64 it will always be called with headerless=false.
|
||||
sub internal_load_routine(ubyte drivenumber, uword filenameptr, uword address_override, bool headerless) -> uword {
|
||||
c64.SETNAM(string.length(filenameptr), filenameptr)
|
||||
ubyte secondary = 1
|
||||
cx16.r1 = 0
|
||||
|
@ -1 +1 @@
|
||||
8.8
|
||||
8.10
|
||||
|
@ -43,7 +43,6 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental/alternative codegen")
|
||||
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
|
||||
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
|
||||
val dontReinitGlobals by cli.option(ArgType.Boolean, fullName = "noreinit", description = "don't create code to reinitialize globals on multiple runs of the program (experimental)")
|
||||
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
|
||||
val optimizeFloatExpressions by cli.option(ArgType.Boolean, fullName = "optfloatx", description = "optimize float expressions (warning: can increase program size)")
|
||||
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
|
||||
@ -52,6 +51,7 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}', '${AtariTarget.NAME}', '${VMTarget.NAME}')").default(C64Target.NAME)
|
||||
val startVm by cli.option(ArgType.Boolean, fullName = "vm", description = "load and run a .p8ir IR source file in the VM")
|
||||
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watch for file changes)")
|
||||
val varsHigh by cli.option(ArgType.Boolean, fullName = "varshigh", description = "put uninitialized variables in high memory area instead of at the end of the program")
|
||||
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
|
||||
|
||||
try {
|
||||
@ -120,12 +120,12 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
filepath,
|
||||
dontOptimize != true,
|
||||
optimizeFloatExpressions == true,
|
||||
dontReinitGlobals == true,
|
||||
dontWriteAssembly != true,
|
||||
slowCodegenWarnings == true,
|
||||
quietAssembler == true,
|
||||
asmListfile == true,
|
||||
experimentalCodegen == true,
|
||||
varsHigh == true,
|
||||
compilationTarget,
|
||||
evalStackAddr,
|
||||
processedSymbols,
|
||||
@ -184,12 +184,12 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
filepath,
|
||||
dontOptimize != true,
|
||||
optimizeFloatExpressions == true,
|
||||
dontReinitGlobals == true,
|
||||
dontWriteAssembly != true,
|
||||
slowCodegenWarnings == true,
|
||||
quietAssembler == true,
|
||||
asmListfile == true,
|
||||
experimentalCodegen == true,
|
||||
varsHigh == true,
|
||||
compilationTarget,
|
||||
evalStackAddr,
|
||||
processedSymbols,
|
||||
@ -206,13 +206,13 @@ private fun compileMain(args: Array<String>): Boolean {
|
||||
}
|
||||
|
||||
if(startEmulator1==true || startEmulator2==true) {
|
||||
if (compilationResult.program.name.isEmpty()) {
|
||||
if (compilationResult.compilerAst.name.isEmpty()) {
|
||||
println("\nCan't start emulator because no program was assembled.")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
val programNameInPath = outputPath.resolve(compilationResult.program.name)
|
||||
val programNameInPath = outputPath.resolve(compilationResult.compilerAst.name)
|
||||
|
||||
if(startEmulator1==true || startEmulator2==true) {
|
||||
if (compilationResult.compilationOptions.launcher != CbmPrgLauncherType.NONE || compilationTarget=="atari") {
|
||||
|
158
compiler/src/prog8/compiler/BuiltinFunctions.kt
Normal file
158
compiler/src/prog8/compiler/BuiltinFunctions.kt
Normal file
@ -0,0 +1,158 @@
|
||||
package prog8.compiler
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.base.SyntaxError
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.code.core.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sign
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
||||
private typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteral
|
||||
|
||||
internal val constEvaluatorsForBuiltinFuncs: Map<String, ConstExpressionCaller> = mapOf(
|
||||
"abs" to ::builtinAbs,
|
||||
"len" to ::builtinLen,
|
||||
"sizeof" to ::builtinSizeof,
|
||||
"sgn" to ::builtinSgn,
|
||||
"sqrt16" to { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()) } },
|
||||
"any" to { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
|
||||
"all" to { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
|
||||
"lsb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x and 255).toDouble() } },
|
||||
"msb" to { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> (x ushr 8 and 255).toDouble()} },
|
||||
"mkword" to ::builtinMkword
|
||||
)
|
||||
|
||||
private fun builtinAny(array: List<Double>): Double = if(array.any { it!=0.0 }) 1.0 else 0.0
|
||||
|
||||
private fun builtinAll(array: List<Double>): Double = if(array.all { it!=0.0 }) 1.0 else 0.0
|
||||
|
||||
internal fun builtinFunctionReturnType(function: String): InferredTypes.InferredType {
|
||||
if(function in arrayOf("set_carry", "set_irqd", "clear_carry", "clear_irqd"))
|
||||
return InferredTypes.InferredType.void()
|
||||
|
||||
val func = BuiltinFunctions.getValue(function)
|
||||
val returnType = func.returnType
|
||||
return if(returnType==null)
|
||||
InferredTypes.InferredType.void()
|
||||
else
|
||||
InferredTypes.knownFor(returnType)
|
||||
}
|
||||
|
||||
|
||||
internal class NotConstArgumentException: AstException("not a const argument to a built-in function")
|
||||
internal class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
|
||||
|
||||
|
||||
private fun oneIntArgOutputInt(args: List<Expression>, position: Position, program: Program, function: (arg: Int)->Double): NumericLiteral {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("built-in function requires one integer argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
if(constval.type != DataType.UBYTE && constval.type!= DataType.UWORD)
|
||||
throw SyntaxError("built-in function requires one integer argument", position)
|
||||
|
||||
val integer = constval.number.toInt()
|
||||
return NumericLiteral.optimalInteger(function(integer).toInt(), args[0].position)
|
||||
}
|
||||
|
||||
private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Double>)->Double): NumericLiteral {
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("builtin function requires one non-scalar argument", position)
|
||||
|
||||
val array= args[0] as? ArrayLiteral ?: throw NotConstArgumentException()
|
||||
val constElements = array.value.map{it.constValue(program)?.number}
|
||||
if(constElements.contains(null))
|
||||
throw NotConstArgumentException()
|
||||
|
||||
return NumericLiteral.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
|
||||
}
|
||||
|
||||
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
// 1 arg, type = int, result type= uword
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("abs requires one integer argument", position)
|
||||
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
return when (constval.type) {
|
||||
in IntegerDatatypesNoBool -> NumericLiteral.optimalInteger(abs(constval.number.toInt()), args[0].position)
|
||||
else -> throw SyntaxError("abs requires one integer argument", position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
// 1 arg, type = anything, result type = ubyte
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("sizeof requires one argument", position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("sizeof argument should be an identifier", position)
|
||||
|
||||
val dt = args[0].inferType(program)
|
||||
if(dt.isKnown) {
|
||||
val target = (args[0] as IdentifierReference).targetStatement(program)
|
||||
?: throw CannotEvaluateException("sizeof", "no target")
|
||||
|
||||
return when {
|
||||
dt.isArray -> {
|
||||
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
|
||||
val elementDt = ArrayToElementTypes.getValue(dt.getOr(DataType.UNDEFINED))
|
||||
NumericLiteral.optimalInteger(program.memsizer.memorySize(elementDt) * length, position)
|
||||
}
|
||||
dt istype DataType.STR -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
|
||||
else -> NumericLiteral(DataType.UBYTE, program.memsizer.memorySize(dt.getOr(DataType.UNDEFINED)).toDouble(), position)
|
||||
}
|
||||
} else {
|
||||
throw SyntaxError("sizeof invalid argument type", position)
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
// note: in some cases the length is > 255, and then we have to return a UWORD type instead of a UBYTE.
|
||||
if(args.size!=1)
|
||||
throw SyntaxError("len requires one argument", position)
|
||||
|
||||
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
|
||||
var arraySize = directMemVar?.arraysize?.constIndex()
|
||||
if(arraySize != null)
|
||||
return NumericLiteral.optimalInteger(arraySize, position)
|
||||
if(args[0] is ArrayLiteral)
|
||||
return NumericLiteral.optimalInteger((args[0] as ArrayLiteral).value.size, position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("len argument should be an identifier", position)
|
||||
val target = (args[0] as IdentifierReference).targetVarDecl(program)
|
||||
?: throw CannotEvaluateException("len", "no target vardecl")
|
||||
|
||||
return when(target.datatype) {
|
||||
in ArrayDatatypes -> {
|
||||
arraySize = target.arraysize?.constIndex()
|
||||
if(arraySize==null)
|
||||
throw CannotEvaluateException("len", "arraysize unknown")
|
||||
NumericLiteral.optimalInteger(arraySize, args[0].position)
|
||||
}
|
||||
DataType.STR -> {
|
||||
val refLv = target.value as? StringLiteral ?: throw CannotEvaluateException("len", "stringsize unknown")
|
||||
NumericLiteral.optimalInteger(refLv.value.length, args[0].position)
|
||||
}
|
||||
in NumericDatatypes -> throw SyntaxError("cannot use len on numeric value, did you mean sizeof?", args[0].position)
|
||||
else -> throw InternalCompilerException("weird datatype")
|
||||
}
|
||||
}
|
||||
|
||||
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
if (args.size != 2)
|
||||
throw SyntaxError("mkword requires msb and lsb arguments", position)
|
||||
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val constLsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||
val result = (constMsb.number.toInt() shl 8) or constLsb.number.toInt()
|
||||
return NumericLiteral(DataType.UWORD, result.toDouble(), position)
|
||||
}
|
||||
|
||||
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteral {
|
||||
if (args.size != 1)
|
||||
throw SyntaxError("sgn requires one argument", position)
|
||||
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
return NumericLiteral(DataType.BYTE, constval.number.sign, position)
|
||||
}
|
@ -1,17 +1,14 @@
|
||||
package prog8.compiler
|
||||
|
||||
import com.github.michaelbull.result.onFailure
|
||||
import prog8.ast.AstToSourceTextConverter
|
||||
import prog8.ast.IBuiltinFunctions
|
||||
import prog8.ast.IStatementContainer
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.AstException
|
||||
import prog8.ast.expressions.Expression
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Directive
|
||||
import prog8.ast.statements.VarDecl
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.SymbolTable
|
||||
import prog8.code.SymbolTableMaker
|
||||
import prog8.code.ast.PtProgram
|
||||
import prog8.code.core.*
|
||||
import prog8.code.target.*
|
||||
import prog8.codegen.vm.VmCodeGen
|
||||
@ -25,19 +22,20 @@ import kotlin.math.round
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
||||
class CompilationResult(val program: Program,
|
||||
class CompilationResult(val compilerAst: Program, // deprecated, use codegenAst instead
|
||||
val codegenAst: PtProgram?,
|
||||
val compilationOptions: CompilationOptions,
|
||||
val importedFiles: List<Path>)
|
||||
|
||||
class CompilerArguments(val filepath: Path,
|
||||
val optimize: Boolean,
|
||||
val optimizeFloatExpressions: Boolean,
|
||||
val dontReinitGlobals: Boolean,
|
||||
val writeAssembly: Boolean,
|
||||
val slowCodegenWarnings: Boolean,
|
||||
val quietAssembler: Boolean,
|
||||
val asmListfile: Boolean,
|
||||
val experimentalCodegen: Boolean,
|
||||
val varsHigh: Boolean,
|
||||
val compilationTarget: String,
|
||||
val evalStackBaseAddress: UInt?,
|
||||
val symbolDefs: Map<String, String>,
|
||||
@ -63,23 +61,21 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
}
|
||||
|
||||
var compilationOptions: CompilationOptions
|
||||
var ast: PtProgram? = null
|
||||
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and everything it needs
|
||||
val (programresult, options, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
|
||||
val (programresult, options, imported) = parseMainModule(args.filepath, args.errors, compTarget, args.sourceDirs)
|
||||
compilationOptions = options
|
||||
print("Parsed ${args.filepath}")
|
||||
ModuleImporter.ansiEraseRestOfLine(true)
|
||||
|
||||
with(compilationOptions) {
|
||||
slowCodegenWarnings = args.slowCodegenWarnings
|
||||
optimize = args.optimize
|
||||
optimizeFloatExpressions = optimizeFloatExpr
|
||||
dontReinitGlobals = args.dontReinitGlobals
|
||||
asmQuiet = args.quietAssembler
|
||||
asmListfile = args.asmListfile
|
||||
experimentalCodegen = args.experimentalCodegen
|
||||
varsHigh = args.varsHigh
|
||||
evalStackBaseAddress = args.evalStackBaseAddress
|
||||
outputDir = args.outputDir.normalize()
|
||||
symbolDefs = args.symbolDefs
|
||||
@ -113,10 +109,22 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
args.errors.report()
|
||||
|
||||
if (args.writeAssembly) {
|
||||
if(!createAssemblyAndAssemble(program, args.errors, compilationOptions)) {
|
||||
|
||||
compilationOptions.compTarget.machine.initializeMemoryAreas(compilationOptions)
|
||||
program.processAstBeforeAsmGeneration(compilationOptions, args.errors)
|
||||
args.errors.report()
|
||||
|
||||
val intermediateAst = IntermediateAstMaker(program, compilationOptions).transform()
|
||||
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
|
||||
// printProgram(program)
|
||||
// println("*********** AST RIGHT BEFORE ASM GENERATION *************")
|
||||
// printAst(intermediateAst, ::println)
|
||||
|
||||
if(!createAssemblyAndAssemble(intermediateAst, args.errors, compilationOptions)) {
|
||||
System.err.println("Error in codegeneration or assembler")
|
||||
return null
|
||||
}
|
||||
ast = intermediateAst
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,7 +132,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult? {
|
||||
System.err.flush()
|
||||
val seconds = totalTime/1000.0
|
||||
println("\nTotal compilation+assemble time: ${round(seconds*100.0)/100.0} sec.")
|
||||
return CompilationResult(program, compilationOptions, importedFiles)
|
||||
return CompilationResult(program, ast, compilationOptions, importedFiles)
|
||||
} catch (px: ParseError) {
|
||||
System.err.print("\n\u001b[91m") // bright red
|
||||
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
|
||||
@ -208,7 +216,7 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
override fun constValue(funcName: String, args: List<Expression>, position: Position): NumericLiteral? {
|
||||
val func = BuiltinFunctions[funcName]
|
||||
if(func!=null) {
|
||||
val exprfunc = func.constExpressionFunc
|
||||
val exprfunc = constEvaluatorsForBuiltinFuncs[funcName]
|
||||
if(exprfunc!=null) {
|
||||
return try {
|
||||
exprfunc(args, position, program)
|
||||
@ -226,17 +234,16 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
|
||||
override fun returnType(funcName: String) = builtinFunctionReturnType(funcName)
|
||||
}
|
||||
|
||||
fun parseImports(filepath: Path,
|
||||
errors: IErrorReporter,
|
||||
compTarget: ICompilationTarget,
|
||||
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||
println("Compilation target: ${compTarget.name}")
|
||||
fun parseMainModule(filepath: Path,
|
||||
errors: IErrorReporter,
|
||||
compTarget: ICompilationTarget,
|
||||
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
|
||||
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
|
||||
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
|
||||
bf.program = program
|
||||
|
||||
val importer = ModuleImporter(program, compTarget.name, errors, sourceDirs)
|
||||
val importedModuleResult = importer.importModule(filepath)
|
||||
val importedModuleResult = importer.importMainModule(filepath)
|
||||
importedModuleResult.onFailure { throw it }
|
||||
errors.report()
|
||||
|
||||
@ -246,11 +253,11 @@ fun parseImports(filepath: Path,
|
||||
val compilerOptions = determineCompilationOptions(program, compTarget)
|
||||
// depending on the machine and compiler options we may have to include some libraries
|
||||
for(lib in compTarget.machine.importLibs(compilerOptions, compTarget.name))
|
||||
importer.importLibraryModule(lib)
|
||||
importer.importImplicitLibraryModule(lib)
|
||||
|
||||
// always import prog8_lib and math
|
||||
importer.importLibraryModule("math")
|
||||
importer.importLibraryModule("prog8_lib")
|
||||
importer.importImplicitLibraryModule("math")
|
||||
importer.importImplicitLibraryModule("prog8_lib")
|
||||
|
||||
if (compilerOptions.launcher == CbmPrgLauncherType.BASIC && compilerOptions.output != OutputType.PRG)
|
||||
errors.err("BASIC launcher requires output type PRG", program.toplevelModule.position)
|
||||
@ -328,7 +335,6 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
|
||||
}
|
||||
|
||||
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
|
||||
println("Analyzing code...")
|
||||
program.preprocessAst(errors, compilerOptions)
|
||||
program.checkIdentifiers(errors, compilerOptions)
|
||||
errors.report()
|
||||
@ -353,7 +359,6 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
|
||||
}
|
||||
|
||||
private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
|
||||
println("Optimizing...")
|
||||
val remover = UnusedCodeRemover(program, errors, compTarget)
|
||||
remover.visit(program)
|
||||
remover.applyModifications()
|
||||
@ -385,25 +390,23 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
|
||||
errors.report()
|
||||
}
|
||||
|
||||
private fun createAssemblyAndAssemble(program: Program,
|
||||
private fun createAssemblyAndAssemble(program: PtProgram,
|
||||
errors: IErrorReporter,
|
||||
compilerOptions: CompilationOptions
|
||||
): Boolean {
|
||||
compilerOptions.compTarget.machine.initializeMemoryAreas(compilerOptions)
|
||||
program.processAstBeforeAsmGeneration(compilerOptions, errors)
|
||||
errors.report()
|
||||
val symbolTable = SymbolTableMaker().makeFrom(program, compilerOptions)
|
||||
|
||||
// TODO make removing all VarDecls work, but this needs inferType to be able to get its information from somewhere else as the VarDecl nodes in the Ast,
|
||||
// or don't use inferType at all anymore and "bake the type information" into the Ast somehow.
|
||||
// Note: we don't actually *need* to remove the VarDecl nodes, but it is nice as a temporary measure
|
||||
// to help clean up the code that still depends on them.
|
||||
// removeAllVardeclsFromAst(program)
|
||||
val asmgen = if(compilerOptions.experimentalCodegen)
|
||||
prog8.codegen.experimental.ExperiCodeGen()
|
||||
else if (compilerOptions.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
||||
prog8.codegen.cpu6502.AsmGen6502()
|
||||
else if (compilerOptions.compTarget.name == VMTarget.NAME)
|
||||
VmCodeGen()
|
||||
else
|
||||
throw NotImplementedError("no asm generator for cpu ${compilerOptions.compTarget.machine.cpu}")
|
||||
|
||||
// println("*********** COMPILER AST RIGHT BEFORE ASM GENERATION *************")
|
||||
// printProgram(program)
|
||||
|
||||
val assembly = asmGeneratorFor(program, errors, symbolTable, compilerOptions).compileToAssembly()
|
||||
val stMaker = SymbolTableMaker(program, compilerOptions)
|
||||
val symbolTable = stMaker.make()
|
||||
val assembly = asmgen.generate(program, symbolTable, compilerOptions, errors)
|
||||
errors.report()
|
||||
|
||||
return if(assembly!=null && errors.noErrors()) {
|
||||
@ -412,51 +415,3 @@ private fun createAssemblyAndAssemble(program: Program,
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeAllVardeclsFromAst(program: Program) {
|
||||
// remove all VarDecl nodes from the AST.
|
||||
// code generation doesn't require them anymore, it operates only on the 'variables' collection.
|
||||
|
||||
class SearchAndRemove: IAstVisitor {
|
||||
private val allVars = mutableListOf<VarDecl>()
|
||||
init {
|
||||
visit(program)
|
||||
for (it in allVars) {
|
||||
require((it.parent as IStatementContainer).statements.remove(it))
|
||||
}
|
||||
}
|
||||
override fun visit(decl: VarDecl) {
|
||||
allVars.add(decl)
|
||||
}
|
||||
}
|
||||
|
||||
SearchAndRemove()
|
||||
}
|
||||
|
||||
fun printProgram(program: Program) {
|
||||
println()
|
||||
val printer = AstToSourceTextConverter(::print, program)
|
||||
printer.visit(program)
|
||||
println()
|
||||
}
|
||||
|
||||
internal fun asmGeneratorFor(program: Program,
|
||||
errors: IErrorReporter,
|
||||
symbolTable: SymbolTable,
|
||||
options: CompilationOptions): IAssemblyGenerator
|
||||
{
|
||||
if(options.experimentalCodegen) {
|
||||
val intermediateAst = IntermediateAstMaker(program).transform()
|
||||
return prog8.codegen.experimental.CodeGen(intermediateAst, symbolTable, options, errors)
|
||||
} else {
|
||||
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
|
||||
// TODO rewrite 6502 codegen on new Intermediary Ast or on new Intermediate Representation
|
||||
return prog8.codegen.cpu6502.AsmGen(program, symbolTable, options, errors)
|
||||
if (options.compTarget.name == VMTarget.NAME) {
|
||||
val intermediateAst = IntermediateAstMaker(program).transform()
|
||||
return VmCodeGen(intermediateAst, symbolTable, options, errors)
|
||||
}
|
||||
}
|
||||
|
||||
throw NotImplementedError("no asm generator for cpu ${options.compTarget.machine.cpu}")
|
||||
}
|
||||
|
@ -13,7 +13,9 @@ import prog8.code.core.SourceCode
|
||||
import prog8.parser.Prog8Parser
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.exists
|
||||
|
||||
|
||||
class ModuleImporter(private val program: Program,
|
||||
@ -21,30 +23,25 @@ class ModuleImporter(private val program: Program,
|
||||
val errors: IErrorReporter,
|
||||
sourceDirs: List<String>) {
|
||||
|
||||
private val sourcePaths: List<Path> = sourceDirs.map { Path(it) }
|
||||
private val sourcePaths: List<Path> = sourceDirs.map { Path(it).absolute().normalize() }.toSortedSet().toList()
|
||||
|
||||
fun importModule(filePath: Path): Result<Module, NoSuchFileException> {
|
||||
val currentDir = Path("").absolute()
|
||||
val searchIn = listOf(currentDir) + sourcePaths
|
||||
val candidates = searchIn
|
||||
.map { it.absolute().div(filePath).normalize().absolute() }
|
||||
.filter { it.exists() }
|
||||
.map { currentDir.relativize(it) }
|
||||
.map { if (it.isAbsolute) it else Path(".", "$it") }
|
||||
|
||||
val srcPath = when (candidates.size) {
|
||||
0 -> return Err(NoSuchFileException(
|
||||
file = filePath.normalize().toFile(),
|
||||
reason = "Searched in $searchIn"))
|
||||
1 -> candidates.first()
|
||||
else -> candidates.first() // when more candiates, pick the one from the first location
|
||||
fun importMainModule(filePath: Path): Result<Module, NoSuchFileException> {
|
||||
val searchIn = (listOf(Path("").absolute()) + sourcePaths).toSortedSet()
|
||||
val normalizedFilePath = filePath.normalize()
|
||||
for(path in searchIn) {
|
||||
val programPath = path.resolve(normalizedFilePath)
|
||||
if(programPath.exists()) {
|
||||
println("Compiling program ${Path("").absolute().relativize(programPath)}")
|
||||
val source = SourceCode.File(programPath)
|
||||
return Ok(importModule(source))
|
||||
}
|
||||
}
|
||||
|
||||
val source = SourceCode.File(srcPath)
|
||||
return Ok(importModule(source))
|
||||
return Err(NoSuchFileException(
|
||||
file = normalizedFilePath.toFile(),
|
||||
reason = "Searched in $searchIn"))
|
||||
}
|
||||
|
||||
fun importLibraryModule(name: String): Module? {
|
||||
fun importImplicitLibraryModule(name: String): Module? {
|
||||
val import = Directive("%import", listOf(
|
||||
DirectiveArg("", name, 42u, position = Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
), Position("<<<implicit-import>>>", 0, 0, 0))
|
||||
@ -52,7 +49,6 @@ class ModuleImporter(private val program: Program,
|
||||
}
|
||||
|
||||
private fun importModule(src: SourceCode) : Module {
|
||||
printImportingMessage(src.name, src.origin)
|
||||
val moduleAst = Prog8Parser.parseModule(src)
|
||||
program.addModule(moduleAst)
|
||||
|
||||
@ -146,10 +142,8 @@ class ModuleImporter(private val program: Program,
|
||||
if (importingModule == null) { // <=> imported from library module
|
||||
sourcePaths
|
||||
} else {
|
||||
val dropCurDir = if(sourcePaths.isNotEmpty() && sourcePaths[0].name == ".") 1 else 0
|
||||
sourcePaths.drop(dropCurDir) +
|
||||
listOf(Path(importingModule.position.file).parent ?: Path("")) +
|
||||
listOf(Path(".", "prog8lib"))
|
||||
val pathFromImportingModule = (Path(importingModule.position.file).parent ?: Path("")).absolute()
|
||||
listOf(pathFromImportingModule) + sourcePaths
|
||||
}
|
||||
|
||||
locations.forEach {
|
||||
@ -161,18 +155,4 @@ class ModuleImporter(private val program: Program,
|
||||
|
||||
return Err(NoSuchFileException(File("name")))
|
||||
}
|
||||
|
||||
fun printImportingMessage(module: String, origin: String) {
|
||||
print(" importing '$module' (from ${origin})")
|
||||
ansiEraseRestOfLine(false)
|
||||
print("\r")
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun ansiEraseRestOfLine(newline: Boolean) {
|
||||
print("\u001b[0K")
|
||||
if(newline)
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,8 +7,7 @@ import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.*
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
import prog8.compiler.InplaceModifyingBuiltinFunctions
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.compiler.builtinFunctionReturnType
|
||||
import java.io.CharConversionException
|
||||
import java.io.File
|
||||
@ -60,6 +59,16 @@ internal class AstChecker(private val program: Program,
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(identifier: IdentifierReference) {
|
||||
val target = identifier.targetVarDecl(program)
|
||||
if(target != null && target.origin==VarDeclOrigin.SUBROUTINEPARAM) {
|
||||
if(target.definingSubroutine!!.isAsmSubroutine) {
|
||||
if(target.definingSubroutine!!.parameters.any { it.name == identifier.nameInSource.last() })
|
||||
errors.err("cannot refer to parameter of asmsub by name", identifier.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(returnStmt: Return) {
|
||||
val expectedReturnValues = returnStmt.definingSubroutine?.returntypes ?: emptyList()
|
||||
if(expectedReturnValues.size>1) {
|
||||
@ -299,6 +308,8 @@ internal class AstChecker(private val program: Program,
|
||||
err("subroutines can only be defined in the scope of a block or within another subroutine")
|
||||
|
||||
if(subroutine.isAsmSubroutine) {
|
||||
if(compilerOptions.compTarget.name==VMTarget.NAME)
|
||||
err("cannot use asmsub for vm target, use regular subs")
|
||||
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
|
||||
err("number of asm parameter registers is not the isSameAs as number of parameters")
|
||||
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
|
||||
@ -653,8 +664,12 @@ internal class AstChecker(private val program: Program,
|
||||
err("string var must be initialized with a string literal")
|
||||
}
|
||||
|
||||
if(decl.value !is StringLiteral)
|
||||
err("string var must be initialized with a string literal")
|
||||
if(decl.value !is StringLiteral) {
|
||||
if(decl.type==VarDeclType.MEMORY)
|
||||
err("strings can't be memory mapped")
|
||||
else
|
||||
err("string var must be initialized with a string literal")
|
||||
}
|
||||
}
|
||||
|
||||
if(compilerOptions.zeropage==ZeropageType.DONTUSE && decl.zeropage == ZeropageWish.REQUIRE_ZEROPAGE)
|
||||
@ -834,6 +849,8 @@ internal class AstChecker(private val program: Program,
|
||||
else if(expr.operator == "~") {
|
||||
if(dt !in IntegerDatatypes)
|
||||
errors.err("can only use bitwise invert on integer types", expr.position)
|
||||
if(dt==DataType.BOOL)
|
||||
errors.err("bitwise invert is for integer types, use 'not' on booleans", expr.position)
|
||||
}
|
||||
super.visit(expr)
|
||||
}
|
||||
@ -1259,8 +1276,14 @@ internal class AstChecker(private val program: Program,
|
||||
val elementDt = containment.element.inferType(program)
|
||||
val iterableDt = containment.iterable.inferType(program)
|
||||
|
||||
if(containment.parent is BinaryExpression)
|
||||
errors.err("containment check is currently not supported inside complex expressions", containment.position)
|
||||
if(compilerOptions.compTarget.name!=VMTarget.NAME) {
|
||||
val parentBinexpr = containment.parent as? BinaryExpression
|
||||
if(parentBinexpr!=null) {
|
||||
// only supported if compared to 1 or 0, more complex expressions aren't supported in the 6502 code-gen.
|
||||
if(parentBinexpr.operator!="==" || parentBinexpr.right.constValue(program)?.number !in listOf(0.0, 1.0))
|
||||
errors.err("containment check is currently not supported inside complex expressions", containment.position)
|
||||
}
|
||||
}
|
||||
|
||||
if(iterableDt.isIterable && containment.iterable !is RangeExpression) {
|
||||
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))
|
||||
|
@ -136,12 +136,8 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, options: Compilati
|
||||
internal fun Program.variousCleanups(errors: IErrorReporter, options: CompilationOptions) {
|
||||
val process = VariousCleanups(this, errors, options)
|
||||
process.visit(this)
|
||||
if(errors.noErrors()) {
|
||||
if(process.applyModifications()>0) {
|
||||
process.visit(this)
|
||||
if(errors.noErrors())
|
||||
process.applyModifications()
|
||||
}
|
||||
while(errors.noErrors() && process.applyModifications()>0) {
|
||||
process.visit(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,11 +7,11 @@ import prog8.ast.expressions.FunctionCallExpression
|
||||
import prog8.ast.expressions.StringLiteral
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.BuiltinFunctions
|
||||
import prog8.code.core.ICompilationTarget
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.target.VMTarget
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
|
||||
|
||||
internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
@ -92,7 +92,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||
for(name in paramsToCheck) {
|
||||
val symbol = subroutine.searchSymbol(name)
|
||||
if(symbol!=null && (symbol as? VarDecl)?.subroutineParameter==null)
|
||||
if(symbol!=null && (symbol as? VarDecl)?.origin!=VarDeclOrigin.SUBROUTINEPARAM)
|
||||
nameError(name, symbol.position, subroutine)
|
||||
}
|
||||
|
||||
@ -163,7 +163,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
|
||||
val pos = (if(call.args.any()) call.args[0] else (call as Node)).position
|
||||
errors.err("invalid number of arguments", pos)
|
||||
}
|
||||
if(func.name=="memory") {
|
||||
if(target.name=="memory") {
|
||||
val name = call.args[0] as? StringLiteral
|
||||
if(name!=null) {
|
||||
val processed = name.value.map {
|
||||
|
@ -31,7 +31,7 @@ internal class AstOnetimeTransforms(private val program: Program, private val op
|
||||
// note: The CodeDesugarer already does something similar, but that is meant ONLY to take
|
||||
// into account the case where the index value is a word type.
|
||||
// The replacement here is to fix missing cases in the 6502 codegen.
|
||||
// TODO make the 6502 codegen better so this work around can be removed
|
||||
// TODO make the 6502 codegen better so this workaround can be removed
|
||||
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
|
||||
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
|
||||
if(parent is AssignTarget) {
|
||||
|
@ -120,16 +120,19 @@ class AstPreprocessor(val program: Program,
|
||||
movements.add(IAstModification.InsertFirst(decl, parentscope))
|
||||
replacements.add(IAstModification.Remove(decl, scope))
|
||||
} else {
|
||||
val declToInsert: VarDecl
|
||||
if(decl.value!=null && decl.datatype in NumericDatatypes) {
|
||||
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
|
||||
val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
|
||||
replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
|
||||
decl.value = null
|
||||
decl.allowInitializeWithZero = false
|
||||
declToInsert = decl.copy()
|
||||
} else {
|
||||
replacements.add(IAstModification.Remove(decl, scope))
|
||||
declToInsert = decl
|
||||
}
|
||||
movements.add(IAstModification.InsertFirst(decl, parentscope))
|
||||
movements.add(IAstModification.InsertFirst(declToInsert, parentscope))
|
||||
}
|
||||
}
|
||||
return movements + replacements
|
||||
@ -138,12 +141,15 @@ class AstPreprocessor(val program: Program,
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
// this has to be done here becuse otherwise the string / range literal values will have been replaced by variables
|
||||
if(expr.operator=="in") {
|
||||
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
|
||||
}
|
||||
|
||||
if(expr.operator=="not in") {
|
||||
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
||||
val notContainment = PrefixExpression("not", containment, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, notContainment, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
|
@ -36,27 +36,9 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
|
||||
// adjust global variables initialization
|
||||
if(options.dontReinitGlobals) {
|
||||
block.statements.asSequence().filterIsInstance<VarDecl>().forEach {
|
||||
if(it.type==VarDeclType.VAR) {
|
||||
it.zeropage = ZeropageWish.NOT_IN_ZEROPAGE
|
||||
it.findInitializer(program)?.let { initializer ->
|
||||
it.value = initializer.value // put the init value back into the vardecl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
|
||||
if(!options.dontReinitGlobals) {
|
||||
if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
|
||||
throw InternalCompilerException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
|
||||
}
|
||||
if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
|
||||
throw InternalCompilerException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
|
||||
|
||||
return noModifications
|
||||
}
|
||||
@ -90,7 +72,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
"unknown dt"
|
||||
)
|
||||
}, sourceDt, implicit=true)
|
||||
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.BEFOREASMGEN, assignment.position)
|
||||
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.ASMGEN, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
|
||||
@ -103,7 +85,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
"unknown dt"
|
||||
)
|
||||
}, sourceDt, implicit=true)
|
||||
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.BEFOREASMGEN, assignment.position)
|
||||
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.ASMGEN, assignment.position)
|
||||
return listOf(
|
||||
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
|
||||
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
|
||||
@ -293,7 +275,7 @@ internal class BeforeAsmAstChanger(val program: Program,
|
||||
val dt = expr.indexer.indexExpr.inferType(program)
|
||||
val (tempVarName, _) = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
|
||||
val target = AssignTarget(IdentifierReference(tempVarName, expr.indexer.position), null, null, expr.indexer.position)
|
||||
val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.BEFOREASMGEN, expr.indexer.position)
|
||||
val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.ASMGEN, expr.indexer.position)
|
||||
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
|
||||
modifications.add(
|
||||
IAstModification.ReplaceNode(
|
||||
|
@ -2,6 +2,7 @@ package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.Subroutine
|
||||
import prog8.ast.statements.SubroutineParameter
|
||||
@ -14,7 +15,8 @@ internal class BoolRemover(val program: Program) : AstWalker() {
|
||||
|
||||
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(typecast.type == DataType.BOOL) {
|
||||
val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(DataType.UBYTE, 0.0, typecast.position), typecast.position)
|
||||
val valueDt = typecast.expression.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
|
||||
val notZero = BinaryExpression(typecast.expression, "!=", NumericLiteral(valueDt, 0.0, typecast.position), typecast.position)
|
||||
return listOf(IAstModification.ReplaceNode(typecast, notZero, parent))
|
||||
}
|
||||
return noModifications
|
||||
@ -28,7 +30,7 @@ internal class BoolRemover(val program: Program) : AstWalker() {
|
||||
newvalue = NumericLiteral(DataType.UBYTE, 1.0, newvalue.position)
|
||||
}
|
||||
val ubyteDecl = VarDecl(decl.type, decl.origin, DataType.UBYTE, decl.zeropage, decl.arraysize, decl.name,
|
||||
newvalue, decl.isArray, decl.sharedWithAsm, decl.subroutineParameter, decl.position)
|
||||
newvalue, decl.isArray, decl.sharedWithAsm, decl.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl, ubyteDecl, parent))
|
||||
}
|
||||
|
||||
@ -45,7 +47,7 @@ internal class BoolRemover(val program: Program) : AstWalker() {
|
||||
newarray = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_UB), convertedArray, decl.position)
|
||||
}
|
||||
val ubyteArrayDecl = VarDecl(decl.type, decl.origin, DataType.ARRAY_UB, decl.zeropage, decl.arraysize, decl.name,
|
||||
newarray, true, decl.sharedWithAsm, decl.subroutineParameter, decl.position)
|
||||
newarray, true, decl.sharedWithAsm, decl.position)
|
||||
return listOf(IAstModification.ReplaceNode(decl, ubyteArrayDecl, parent))
|
||||
}
|
||||
|
||||
|
@ -163,4 +163,12 @@ _after:
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
|
||||
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
|
||||
if(expr.operator=="in") {
|
||||
val containment = ContainmentCheck(expr.left, expr.right, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, containment, parent))
|
||||
}
|
||||
return noModifications
|
||||
}
|
||||
}
|
||||
|
@ -9,10 +9,10 @@ import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.code.ast.*
|
||||
import prog8.code.core.BuiltinFunctions
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import prog8.code.core.SourceCode
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
import prog8.compiler.builtinFunctionReturnType
|
||||
import java.io.File
|
||||
import kotlin.io.path.Path
|
||||
@ -22,7 +22,7 @@ import kotlin.io.path.isRegularFile
|
||||
/**
|
||||
* Convert 'old' compiler-AST into the 'new' simplified AST with baked types.
|
||||
*/
|
||||
class IntermediateAstMaker(val program: Program) {
|
||||
class IntermediateAstMaker(private val program: Program, private val options: CompilationOptions) {
|
||||
fun transform(): PtProgram {
|
||||
val ptProgram = PtProgram(
|
||||
program.name,
|
||||
@ -90,6 +90,54 @@ class IntermediateAstMaker(val program: Program) {
|
||||
}
|
||||
|
||||
private fun transform(srcAssign: Assignment): PtNode {
|
||||
if(srcAssign.isAugmentable) {
|
||||
val srcExpr = srcAssign.value
|
||||
val (operator: String, augmentedValue: Expression?) = when(srcExpr) {
|
||||
is BinaryExpression -> {
|
||||
if(srcExpr.operator=="==" || srcExpr.operator=="%") {
|
||||
// no special code possible for 'in-place comparison and result as boolean' or 'remainder'
|
||||
Pair("", null)
|
||||
}
|
||||
else if(srcExpr.left isSameAs srcAssign.target) {
|
||||
Pair(srcExpr.operator+'=', srcExpr.right)
|
||||
} else if(srcExpr.right isSameAs srcAssign.target) {
|
||||
Pair(srcExpr.operator+'=', srcExpr.left)
|
||||
} else {
|
||||
// either left or right is same as target, other combinations are not supported here
|
||||
Pair("", null)
|
||||
}
|
||||
}
|
||||
is PrefixExpression -> {
|
||||
require(srcExpr.expression isSameAs srcAssign.target)
|
||||
Pair(srcExpr.operator, srcExpr.expression)
|
||||
}
|
||||
is TypecastExpression -> {
|
||||
// At this time, there are no special optimized instructions to do an in-place type conversion.
|
||||
// so we simply revert to a regular type converting assignment.
|
||||
// Also, an in-place type cast is very uncommon so probably not worth optimizing anyway.
|
||||
Pair("", null)
|
||||
// the following is what *could* be used here if such instructions *were* available:
|
||||
// if(srcExpr.expression isSameAs srcAssign.target)
|
||||
// Pair("cast", srcExpr.expression)
|
||||
// else {
|
||||
// val subTypeCast = srcExpr.expression as? TypecastExpression
|
||||
// val targetDt = srcAssign.target.inferType(program).getOrElse { DataType.UNDEFINED }
|
||||
// if (subTypeCast!=null && srcExpr.type==targetDt && subTypeCast.expression isSameAs srcAssign.target) {
|
||||
// Pair("cast", subTypeCast)
|
||||
// } else
|
||||
// Pair("", null)
|
||||
// }
|
||||
}
|
||||
else -> Pair("", null)
|
||||
}
|
||||
if(augmentedValue!=null) {
|
||||
val assign = PtAugmentedAssign(operator, srcAssign.position)
|
||||
assign.add(transform(srcAssign.target))
|
||||
assign.add(transformExpression(augmentedValue))
|
||||
return assign
|
||||
}
|
||||
}
|
||||
|
||||
val assign = PtAssignment(srcAssign.position)
|
||||
assign.add(transform(srcAssign.target))
|
||||
assign.add(transformExpression(srcAssign.value))
|
||||
@ -109,19 +157,9 @@ class IntermediateAstMaker(val program: Program) {
|
||||
return target
|
||||
}
|
||||
|
||||
private fun targetOf(identifier: IdentifierReference): Pair<List<String>, DataType> {
|
||||
val target=identifier.targetStatement(program)!! as INamedStatement
|
||||
val targetname = if(target.name in program.builtinFunctions.names)
|
||||
listOf("<builtin>", target.name)
|
||||
else
|
||||
target.scopedName
|
||||
val type = identifier.inferType(program).getOr(DataType.UNDEFINED)
|
||||
return Pair(targetname, type)
|
||||
}
|
||||
|
||||
private fun transform(identifier: IdentifierReference): PtIdentifier {
|
||||
val (target, type) = targetOf(identifier)
|
||||
return PtIdentifier(identifier.nameInSource, target, type, identifier.position)
|
||||
val (target, type) = identifier.targetNameAndType(program)
|
||||
return PtIdentifier(target, type, identifier.position)
|
||||
}
|
||||
|
||||
private fun transform(srcBlock: Block): PtBlock {
|
||||
@ -139,17 +177,18 @@ class IntermediateAstMaker(val program: Program) {
|
||||
}
|
||||
}
|
||||
val (vardecls, statements) = srcBlock.statements.partition { it is VarDecl }
|
||||
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, srcBlock.position)
|
||||
if(vardecls.isNotEmpty()) block.add(makeScopeVarsDecls(vardecls, srcBlock.position))
|
||||
val src = srcBlock.definingModule.source
|
||||
val block = PtBlock(srcBlock.name, srcBlock.address, srcBlock.isInLibrary, forceOutput, alignment, src, srcBlock.position)
|
||||
makeScopeVarsDecls(vardecls).forEach { block.add(it) }
|
||||
for (stmt in statements)
|
||||
block.add(transformStatement(stmt))
|
||||
return block
|
||||
}
|
||||
|
||||
private fun makeScopeVarsDecls(vardecls: List<Statement>, position: Position): PtNode {
|
||||
val decls = PtScopeVarsDecls(position)
|
||||
private fun makeScopeVarsDecls(vardecls: Iterable<Statement>): Iterable<PtNamedNode> {
|
||||
val decls = mutableListOf<PtNamedNode>()
|
||||
vardecls.forEach {
|
||||
decls.add(transformStatement(it as VarDecl))
|
||||
decls.add(transformStatement(it as VarDecl) as PtNamedNode)
|
||||
}
|
||||
return decls
|
||||
}
|
||||
@ -218,7 +257,7 @@ class IntermediateAstMaker(val program: Program) {
|
||||
}
|
||||
|
||||
private fun transform(srcCall: FunctionCallStatement): PtFunctionCall {
|
||||
val (target, type) = targetOf(srcCall.target)
|
||||
val (target, type) = srcCall.target.targetNameAndType(program)
|
||||
val call = PtFunctionCall(target,true, type, srcCall.position)
|
||||
for (arg in srcCall.args)
|
||||
call.add(transformExpression(arg))
|
||||
@ -226,7 +265,7 @@ class IntermediateAstMaker(val program: Program) {
|
||||
}
|
||||
|
||||
private fun transform(srcCall: FunctionCallExpression): PtFunctionCall {
|
||||
val (target, _) = targetOf(srcCall.target)
|
||||
val (target, _) = srcCall.target.targetNameAndType(program)
|
||||
val type = srcCall.inferType(program).getOrElse {
|
||||
throw FatalAstException("unknown dt $srcCall")
|
||||
}
|
||||
@ -294,18 +333,15 @@ class IntermediateAstMaker(val program: Program) {
|
||||
}
|
||||
|
||||
private fun transformAsmSub(srcSub: Subroutine): PtAsmSub {
|
||||
val params = srcSub.parameters
|
||||
.map { PtSubroutineParameter(it.name, it.type, it.position) }
|
||||
.zip(srcSub.asmParameterRegisters)
|
||||
val params = srcSub.asmParameterRegisters.zip(srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) })
|
||||
val sub = PtAsmSub(srcSub.name,
|
||||
srcSub.asmAddress,
|
||||
srcSub.asmClobbers,
|
||||
params,
|
||||
srcSub.returntypes,
|
||||
srcSub.asmReturnvaluesRegisters,
|
||||
srcSub.asmReturnvaluesRegisters.zip(srcSub.returntypes),
|
||||
srcSub.inline,
|
||||
srcSub.position)
|
||||
sub.parameters.forEach { it.first.parent=sub }
|
||||
sub.parameters.forEach { it.second.parent=sub }
|
||||
|
||||
if(srcSub.asmAddress==null) {
|
||||
var combinedTrueAsm = ""
|
||||
@ -338,14 +374,14 @@ class IntermediateAstMaker(val program: Program) {
|
||||
var returntype = srcSub.returntypes.singleOrNull()
|
||||
if(returntype==DataType.STR)
|
||||
returntype=DataType.UWORD // if a sub returns 'str', replace with uword. Intermediate AST and I.R. don't contain 'str' datatype anymore.
|
||||
|
||||
// do not bother about the 'inline' hint of the source subroutine.
|
||||
val sub = PtSub(srcSub.name,
|
||||
srcSub.parameters.map { PtSubroutineParameter(it.name, it.type, it.position) },
|
||||
returntype,
|
||||
srcSub.inline,
|
||||
srcSub.position)
|
||||
sub.parameters.forEach { it.parent=sub }
|
||||
|
||||
if(vardecls.isNotEmpty()) sub.add(makeScopeVarsDecls(vardecls, sub.position))
|
||||
makeScopeVarsDecls(vardecls).forEach { sub.add(it) }
|
||||
for (statement in statements)
|
||||
sub.add(transformStatement(statement))
|
||||
|
||||
@ -356,10 +392,10 @@ class IntermediateAstMaker(val program: Program) {
|
||||
return when(srcVar.type) {
|
||||
VarDeclType.VAR -> {
|
||||
val value = if(srcVar.value!=null) transformExpression(srcVar.value!!) else null
|
||||
PtVariable(srcVar.name, srcVar.datatype, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
||||
PtVariable(srcVar.name, srcVar.datatype, srcVar.zeropage, value, srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
||||
}
|
||||
VarDeclType.CONST -> PtConstant(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number, srcVar.position)
|
||||
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.position)
|
||||
VarDeclType.MEMORY -> PtMemMapped(srcVar.name, srcVar.datatype, (srcVar.value as NumericLiteral).number.toUInt(), srcVar.arraysize?.constIndex()?.toUInt(), srcVar.position)
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,8 +431,8 @@ class IntermediateAstMaker(val program: Program) {
|
||||
}
|
||||
|
||||
private fun transform(srcArr: ArrayIndexedExpression): PtArrayIndexer {
|
||||
val type = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
|
||||
val array = PtArrayIndexer(type, srcArr.position)
|
||||
val arrayVarType = srcArr.inferType(program).getOrElse { throw FatalAstException("unknown dt") }
|
||||
val array = PtArrayIndexer(arrayVarType, srcArr.position)
|
||||
array.add(transform(srcArr.arrayvar))
|
||||
array.add(transformExpression(srcArr.indexer.indexExpr))
|
||||
return array
|
||||
@ -474,6 +510,7 @@ class IntermediateAstMaker(val program: Program) {
|
||||
private fun transform(srcCast: TypecastExpression): PtTypeCast {
|
||||
val cast = PtTypeCast(srcCast.type, srcCast.position)
|
||||
cast.add(transformExpression(srcCast.expression))
|
||||
require(cast.type!=cast.value.type)
|
||||
return cast
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,13 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(expr.operator=="^" && expr.left.inferType(program) istype DataType.BOOL && expr.right.constValue(program)?.number == 1.0) {
|
||||
// boolean ^ 1 --> not boolean
|
||||
val notExpr = PrefixExpression("not", expr.left, expr.position)
|
||||
return listOf(IAstModification.ReplaceNode(expr, notExpr, parent))
|
||||
}
|
||||
|
||||
return noModifications
|
||||
}
|
||||
|
||||
@ -146,17 +153,6 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
|
||||
if(compTarget.name == VMTarget.NAME) // don't apply this optimization for Vm target
|
||||
return CondExprSimplificationResult(null, null, null, null)
|
||||
|
||||
var leftAssignment: Assignment? = null
|
||||
var leftOperandReplacement: Expression? = null
|
||||
var rightAssignment: Assignment? = null
|
||||
var rightOperandReplacement: Expression? = null
|
||||
|
||||
val separateLeftExpr = !expr.left.isSimple
|
||||
&& expr.left !is IFunctionCall
|
||||
&& expr.left !is ContainmentCheck
|
||||
val separateRightExpr = !expr.right.isSimple
|
||||
&& expr.right !is IFunctionCall
|
||||
&& expr.right !is ContainmentCheck
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
|
||||
@ -165,13 +161,24 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
|
||||
return CondExprSimplificationResult(null, null, null, null)
|
||||
}
|
||||
|
||||
var leftAssignment: Assignment? = null
|
||||
var leftOperandReplacement: Expression? = null
|
||||
var rightAssignment: Assignment? = null
|
||||
var rightOperandReplacement: Expression? = null
|
||||
val separateLeftExpr = !expr.left.isSimple
|
||||
&& expr.left !is IFunctionCall
|
||||
&& expr.left !is ContainmentCheck
|
||||
val separateRightExpr = !expr.right.isSimple
|
||||
&& expr.right !is IFunctionCall
|
||||
&& expr.right !is ContainmentCheck
|
||||
|
||||
if(separateLeftExpr) {
|
||||
val name = getTempRegisterName(leftDt)
|
||||
leftOperandReplacement = IdentifierReference(name, expr.position)
|
||||
leftAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
|
||||
expr.left.copy(),
|
||||
AssignmentOrigin.BEFOREASMGEN, expr.position
|
||||
AssignmentOrigin.ASMGEN, expr.position
|
||||
)
|
||||
}
|
||||
if(separateRightExpr) {
|
||||
@ -180,7 +187,7 @@ internal class NotExpressionAndIfComparisonExprChanger(val program: Program, val
|
||||
rightAssignment = Assignment(
|
||||
AssignTarget(IdentifierReference(tempVarName, expr.position), null, null, expr.position),
|
||||
expr.right.copy(),
|
||||
AssignmentOrigin.BEFOREASMGEN, expr.position
|
||||
AssignmentOrigin.ASMGEN, expr.position
|
||||
)
|
||||
}
|
||||
return CondExprSimplificationResult(
|
||||
|
@ -127,15 +127,6 @@ internal class StatementReorderer(val program: Program,
|
||||
}
|
||||
|
||||
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
|
||||
if(subroutine.name=="start" && parent is Block) {
|
||||
if(parent.statements.asSequence().filterIsInstance<Subroutine>().first().name!="start") {
|
||||
return listOf(
|
||||
IAstModification.Remove(subroutine, parent),
|
||||
IAstModification.InsertFirst(subroutine, parent)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val modifications = mutableListOf<IAstModification>()
|
||||
|
||||
val subs = subroutine.statements.filterIsInstance<Subroutine>()
|
||||
@ -162,7 +153,7 @@ internal class StatementReorderer(val program: Program,
|
||||
subroutine.statements
|
||||
.asSequence()
|
||||
.filterIsInstance<VarDecl>()
|
||||
.filter { it.subroutineParameter!=null && it.name in stringParamsByNames }
|
||||
.filter { it.origin==VarDeclOrigin.SUBROUTINEPARAM && it.name in stringParamsByNames }
|
||||
.map {
|
||||
val newvar = VarDecl(it.type, it.origin, DataType.UWORD,
|
||||
it.zeropage,
|
||||
@ -171,7 +162,6 @@ internal class StatementReorderer(val program: Program,
|
||||
null,
|
||||
false,
|
||||
it.sharedWithAsm,
|
||||
stringParamsByNames.getValue(it.name),
|
||||
it.position
|
||||
)
|
||||
IAstModification.ReplaceNode(it, newvar, subroutine)
|
||||
|
@ -1,131 +0,0 @@
|
||||
package prog8.compiler.astprocessing
|
||||
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.*
|
||||
import prog8.code.core.ArrayDatatypes
|
||||
import prog8.code.core.CompilationOptions
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.Position
|
||||
import java.util.*
|
||||
|
||||
internal class SymbolTableMaker: IAstVisitor {
|
||||
|
||||
private val st = SymbolTable()
|
||||
private val scopestack = Stack<StNode>()
|
||||
private var dontReinitGlobals = false
|
||||
|
||||
fun makeFrom(program: Program, options: CompilationOptions): SymbolTable {
|
||||
scopestack.clear()
|
||||
st.children.clear()
|
||||
dontReinitGlobals = options.dontReinitGlobals
|
||||
this.visit(program)
|
||||
program.builtinFunctions.names.forEach {
|
||||
val node = StNode(it, StNodeType.BUILTINFUNC, Position.DUMMY)
|
||||
st.add(node)
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
override fun visit(block: Block) {
|
||||
val node = StNode(block.name, StNodeType.BLOCK, block.position)
|
||||
st.add(node)
|
||||
scopestack.push(node)
|
||||
super.visit(block)
|
||||
scopestack.pop()
|
||||
// st.origAstLinks[block] = node
|
||||
}
|
||||
|
||||
override fun visit(subroutine: Subroutine) {
|
||||
if(subroutine.asmAddress!=null) {
|
||||
val parameters = subroutine.parameters.zip(subroutine.asmParameterRegisters).map { StRomSubParameter(it.second, it.first.type) }
|
||||
val node = StRomSub(subroutine.name, subroutine.asmAddress!!, parameters, subroutine.asmReturnvaluesRegisters, subroutine.position)
|
||||
scopestack.peek().add(node)
|
||||
// st.origAstLinks[subroutine] = node
|
||||
} else {
|
||||
val parameters = subroutine.parameters.map { StSubroutineParameter(it.name, it.type) }
|
||||
val returnType = if(subroutine.returntypes.isEmpty()) null else subroutine.returntypes.first()
|
||||
val node = StSub(subroutine.name, parameters, returnType, subroutine.position)
|
||||
scopestack.peek().add(node)
|
||||
scopestack.push(node)
|
||||
super.visit(subroutine)
|
||||
scopestack.pop()
|
||||
// st.origAstLinks[subroutine] = node
|
||||
}
|
||||
}
|
||||
|
||||
override fun visit(decl: VarDecl) {
|
||||
val node =
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR -> {
|
||||
var initialNumeric = (decl.value as? NumericLiteral)?.number
|
||||
if(initialNumeric==0.0)
|
||||
initialNumeric=null // variable will go into BSS and this will be set to 0
|
||||
val initialStringLit = decl.value as? StringLiteral
|
||||
val initialString = if(initialStringLit==null) null else Pair(initialStringLit.value, initialStringLit.encoding)
|
||||
val initialArrayLit = decl.value as? ArrayLiteral
|
||||
val initialArray = makeInitialArray(initialArrayLit)
|
||||
if(decl.isArray && decl.datatype !in ArrayDatatypes)
|
||||
throw FatalAstException("array vardecl has mismatched dt ${decl.datatype}")
|
||||
val numElements =
|
||||
if(decl.isArray)
|
||||
decl.arraysize!!.constIndex()
|
||||
else if(initialStringLit!=null)
|
||||
initialStringLit.value.length+1 // include the terminating 0-byte
|
||||
else
|
||||
null
|
||||
val bss = if(decl.datatype==DataType.STR)
|
||||
false
|
||||
else if(decl.isArray)
|
||||
initialArray.isNullOrEmpty()
|
||||
else
|
||||
initialNumeric == null
|
||||
StStaticVariable(decl.name, decl.datatype, bss, initialNumeric, initialString, initialArray, numElements, decl.zeropage, decl.position)
|
||||
}
|
||||
VarDeclType.CONST -> StConstant(decl.name, decl.datatype, (decl.value as NumericLiteral).number, decl.position)
|
||||
VarDeclType.MEMORY -> {
|
||||
val numElements =
|
||||
if(decl.datatype in ArrayDatatypes)
|
||||
decl.arraysize!!.constIndex()
|
||||
else null
|
||||
StMemVar(decl.name, decl.datatype, (decl.value as NumericLiteral).number.toUInt(), numElements, decl.position)
|
||||
}
|
||||
}
|
||||
scopestack.peek().add(node)
|
||||
// st.origAstLinks[decl] = node
|
||||
}
|
||||
|
||||
private fun makeInitialArray(arrayLit: ArrayLiteral?): StArray? {
|
||||
if(arrayLit==null)
|
||||
return null
|
||||
return arrayLit.value.map {
|
||||
when(it){
|
||||
is AddressOf -> StArrayElement(null, it.identifier.nameInSource)
|
||||
is IdentifierReference -> StArrayElement(null, it.nameInSource)
|
||||
is NumericLiteral -> StArrayElement(it.number, null)
|
||||
else -> throw FatalAstException("weird element dt in array literal")
|
||||
}
|
||||
}.toList()
|
||||
}
|
||||
|
||||
override fun visit(label: Label) {
|
||||
val node = StNode(label.name, StNodeType.LABEL, label.position)
|
||||
scopestack.peek().add(node)
|
||||
// st.origAstLinks[label] = node
|
||||
}
|
||||
|
||||
override fun visit(bfc: BuiltinFunctionCall) {
|
||||
if(bfc.name=="memory") {
|
||||
// memory slab allocations are a builtin functioncall in the program, but end up named as well in the symboltable
|
||||
val name = (bfc.args[0] as StringLiteral).value
|
||||
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
|
||||
val size = (bfc.args[1] as NumericLiteral).number.toUInt()
|
||||
val align = (bfc.args[2] as NumericLiteral).number.toUInt()
|
||||
st.add(StMemorySlab("prog8_memoryslab_$name", size, align, bfc.position))
|
||||
}
|
||||
super.visit(bfc)
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@ import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
|
||||
|
||||
class TypecastsAdder(val program: Program, val options: CompilationOptions, val errors: IErrorReporter) : AstWalker() {
|
||||
|
@ -6,10 +6,7 @@ import prog8.ast.Node
|
||||
import prog8.ast.Program
|
||||
import prog8.ast.base.FatalAstException
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.AnonymousScope
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.ast.statements.ConditionalBranch
|
||||
import prog8.ast.statements.IfElse
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.AstWalker
|
||||
import prog8.ast.walk.IAstModification
|
||||
import prog8.code.core.*
|
||||
@ -43,7 +40,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
}
|
||||
|
||||
val sourceDt = typecast.expression.inferType(program)
|
||||
if(sourceDt istype typecast.type)
|
||||
if(sourceDt istype typecast.type || (sourceDt istype DataType.BOOL && typecast.type==DataType.UBYTE))
|
||||
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
|
||||
|
||||
if(parent is Assignment) {
|
||||
@ -65,10 +62,23 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
}
|
||||
|
||||
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
|
||||
val nextAssign = assignment.nextSibling() as? Assignment
|
||||
if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) {
|
||||
if(!nextAssign.isAugmentable && nextAssign.value isSameAs assignment.value && assignment.value !is IFunctionCall) // don't remove function calls even when they're duplicates
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
if(assignment.target isSameAs assignment.value) {
|
||||
// remove assignment to self
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
}
|
||||
|
||||
// remove duplicated assignments, but not if it's a memory mapped IO register
|
||||
val isIO = try {
|
||||
assignment.target.isIOAddress(options.compTarget.machine)
|
||||
} catch (_: FatalAstException) {
|
||||
false
|
||||
}
|
||||
if(!isIO) {
|
||||
val nextAssign = assignment.nextSibling() as? Assignment
|
||||
if (nextAssign != null && nextAssign.target.isSameAs(assignment.target, program)) {
|
||||
if (!nextAssign.isAugmentable && nextAssign.value isSameAs assignment.value && assignment.value !is IFunctionCall) // don't remove function calls even when they're duplicates
|
||||
return listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
|
||||
}
|
||||
}
|
||||
|
||||
return noModifications
|
||||
@ -175,6 +185,19 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
return noModifications
|
||||
}
|
||||
|
||||
fun checkArray(variable: VarDecl): Iterable<IAstModification> {
|
||||
return if(variable.value==null) {
|
||||
val arraySpec = variable.arraysize!!
|
||||
val size = arraySpec.indexExpr.constValue(program)?.number?.toInt() ?: throw FatalAstException("no array size")
|
||||
return if(size==0)
|
||||
replaceWithFalse()
|
||||
else
|
||||
noModifications
|
||||
}
|
||||
else
|
||||
checkArray((variable.value as ArrayLiteral).value)
|
||||
}
|
||||
|
||||
fun checkString(stringVal: StringLiteral): Iterable<IAstModification> {
|
||||
if(stringVal.value.isEmpty())
|
||||
return replaceWithFalse()
|
||||
@ -198,8 +221,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter,
|
||||
return checkString(stringVal)
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
val array = (variable.value as ArrayLiteral).value
|
||||
return checkArray(array)
|
||||
return checkArray(variable)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
|
@ -5,11 +5,7 @@ import prog8.ast.Program
|
||||
import prog8.ast.expressions.*
|
||||
import prog8.ast.statements.*
|
||||
import prog8.ast.walk.IAstVisitor
|
||||
import prog8.code.core.ByteDatatypes
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.IErrorReporter
|
||||
import prog8.code.core.Position
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
import prog8.code.core.*
|
||||
|
||||
internal class VerifyFunctionArgTypes(val program: Program, val errors: IErrorReporter) : IAstVisitor {
|
||||
|
||||
|
@ -43,7 +43,7 @@ class TestModuleImporter: FunSpec({
|
||||
val importer = makeImporter(null, dirRel.invariantSeparatorsPathString)
|
||||
val srcPathRel = assumeNotExists(dirRel, "i_do_not_exist")
|
||||
val srcPathAbs = srcPathRel.absolute()
|
||||
val error1 = importer.importModule(srcPathRel).getErrorOrElse { fail("should have import error") }
|
||||
val error1 = importer.importMainModule(srcPathRel).getErrorOrElse { fail("should have import error") }
|
||||
withClue(".file should be normalized") {
|
||||
"${error1.file}" shouldBe "${error1.file.normalize()}"
|
||||
}
|
||||
@ -51,7 +51,7 @@ class TestModuleImporter: FunSpec({
|
||||
error1.file.absolutePath shouldBe "${srcPathAbs.normalize()}"
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
val error2 = importer.importModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
|
||||
val error2 = importer.importMainModule(srcPathAbs).getErrorOrElse { fail("should have import error") }
|
||||
withClue(".file should be normalized") {
|
||||
"${error2.file}" shouldBe "${error2.file.normalize()}"
|
||||
}
|
||||
@ -67,7 +67,7 @@ class TestModuleImporter: FunSpec({
|
||||
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
|
||||
val importer = makeImporter(null, searchIn)
|
||||
|
||||
shouldThrow<FileSystemException> { importer.importModule(srcPathRel) }
|
||||
shouldThrow<FileSystemException> { importer.importMainModule(srcPathRel) }
|
||||
.let {
|
||||
withClue(".file should be normalized") {
|
||||
"${it.file}" shouldBe "${it.file.normalize()}"
|
||||
@ -78,7 +78,7 @@ class TestModuleImporter: FunSpec({
|
||||
}
|
||||
program.modules.size shouldBe 1
|
||||
|
||||
shouldThrow<FileSystemException> { importer.importModule(srcPathAbs) }
|
||||
shouldThrow<FileSystemException> { importer.importMainModule(srcPathAbs) }
|
||||
.let {
|
||||
withClue(".file should be normalized") {
|
||||
"${it.file}" shouldBe "${it.file.normalize()}"
|
||||
@ -101,7 +101,7 @@ class TestModuleImporter: FunSpec({
|
||||
val fileName = "ast_simple_main.p8"
|
||||
val path = assumeReadableFile(searchIn[0], fileName)
|
||||
|
||||
val module = importer.importModule(path.absolute()).getOrElse { throw it }
|
||||
val module = importer.importMainModule(path.absolute()).getOrElse { throw it }
|
||||
program.modules.size shouldBe 2
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
@ -118,7 +118,7 @@ class TestModuleImporter: FunSpec({
|
||||
path.isAbsolute shouldBe false
|
||||
}
|
||||
|
||||
val module = importer.importModule(path).getOrElse { throw it }
|
||||
val module = importer.importMainModule(path).getOrElse { throw it }
|
||||
program.modules.size shouldBe 2
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
@ -133,7 +133,7 @@ class TestModuleImporter: FunSpec({
|
||||
val path = Path(".", fileName)
|
||||
assumeReadableFile(searchIn, path)
|
||||
|
||||
val module = importer.importModule(path).getOrElse { throw it }
|
||||
val module = importer.importMainModule(path).getOrElse { throw it }
|
||||
program.modules.size shouldBe 2
|
||||
module shouldBeIn program.modules
|
||||
module.program shouldBe program
|
||||
@ -145,7 +145,7 @@ class TestModuleImporter: FunSpec({
|
||||
val importer = makeImporter(null, searchIn.invariantSeparatorsPathString)
|
||||
val srcPath = assumeReadableFile(fixturesDir, "ast_file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(srcPath) }
|
||||
val act = { importer.importMainModule(srcPath) }
|
||||
|
||||
repeat(2) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError> { act() }.let {
|
||||
@ -165,7 +165,7 @@ class TestModuleImporter: FunSpec({
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importModule(importing) }
|
||||
val act = { importer.importMainModule(importing) }
|
||||
|
||||
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError> { act() }.let {
|
||||
@ -201,14 +201,14 @@ class TestModuleImporter: FunSpec({
|
||||
val filenameWithExt = assumeNotExists(fixturesDir, "i_do_not_exist.p8").name
|
||||
|
||||
repeat(2) { n ->
|
||||
val result = importer.importLibraryModule(filenameNoExt)
|
||||
val result = importer.importImplicitLibraryModule(filenameNoExt)
|
||||
withClue(count[n] + " call / NO .p8 extension") { result shouldBe null }
|
||||
withClue(count[n] + " call / NO .p8 extension") { errors.noErrors() shouldBe false }
|
||||
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist"
|
||||
errors.report()
|
||||
program.modules.size shouldBe 1
|
||||
|
||||
val result2 = importer.importLibraryModule(filenameWithExt)
|
||||
val result2 = importer.importImplicitLibraryModule(filenameWithExt)
|
||||
withClue(count[n] + " call / with .p8 extension") { result2 shouldBe null }
|
||||
withClue(count[n] + " call / with .p8 extension") { importer.errors.noErrors() shouldBe false }
|
||||
errors.errors.single() shouldContain "0:0: no module found with name i_do_not_exist.p8"
|
||||
@ -228,7 +228,7 @@ class TestModuleImporter: FunSpec({
|
||||
repeat(2) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError>()
|
||||
{
|
||||
importer.importLibraryModule(srcPath.nameWithoutExtension) }.let {
|
||||
importer.importImplicitLibraryModule(srcPath.nameWithoutExtension) }.let {
|
||||
it.position.file shouldBe SourceCode.relative(srcPath).toString()
|
||||
withClue("line; should be 1-based") { it.position.line shouldBe 2 }
|
||||
withClue("startCol; should be 0-based") { it.position.startCol shouldBe 6 }
|
||||
@ -246,7 +246,7 @@ class TestModuleImporter: FunSpec({
|
||||
val importing = assumeReadableFile(fixturesDir, "import_file_with_syntax_error.p8")
|
||||
val imported = assumeReadableFile(fixturesDir, "file_with_syntax_error.p8")
|
||||
|
||||
val act = { importer.importLibraryModule(importing.nameWithoutExtension) }
|
||||
val act = { importer.importImplicitLibraryModule(importing.nameWithoutExtension) }
|
||||
|
||||
repeat(repetitions) { n -> withClue(count[n] + " call") {
|
||||
shouldThrow<ParseError> {
|
||||
|
@ -3,31 +3,19 @@ package prog8tests
|
||||
import io.kotest.core.spec.style.FunSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import io.kotest.matchers.shouldNotBe
|
||||
import prog8.ast.expressions.NumericLiteral
|
||||
import prog8.ast.statements.Assignment
|
||||
import prog8.code.core.BuiltinFunctions
|
||||
import prog8.code.core.DataType
|
||||
import prog8.code.core.NumericDatatypesNoBool
|
||||
import prog8.code.core.RegisterOrPair
|
||||
import prog8.code.target.Cx16Target
|
||||
import prog8.compiler.BuiltinFunctions
|
||||
import prog8tests.helpers.compileText
|
||||
|
||||
class TestBuiltinFunctions: FunSpec({
|
||||
|
||||
test("push pop") {
|
||||
val src="""
|
||||
main {
|
||||
sub start () {
|
||||
pushw(cx16.r0)
|
||||
push(cx16.r1L)
|
||||
pop(cx16.r1L)
|
||||
popw(cx16.r0)
|
||||
}
|
||||
}"""
|
||||
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("pure func with fixed type") {
|
||||
val func = BuiltinFunctions.getValue("sgn")
|
||||
func.name shouldBe "sgn"
|
||||
func.parameters.size shouldBe 1
|
||||
func.parameters[0].name shouldBe "value"
|
||||
func.parameters[0].possibleDatatypes shouldBe NumericDatatypesNoBool
|
||||
@ -46,7 +34,6 @@ class TestBuiltinFunctions: FunSpec({
|
||||
|
||||
test("not-pure func with varying result value type") {
|
||||
val func = BuiltinFunctions.getValue("cmp")
|
||||
func.name shouldBe "cmp"
|
||||
func.parameters.size shouldBe 2
|
||||
func.pure shouldBe false
|
||||
func.returnType shouldBe null
|
||||
@ -60,7 +47,6 @@ class TestBuiltinFunctions: FunSpec({
|
||||
|
||||
test("func without return type") {
|
||||
val func = BuiltinFunctions.getValue("poke")
|
||||
func.name shouldBe "poke"
|
||||
func.parameters.size shouldBe 2
|
||||
func.parameters[0].name shouldBe "address"
|
||||
func.parameters[0].possibleDatatypes shouldBe arrayOf(DataType.UWORD)
|
||||
@ -81,5 +67,44 @@ class TestBuiltinFunctions: FunSpec({
|
||||
conv.returns.floatFac1 shouldBe false
|
||||
conv.returns.reg shouldBe null
|
||||
}
|
||||
|
||||
test("push pop") {
|
||||
val src="""
|
||||
main {
|
||||
sub start () {
|
||||
pushw(cx16.r0)
|
||||
push(cx16.r1L)
|
||||
pop(cx16.r1L)
|
||||
popw(cx16.r0)
|
||||
}
|
||||
}"""
|
||||
compileText(Cx16Target(), false, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
|
||||
test("certain builtin functions should be compile time evaluated") {
|
||||
val src="""
|
||||
main {
|
||||
sub start() {
|
||||
|
||||
uword[] array = [1,2,3]
|
||||
str name = "hello"
|
||||
cx16.r0L = len(array)
|
||||
cx16.r0L = len(name)
|
||||
cx16.r0L = sizeof(array)
|
||||
cx16.r0 = mkword(200,100)
|
||||
}
|
||||
}"""
|
||||
val result = compileText(Cx16Target(), false, src, writeAssembly = false)
|
||||
val statements = result!!.compilerAst.entrypoint.statements
|
||||
statements.size shouldBe 6
|
||||
val a1 = statements[2] as Assignment
|
||||
val a2 = statements[3] as Assignment
|
||||
val a3 = statements[4] as Assignment
|
||||
val a4 = statements[5] as Assignment
|
||||
(a1.value as NumericLiteral).number shouldBe 3.0
|
||||
(a2.value as NumericLiteral).number shouldBe 5.0
|
||||
(a3.value as NumericLiteral).number shouldBe 6.0
|
||||
(a4.value as NumericLiteral).number shouldBe 200*256+100
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -27,11 +27,11 @@ class TestCallgraph: FunSpec({
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, sourcecode)!!
|
||||
val graph = CallGraph(result.program)
|
||||
val graph = CallGraph(result.compilerAst)
|
||||
|
||||
graph.imports.size shouldBe 1
|
||||
graph.importedBy.size shouldBe 1
|
||||
val toplevelModule = result.program.toplevelModule
|
||||
val toplevelModule = result.compilerAst.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
importedModule.name shouldBe "string"
|
||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
||||
@ -46,7 +46,7 @@ class TestCallgraph: FunSpec({
|
||||
graph.calls shouldNotContainKey sub
|
||||
graph.calledBy shouldNotContainKey sub
|
||||
|
||||
if(sub === result.program.entrypoint)
|
||||
if(sub === result.compilerAst.entrypoint)
|
||||
withClue("start() should always be marked as used to avoid having it removed") {
|
||||
graph.unused(sub) shouldBe false
|
||||
}
|
||||
@ -68,11 +68,11 @@ class TestCallgraph: FunSpec({
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, sourcecode)!!
|
||||
val graph = CallGraph(result.program)
|
||||
val graph = CallGraph(result.compilerAst)
|
||||
|
||||
graph.imports.size shouldBe 1
|
||||
graph.importedBy.size shouldBe 1
|
||||
val toplevelModule = result.program.toplevelModule
|
||||
val toplevelModule = result.compilerAst.toplevelModule
|
||||
val importedModule = graph.imports.getValue(toplevelModule).single()
|
||||
importedModule.name shouldBe "string"
|
||||
val importedBy = graph.importedBy.getValue(importedModule).single()
|
||||
@ -111,8 +111,8 @@ class TestCallgraph: FunSpec({
|
||||
}
|
||||
"""
|
||||
val result = compileText(C64Target(), false, sourcecode)!!
|
||||
val graph = CallGraph(result.program)
|
||||
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 9
|
||||
val graph = CallGraph(result.compilerAst)
|
||||
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 5
|
||||
val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") }
|
||||
empties.size shouldBe 3
|
||||
empties[0].position.line shouldBe 4
|
||||
|
@ -33,7 +33,7 @@ class TestCompilerOnCharLit: FunSpec({
|
||||
}
|
||||
""")!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
@ -57,7 +57,7 @@ class TestCompilerOnCharLit: FunSpec({
|
||||
}
|
||||
""")!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
@ -92,7 +92,7 @@ class TestCompilerOnCharLit: FunSpec({
|
||||
}
|
||||
""")!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
|
||||
|
||||
|
@ -27,12 +27,12 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
|
||||
filepath,
|
||||
optimize,
|
||||
optimizeFloatExpressions = true,
|
||||
dontReinitGlobals = false,
|
||||
writeAssembly = true,
|
||||
slowCodegenWarnings = false,
|
||||
quietAssembler = true,
|
||||
asmListfile = false,
|
||||
experimentalCodegen = false,
|
||||
varsHigh = false,
|
||||
compilationTarget = target.name,
|
||||
evalStackBaseAddress = null,
|
||||
symbolDefs = emptyMap(),
|
||||
@ -188,8 +188,8 @@ class TestCompilerOnExamplesVirtual: FunSpec({
|
||||
val (displayName, filepath) = prepareTestFiles(it, false, target)
|
||||
test(displayName) {
|
||||
val src = filepath.readText()
|
||||
compileText(target, false, src, writeAssembly = true) shouldNotBe null
|
||||
compileText(target, false, src, writeAssembly = true) shouldNotBe null
|
||||
compileText(target, true, src, writeAssembly = true) shouldNotBe null
|
||||
compileText(target, true, src, writeAssembly = true) shouldNotBe null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -30,7 +30,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
val platform = Cx16Target()
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val strLits = startSub.statements
|
||||
.filterIsInstance<FunctionCallStatement>()
|
||||
@ -52,7 +52,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
|
||||
val platform = Cx16Target()
|
||||
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val args = startSub.statements
|
||||
.filterIsInstance<FunctionCallStatement>()
|
||||
|
@ -40,7 +40,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
""")!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val decl = startSub
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
@ -72,7 +72,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
""")!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val decl = startSub
|
||||
.statements.filterIsInstance<VarDecl>()[0]
|
||||
@ -143,7 +143,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
""")!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val iterable = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
@ -177,7 +177,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
""")!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val rangeExpr = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
@ -203,7 +203,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
""")!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val rangeExpr = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
@ -247,7 +247,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
""")!!
|
||||
|
||||
val program = result.program
|
||||
val program = result.compilerAst
|
||||
val startSub = program.entrypoint
|
||||
val iterable = startSub
|
||||
.statements.filterIsInstance<ForLoop>()
|
||||
@ -279,7 +279,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
}
|
||||
""")!!
|
||||
val statements = result.program.entrypoint.statements
|
||||
val statements = result.compilerAst.entrypoint.statements
|
||||
val array = (statements[0] as VarDecl).value
|
||||
array shouldBe instanceOf<ArrayLiteral>()
|
||||
(array as ArrayLiteral).value.size shouldBe 26
|
||||
@ -301,7 +301,7 @@ class TestCompilerOnRanges: FunSpec({
|
||||
}
|
||||
}
|
||||
""")!!
|
||||
val statements = result.program.entrypoint.statements
|
||||
val statements = result.compilerAst.entrypoint.statements
|
||||
val forloop = (statements.dropLast(1).last() as ForLoop)
|
||||
forloop.iterable shouldBe instanceOf<RangeExpression>()
|
||||
(forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.UBYTE, -2.0, Position.DUMMY)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user