Compare commits

...

131 Commits
v7.6 ... v7.8

Author SHA1 Message Date
a3a6812608 version 7.8 2022-02-12 17:40:32 +01:00
2725c4ad4d slight tweaks to zp and allocator 2022-02-12 00:15:52 +01:00
c8cd6e9460 removed old @"screencodes" string encoding syntax (use sc:"hello" instead) 2022-02-11 22:07:14 +01:00
0cd27d6129 fix empty lines in subroutine ast printing 2022-02-11 21:44:38 +01:00
b47fc1c020 renames of some Ast node classes 2022-02-11 00:34:36 +01:00
de6ef7ef5e doc 2022-02-11 00:16:39 +01:00
f95fe8f1da note about removing VarDecls 2022-02-10 23:20:19 +01:00
bd0dee5db5 cleanup 2022-02-10 22:22:50 +01:00
c13b7fd883 report free/occupied Zeropage space at end of compilation 2022-02-10 21:59:44 +01:00
f7e74b3088 naming 2022-02-10 03:18:56 +01:00
343f01d5e1 re-enabled unused variable removal from library modules (+fixed some @shared vars in libraries) 2022-02-10 03:10:47 +01:00
08bacdd090 temp vars are now dynamically added to AST as needed 2022-02-10 02:52:47 +01:00
41b1c80492 label name from memory() no longer interned as string var 2022-02-10 00:45:20 +01:00
e5d7316e5d streamlining non-zpvars asmgen using new mechanism 2022-02-10 00:09:09 +01:00
b043c3a6da streamlining vars asmgen using new mechanism 2022-02-09 21:58:25 +01:00
98b2855b9c cleanups 2022-02-09 16:35:52 +01:00
f3c52c409f variable zp allocation now only done in the allocator 2022-02-08 23:44:21 +01:00
1307bdc612 more cleanups to the allocator 2022-02-08 22:46:49 +01:00
8c2e6971fc start using vars instead of callgraph (2) 2022-02-08 21:09:00 +01:00
1903990f30 start using vars instead of callgraph 2022-02-08 20:40:10 +01:00
7d67005709 more rewrite variable allocation 2022-02-08 20:40:10 +01:00
9acc2f92d1 start to rewrite variable allocation 2022-02-08 20:40:10 +01:00
72dfb0bda3 fix: undefined sys.memcopy when initializing array on cx16 2022-02-08 20:29:47 +01:00
1635612430 tiny tweak in asm optimizer 2022-02-08 02:19:50 +01:00
abda837d2f split program structure codegen out of AsmGen into separate class ProgramGen 2022-02-07 00:12:25 +01:00
101fb0b8aa some naming changes and cleanups 2022-02-06 23:14:44 +01:00
10de7dc1f9 fixed the concurrent modification issue on zeropage when running unit tests in parallel, by not having machine targets be static objects 2022-02-06 21:29:06 +01:00
d2309b8114 introducing IVariableAllocation (WIP) 2022-02-06 18:57:23 +01:00
6bdd81623f cleaning up AsmGen interface 2022-02-06 17:07:03 +01:00
f538c9f0c3 remove bogus double var decl check 2022-02-06 14:04:54 +01:00
8ae3bad6f7 fix rts in empty asmsub 2022-02-06 05:05:58 +01:00
77de99b383 rts-check for non-inlined subroutines + var init adjustment when noreinit, moved out of codegen 2022-02-06 04:03:03 +01:00
312949f336 added experimental codegen backend option 2022-02-05 21:42:03 +01:00
1ab635bd7e small tweak of parse messages 2022-02-05 14:02:24 +01:00
b35abd548c less noisy output about what module files are being imported. 2022-02-05 04:25:34 +01:00
30e1c3307c simplify SourceCode: just read the full text immediately. Also optimized imports. 2022-02-05 03:50:54 +01:00
08e052380a comments 2022-02-05 03:14:26 +01:00
0e824c35cc Merge pull request #73 from akumanatt/master
Codegen and runtime library optimizations
2022-02-05 02:21:53 +01:00
548374ac2d fix: do proper sign exension when multiplying signed word and byte vars 2022-02-05 01:52:13 +01:00
9ad79fefc9 Merge branch 'master' of https://github.com/irmen/prog8 2022-02-04 22:55:41 +07:00
49d37c016e Optimize strcmp_mem 2022-02-04 22:07:03 +07:00
7c70c79a84 Optimize in-place word subtraction and negation 2022-02-04 21:21:06 +07:00
6916b8bff7 remove redundant properties 2022-02-03 23:59:24 +01:00
73dfb5f443 Optimize sign extension to AY 2022-02-04 00:59:44 +07:00
69b9dfa468 fix invalid recursion warning for code referencing subroutine but not via a call 2022-02-01 23:09:52 +01:00
ab61b8ba0a doc ref 2022-02-01 21:47:53 +01:00
5c8c64242f callgraph: nameInAssemblyCode searches smarter (for unused()) 2022-02-01 00:33:05 +01:00
ddf96943f0 remove Nop ast node. 2022-01-31 22:36:10 +01:00
e773be2f58 remove no longer needed asmSymbol scoping prefixing, now asmSymbolName are identical to asmVarName 2022-01-31 01:47:22 +01:00
f965804e6d fix invalid optimization of returning a parameter variable in a subroutine 2022-01-28 16:44:42 +01:00
ec078eba72 optimize w=msb(w) => w>>=8, w=lsb(w) ==> w&=$00ff 2022-01-28 16:11:52 +01:00
1815cb1bc3 fixed bug in assembly optimizer removing too many instructions 2022-01-28 15:19:08 +01:00
7b3cd71085 fixed improper optimization of word<<8 and word>>8 2022-01-28 13:54:06 +01:00
06128b5d07 optimize word&=$ff00 and word&=$00ff 2022-01-28 13:40:28 +01:00
990c8e1f18 split out 6502 codegen module from various compilertargets module. 2022-01-28 00:32:09 +01:00
a170506356 simplify IdentifierReference equality check back to default (name+pos) 2022-01-27 23:32:55 +01:00
5ecf2a3357 enable more optimizations for typecasted assignments. Fixed missing codegen for assigning bytes to words in certain cases. 2022-01-27 18:05:25 +01:00
fa48746ba9 increase internal buffer for diskio.list_files to be able to list larger directories 2022-01-26 03:17:33 +01:00
e2b8c069d7 check for missing '&' in string + value expressions (can't just add a value to a string) 2022-01-24 23:30:40 +01:00
14407bd1aa fix memory() existing check typo 2022-01-24 23:21:31 +01:00
08db72903c for long containment checks use a subroutine instead of huge cmp-table 2022-01-24 22:40:22 +01:00
46f9fab140 library API change: string.find now returns index of character + carry bit status (instead of substring address) 2022-01-24 21:37:04 +01:00
b7d06f2c0a API change: added alignment parameter to memory() function 2022-01-24 18:58:57 +01:00
118196a0bf library API change: moved cx16.vload() to cx16diskio module 2022-01-24 18:31:18 +01:00
586ce1fc80 tweak return's use of intermediate variable 2022-01-24 01:10:04 +01:00
adb979df38 tweak comment 2022-01-23 22:34:05 +01:00
3401cb5b4a fixed compiler recursion crash when returning certain typecasted value 2022-01-23 19:13:20 +01:00
ebf1f12e97 inferred type for len() is now more precise 2022-01-23 17:24:39 +01:00
5766208207 fix compiler crash when initializing an array var with another array var 2022-01-23 14:23:34 +01:00
4bf4771f08 fix @requirezp in astToSource. Fix sometimes allocating zeropage variables in normal ram. 2022-01-23 13:42:52 +01:00
0e87db9eb7 fix invalid size copied when initializing arrays in Zeropage 2022-01-23 13:00:01 +01:00
1e053783f3 fix invalid size copied when assigning non-byte arrays 2022-01-23 02:42:36 +01:00
7afc96112b now correctly requires using & (address-of) when assigning the address of a label or subroutine, used to generate invalid code when it was omitted 2022-01-23 02:23:30 +01:00
7bb41a30ed fixed compiler crash when assigning number larger than 65535 2022-01-23 01:44:16 +01:00
3d1b0eb843 fixed compiler crash when using cx16.r0H as function call argument 2022-01-23 01:28:16 +01:00
5b9af0b5ae tweaks 2022-01-21 23:38:54 +01:00
9219ec539d allow "goto pointervar" for indirect jumps 2022-01-21 22:55:59 +01:00
c8bd57cd4d fixed signature of mouse_get(): it returns the buttonstatus in A. Added convenience cx16.mouse_pos() routine. 2022-01-21 22:06:17 +01:00
53bf8c09fd fix screencode encoding selection 2022-01-19 21:37:27 +01:00
651c383668 refactor encoder to be the same for all 3 machine targets now 2022-01-19 21:21:33 +01:00
9ed7587e3e document new string encoding syntax 2022-01-19 21:21:33 +01:00
674295e800 improve error reporting from string encoders 2022-01-19 21:21:33 +01:00
6b02f2eea0 implement iso encoding and new string encoding syntax, fixes #38 2022-01-19 21:21:32 +01:00
5237e55326 added txt.iso() to enable iso-charset on cx16 2022-01-18 21:35:29 +01:00
3b59592110 generalize string encoding flag into enum 2022-01-18 21:21:49 +01:00
72640ae058 no longer add nops around breakpoint for vice 2022-01-17 22:12:58 +01:00
d916027e75 labels no longer start with '_' fixes #62 2022-01-17 22:03:53 +01:00
8966d2aa06 comments and prepare new version 7.7 2022-01-16 23:03:00 +01:00
de7ea04f54 when zp option = dontuse, print error for any variable with @requirezp 2022-01-16 18:13:24 +01:00
bf71fabe0e fix codegen mistake for zp allocated loop vars 2022-01-16 18:09:09 +01:00
b3368acb33 todos to fix broken examples 2022-01-16 17:57:47 +01:00
87220c6697 docs for @requirezp 2022-01-16 17:20:36 +01:00
a3b5c2ad71 fix zp address output and adjust vars datastructure 2022-01-16 17:20:36 +01:00
fb4c1473c5 array and string initialization in zeropage 2022-01-16 17:20:36 +01:00
2bb2502d20 added @requirezp to syntax files 2022-01-16 17:20:36 +01:00
fe51698579 tweak how zp varnames are stored 2022-01-16 17:20:36 +01:00
0f0f40bff3 improved ForloopAsmGen loopvar zp allocation 2022-01-16 17:20:36 +01:00
a798fe72d3 cx16 reserved zp vars (virtual registers) 2022-01-16 17:20:36 +01:00
fba98d03a5 improve %zpreserved error messages 2022-01-16 17:20:36 +01:00
7dd2517f67 fix Zp allocation issues 2022-01-16 17:20:36 +01:00
641477d6f6 add @requirezp and allow str/array to be on zp (with warning) 2022-01-16 17:20:32 +01:00
8e56656c8d fix broken code generated for certain ==/!= expressions 2022-01-16 17:10:49 +01:00
564a6a1f62 fix invalid removal of Jump
that would generate wrong code if occurs at the end of a subroutine
2022-01-16 14:05:42 +01:00
69f0c80cd7 added pokemon() function 2022-01-15 19:04:04 +01:00
6fcb51cea2 add warning when encoded string contains 0-byte 2022-01-15 17:11:40 +01:00
c58b8a4973 fix ast to source: @shared wasn't printed
fix grammar: @shared and @zp can occur in any order now in vardecl
2022-01-13 02:29:55 +01:00
c8f4ab4f06 doc 2022-01-12 22:21:01 +01:00
e425c4cca8 optimizing pipe codegen 2022-01-11 23:17:35 +01:00
056ec986c2 use var initializer assignments in a clearer way 2022-01-11 00:34:44 +01:00
de3b2fb95b slightly optimized certain list iterations into sequences 2022-01-10 23:15:24 +01:00
789e39c719 slightly optimized assembly file handling 2022-01-10 22:48:20 +01:00
b29c3152db Assignment: make its origin explicit 2022-01-10 02:25:02 +01:00
3831679772 VarDecl: make its origin explicit 2022-01-10 01:53:03 +01:00
596f9566d8 todo 2022-01-10 01:00:50 +01:00
124befe9d6 slightly optimized code for assigning boolean expressions such as `b = xx>99` 2022-01-09 18:49:44 +01:00
895534f32b don't remove dead variable assignments if they are a function call 2022-01-09 18:41:01 +01:00
50c16fe6de code size optimization: don't copy floats with inlined copy code but use copy_float routine 2022-01-09 16:18:13 +01:00
b092d1a5d3 fixed code gen issue for uword >= comparison 2022-01-09 13:23:29 +01:00
a9b45630d7 optimized code for variable comparisons to zero 2022-01-09 13:10:01 +01:00
c1a39c269e optimized code for stack eval comparisons with zero 2022-01-09 03:19:49 +01:00
6fa3f0b6cd small refactor 2022-01-08 18:02:38 +01:00
9e5e3d1559 pipe expression not evaluated via stack 2022-01-08 17:51:23 +01:00
7135205299 fix codegen bug for pipe expressions to actually return correct value and not corrupt X register 2022-01-08 17:41:46 +01:00
d99d977d2b fix more typecasting issues 2022-01-08 17:04:25 +01:00
7dd7e562bc pipes also as expressions, cleanup codegen, fix various typecasting issues 2022-01-08 13:45:19 +01:00
75d857027e cleanup of Pipe codegen 2022-01-08 01:33:40 +01:00
17694c1d01 better error handling of invalid number casts 2022-01-07 22:12:13 +01:00
749ad700d8 naming consistency for some expression classes 2022-01-07 21:02:55 +01:00
8f3df3039a added pipe operator `|>` 2022-01-06 22:54:18 +01:00
02c315c194 add missing unit tests and type checking for 'in' expression 2022-01-06 00:01:49 +01:00
b697375573 add note about unspecified order of evaluation of expressions and subroutine call arguments 2022-01-05 23:21:45 +01:00
177 changed files with 6944 additions and 4105 deletions

6
.idea/kotlinc.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Kotlin2JvmCompilerArguments">
<option name="jvmTarget" value="11" />
</component>
</project>

4
.idea/modules.xml generated
View File

@ -2,7 +2,9 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/codeGeneration/codeGeneration.iml" filepath="$PROJECT_DIR$/codeGeneration/codeGeneration.iml" /> <module fileurl="file://$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" filepath="$PROJECT_DIR$/codeGenCpu6502/codeGenCpu6502.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenExperimental6502/codeGenExperimental6502.iml" filepath="$PROJECT_DIR$/codeGenExperimental6502/codeGenExperimental6502.iml" />
<module fileurl="file://$PROJECT_DIR$/codeGenTargets/codeGenTargets.iml" filepath="$PROJECT_DIR$/codeGenTargets/codeGenTargets.iml" />
<module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" /> <module fileurl="file://$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" filepath="$PROJECT_DIR$/codeOptimizers/codeOptimizers.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" /> <module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" /> <module fileurl="file://$PROJECT_DIR$/compilerAst/compilerAst.iml" filepath="$PROJECT_DIR$/compilerAst/compilerAst.iml" />

View File

@ -12,6 +12,9 @@
* The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`. * The same argument applies to `IMemSizer`, and - not entirely sure about that - `IBuiltinFunctions`.
#### Steps to take, in conceptual (!) order: #### 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 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 the local file system (use case: user programs)
- from resources (prog8lib) - from resources (prog8lib)

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<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="compilerAst" />
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>

View File

@ -1,8 +1,8 @@
package prog8.codegen.target.cpu6502.codegen package prog8.codegen.cpu6502
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.compilerinterface.IMachineDefinition import prog8.compilerinterface.IMachineDefinition
@ -10,7 +10,7 @@ import prog8.compilerinterface.IMachineDefinition
// note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations // note: see https://wiki.nesdev.org/w/index.php/6502_assembly_optimisations
fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int { internal fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, program: Program): Int {
var numberOfOptimizations = 0 var numberOfOptimizations = 0
@ -64,6 +64,11 @@ fun optimizeAssembly(lines: MutableList<String>, machine: IMachineDefinition, pr
return numberOfOptimizations return numberOfOptimizations
} }
private fun String.isBranch() = this.startsWith("b")
private fun String.isStoreReg() = this.startsWith("sta") || this.startsWith("sty") || this.startsWith("stx")
private fun String.isStoreRegOrZero() = this.isStoreReg() || this.startsWith("stz")
private fun String.isLoadReg() = this.startsWith("lda") || this.startsWith("ldy") || this.startsWith("ldx")
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?) private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
private fun apply(modifications: List<Modification>, lines: MutableList<String>) { private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
@ -196,9 +201,9 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
sta A1 sta A1
sty A2 sty A2
*/ */
if(first.startsWith("st") && second.startsWith("st") if(first.isStoreReg() && second.isStoreReg()
&& third.startsWith("ld") && fourth.startsWith("ld") && third.isLoadReg() && fourth.isLoadReg()
&& fifth.startsWith("st") && sixth.startsWith("st")) { && fifth.isStoreReg() && sixth.isStoreReg()) {
val reg1 = first[2] val reg1 = first[2]
val reg2 = second[2] val reg2 = second[2]
val reg3 = third[2] val reg3 = third[2]
@ -227,8 +232,8 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
lda A1 ; can be removed lda A1 ; can be removed
ldy A2 ; can be removed if not followed by a branch instuction ldy A2 ; can be removed if not followed by a branch instuction
*/ */
if(!overlappingMods && first.startsWith("st") && second.startsWith("st") if(!overlappingMods && first.isStoreReg() && second.isStoreReg()
&& third.startsWith("ld") && fourth.startsWith("ld")) { && third.isLoadReg() && fourth.isLoadReg()) {
val reg1 = first[2] val reg1 = first[2]
val reg2 = second[2] val reg2 = second[2]
val reg3 = third[2] val reg3 = third[2]
@ -249,13 +254,14 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
} }
} }
} }
/* /*
sta A1 sta A1
sty A2 sty A2 ; ... or stz
lda A1 ; can be removed if not followed by a branch instruction lda A1 ; can be removed if not followed by a branch instruction
*/ */
if(!overlappingMods && first.startsWith("st") && second.startsWith("st") if(!overlappingMods && first.isStoreReg() && second.isStoreRegOrZero()
&& third.startsWith("ld") && !fourth.startsWith("b")) { && third.isLoadReg() && !fourth.isBranch()) {
val reg1 = first[2] val reg1 = first[2]
val reg3 = third[2] val reg3 = third[2]
if(reg1==reg3) { if(reg1==reg3) {
@ -276,7 +282,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
ldy A1 ; make tay ldy A1 ; make tay
sta A1 ; remove sta A1 ; remove
*/ */
if(!overlappingMods && first.startsWith("sta") && second.startsWith("ld") if(!overlappingMods && first.startsWith("sta") && second.isLoadReg()
&& third.startsWith("sta") && second.length>4) { && third.startsWith("sta") && second.length>4) {
val firstvalue = first.substring(4) val firstvalue = first.substring(4)
val secondvalue = second.substring(4) val secondvalue = second.substring(4)
@ -293,10 +299,10 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
} }
/* /*
sta A sta A ; or stz double store, remove this first one
sta A sta A ; or stz
*/ */
if(!overlappingMods && first.startsWith("st") && second.startsWith("st")) { if(!overlappingMods && first.isStoreRegOrZero() && second.isStoreRegOrZero()) {
if(first[2]==second[2]) { if(first[2]==second[2]) {
val firstvalue = first.substring(4) val firstvalue = first.substring(4)
val secondvalue = second.substring(4) val secondvalue = second.substring(4)
@ -304,7 +310,7 @@ private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<Stri
val address = getAddressArg(first, program) val address = getAddressArg(first, program)
if(address==null || !machine.isIOAddress(address)) { if(address==null || !machine.isIOAddress(address)) {
overlappingMods = true overlappingMods = true
mods.add(Modification(lines[1].index, true, null)) mods.add(Modification(lines[0].index, true, null))
} }
} }
} }
@ -333,7 +339,7 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>,
) { ) {
val third = lines[3].value.trimStart() val third = lines[3].value.trimStart()
val attemptRemove = val attemptRemove =
if(third.startsWith("b")) { if(third.isBranch()) {
// a branch instruction follows, we can only remove the load instruction if // a branch instruction follows, we can only remove the load instruction if
// another load instruction of the same register precedes the store instruction // another load instruction of the same register precedes the store instruction
// (otherwise wrong cpu flags are used) // (otherwise wrong cpu flags are used)
@ -400,7 +406,7 @@ private fun getAddressArg(line: String, program: Program): UInt? {
when(decl.type){ when(decl.type){
VarDeclType.VAR -> null VarDeclType.VAR -> null
VarDeclType.CONST, VarDeclType.CONST,
VarDeclType.MEMORY -> (decl.value as NumericLiteralValue).number.toUInt() VarDeclType.MEMORY -> (decl.value as NumericLiteral).number.toUInt()
} }
} }
else null else null

View File

@ -1,12 +1,9 @@
package prog8.codegen.target.cbm package prog8.codegen.cpu6502
import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapError import com.github.michaelbull.result.mapError
import prog8.compilerinterface.CompilationOptions import prog8.compilerinterface.*
import prog8.compilerinterface.IAssemblyProgram
import prog8.compilerinterface.OutputType
import prog8.compilerinterface.generatedLabelPrefix
import prog8.parser.SourceCode import prog8.parser.SourceCode
import java.io.File import java.io.File
import java.nio.file.Path import java.nio.file.Path
@ -14,13 +11,10 @@ import kotlin.io.path.Path
import kotlin.io.path.isRegularFile import kotlin.io.path.isRegularFile
internal fun viceMonListName(baseFilename: String) = "$baseFilename.vice-mon-list" internal class AssemblyProgram(
class AssemblyProgram(
override val valid: Boolean,
override val name: String, override val name: String,
outputDir: Path, outputDir: Path,
private val compTarget: String) : IAssemblyProgram { private val compTarget: ICompilationTarget) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm") private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg") private val prgFile = outputDir.resolve("$name.prg")
@ -28,7 +22,7 @@ class AssemblyProgram(
private val viceMonListFile = outputDir.resolve(viceMonListName(name)) private val viceMonListFile = outputDir.resolve(viceMonListName(name))
private val listFile = outputDir.resolve("$name.list") private val listFile = outputDir.resolve("$name.list")
override fun assemble(options: CompilationOptions): Int { override fun assemble(options: CompilationOptions): Boolean {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently) // add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps (default = do this silently)
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror", "-Wall", "-Wno-strict-bool", "-Wno-shadow", // "-Werror",
@ -44,12 +38,12 @@ class AssemblyProgram(
val outFile = when (options.output) { val outFile = when (options.output) {
OutputType.PRG -> { OutputType.PRG -> {
command.add("--cbm-prg") command.add("--cbm-prg")
println("\nCreating prg for target $compTarget.") println("\nCreating prg for target ${compTarget.name}.")
prgFile prgFile
} }
OutputType.RAW -> { OutputType.RAW -> {
command.add("--nostart") command.add("--nostart")
println("\nCreating raw binary for target $compTarget.") println("\nCreating raw binary for target ${compTarget.name}.")
binFile binFile
} }
} }
@ -61,7 +55,7 @@ class AssemblyProgram(
removeGeneratedLabelsFromMonlist() removeGeneratedLabelsFromMonlist()
generateBreakpointList() generateBreakpointList()
} }
return result return result==0
} }
private fun removeGeneratedLabelsFromMonlist() { private fun removeGeneratedLabelsFromMonlist() {
@ -96,13 +90,13 @@ class AssemblyProgram(
internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> { internal fun loadAsmIncludeFile(filename: String, source: SourceCode): Result<String, NoSuchFileException> {
return if (filename.startsWith(SourceCode.libraryFilePrefix)) { return if (filename.startsWith(SourceCode.libraryFilePrefix)) {
return com.github.michaelbull.result.runCatching { return com.github.michaelbull.result.runCatching {
SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").readText() SourceCode.Resource("/prog8lib/${filename.substring(SourceCode.libraryFilePrefix.length)}").text
}.mapError { NoSuchFileException(File(filename)) } }.mapError { NoSuchFileException(File(filename)) }
} else { } else {
val sib = Path(source.origin).resolveSibling(filename) val sib = Path(source.origin).resolveSibling(filename)
if (sib.isRegularFile()) if (sib.isRegularFile())
Ok(SourceCode.File(sib).readText()) Ok(SourceCode.File(sib).text)
else else
Ok(SourceCode.File(Path(filename)).readText()) Ok(SourceCode.File(Path(filename)).text)
} }
} }

View File

@ -1,4 +1,4 @@
package prog8.codegen.target.cpu6502.codegen package prog8.codegen.cpu6502
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.Node import prog8.ast.Node
@ -10,16 +10,19 @@ import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.toHex import prog8.ast.toHex
import prog8.codegen.target.AssemblyError import prog8.codegen.cpu6502.assignment.*
import prog8.codegen.target.Cx16Target import prog8.compilerinterface.AssemblyError
import prog8.codegen.target.cpu6502.codegen.assignment.* import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType import prog8.compilerinterface.CpuType
import prog8.compilerinterface.FSignature import prog8.compilerinterface.FSignature
import prog8.compilerinterface.subroutineFloatEvalResultVar2
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
internal fun translateFunctioncallExpression(fcall: FunctionCallExpr, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) { internal class BuiltinFunctionsAsmGen(private val program: Program,
private val asmgen: AsmGen,
private val assignAsmGen: AssignmentAsmGen,
private val allocations: VariableAllocator) {
internal fun translateFunctioncallExpression(fcall: FunctionCallExpression, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister) translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
} }
@ -27,6 +30,37 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null) translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
} }
internal fun translateFunctioncall(name: String, args: List<AsmAssignSource>, isStatement: Boolean, scope: Subroutine): DataType {
val func = BuiltinFunctions.getValue(name)
val argExpressions = args.map { src ->
when(src.kind) {
SourceStorageKind.LITERALNUMBER -> src.number!!
SourceStorageKind.EXPRESSION -> src.expression!!
SourceStorageKind.ARRAY -> src.array!!
else -> {
// TODO make it so that we can assign efficiently from something else as an expression....namely: register(s)
// this is useful in pipe expressions for instance, to skip the use of a temporary variable
// but for now, just assign it to a temporary variable and use that as a source
val tempvar = asmgen.getTempVarName(src.datatype)
val assignTempvar = AsmAssignment(
src,
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, src.datatype, scope, variableAsmName = asmgen.asmVariableName(tempvar)),
false, program.memsizer, Position.DUMMY
)
assignAsmGen.translateNormalAssignment(assignTempvar)
// now use an expression to assign this tempvar
val ident = IdentifierReference(tempvar, Position.DUMMY)
ident.linkParents(scope)
ident
}
}
}.toMutableList()
val fcall = FunctionCallExpression(IdentifierReference(listOf(name), Position.DUMMY), argExpressions, Position.DUMMY)
fcall.linkParents(scope)
translateFunctioncall(fcall, func, discardResult = false, resultToStack = false, null)
return if(isStatement) DataType.UNDEFINED else func.known_returntype!!
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) { private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if (discardResult && func.pure) if (discardResult && func.pure)
return // can just ignore the whole function call altogether return // can just ignore the whole function call altogether
@ -66,6 +100,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
"peekw" -> funcPeekW(fcall, resultToStack, resultRegister) "peekw" -> funcPeekW(fcall, resultToStack, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()") "peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall) "pokew" -> funcPokeW(fcall)
"pokemon" -> { /* meme function */ }
"poke" -> throw AssemblyError("poke() should have been replaced by @()") "poke" -> throw AssemblyError("poke() should have been replaced by @()")
"push", "pushw" -> funcPush(fcall, func) "push", "pushw" -> funcPush(fcall, func)
"pop", "popw" -> funcPop(fcall, func) "pop", "popw" -> funcPop(fcall, func)
@ -243,7 +278,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
private fun funcCallFar(fcall: IFunctionCall) { private fun funcCallFar(fcall: IFunctionCall) {
if(asmgen.options.compTarget !is Cx16Target) if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callfar only works on cx16 target at this time") throw AssemblyError("callfar only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt() val bank = fcall.args[0].constValue(program)?.number?.toInt()
@ -274,7 +309,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
.byte ${bank.toHex()} .byte ${bank.toHex()}
sta ${asmgen.asmVariableName(argAddrArg.identifier)}""") sta ${asmgen.asmVariableName(argAddrArg.identifier)}""")
} }
is NumericLiteralValue -> { is NumericLiteral -> {
asmgen.out(""" asmgen.out("""
lda ${argAddrArg.number.toHex()} lda ${argAddrArg.number.toHex()}
jsr cx16.jsrfar jsr cx16.jsrfar
@ -288,7 +323,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
private fun funcCallRom(fcall: IFunctionCall) { private fun funcCallRom(fcall: IFunctionCall) {
if(asmgen.options.compTarget !is Cx16Target) if(asmgen.options.compTarget.name != "cx16")
throw AssemblyError("callrom only works on cx16 target at this time") throw AssemblyError("callrom only works on cx16 target at this time")
val bank = fcall.args[0].constValue(program)?.number?.toInt() val bank = fcall.args[0].constValue(program)?.number?.toInt()
@ -327,7 +362,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
pla pla
sta $01""") sta $01""")
} }
is NumericLiteralValue -> { is NumericLiteral -> {
asmgen.out(""" asmgen.out("""
lda $01 lda $01
pha pha
@ -356,12 +391,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}") asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
} }
is NumericLiteralValue -> { is NumericLiteral -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.number.toInt()}") asmgen.out(" cmp #${arg2.number.toInt()}")
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
if(arg2.addressExpression is NumericLiteralValue) { if(arg2.addressExpression is NumericLiteral) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}") asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
} else { } else {
@ -390,7 +425,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
cmp ${asmgen.asmVariableName(arg2)} cmp ${asmgen.asmVariableName(arg2)}
+""") +""")
} }
is NumericLiteralValue -> { is NumericLiteral -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY) asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out(""" asmgen.out("""
cpy #>${arg2.number.toInt()} cpy #>${arg2.number.toInt()}
@ -414,16 +449,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) { private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult || fcall !is FunctionCallExpr) if(discardResult || fcall !is FunctionCallExpression)
throw AssemblyError("should not discard result of memory allocation at $fcall") throw AssemblyError("should not discard result of memory allocation at $fcall")
val nameRef = fcall.args[0] as IdentifierReference val name = (fcall.args[0] as StringLiteral).value
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"} require(name.all { it.isLetterOrDigit() || it=='_' }) {"memory name should be a valid symbol name"}
val size = (fcall.args[1] as NumericLiteralValue).number.toUInt() val size = (fcall.args[1] as NumericLiteral).number.toUInt()
val align = (fcall.args[2] as NumericLiteral).number.toUInt()
val existingSize = asmgen.slabs[name] val existing = allocations.getMemorySlab(name)
if(existingSize!=null && existingSize!=size) if(existing!=null && (existing.first!=size || existing.second!=align))
throw AssemblyError("memory slab '$name' already exists with a different size ($size) at ${fcall.position}") throw AssemblyError("memory slab '$name' already exists with a different size or alignment at ${fcall.position}")
val slabname = IdentifierReference(listOf("prog8_slabs", name), fcall.position) val slabname = IdentifierReference(listOf("prog8_slabs", name), fcall.position)
slabname.linkParents(fcall) slabname.linkParents(fcall)
@ -435,7 +470,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen) AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, false, null, program, asmgen)
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position) val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign) asmgen.translateNormalAssignment(assign)
asmgen.slabs[name] = size allocations.allocateMemorySlab(name, size, align)
} }
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) { private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
@ -547,8 +582,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.ror2_array_ub") asmgen.out(" jsr prog8_lib.ror2_array_ub")
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) { if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteralValue).number val number = (what.addressExpression as NumericLiteral).number
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}") asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
} else { } else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY) asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
@ -590,8 +625,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.ror_array_ub") asmgen.out(" jsr prog8_lib.ror_array_ub")
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) { if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteralValue).number val number = (what.addressExpression as NumericLiteral).number
asmgen.out(" ror ${number.toHex()}") asmgen.out(" ror ${number.toHex()}")
} else { } else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression) val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
@ -648,8 +683,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.rol2_array_ub") asmgen.out(" jsr prog8_lib.rol2_array_ub")
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) { if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteralValue).number val number = (what.addressExpression as NumericLiteral).number
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}") asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
} else { } else {
asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY) asmgen.assignExpressionToRegister(what.addressExpression, RegisterOrPair.AY)
@ -691,8 +726,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" jsr prog8_lib.rol_array_ub") asmgen.out(" jsr prog8_lib.rol_array_ub")
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) { if (what.addressExpression is NumericLiteral) {
val number = (what.addressExpression as NumericLiteralValue).number val number = (what.addressExpression as NumericLiteral).number
asmgen.out(" rol ${number.toHex()}") asmgen.out(" rol ${number.toHex()}")
} else { } else {
val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression) val ptrAndIndex = asmgen.pointerViaIndexRegisterPossible(what.addressExpression)
@ -921,8 +956,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// optimized simple case: swap two memory locations // optimized simple case: swap two memory locations
if(first is DirectMemoryRead && second is DirectMemoryRead) { if(first is DirectMemoryRead && second is DirectMemoryRead) {
val addr1 = (first.addressExpression as? NumericLiteralValue)?.number?.toHex() val addr1 = (first.addressExpression as? NumericLiteral)?.number?.toHex()
val addr2 = (second.addressExpression as? NumericLiteralValue)?.number?.toHex() val addr2 = (second.addressExpression as? NumericLiteral)?.number?.toHex()
val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null val name1 = if(first.addressExpression is IdentifierReference) asmgen.asmVariableName(first.addressExpression as IdentifierReference) else null
val name2 = if(second.addressExpression is IdentifierReference) asmgen.asmVariableName(second.addressExpression as IdentifierReference) else null val name2 = if(second.addressExpression is IdentifierReference) asmgen.asmVariableName(second.addressExpression as IdentifierReference) else null
@ -953,10 +988,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(pointerVariable != null if(pointerVariable != null
&& pointerVariable isSameAs secondExpr.left && pointerVariable isSameAs secondExpr.left
&& firstExpr.operator == "+" && secondExpr.operator == "+" && firstExpr.operator == "+" && secondExpr.operator == "+"
&& (firstOffset is NumericLiteralValue || firstOffset is IdentifierReference || firstOffset is TypecastExpression) && (firstOffset is NumericLiteral || firstOffset is IdentifierReference || firstOffset is TypecastExpression)
&& (secondOffset is NumericLiteralValue || secondOffset is IdentifierReference || secondOffset is TypecastExpression) && (secondOffset is NumericLiteral || secondOffset is IdentifierReference || secondOffset is TypecastExpression)
) { ) {
if(firstOffset is NumericLiteralValue && secondOffset is NumericLiteralValue) { if(firstOffset is NumericLiteral && secondOffset is NumericLiteral) {
if(firstOffset!=secondOffset) { if(firstOffset!=secondOffset) {
swapArrayValues( swapArrayValues(
DataType.UBYTE, DataType.UBYTE,
@ -995,9 +1030,9 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val elementIDt = first.inferType(program) val elementIDt = first.inferType(program)
val elementDt = elementIDt.getOrElse { throw AssemblyError("unknown dt") } val elementDt = elementIDt.getOrElse { throw AssemblyError("unknown dt") }
val firstNum = first.indexer.indexExpr as? NumericLiteralValue val firstNum = first.indexer.indexExpr as? NumericLiteral
val firstVar = first.indexer.indexExpr as? IdentifierReference val firstVar = first.indexer.indexExpr as? IdentifierReference
val secondNum = second.indexer.indexExpr as? NumericLiteralValue val secondNum = second.indexer.indexExpr as? NumericLiteral
val secondVar = second.indexer.indexExpr as? IdentifierReference val secondVar = second.indexer.indexExpr as? IdentifierReference
if(firstNum!=null && secondNum!=null) { if(firstNum!=null && secondNum!=null) {
@ -1066,7 +1101,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} }
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) { private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteral, arrayVarName2: String, indexValue2: NumericLiteral) {
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt) val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt) val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
when(elementDt) { when(elementDt) {
@ -1180,7 +1215,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} }
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) { private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteral, arrayVarName2: String, indexName2: IdentifierReference) {
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt) val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
val idxAsmName2 = asmgen.asmVariableName(indexName2) val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) { when(elementDt) {
@ -1238,7 +1273,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} }
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) { private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteral) {
val idxAsmName1 = asmgen.asmVariableName(indexName1) val idxAsmName1 = asmgen.asmVariableName(indexName1)
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt) val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
when(elementDt) { when(elementDt) {
@ -1349,7 +1384,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcPokeW(fcall: IFunctionCall) { private fun funcPokeW(fcall: IFunctionCall) {
when(val addrExpr = fcall.args[0]) { when(val addrExpr = fcall.args[0]) {
is NumericLiteralValue -> { is NumericLiteral -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY) asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
val addr = addrExpr.number.toHex() val addr = addrExpr.number.toHex()
asmgen.out(" sta $addr | sty ${addr}+1") asmgen.out(" sta $addr | sty ${addr}+1")
@ -1380,13 +1415,13 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} }
is BinaryExpression -> { is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) { if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference) val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) { if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
// pointervar is already in the zero page, no need to copy // pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!) asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX) asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
val index = (addrExpr.right as NumericLiteralValue).number.toHex() val index = (addrExpr.right as NumericLiteral).number.toHex()
asmgen.out(""" asmgen.out("""
ldy #$index ldy #$index
sta ($varname),y sta ($varname),y
@ -1408,7 +1443,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcPeekW(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) { private fun funcPeekW(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
when(val addrExpr = fcall.args[0]) { when(val addrExpr = fcall.args[0]) {
is NumericLiteralValue -> { is NumericLiteral -> {
val addr = addrExpr.number.toHex() val addr = addrExpr.number.toHex()
asmgen.out(" lda $addr | ldy ${addr}+1") asmgen.out(" lda $addr | ldy ${addr}+1")
} }
@ -1438,11 +1473,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
} }
} }
is BinaryExpression -> { is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) { if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteral) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference) val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) { if(asmgen.isZpVar(addrExpr.left as IdentifierReference)) {
// pointervar is already in the zero page, no need to copy // pointervar is already in the zero page, no need to copy
val index = (addrExpr.right as NumericLiteralValue).number.toHex() val index = (addrExpr.right as NumericLiteral).number.toHex()
asmgen.out(""" asmgen.out("""
ldy #$index ldy #$index
lda ($varname),y lda ($varname),y
@ -1489,14 +1524,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex") asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
} else { } else {
val reg = resultRegister ?: RegisterOrPair.AY val reg = resultRegister ?: RegisterOrPair.AY
var needAsave = !(fcall.args[0] is DirectMemoryRead || fcall.args[0] is NumericLiteralValue || fcall.args[0] is IdentifierReference) var needAsave = !(fcall.args[0] is DirectMemoryRead || fcall.args[0] is NumericLiteral || fcall.args[0] is IdentifierReference)
if(!needAsave) { if(!needAsave) {
val mr0 = fcall.args[0] as? DirectMemoryRead val mr0 = fcall.args[0] as? DirectMemoryRead
val mr1 = fcall.args[1] as? DirectMemoryRead val mr1 = fcall.args[1] as? DirectMemoryRead
if (mr0 != null) if (mr0 != null)
needAsave = mr0.addressExpression !is NumericLiteralValue && mr0.addressExpression !is IdentifierReference needAsave = mr0.addressExpression !is NumericLiteral && mr0.addressExpression !is IdentifierReference
if (mr1 != null) if (mr1 != null)
needAsave = needAsave or (mr1.addressExpression !is NumericLiteralValue && mr1.addressExpression !is IdentifierReference) needAsave = needAsave or (mr1.addressExpression !is NumericLiteral && mr1.addressExpression !is IdentifierReference)
} }
when(reg) { when(reg) {
RegisterOrPair.AX -> { RegisterOrPair.AX -> {
@ -1539,7 +1574,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val arg = fcall.args.single() val arg = fcall.args.single()
if (!arg.inferType(program).isWords) if (!arg.inferType(program).isWords)
throw AssemblyError("msb required word argument") throw AssemblyError("msb required word argument")
if (arg is NumericLiteralValue) if (arg is NumericLiteral)
throw AssemblyError("msb(const) should have been const-folded away") throw AssemblyError("msb(const) should have been const-folded away")
if (arg is IdentifierReference) { if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg) val sourceName = asmgen.asmVariableName(arg)
@ -1550,6 +1585,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName+1") null, RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1") RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1") RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
RegisterOrPair.AX -> asmgen.out(" lda $sourceName+1 | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName+1 | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName+1 | ldy #0")
in Cx16VirtualRegisters -> {
val regname = resultRegister.name.lowercase()
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | stz cx16.$regname+1")
else
asmgen.out(" lda $sourceName+1 | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
}
else -> throw AssemblyError("invalid reg") else -> throw AssemblyError("invalid reg")
} }
} }
@ -1583,7 +1628,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val arg = fcall.args.single() val arg = fcall.args.single()
if (!arg.inferType(program).isWords) if (!arg.inferType(program).isWords)
throw AssemblyError("lsb required word argument") throw AssemblyError("lsb required word argument")
if (arg is NumericLiteralValue) if (arg is NumericLiteral)
throw AssemblyError("lsb(const) should have been const-folded away") throw AssemblyError("lsb(const) should have been const-folded away")
if (arg is IdentifierReference) { if (arg is IdentifierReference) {
@ -1595,6 +1640,16 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName") null, RegisterOrPair.A -> asmgen.out(" lda $sourceName")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName") RegisterOrPair.X -> asmgen.out(" ldx $sourceName")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName") RegisterOrPair.Y -> asmgen.out(" ldy $sourceName")
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx #0")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy #0")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy #0")
in Cx16VirtualRegisters -> {
val regname = resultRegister.name.lowercase()
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceName | sta cx16.$regname | stz cx16.$regname+1")
else
asmgen.out(" lda $sourceName | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
}
else -> throw AssemblyError("invalid reg") else -> throw AssemblyError("invalid reg")
} }
} }
@ -1655,14 +1710,14 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val addr = AddressOf(value, value.position) val addr = AddressOf(value, value.position)
AsmAssignSource.fromAstSource(addr, program, asmgen) AsmAssignSource.fromAstSource(addr, program, asmgen)
} }
is NumericLiteralValue -> { is NumericLiteral -> {
throw AssemblyError("float literals should have been converted into autovar") throw AssemblyError("float literals should have been converted into autovar")
} }
else -> { else -> {
if(scope==null) if(scope==null)
throw AssemblyError("cannot use float arguments outside of a subroutine scope") throw AssemblyError("cannot use float arguments outside of a subroutine scope")
scope.asmGenInfo.usedFloatEvalResultVar2 = true allocations.subroutineExtra(scope).usedFloatEvalResultVar2 = true
val variable = IdentifierReference(listOf(subroutineFloatEvalResultVar2), value.position) val variable = IdentifierReference(listOf(subroutineFloatEvalResultVar2), value.position)
val addr = AddressOf(variable, value.position) val addr = AddressOf(variable, value.position)
addr.linkParents(value) addr.linkParents(value)

View File

@ -1,4 +1,4 @@
package prog8.codegen.target.cpu6502.codegen package prog8.codegen.cpu6502
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
@ -6,12 +6,14 @@ import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionPlaceholder import prog8.ast.statements.BuiltinFunctionPlaceholder
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.toHex import prog8.ast.toHex
import prog8.codegen.target.AssemblyError import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.BuiltinFunctions import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType import prog8.compilerinterface.CpuType
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class ExpressionsAsmGen(private val program: Program,
private val asmgen: AsmGen,
private val allocator: VariableAllocator) {
@Deprecated("avoid calling this as it generates slow evalstack based code") @Deprecated("avoid calling this as it generates slow evalstack based code")
internal fun translateExpression(expression:Expression) { internal fun translateExpression(expression:Expression) {
@ -34,18 +36,19 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is TypecastExpression -> translateExpression(expression) is TypecastExpression -> translateExpression(expression)
is AddressOf -> translateExpression(expression) is AddressOf -> translateExpression(expression)
is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true) is DirectMemoryRead -> asmgen.translateDirectMemReadExpressionToRegAorStack(expression, true)
is NumericLiteralValue -> translateExpression(expression) is NumericLiteral -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression) is IdentifierReference -> translateExpression(expression)
is FunctionCallExpr -> translateFunctionCallResultOntoStack(expression) is FunctionCallExpression -> translateFunctionCallResultOntoStack(expression)
is PipeExpression -> asmgen.translatePipeExpression(expression.expressions, expression,false, true)
is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported") is ContainmentCheck -> throw AssemblyError("containment check as complex expression value is not supported")
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable") is ArrayLiteral, is StringLiteral -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values") 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 CharLiteral -> throw AssemblyError("charliteral should have been replaced by ubyte using certain encoding")
else -> TODO("missing expression asmgen for $expression") else -> TODO("missing expression asmgen for $expression")
} }
} }
private fun translateFunctionCallResultOntoStack(call: FunctionCallExpr) { private fun translateFunctionCallResultOntoStack(call: FunctionCallExpression) {
// only for use in nested expression evaluation // only for use in nested expression evaluation
val sub = call.target.targetStatement(program) val sub = call.target.targetStatement(program)
@ -211,7 +214,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex") asmgen.out(" lda #<$name | sta P8ESTACK_LO,x | lda #>$name | sta P8ESTACK_HI,x | dex")
} }
private fun translateExpression(expr: NumericLiteralValue) { private fun translateExpression(expr: NumericLiteral) {
when(expr.type) { when(expr.type) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex") DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta P8ESTACK_LO,x | dex")
DataType.UWORD, DataType.WORD -> asmgen.out(""" DataType.UWORD, DataType.WORD -> asmgen.out("""
@ -222,7 +225,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
dex dex
""") """)
DataType.FLOAT -> { DataType.FLOAT -> {
val floatConst = asmgen.getFloatAsmConst(expr.number) val floatConst = allocator.getFloatAsmConst(expr.number)
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float") asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
} }
else -> throw AssemblyError("weird type") else -> throw AssemblyError("weird type")
@ -539,6 +542,13 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
} }
} }
in ComparisonOperators -> {
if(leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
val rightVal = expr.right.constValue(program)?.number?.toInt()
if(rightVal==0)
return translateComparisonWithZero(expr.left, leftDt, expr.operator)
}
}
} }
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes) if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
@ -561,6 +571,69 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
} }
} }
private fun translateComparisonWithZero(expr: Expression, dt: DataType, operator: String) {
translateExpressionInternal(expr)
when(operator) {
"==" -> {
when(dt) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
DataType.FLOAT -> asmgen.out(" jsr floats.equal_zero")
else -> throw AssemblyError("wrong dt")
}
}
"!=" -> {
when(dt) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" jsr prog8_lib.notequalzero_b")
DataType.UWORD, DataType.WORD -> asmgen.out(" jsr prog8_lib.notequalzero_w")
DataType.FLOAT -> asmgen.out(" jsr floats.notequal_zero")
else -> throw AssemblyError("wrong dt")
}
}
"<" -> {
if(dt==DataType.UBYTE || dt==DataType.UWORD)
return translateExpressionInternal(NumericLiteral.fromBoolean(false, expr.position))
when(dt) {
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lesszero_b")
DataType.WORD -> asmgen.out(" jsr prog8_lib.lesszero_w")
DataType.FLOAT -> asmgen.out(" jsr floats.less_zero")
else -> throw AssemblyError("wrong dt")
}
}
">" -> {
when(dt) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.greaterzero_ub")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterzero_sb")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.greaterzero_uw")
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterzero_sw")
DataType.FLOAT -> asmgen.out(" jsr floats.greater_zero")
else -> throw AssemblyError("wrong dt")
}
}
"<=" -> {
when(dt) {
DataType.UBYTE -> asmgen.out(" jsr prog8_lib.equalzero_b")
DataType.BYTE -> asmgen.out(" jsr prog8_lib.lessequalzeros_b")
DataType.UWORD -> asmgen.out(" jsr prog8_lib.equalzero_w")
DataType.WORD -> asmgen.out(" jsr prog8_lib.lessequalzero_sw")
DataType.FLOAT -> asmgen.out(" jsr floats.lessequal_zero")
else -> throw AssemblyError("wrong dt")
}
}
">=" -> {
if(dt==DataType.UBYTE || dt==DataType.UWORD)
return translateExpressionInternal(NumericLiteral.fromBoolean(true, expr.position))
when(dt) {
DataType.BYTE -> asmgen.out(" jsr prog8_lib.greaterequalzero_sb")
DataType.WORD -> asmgen.out(" jsr prog8_lib.greaterequalzero_sw")
DataType.FLOAT -> asmgen.out(" jsr floats.greaterequal_zero")
else -> throw AssemblyError("wrong dt")
}
}
else -> throw AssemblyError("invalid comparison operator")
}
}
private fun translateSquared(variable: IdentifierReference, dt: DataType) { private fun translateSquared(variable: IdentifierReference, dt: DataType) {
val asmVar = asmgen.asmVariableName(variable) val asmVar = asmgen.asmVariableName(variable)
when(dt) { when(dt) {
@ -631,7 +704,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex") asmgen.out(" lda $arrayVarName+$indexValue | sta P8ESTACK_LO,x | lda $arrayVarName+$indexValue+1 | sta P8ESTACK_HI,x | dex")
} }
DataType.FLOAT -> { DataType.FLOAT -> {
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr floats.push_float") asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue) | jsr floats.push_float")
} }
else -> throw AssemblyError("weird element type") else -> throw AssemblyError("weird element type")
} }
@ -727,7 +800,8 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
"<=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w") "<=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
">=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w") ">=" -> asmgen.out(if(dt==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
"==" -> asmgen.out(" jsr prog8_lib.equal_w") "==" -> asmgen.out(" jsr prog8_lib.equal_w")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w") "&" -> asmgen.out(" jsr prog8_lib.bitand_w") "!=" -> asmgen.out(" jsr prog8_lib.notequal_w")
"&" -> asmgen.out(" jsr prog8_lib.bitand_w")
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w") "^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
"|" -> asmgen.out(" jsr prog8_lib.bitor_w") "|" -> asmgen.out(" jsr prog8_lib.bitor_w")
"and" -> asmgen.out(" jsr prog8_lib.and_w") "and" -> asmgen.out(" jsr prog8_lib.and_w")

View File

@ -1,27 +1,29 @@
package prog8.codegen.target.cpu6502.codegen package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.ArrayToElementTypes import prog8.ast.base.ArrayToElementTypes
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.RegisterOrPair import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr import prog8.ast.expressions.RangeExpression
import prog8.ast.statements.ForLoop import prog8.ast.statements.ForLoop
import prog8.ast.toHex import prog8.ast.toHex
import prog8.codegen.target.AssemblyError import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.Zeropage
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen, private val zeropage: Zeropage) {
internal fun translate(stmt: ForLoop) { internal fun translate(stmt: ForLoop) {
val iterableDt = stmt.iterable.inferType(program) val iterableDt = stmt.iterable.inferType(program)
if(!iterableDt.isKnown) if(!iterableDt.isKnown)
throw AssemblyError("unknown dt") throw AssemblyError("unknown dt")
when(stmt.iterable) { when(stmt.iterable) {
is RangeExpr -> { is RangeExpression -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange() val range = (stmt.iterable as RangeExpression).toConstantIntegerRange()
if(range==null) { if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpr) translateForOverNonconstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, stmt.iterable as RangeExpression)
} else { } else {
translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range) translateForOverConstRange(stmt, iterableDt.getOrElse { throw AssemblyError("unknown dt") }, range)
} }
@ -33,7 +35,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
} }
} }
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) { private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpression) {
val loopLabel = asmgen.makeLabel("for_loop") val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end") val endLabel = asmgen.makeLabel("for_end")
val modifiedLabel = asmgen.makeLabel("for_modified") val modifiedLabel = asmgen.makeLabel("for_modified")
@ -288,13 +290,15 @@ $loopLabel sty $indexVar
bne $loopLabel bne $loopLabel
beq $endLabel""") beq $endLabel""")
} }
if(length>=16 && asmgen.zeropage.hasByteAvailable()) { if(length>=16) {
// allocate index var on ZP // allocate index var on ZP if possible
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, stmt.definingScope, null, null, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""") result.fold(
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else { } else {
asmgen.out(""" asmgen.out("$indexVar .byte 0")
$indexVar .byte 0""")
} }
asmgen.out(endLabel) asmgen.out(endLabel)
} }
@ -327,13 +331,15 @@ $loopLabel sty $indexVar
bne $loopLabel bne $loopLabel
beq $endLabel""") beq $endLabel""")
} }
if(length>=16 && asmgen.zeropage.hasByteAvailable()) { if(length>=16) {
// allocate index var on ZP // allocate index var on ZP if possible
val zpAddr = asmgen.zeropage.allocate(indexVar, DataType.UBYTE, stmt.position, asmgen.errors) val result = zeropage.allocate(listOf(indexVar), DataType.UBYTE, stmt.definingScope, null, null, stmt.position, asmgen.errors)
asmgen.out("""$indexVar = $zpAddr ; auto zp UBYTE""") result.fold(
success = { (address,_)-> asmgen.out("""$indexVar = $address ; auto zp UBYTE""") },
failure = { asmgen.out("$indexVar .byte 0") }
)
} else { } else {
asmgen.out(""" asmgen.out("$indexVar .byte 0")
$indexVar .byte 0""")
} }
asmgen.out(endLabel) asmgen.out(endLabel)
} }
@ -587,7 +593,7 @@ $loopLabel""")
asmgen.loopEndLabels.pop() asmgen.loopEndLabels.pop()
} }
private fun assignLoopvar(stmt: ForLoop, range: RangeExpr) = private fun assignLoopvar(stmt: ForLoop, range: RangeExpression) =
asmgen.assignExpressionToVariable( asmgen.assignExpressionToVariable(
range.from, range.from,
asmgen.asmVariableName(stmt.loopVar), asmgen.asmVariableName(stmt.loopVar),

View File

@ -1,16 +1,19 @@
package prog8.codegen.target.cpu6502.codegen package prog8.codegen.cpu6502
import prog8.ast.IFunctionCall import prog8.ast.IFunctionCall
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.codegen.target.AssemblyError import prog8.codegen.cpu6502.assignment.AsmAssignSource
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignSource import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignTarget import prog8.codegen.cpu6502.assignment.AsmAssignment
import prog8.codegen.target.cpu6502.codegen.assignment.AsmAssignment import prog8.codegen.cpu6502.assignment.TargetStorageKind
import prog8.codegen.target.cpu6502.codegen.assignment.TargetStorageKind import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.CpuType import prog8.compilerinterface.CpuType
@ -93,8 +96,7 @@ 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 // 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) // (this condition has been enforced by an ast check earlier)
asmgen.out(" \t; inlined routine follows: ${sub.name}") asmgen.out(" \t; inlined routine follows: ${sub.name}")
val assembly = sub.statements.single() as InlineAssembly sub.statements.forEach { asmgen.translate(it as InlineAssembly) }
asmgen.translate(assembly)
asmgen.out(" \t; inlined routine end: ${sub.name}") asmgen.out(" \t; inlined routine end: ${sub.name}")
} else { } else {
asmgen.out(" jsr $subAsmName") asmgen.out(" jsr $subAsmName")
@ -126,9 +128,67 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// remember: dealing with the X register and/or dealing with return values is the responsibility of the caller // remember: dealing with the X register and/or dealing with return values is the responsibility of the caller
} }
internal fun translateUnaryFunctionCallWithArgSource(target: IdentifierReference, arg: AsmAssignSource, isStatement: Boolean, scope: Subroutine): DataType {
when(val targetStmt = target.targetStatement(program)!!) {
is BuiltinFunctionPlaceholder -> {
return if(isStatement) {
asmgen.translateBuiltinFunctionCallStatement(targetStmt.name, listOf(arg), scope)
DataType.UNDEFINED
} else {
asmgen.translateBuiltinFunctionCallExpression(targetStmt.name, listOf(arg), scope)
}
}
is Subroutine -> {
val argDt = targetStmt.parameters.single().type
if(targetStmt.isAsmSubroutine) {
// argument via registers
val argRegister = targetStmt.asmParameterRegisters.single().registerOrPair!!
val assignArgument = AsmAssignment(
arg,
AsmAssignTarget.fromRegisters(argRegister, argDt in SignedDatatypes, scope, program, asmgen),
false, program.memsizer, target.position
)
asmgen.translateNormalAssignment(assignArgument)
} else {
val assignArgument: AsmAssignment =
if(optimizeIntArgsViaRegisters(targetStmt)) {
// argument goes via registers as optimization
val paramReg: RegisterOrPair = when(argDt) {
in ByteDatatypes -> RegisterOrPair.A
in WordDatatypes -> RegisterOrPair.AY
DataType.FLOAT -> RegisterOrPair.FAC1
else -> throw AssemblyError("invalid dt")
}
AsmAssignment(
arg,
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, argDt, scope, register = paramReg),
false, program.memsizer, target.position
)
} else {
// arg goes via parameter variable
val argVarName = asmgen.asmVariableName(targetStmt.scopedName + targetStmt.parameters.single().name)
AsmAssignment(
arg,
AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, argDt, scope, argVarName),
false, program.memsizer, target.position
)
}
asmgen.translateNormalAssignment(assignArgument)
}
if(targetStmt.shouldSaveX())
asmgen.saveRegisterLocal(CpuRegister.X, scope)
asmgen.out(" jsr ${asmgen.asmSymbolName(target)}")
if(targetStmt.shouldSaveX())
asmgen.restoreRegisterLocal(CpuRegister.X)
return if(isStatement) DataType.UNDEFINED else targetStmt.returntypes.single()
}
else -> throw AssemblyError("invalid call target")
}
}
private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) { private fun argumentsViaVariables(sub: Subroutine, call: IFunctionCall) {
for(arg in sub.parameters.withIndex().zip(call.args)) for(arg in sub.parameters.withIndex().zip(call.args))
argumentViaVariable(sub, arg.first, arg.second) argumentViaVariable(sub, arg.first.value, arg.second)
} }
private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) { private fun argumentsViaRegisters(sub: Subroutine, call: IFunctionCall) {
@ -272,15 +332,15 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.out(" plp") // set the carry flag back to correct value asmgen.out(" plp") // set the carry flag back to correct value
} }
private fun argumentViaVariable(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression) { private fun argumentViaVariable(sub: Subroutine, parameter: SubroutineParameter, value: Expression) {
// pass parameter via a regular variable (not via registers) // pass parameter via a regular variable (not via registers)
val valueIDt = value.inferType(program) val valueIDt = value.inferType(program)
val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") } val valueDt = valueIDt.getOrElse { throw AssemblyError("unknown dt") }
if(!isArgumentTypeCompatible(valueDt, parameter.value.type)) if(!isArgumentTypeCompatible(valueDt, parameter.type))
throw AssemblyError("argument type incompatible") throw AssemblyError("argument type incompatible")
val varName = asmgen.asmVariableName(sub.scopedName + parameter.value.name) val varName = asmgen.asmVariableName(sub.scopedName + parameter.name)
asmgen.assignExpressionToVariable(value, varName, parameter.value.type, sub) asmgen.assignExpressionToVariable(value, varName, parameter.type, sub)
} }
private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) { private fun argumentViaRegister(sub: Subroutine, parameter: IndexedValue<SubroutineParameter>, value: Expression, registerOverride: RegisterOrPair? = null) {
@ -305,7 +365,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// this param needs to be set last, right before the jsr // 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 // for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) { when(value) {
is NumericLiteralValue -> { is NumericLiteral -> {
val carrySet = value.number.toInt() != 0 val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc") asmgen.out(if(carrySet) " sec" else " clc")
} }

View File

@ -1,12 +1,12 @@
package prog8.codegen.target.cpu6502.codegen package prog8.codegen.cpu6502
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.PostIncrDecr import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex import prog8.ast.toHex
import prog8.codegen.target.AssemblyError import prog8.compilerinterface.AssemblyError
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -41,7 +41,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
} }
targetMemory!=null -> { targetMemory!=null -> {
when (val addressExpr = targetMemory.addressExpression) { when (val addressExpr = targetMemory.addressExpression) {
is NumericLiteralValue -> { is NumericLiteral -> {
val what = addressExpr.number.toHex() val what = addressExpr.number.toHex()
asmgen.out(if(incr) " inc $what" else " dec $what") asmgen.out(if(incr) " inc $what" else " dec $what")
} }
@ -83,7 +83,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
""") """)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
asmgen.out(" lda #<$asmArrayvarname+$indexValue | ldy #>$asmArrayvarname+$indexValue") asmgen.out(" lda #<($asmArrayvarname+$indexValue) | ldy #>($asmArrayvarname+$indexValue)")
asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f") asmgen.out(if(incr) " jsr floats.inc_var_f" else " jsr floats.dec_var_f")
} }
else -> throw AssemblyError("need numeric type") else -> throw AssemblyError("need numeric type")

View File

@ -0,0 +1,590 @@
package prog8.codegen.cpu6502
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.toHex
import prog8.codegen.cpu6502.assignment.AsmAssignTarget
import prog8.codegen.cpu6502.assignment.TargetStorageKind
import prog8.compilerinterface.*
import java.time.LocalDate
import java.time.LocalDateTime
import kotlin.math.absoluteValue
/**
* Generates the main parts of the program:
* - entry/exit code
* - initialization routines
* - blocks
* - subroutines
* - all variables (note: VarDecl ast nodes are *NOT* used anymore for this! now uses IVariablesAndConsts data tables!)
*/
internal class ProgramAndVarsGen(
val program: Program,
val variables: IVariablesAndConsts,
val options: CompilationOptions,
val errors: IErrorReporter,
private val functioncallAsmGen: FunctionCallAsmGen,
private val asmgen: AsmGen,
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>() }
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"}
allocator.allocateZeropageVariables()
header()
val allBlocks = program.allBlocks
if(allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main'")
if(errors.noErrors()) {
program.allBlocks.forEach { block2asm(it) }
memorySlabs()
footer()
}
}
private fun header() {
val ourName = this.javaClass.name
val cpu = when(compTarget.machine.cpu) {
CpuType.CPU6502 -> "6502"
CpuType.CPU65c02 -> "w65c02"
else -> "unsupported"
}
asmgen.out("; $cpu assembly code for '${program.name}'")
asmgen.out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}")
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")
program.actualLoadAddress = program.definedLoadAddress
if (program.actualLoadAddress == 0u) // fix load address
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
compTarget.machine.BASIC_LOAD_ADDRESS else compTarget.machine.RAW_LOAD_ADDRESS
// the global prog8 variables needed
val zp = zeropage
asmgen.out("P8ZP_SCRATCH_B1 = ${zp.SCRATCH_B1}")
asmgen.out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
asmgen.out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
asmgen.out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
asmgen.out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
asmgen.out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
when {
options.launcher == LauncherType.BASIC -> {
if (program.actualLoadAddress != options.compTarget.machine.BASIC_LOAD_ADDRESS)
throw AssemblyError("BASIC output must have correct load address")
asmgen.out("; ---- basic program with sys call ----")
asmgen.out("* = ${program.actualLoadAddress.toHex()}")
val year = LocalDate.now().year
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")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
options.output == OutputType.PRG -> {
asmgen.out("; ---- program without basic sys call ----")
asmgen.out("* = ${program.actualLoadAddress.toHex()}\n")
if(!options.noSysInit)
asmgen.out(" jsr ${compTarget.name}.init_system")
asmgen.out(" jsr ${compTarget.name}.init_system_phase2")
}
options.output == OutputType.RAW -> {
asmgen.out("; ---- raw assembler program ----")
asmgen.out("* = ${program.actualLoadAddress.toHex()}\n")
}
}
if(options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE)) {
asmgen.out("""
; zeropage is clobbered so we need to reset the machine at exit
lda #>sys.reset_system
pha
lda #<sys.reset_system
pha""")
}
// make sure that on the cx16 and c64, basic rom is banked in again when we exit the program
when(compTarget.name) {
"cx16" -> {
if(options.floats)
asmgen.out(" lda #4 | sta $01") // to use floats, make sure Basic rom is banked in
asmgen.out(" jsr main.start | lda #4 | sta $01 | rts")
}
"c64" -> asmgen.out(" jsr main.start | lda #31 | sta $01 | rts")
else -> asmgen.jmp("main.start")
}
}
private fun memorySlabs() {
asmgen.out("; memory slabs")
asmgen.out("prog8_slabs\t.block")
for((name, info) in allocator.memorySlabs) {
if(info.second>1u)
asmgen.out("\t.align ${info.second.toHex()}")
asmgen.out("$name\t.fill ${info.first}")
}
asmgen.out("\t.bend")
}
private fun footer() {
// the global list of all floating point constants for the whole program
asmgen.out("; global float constants")
for (flt in allocator.globalFloatConsts) {
val floatFill = compTarget.machine.getFloat(flt.key).makeFloatFillAsm()
val floatvalue = flt.key
asmgen.out("${flt.value}\t.byte $floatFill ; float $floatvalue")
}
// program end
asmgen.out("prog8_program_end\t; end of program label for progend()")
}
private fun block2asm(block: Block) {
asmgen.out("")
asmgen.out("; ---- block: '${block.name}' ----")
if(block.address!=null)
asmgen.out("* = ${block.address!!.toHex()}")
else {
if("align_word" in block.options())
asmgen.out("\t.align 2")
else if("align_page" in block.options())
asmgen.out("\t.align $100")
}
asmgen.out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
asmgen.outputSourceLine(block)
zeropagevars2asm(block)
memdefsAndConsts2asm(block)
asmsubs2asm(block.statements)
nonZpVariables2asm(block)
asmgen.out("")
asmgen.out("; subroutines in this block")
// First translate regular statements, and then put the subroutines at the end.
// (regular statements = everything except the initialization assignments;
// these will be part of the prog8_init_vars init routine generated below)
val initializers = blockVariableInitializers.getValue(block)
val statements = block.statements.filterNot { it in initializers }
val (subroutine, stmts) = statements.partition { it is Subroutine }
stmts.forEach { asmgen.translate(it) }
subroutine.forEach { asmgen.translate(it) }
if(!options.dontReinitGlobals) {
// generate subroutine to initialize block-level (global) variables
if (initializers.isNotEmpty()) {
asmgen.out("prog8_init_vars\t.proc\n")
initializers.forEach { assign -> asmgen.translate(assign) }
asmgen.out(" rts\n .pend")
}
}
asmgen.out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
}
internal fun translateSubroutine(sub: Subroutine) {
var onlyVariables = false
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
}
}
asmgen.out("")
if(sub.isAsmSubroutine) {
if(sub.asmAddress!=null)
return // already done at the memvars section
// asmsub with most likely just an inline asm in it
asmgen.out("${sub.name}\t.proc")
sub.statements.forEach { asmgen.translate(it) }
asmgen.out(" .pend\n")
} else {
// regular subroutine
asmgen.out("${sub.name}\t.proc")
zeropagevars2asm(sub)
memdefsAndConsts2asm(sub)
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, program, 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, program, asmgen, sub.parameters[0].type, sub, variableAsmName = sub.parameters[0].name)
val target2 = AsmAssignTarget(TargetStorageKind.VARIABLE, program, 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 = allocator.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")
nonZpVariables2asm(sub)
asmgen.out(" .pend\n")
}
}
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")
}
}
// string and array variables in zeropage that have initializer value, should be initialized
val stringVarsWithInitInZp = allocator.zeropageVars.filter { it.value.dt==DataType.STR && it.value.initialStringValue!=null }
val arrayVarsWithInitInZp = allocator.zeropageVars.filter { it.value.dt in ArrayDatatypes && it.value.initialArrayValue!=null }
if(stringVarsWithInitInZp.isNotEmpty() || arrayVarsWithInitInZp.isNotEmpty()) {
asmgen.out("; zp str and array initializations")
stringVarsWithInitInZp.forEach {
val name = asmgen.asmVariableName(it.key)
asmgen.out("""
lda #<${name}
ldy #>${name}
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #<${name}_init_value
ldy #>${name}_init_value
jsr prog8_lib.strcpy""")
}
arrayVarsWithInitInZp.forEach {
val size = it.value.size
val name = asmgen.asmVariableName(it.key)
asmgen.out("""
lda #<${name}_init_value
ldy #>${name}_init_value
sta cx16.r0L
sty cx16.r0H
lda #<${name}
ldy #>${name}
sta cx16.r1L
sty cx16.r1H
lda #<$size
ldy #>$size
jsr sys.memcopy""")
}
asmgen.out(" jmp +")
}
stringVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.key)+"_init_value"
val stringvalue = it.value.initialStringValue!!
outputStringvar(varname, it.value.dt, stringvalue.encoding, stringvalue.value)
}
arrayVarsWithInitInZp.forEach {
val varname = asmgen.asmVariableName(it.key)+"_init_value"
arrayVariable2asm(varname, it.value.dt, it.value.initialArrayValue!!, null)
}
asmgen.out("""+ tsx
stx prog8_lib.orig_stackpointer ; required for sys.exit()
ldx #255 ; init estack ptr
clv
clc""")
}
private fun zeropagevars2asm(scope: INameScope) {
val zpVariables = allocator.zeropageVars.filter { it.value.originalScope==scope }
for ((scopedName, zpvar) in zpVariables) {
if (scopedName.size == 2 && scopedName[0] == "cx16" && scopedName[1][0] == 'r' && scopedName[1][1].isDigit())
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}")
}
}
private fun nonZpVariables2asm(block: Block) {
val variables = variables.blockVars[block]?.filter { !allocator.isZpVar(it.scopedname) } ?: emptyList()
nonZpVariables2asm(variables)
}
private fun nonZpVariables2asm(sub: Subroutine) {
val variables = variables.subroutineVars[sub]?.filter { !allocator.isZpVar(it.scopedname) } ?: emptyList()
nonZpVariables2asm(variables)
}
private fun nonZpVariables2asm(variables: List<IVariablesAndConsts.StaticVariable>) {
asmgen.out("")
asmgen.out("; non-zeropage variables")
val (stringvars, othervars) = variables.partition { it.type==DataType.STR }
stringvars.forEach {
val stringvalue = it.initialValue as StringLiteral
outputStringvar(it.scopedname.last(), it.type, stringvalue.encoding, stringvalue.value)
}
othervars.sortedBy { it.type }.forEach {
staticVariable2asm(it)
}
}
private fun staticVariable2asm(variable: IVariablesAndConsts.StaticVariable) {
val name = variable.scopedname.last()
val value = variable.initialValue
val staticValue: Number =
if(value!=null) {
if(value is NumericLiteral) {
if(value.type== DataType.FLOAT)
value.number
else
value.number.toInt()
} else {
if(variable.type in NumericDatatypes)
throw AssemblyError("can only deal with constant numeric values for global vars")
else 0
}
} else 0
when (variable.type) {
DataType.UBYTE -> asmgen.out("$name\t.byte ${staticValue.toHex()}")
DataType.BYTE -> asmgen.out("$name\t.char $staticValue")
DataType.UWORD -> asmgen.out("$name\t.word ${staticValue.toHex()}")
DataType.WORD -> asmgen.out("$name\t.sint $staticValue")
DataType.FLOAT -> {
if(staticValue==0) {
asmgen.out("$name\t.byte 0,0,0,0,0 ; float")
} else {
val floatFill = compTarget.machine.getFloat(staticValue).makeFloatFillAsm()
asmgen.out("$name\t.byte $floatFill ; float $staticValue")
}
}
DataType.STR -> {
throw AssemblyError("all string vars should have been interned into prog")
}
in ArrayDatatypes -> arrayVariable2asm(name, variable.type, value as? ArrayLiteral, variable.arraysize)
else -> {
throw AssemblyError("weird dt")
}
}
}
private fun arrayVariable2asm(varname: String, dt: DataType, value: ArrayLiteral?, orNumberOfZeros: Int?) {
when(dt) {
DataType.ARRAY_UB -> {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.byte ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .byte " + chunk.joinToString())
}
}
DataType.ARRAY_B -> {
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.char ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .char " + chunk.joinToString())
}
}
DataType.ARRAY_UW -> {
val data = makeArrayFillDataUnsigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.word ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .word " + chunk.joinToString())
}
}
DataType.ARRAY_W -> {
val data = makeArrayFillDataSigned(dt, value, orNumberOfZeros)
if (data.size <= 16)
asmgen.out("$varname\t.sint ${data.joinToString()}")
else {
asmgen.out(varname)
for (chunk in data.chunked(16))
asmgen.out(" .sint " + chunk.joinToString())
}
}
DataType.ARRAY_F -> {
val array = value?.value ?:
Array(orNumberOfZeros!!) { defaultZero(ArrayToElementTypes.getValue(dt), Position.DUMMY) }
val floatFills = array.map {
val number = (it as NumericLiteral).number
compTarget.machine.getFloat(number).makeFloatFillAsm()
}
asmgen.out(varname)
for (f in array.zip(floatFills))
asmgen.out(" .byte ${f.second} ; float ${f.first}")
}
else -> throw AssemblyError("require array dt")
}
}
private fun memdefsAndConsts2asm(block: Block) {
val mvs = variables.blockMemvars[block] ?: emptySet()
val consts = variables.blockConsts[block] ?: emptySet()
memdefsAndConsts2asm(mvs, consts)
}
private fun memdefsAndConsts2asm(sub: Subroutine) {
val mvs = variables.subroutineMemvars[sub] ?: emptySet()
val consts = variables.subroutineConsts[sub] ?: emptySet()
memdefsAndConsts2asm(mvs, consts)
}
private fun memdefsAndConsts2asm(
memvars: Set<IVariablesAndConsts.MemoryMappedVariable>,
consts: Set<IVariablesAndConsts.ConstantNumberSymbol>
) {
memvars.forEach {
asmgen.out(" ${it.scopedname.last()} = ${it.address.toHex()}")
}
consts.forEach {
if(it.type==DataType.FLOAT)
asmgen.out(" ${it.scopedname.last()} = ${it.value}")
else
asmgen.out(" ${it.scopedname.last()} = ${it.value.toHex()}")
}
}
private fun asmsubs2asm(statements: List<Statement>) {
statements
.filter { it is Subroutine && it.isAsmSubroutine && it.asmAddress!=null }
.forEach { asmsub ->
asmsub as Subroutine
asmgen.out(" ${asmsub.name} = ${asmsub.asmAddress!!.toHex()}")
}
}
private fun outputStringvar(varname: String, dt: DataType, encoding: Encoding, value: String) {
asmgen.out("$varname\t; $dt $encoding:\"${escape(value).replace("\u0000", "<NULL>")}\"")
val bytes = compTarget.encodeString(value, encoding).plus(0.toUByte())
val outputBytes = bytes.map { "$" + it.toString(16).padStart(2, '0') }
for (chunk in outputBytes.chunked(16))
asmgen.out(" .byte " + chunk.joinToString())
}
private fun makeArrayFillDataUnsigned(dt: DataType, value: ArrayLiteral?, orNumberOfZeros: Int?): List<String> {
val array = value?.value ?:
Array(orNumberOfZeros!!) { defaultZero(ArrayToElementTypes.getValue(dt), Position.DUMMY) }
return when (dt) {
DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteral).number.toInt()
"$"+number.toString(16).padStart(2, '0')
}
DataType.ARRAY_UW -> array.map {
when (it) {
is NumericLiteral -> {
"$" + it.number.toInt().toString(16).padStart(4, '0')
}
is AddressOf -> {
asmgen.asmSymbolName(it.identifier)
}
is IdentifierReference -> {
asmgen.asmSymbolName(it)
}
else -> throw AssemblyError("weird array elt dt")
}
}
else -> throw AssemblyError("invalid dt")
}
}
private fun makeArrayFillDataSigned(dt: DataType, value: ArrayLiteral?, orNumberOfZeros: Int?): List<String> {
val array = value?.value ?:
Array(orNumberOfZeros!!) { defaultZero(ArrayToElementTypes.getValue(dt), Position.DUMMY) }
return when (dt) {
DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteral).number.toInt()
"$"+number.toString(16).padStart(2, '0')
}
DataType.ARRAY_B ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteral).number.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
DataType.ARRAY_UW -> array.map {
val number = (it as NumericLiteral).number.toInt()
"$" + number.toString(16).padStart(4, '0')
}
DataType.ARRAY_W -> array.map {
val number = (it as NumericLiteral).number.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
else -> throw AssemblyError("invalid dt")
}
}
}

View File

@ -0,0 +1,166 @@
package prog8.codegen.cpu6502
import com.github.michaelbull.result.fold
import com.github.michaelbull.result.onSuccess
import prog8.ast.base.ArrayDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.IntegerDatatypes
import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.Subroutine
import prog8.ast.statements.ZeropageWish
import prog8.compilerinterface.*
internal class VariableAllocator(private val vars: IVariablesAndConsts,
private val options: CompilationOptions,
private val errors: IErrorReporter) {
private val zeropage = options.compTarget.machine.zeropage
private val subroutineExtras = mutableMapOf<Subroutine, SubroutineExtraAsmInfo>()
private val memorySlabsInternal = mutableMapOf<String, Pair<UInt, UInt>>()
internal val memorySlabs: Map<String, Pair<UInt, UInt>> = memorySlabsInternal
internal val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
internal val zeropageVars: Map<List<String>, Zeropage.ZpAllocation> = zeropage.allocatedVariables
internal fun getMemorySlab(name: String) = memorySlabsInternal[name]
internal fun allocateMemorySlab(name: String, size: UInt, align: UInt) {
memorySlabsInternal[name] = Pair(size, align)
}
/**
* Allocate variables into the Zeropage.
* The result should be retrieved from the current machine's zeropage object!
*/
internal fun allocateZeropageVariables() {
if(options.zeropage== ZeropageType.DONTUSE)
return
val allVariables = (
vars.blockVars.asSequence().flatMap { it.value } +
vars.subroutineVars.asSequence().flatMap { it.value }
).toList()
val numberOfAllocatableVariables = allVariables.size
val varsRequiringZp = allVariables.filter { it.zp == ZeropageWish.REQUIRE_ZEROPAGE }
val varsPreferringZp = allVariables.filter { it.zp == ZeropageWish.PREFER_ZEROPAGE }
val varsDontCare = allVariables.filter { it.zp == ZeropageWish.DONTCARE }
val numberOfExplicitNonZpVariables = allVariables.count { it.zp == ZeropageWish.NOT_IN_ZEROPAGE }
require(varsDontCare.size + varsRequiringZp.size + varsPreferringZp.size + numberOfExplicitNonZpVariables == numberOfAllocatableVariables)
var numVariablesAllocatedInZP: Int = 0
var numberOfNonIntegerVariables: Int = 0
varsRequiringZp.forEach { variable ->
val numElements = numArrayElements(variable)
val result = zeropage.allocate(
variable.scopedname,
variable.type,
variable.scope,
numElements,
variable.initialValue,
variable.position,
errors
)
result.fold(
success = {
numVariablesAllocatedInZP++
},
failure = {
errors.err(it.message!!, variable.position)
}
)
}
if(errors.noErrors()) {
varsPreferringZp.forEach { variable ->
val numElements = numArrayElements(variable)
val result = zeropage.allocate(
variable.scopedname,
variable.type,
variable.scope,
numElements,
variable.initialValue,
variable.position,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
// no need to check for allocation error, if there is one, just allocate in normal system ram.
}
// 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) {
if(variable.type in IntegerDatatypes) {
if(zeropage.free.isEmpty()) {
break
} else {
val numElements = numArrayElements(variable)
val result = zeropage.allocate(
variable.scopedname,
variable.type,
variable.scope,
numElements,
variable.initialValue,
variable.position,
errors
)
result.onSuccess { numVariablesAllocatedInZP++ }
}
} else
numberOfNonIntegerVariables++
}
}
}
println(" number of allocated vars: $numberOfAllocatableVariables")
println(" put into zeropage: $numVariablesAllocatedInZP, non-zp allocatable: ${numberOfNonIntegerVariables+numberOfExplicitNonZpVariables}")
println(" zeropage free space: ${zeropage.free.size} bytes")
}
internal fun isZpVar(scopedName: List<String>) = scopedName in zeropage.allocatedVariables
private fun numArrayElements(variable: IVariablesAndConsts.StaticVariable) =
when(variable.type) {
DataType.STR -> (variable.initialValue as StringLiteral).value.length
in ArrayDatatypes -> variable.arraysize!!
else -> null
}
internal fun subroutineExtra(sub: Subroutine): SubroutineExtraAsmInfo {
var extra = subroutineExtras[sub]
return if(extra==null) {
extra = SubroutineExtraAsmInfo()
subroutineExtras[sub] = extra
extra
}
else
extra
}
internal fun getFloatAsmConst(number: Double): String {
val asmName = globalFloatConsts[number]
if(asmName!=null)
return asmName
val newName = "prog8_float_const_${globalFloatConsts.size}"
globalFloatConsts[number] = newName
return newName
}
}
/**
* This class contains various attributes that influence the assembly code generator.
* Conceptually it should be part of any INameScope.
* But because the resulting code only creates "real" scopes on a subroutine level,
* it's more consistent to only define these attributes on a Subroutine node.
*/
internal class SubroutineExtraAsmInfo {
var usedRegsaveA = false
var usedRegsaveX = false
var usedRegsaveY = false
var usedFloatEvalResultVar1 = false
var usedFloatEvalResultVar2 = false
val extraVars = mutableListOf<Triple<DataType, String, UInt?>>()
}

View File

@ -1,12 +1,12 @@
package prog8.codegen.target.cpu6502.codegen.assignment package prog8.codegen.cpu6502.assignment
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.codegen.cpu6502.AsmGen
import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.IMemSizer import prog8.compilerinterface.IMemSizer
import prog8.codegen.target.AssemblyError
import prog8.codegen.target.cpu6502.codegen.AsmGen
internal enum class TargetStorageKind { internal enum class TargetStorageKind {
@ -120,7 +120,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val array: ArrayIndexedExpression? = null, val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryRead? = null, val memory: DirectMemoryRead? = null,
val register: RegisterOrPair? = null, val register: RegisterOrPair? = null,
val number: NumericLiteralValue? = null, val number: NumericLiteral? = null,
val expression: Expression? = null val expression: Expression? = null
) )
{ {
@ -142,9 +142,9 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv) return AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, cv.type, number = cv)
return when(value) { return when(value) {
is NumericLiteralValue -> throw AssemblyError("should have been constant value") is NumericLiteral -> throw AssemblyError("should have been constant value")
is StringLiteralValue -> throw AssemblyError("string literal value should not occur anymore for asm generation") is StringLiteral -> throw AssemblyError("string literal value should not occur anymore for asm generation")
is ArrayLiteralValue -> throw AssemblyError("array 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 -> { is IdentifierReference -> {
val parameter = value.targetVarDecl(program)?.subroutineParameter val parameter = value.targetVarDecl(program)?.subroutineParameter
if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine) if(parameter!=null && parameter.definingSubroutine!!.isAsmSubroutine)
@ -167,7 +167,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") } val dt = value.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value) AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
} }
is FunctionCallExpr -> { is FunctionCallExpression -> {
when (val sub = value.target.targetStatement(program)) { when (val sub = value.target.targetStatement(program)) {
is Subroutine -> { is Subroutine -> {
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first

View File

@ -1,20 +1,22 @@
package prog8.codegen.target.cpu6502.codegen.assignment package prog8.codegen.cpu6502.assignment
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.toHex import prog8.ast.toHex
import prog8.codegen.target.AssemblyError import prog8.codegen.cpu6502.AsmGen
import prog8.codegen.target.cpu6502.codegen.AsmGen import prog8.codegen.cpu6502.VariableAllocator
import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.BuiltinFunctions import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.CpuType import prog8.compilerinterface.CpuType
import prog8.compilerinterface.builtinFunctionReturnType import prog8.compilerinterface.builtinFunctionReturnType
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) { internal class AssignmentAsmGen(private val program: Program,
private val asmgen: AsmGen,
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen) private val allocator: VariableAllocator) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, asmgen, allocator)
fun translate(assignment: Assignment) { fun translate(assignment: Assignment) {
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen) val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
@ -88,7 +90,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
assignRegisterpairWord(assign.target, RegisterOrPair.AY) assignRegisterpairWord(assign.target, RegisterOrPair.AY)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue") asmgen.out(" lda #<($arrayVarName+$indexValue) | ldy #>($arrayVarName+$indexValue)")
assignFloatFromAY(assign.target) assignFloatFromAY(assign.target)
} }
else -> else ->
@ -131,8 +133,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val value = assign.source.memory!! val value = assign.source.memory!!
when (value.addressExpression) { when (value.addressExpression) {
is NumericLiteralValue -> { is NumericLiteral -> {
val address = (value.addressExpression as NumericLiteralValue).number.toUInt() val address = (value.addressExpression as NumericLiteral).number.toUInt()
assignMemoryByte(assign.target, address, null) assignMemoryByte(assign.target, address, null)
} }
is IdentifierReference -> { is IdentifierReference -> {
@ -154,12 +156,12 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val sourceName = asmgen.asmSymbolName(value.identifier) val sourceName = asmgen.asmSymbolName(value.identifier)
assignAddressOf(assign.target, sourceName) assignAddressOf(assign.target, sourceName)
} }
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber") is NumericLiteral -> throw AssemblyError("source kind should have been literalnumber")
is IdentifierReference -> throw AssemblyError("source kind should have been variable") is IdentifierReference -> throw AssemblyError("source kind should have been variable")
is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array") is ArrayIndexedExpression -> throw AssemblyError("source kind should have been array")
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory") is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value) is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
is FunctionCallExpr -> { is FunctionCallExpression -> {
when (val sub = value.target.targetStatement(program)) { when (val sub = value.target.targetStatement(program)) {
is Subroutine -> { is Subroutine -> {
asmgen.saveXbeforeCall(value) asmgen.saveXbeforeCall(value)
@ -275,16 +277,52 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
containmentCheckIntoA(value) containmentCheckIntoA(value)
assignRegisterByte(assign.target, CpuRegister.A) assignRegisterByte(assign.target, CpuRegister.A)
} }
is PipeExpression -> {
asmgen.translatePipeExpression(value.expressions, value, false, false)
val resultDt = value.inferType(program)
val register =
if(resultDt.isBytes) RegisterOrPair.A
else if(resultDt.isWords) RegisterOrPair.AY
else if(resultDt istype DataType.FLOAT) RegisterOrPair.FAC1
else throw AssemblyError("invalid dt")
asmgen.assignRegister(register, assign.target)
}
is BinaryExpression -> {
if(value.operator in ComparisonOperators) {
// TODO real optimized code for comparison expressions that yield a boolean result value
// for now generate code for this: assign-false; if expr { assign-true }
translateNormalAssignment(
AsmAssignment(
AsmAssignSource(SourceStorageKind.LITERALNUMBER, program, asmgen, DataType.UBYTE, number=NumericLiteral.fromBoolean(false, assign.position)),
assign.target, false, program.memsizer, assign.position
)
)
val origTarget = assign.target.origAstTarget
if(origTarget!=null) {
val assignTrue = AnonymousScope(mutableListOf(
Assignment(origTarget, NumericLiteral.fromBoolean(true, assign.position), AssignmentOrigin.ASMGEN, assign.position)
), assign.position)
val assignFalse = AnonymousScope(mutableListOf(), assign.position)
val ifelse = IfElse(value.copy(), assignTrue, assignFalse, assign.position)
ifelse.linkParents(value)
asmgen.translate(ifelse)
}
else {
// no orig ast assign target, can't use the workaround, so fallback to stack eval
fallbackToStackEval(value, assign)
}
} else {
// Everything else just evaluate via the stack.
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...)
fallbackToStackEval(value, assign)
}
}
else -> { else -> {
// Everything else just evaluate via the stack. // Everything else just evaluate via the stack.
// (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here, // (we can't use the assignment helper functions (assignExpressionTo...) to do it via registers here,
// because the code here is the implementation of exactly that...) // because the code here is the implementation of exactly that...)
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression? fallbackToStackEval(value, assign)
asmgen.translateExpression(value)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
if(assign.target.kind!=TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
} }
} }
} }
@ -298,9 +336,18 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
} }
} }
private fun fallbackToStackEval(value: Expression, assign: AsmAssignment) {
// TODO DON'T STACK-EVAL THIS... by using a temp var? so that it becomes augmentable assignment expression?
asmgen.translateExpression(value)
if (assign.target.datatype in WordDatatypes && assign.source.datatype in ByteDatatypes)
asmgen.signExtendStackLsb(assign.source.datatype)
if (assign.target.kind != TargetStorageKind.STACK || assign.target.datatype != assign.source.datatype)
assignStackValue(assign.target)
}
private fun containmentCheckIntoA(containment: ContainmentCheck) { private fun containmentCheckIntoA(containment: ContainmentCheck) {
val elementDt = containment.element.inferType(program) val elementDt = containment.element.inferType(program)
val range = containment.iterable as? RangeExpr val range = containment.iterable as? RangeExpression
if(range!=null) { if(range!=null) {
val constRange = range.toConstantIntegerRange() val constRange = range.toConstantIntegerRange()
if(constRange!=null) if(constRange!=null)
@ -311,23 +358,57 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
if(variable!=null) { if(variable!=null) {
if(elementDt istype DataType.FLOAT) if(elementDt istype DataType.FLOAT)
throw AssemblyError("containment check of floats not supported") throw AssemblyError("containment check of floats not supported")
if(variable.autogeneratedDontRemove) { if(variable.origin!=VarDeclOrigin.USERCODE) {
when(variable.datatype) { when(variable.datatype) {
DataType.STR -> { DataType.STR -> {
require(elementDt.isBytes) require(elementDt.isBytes)
val stringVal = variable.value as StringLiteralValue val stringVal = variable.value as StringLiteral
val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding) if(stringVal.value.length > ContainmentCheck.max_inlined_string_length) {
// use subroutine
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
asmgen.out(" ldy #${stringVal.value.length}")
asmgen.out(" jsr prog8_lib.containment_bytearray")
return
} else {
// inline cmp table
val encoded = program.encoding.encodeString(stringVal.value, stringVal.encoding)
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() }) return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
} }
}
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
// require(elementDt istype DataType.FLOAT) // require(elementDt istype DataType.FLOAT)
throw AssemblyError("containment check of floats not supported") throw AssemblyError("containment check of floats not supported")
} }
in ArrayDatatypes -> { in ArrayDatatypes -> {
require(elementDt.isInteger) require(elementDt.isInteger)
val arrayVal = variable.value as ArrayLiteralValue val arrayVal = variable.value as ArrayLiteral
val dt = elementDt.getOr(DataType.UNDEFINED)
if(arrayVal.value.size > ContainmentCheck.max_inlined_string_length) {
// use subroutine
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
when(dt) {
in ByteDatatypes -> {
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
asmgen.out(" ldy #${arrayVal.value.size}")
asmgen.out(" jsr prog8_lib.containment_bytearray")
}
in WordDatatypes -> {
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
asmgen.out(" ldy #${arrayVal.value.size}")
asmgen.out(" jsr prog8_lib.containment_wordarray")
}
else -> throw AssemblyError("invalid dt")
}
return
} else {
// inline cmp table
val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() } val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() }
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values) return containmentCheckIntoA(containment.element, dt, values)
}
} }
else -> throw AssemblyError("invalid dt") else -> throw AssemblyError("invalid dt")
} }
@ -335,16 +416,17 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference) val varname = asmgen.asmVariableName(containment.iterable as IdentifierReference)
when(variable.datatype) { when(variable.datatype) {
DataType.STR -> { DataType.STR -> {
// use subroutine
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname) assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE) assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
val stringVal = variable.value as StringLiteralValue val stringVal = variable.value as StringLiteral
asmgen.out(" ldy #${stringVal.value.length}") asmgen.out(" ldy #${stringVal.value.length}")
asmgen.out(" jsr prog8_lib.containment_bytearray") asmgen.out(" jsr prog8_lib.containment_bytearray")
return return
} }
DataType.ARRAY_F -> throw AssemblyError("containment check of floats not supported") DataType.ARRAY_F -> throw AssemblyError("containment check of floats not supported")
DataType.ARRAY_B, DataType.ARRAY_UB -> { DataType.ARRAY_B, DataType.ARRAY_UB -> {
val arrayVal = variable.value as ArrayLiteralValue val arrayVal = variable.value as ArrayLiteral
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname) assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W1"), varname)
assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE) assignExpressionToRegister(containment.element, RegisterOrPair.A, elementDt istype DataType.BYTE)
asmgen.out(" ldy #${arrayVal.value.size}") asmgen.out(" ldy #${arrayVal.value.size}")
@ -352,7 +434,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
return return
} }
DataType.ARRAY_W, DataType.ARRAY_UW -> { DataType.ARRAY_W, DataType.ARRAY_UW -> {
val arrayVal = variable.value as ArrayLiteralValue val arrayVal = variable.value as ArrayLiteral
assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine) assignExpressionToVariable(containment.element, "P8ZP_SCRATCH_W1", elementDt.getOr(DataType.UNDEFINED), containment.definingSubroutine)
assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname) assignAddressOf(AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, DataType.UWORD, containment.definingSubroutine, "P8ZP_SCRATCH_W2"), varname)
asmgen.out(" ldy #${arrayVal.value.size}") asmgen.out(" ldy #${arrayVal.value.size}")
@ -362,15 +444,19 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
else -> throw AssemblyError("invalid dt") else -> throw AssemblyError("invalid dt")
} }
} }
val stringVal = containment.iterable as? StringLiteralValue val stringVal = containment.iterable as? StringLiteral
if(stringVal!=null) { if(stringVal!=null) {
require(elementDt.isBytes) require(elementDt.isBytes)
val encoded = program.encoding.encodeString(stringVal.value, stringVal.altEncoding) if(stringVal.value.length > ContainmentCheck.max_inlined_string_length)
throw AssemblyError("string should have been inlined in if it was this long")
val encoded = program.encoding.encodeString(stringVal.value, stringVal.encoding)
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() }) return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), encoded.map { it.toInt() })
} }
val arrayVal = containment.iterable as? ArrayLiteralValue val arrayVal = containment.iterable as? ArrayLiteral
if(arrayVal!=null) { if(arrayVal!=null) {
require(elementDt.isInteger) require(elementDt.isInteger)
if(arrayVal.value.size > ContainmentCheck.max_inlined_string_length)
throw AssemblyError("array should have been inlined in if it was this long")
val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() } val values = arrayVal.value.map { it.constValue(program)!!.number.toInt() }
return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values) return containmentCheckIntoA(containment.element, elementDt.getOr(DataType.UNDEFINED), values)
} }
@ -381,8 +467,6 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
private fun containmentCheckIntoA(element: Expression, dt: DataType, values: List<Number>) { private fun containmentCheckIntoA(element: Expression, dt: DataType, values: List<Number>) {
if(values.size<2) if(values.size<2)
throw AssemblyError("containment check against 0 or 1 values should have been optimized away") throw AssemblyError("containment check against 0 or 1 values should have been optimized away")
// TODO don't generate a huge cmp-list when we go over a certain number of values
val containsLabel = asmgen.makeLabel("contains") val containsLabel = asmgen.makeLabel("contains")
when(dt) { when(dt) {
in ByteDatatypes -> { in ByteDatatypes -> {
@ -463,8 +547,8 @@ $containsLabel lda #1
} }
when (value.addressExpression) { when (value.addressExpression) {
is NumericLiteralValue -> { is NumericLiteral -> {
val address = (value.addressExpression as NumericLiteralValue).number.toUInt() val address = (value.addressExpression as NumericLiteral).number.toUInt()
assignMemoryByteIntoWord(target, address, null) assignMemoryByteIntoWord(target, address, null)
return return
} }
@ -486,7 +570,7 @@ $containsLabel lda #1
} }
} }
} }
is NumericLiteralValue -> throw AssemblyError("a cast of a literal value should have been const-folded away") is NumericLiteral -> throw AssemblyError("a cast of a literal value should have been const-folded away")
else -> {} else -> {}
} }
@ -564,6 +648,11 @@ $containsLabel lda #1
} }
} }
if(target.kind==TargetStorageKind.REGISTER) {
assignExpressionToRegister(value, target.register!!, targetDt==DataType.BYTE || targetDt==DataType.WORD)
return
}
if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) { if(targetDt==DataType.FLOAT && (target.register==RegisterOrPair.FAC1 || target.register==RegisterOrPair.FAC2)) {
when(valueDt) { when(valueDt) {
DataType.UBYTE -> { DataType.UBYTE -> {
@ -596,7 +685,7 @@ $containsLabel lda #1
} }
private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) { private fun assignCastViaLsbFunc(value: Expression, target: AsmAssignTarget) {
val lsb = FunctionCallExpr(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position) val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
lsb.linkParents(value.parent) lsb.linkParents(value.parent)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb) val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
val assign = AsmAssignment(src, target, false, program.memsizer, value.position) val assign = AsmAssignment(src, target, false, program.memsizer, value.position)
@ -940,8 +1029,8 @@ $containsLabel lda #1
} }
DataType.FLOAT -> { DataType.FLOAT -> {
asmgen.out(""" asmgen.out("""
lda #<${target.asmVarname}+$scaledIdx lda #<(${target.asmVarname}+$scaledIdx)
ldy #>${target.asmVarname}+$scaledIdx ldy #>(${target.asmVarname}+$scaledIdx)
jsr floats.pop_float jsr floats.pop_float
""") """)
} }
@ -1153,8 +1242,8 @@ $containsLabel lda #1
ldy #>$sourceName ldy #>$sourceName
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1 sty P8ZP_SCRATCH_W1+1
lda #<${target.asmVarname}+$scaledIdx lda #<(${target.asmVarname}+$scaledIdx)
ldy #>${target.asmVarname}+$scaledIdx ldy #>(${target.asmVarname}+$scaledIdx)
jsr floats.copy_float jsr floats.copy_float
""") """)
} }
@ -1306,17 +1395,13 @@ $containsLabel lda #1
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
asmgen.out(""" asmgen.out("""
lda $sourceName lda #<$sourceName
sta ${target.asmVarname} ldy #>$sourceName
lda $sourceName+1 sta P8ZP_SCRATCH_W1
sta ${target.asmVarname}+1 sty P8ZP_SCRATCH_W1+1
lda $sourceName+2 lda #<${target.asmVarname}
sta ${target.asmVarname}+2 ldy #>${target.asmVarname}
lda $sourceName+3 jsr floats.copy_float""")
sta ${target.asmVarname}+3
lda $sourceName+4
sta ${target.asmVarname}+4
""")
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
asmgen.out(""" asmgen.out("""
@ -1437,7 +1522,7 @@ $containsLabel lda #1
pha pha
ora #$7f ora #$7f
bmi + bmi +
ldx #0 lda #0
+ tax + tax
pla""") pla""")
RegisterOrPair.AY -> asmgen.out(""" RegisterOrPair.AY -> asmgen.out("""
@ -1445,7 +1530,7 @@ $containsLabel lda #1
pha pha
ora #$7f ora #$7f
bmi + bmi +
ldy #0 lda #0
+ tay + tay
pla""") pla""")
RegisterOrPair.XY -> asmgen.out(""" RegisterOrPair.XY -> asmgen.out("""
@ -1453,9 +1538,19 @@ $containsLabel lda #1
tax tax
ora #$7f ora #$7f
bmi + bmi +
ldy #0 lda #0
+ tay""") + tay""")
else -> throw AssemblyError("only reg pairs are words") in Cx16VirtualRegisters -> {
val regname = wordtarget.register.name.lowercase()
asmgen.out("""
lda $sourceName
sta cx16.$regname
ora #$7f
bmi +
lda #0
+ sta cx16.$regname+1""")
}
else -> throw AssemblyError("only reg pairs allowed as word target ${wordtarget.register}")
} }
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
@ -1506,7 +1601,14 @@ $containsLabel lda #1
RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda $sourceName") RegisterOrPair.AX -> asmgen.out(" ldx #0 | lda $sourceName")
RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda $sourceName") RegisterOrPair.AY -> asmgen.out(" ldy #0 | lda $sourceName")
RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName") RegisterOrPair.XY -> asmgen.out(" ldy #0 | ldx $sourceName")
else -> throw AssemblyError("only reg pairs are words") in Cx16VirtualRegisters -> {
val regname = wordtarget.register.name.lowercase()
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceName | sta cx16.$regname | stz cx16.$regname+1")
else
asmgen.out(" lda $sourceName | sta cx16.$regname | lda #0 | sta cx16.$regname+1")
}
else -> throw AssemblyError("only reg pairs allowed as word target")
} }
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
@ -2019,7 +2121,7 @@ $containsLabel lda #1
} }
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte") TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
TargetStorageKind.REGISTER -> { TargetStorageKind.REGISTER -> {
val floatConst = asmgen.getFloatAsmConst(float) val floatConst = allocator.getFloatAsmConst(float)
when(target.register!!) { when(target.register!!) {
RegisterOrPair.FAC1 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.MOVFM") RegisterOrPair.FAC1 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.MOVFM")
RegisterOrPair.FAC2 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.CONUPK") RegisterOrPair.FAC2 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.CONUPK")
@ -2027,27 +2129,23 @@ $containsLabel lda #1
} }
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
val floatConst = asmgen.getFloatAsmConst(float) val floatConst = allocator.getFloatAsmConst(float)
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float") asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
} }
} }
} else { } else {
// non-zero value // non-zero value
val constFloat = asmgen.getFloatAsmConst(float) val constFloat = allocator.getFloatAsmConst(float)
when(target.kind) { when(target.kind) {
TargetStorageKind.VARIABLE -> { TargetStorageKind.VARIABLE -> {
asmgen.out(""" asmgen.out("""
lda $constFloat lda #<$constFloat
sta ${target.asmVarname} ldy #>$constFloat
lda $constFloat+1 sta P8ZP_SCRATCH_W1
sta ${target.asmVarname}+1 sty P8ZP_SCRATCH_W1+1
lda $constFloat+2 lda #<${target.asmVarname}
sta ${target.asmVarname}+2 ldy #>${target.asmVarname}
lda $constFloat+3 jsr floats.copy_float""")
sta ${target.asmVarname}+3
lda $constFloat+4
sta ${target.asmVarname}+4
""")
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
val arrayVarName = target.asmVarname val arrayVarName = target.asmVarname
@ -2055,17 +2153,13 @@ $containsLabel lda #1
if (constIndex!=null) { if (constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT) val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
asmgen.out(""" asmgen.out("""
lda $constFloat lda #<$constFloat
sta $arrayVarName+$indexValue ldy #>$constFloat
lda $constFloat+1 sta P8ZP_SCRATCH_W1
sta $arrayVarName+$indexValue+1 sty P8ZP_SCRATCH_W1+1
lda $constFloat+2 lda #<($arrayVarName+$indexValue)
sta $arrayVarName+$indexValue+2 ldy #>($arrayVarName+$indexValue)
lda $constFloat+3 jsr floats.copy_float""")
sta $arrayVarName+$indexValue+3
lda $constFloat+4
sta $arrayVarName+$indexValue+4
""")
} else { } else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference) val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out(""" asmgen.out("""
@ -2084,7 +2178,7 @@ $containsLabel lda #1
} }
TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte") TargetStorageKind.MEMORY -> throw AssemblyError("can't assign float to memory byte")
TargetStorageKind.REGISTER -> { TargetStorageKind.REGISTER -> {
val floatConst = asmgen.getFloatAsmConst(float) val floatConst = allocator.getFloatAsmConst(float)
when(target.register!!) { when(target.register!!) {
RegisterOrPair.FAC1 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.MOVFM") RegisterOrPair.FAC1 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.MOVFM")
RegisterOrPair.FAC2 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.CONUPK") RegisterOrPair.FAC2 -> asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.CONUPK")
@ -2092,7 +2186,7 @@ $containsLabel lda #1
} }
} }
TargetStorageKind.STACK -> { TargetStorageKind.STACK -> {
val floatConst = asmgen.getFloatAsmConst(float) val floatConst = allocator.getFloatAsmConst(float)
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float") asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr floats.push_float")
} }
} }
@ -2248,11 +2342,11 @@ $containsLabel lda #1
private fun storeRegisterAInMemoryAddress(memoryAddress: DirectMemoryWrite) { private fun storeRegisterAInMemoryAddress(memoryAddress: DirectMemoryWrite) {
val addressExpr = memoryAddress.addressExpression val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue val addressLv = addressExpr as? NumericLiteral
fun storeViaExprEval() { fun storeViaExprEval() {
when(addressExpr) { when(addressExpr) {
is NumericLiteralValue, is IdentifierReference -> { is NumericLiteral, is IdentifierReference -> {
assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null) assignExpressionToVariable(addressExpr, "P8ZP_SCRATCH_W2", DataType.UWORD, null)
asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2") asmgen.storeAIntoZpPointerVar("P8ZP_SCRATCH_W2")
} }

View File

@ -1,18 +1,20 @@
package prog8.codegen.target.cpu6502.codegen.assignment package prog8.codegen.cpu6502.assignment
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.toHex import prog8.ast.toHex
import prog8.codegen.target.AssemblyError import prog8.codegen.cpu6502.AsmGen
import prog8.codegen.target.cpu6502.codegen.AsmGen import prog8.codegen.cpu6502.VariableAllocator
import prog8.compilerinterface.AssemblyError
import prog8.compilerinterface.CpuType import prog8.compilerinterface.CpuType
internal class AugmentableAssignmentAsmGen(private val program: Program, internal class AugmentableAssignmentAsmGen(private val program: Program,
private val assignmentAsmGen: AssignmentAsmGen, private val assignmentAsmGen: AssignmentAsmGen,
private val asmgen: AsmGen private val asmgen: AsmGen,
private val allocator: VariableAllocator
) { ) {
fun translate(assign: AsmAssignment) { fun translate(assign: AsmAssignment) {
require(assign.isAugmentable) require(assign.isAugmentable)
@ -160,7 +162,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
origValue origValue
} }
val valueLv = (value as? NumericLiteralValue)?.number val valueLv = (value as? NumericLiteral)?.number
val ident = value as? IdentifierReference val ident = value as? IdentifierReference
val memread = value as? DirectMemoryRead val memread = value as? DirectMemoryRead
@ -173,7 +175,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ident != null -> inplaceModification_byte_variable_to_variable(target.asmVarname, target.datatype, operator, ident) 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) memread != null -> inplaceModification_byte_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
value is TypecastExpression -> { value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value) inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
} }
else -> inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value) else -> inplaceModification_byte_value_to_variable(target.asmVarname, target.datatype, operator, value)
@ -185,7 +187,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ident != null -> inplaceModification_word_variable_to_variable(target.asmVarname, target.datatype, operator, ident) 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) memread != null -> inplaceModification_word_memread_to_variable(target.asmVarname, target.datatype, operator, memread)
value is TypecastExpression -> { value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator))
return
inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value) inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
} }
else -> inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value) else -> inplaceModification_word_value_to_variable(target.asmVarname, target.datatype, operator, value)
@ -196,7 +199,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
valueLv != null -> inplaceModification_float_litval_to_variable(target.asmVarname, operator, valueLv.toDouble(), target.scope!!) 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!!) ident != null -> inplaceModification_float_variable_to_variable(target.asmVarname, operator, ident, target.scope!!)
value is TypecastExpression -> { value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!) inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
} }
else -> inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!) else -> inplaceModification_float_value_to_variable(target.asmVarname, operator, value, target.scope!!)
@ -208,15 +211,15 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
val memory = target.memory!! val memory = target.memory!!
when (memory.addressExpression) { when (memory.addressExpression) {
is NumericLiteralValue -> { is NumericLiteral -> {
val addr = (memory.addressExpression as NumericLiteralValue).number.toInt() val addr = (memory.addressExpression as NumericLiteral).number.toInt()
// re-use code to assign a variable, instead this time, use a direct memory address // re-use code to assign a variable, instead this time, use a direct memory address
when { when {
valueLv != null -> inplaceModification_byte_litval_to_variable(addr.toHex(), DataType.UBYTE, operator, valueLv.toInt()) 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) 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) memread != null -> inplaceModification_byte_memread_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
value is TypecastExpression -> { value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value) inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
} }
else -> inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value) else -> inplaceModification_byte_value_to_variable(addr.toHex(), DataType.UBYTE, operator, value)
@ -228,7 +231,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
valueLv != null -> inplaceModification_byte_litval_to_pointer(pointer, operator, valueLv.toInt()) valueLv != null -> inplaceModification_byte_litval_to_pointer(pointer, operator, valueLv.toInt())
ident != null -> inplaceModification_byte_variable_to_pointer(pointer, operator, ident) ident != null -> inplaceModification_byte_variable_to_pointer(pointer, operator, ident)
value is TypecastExpression -> { value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_pointer(pointer, operator, value) inplaceModification_byte_value_to_pointer(pointer, operator, value)
} }
else -> inplaceModification_byte_value_to_pointer(pointer, operator, value) else -> inplaceModification_byte_value_to_pointer(pointer, operator, value)
@ -243,7 +246,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ident != null -> inplaceModification_byte_variable_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, ident) 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) memread != null -> inplaceModification_byte_memread_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, memread)
value is TypecastExpression -> { value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value) inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
} }
else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value) else -> inplaceModification_byte_value_to_variable("P8ZP_SCRATCH_B1", DataType.UBYTE, operator, value)
@ -254,7 +257,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
TargetStorageKind.ARRAY -> { TargetStorageKind.ARRAY -> {
with(target.array!!.indexer) { with(target.array!!.indexer) {
val indexNum = indexExpr as? NumericLiteralValue val indexNum = indexExpr as? NumericLiteral
val indexVar = indexExpr as? IdentifierReference val indexVar = indexExpr as? IdentifierReference
when { when {
indexNum!=null -> { indexNum!=null -> {
@ -266,7 +269,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ident != null -> inplaceModification_byte_variable_to_variable(targetVarName, target.datatype, operator, ident) ident != null -> inplaceModification_byte_variable_to_variable(targetVarName, target.datatype, operator, ident)
memread != null -> inplaceModification_byte_memread_to_variable(targetVarName, target.datatype, operator, memread) memread != null -> inplaceModification_byte_memread_to_variable(targetVarName, target.datatype, operator, memread)
value is TypecastExpression -> { value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value) inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
} }
else -> inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value) else -> inplaceModification_byte_value_to_variable(targetVarName, target.datatype, operator, value)
@ -278,7 +281,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
ident != null -> inplaceModification_word_variable_to_variable(targetVarName, target.datatype, operator, ident) ident != null -> inplaceModification_word_variable_to_variable(targetVarName, target.datatype, operator, ident)
memread != null -> inplaceModification_word_memread_to_variable(targetVarName, target.datatype, operator, memread) memread != null -> inplaceModification_word_memread_to_variable(targetVarName, target.datatype, operator, memread)
value is TypecastExpression -> { value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value) inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
} }
else -> inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value) else -> inplaceModification_word_value_to_variable(targetVarName, target.datatype, operator, value)
@ -289,7 +292,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
valueLv != null -> inplaceModification_float_litval_to_variable(targetVarName, operator, valueLv.toDouble(), target.scope!!) 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!!) ident != null -> inplaceModification_float_variable_to_variable(targetVarName, operator, ident, target.scope!!)
value is TypecastExpression -> { value is TypecastExpression -> {
if (tryRemoveRedundantCast(value, target, operator)) return if (tryInplaceModifyWithRemovedRedundantCast(value, target, operator)) return
inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!) inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
} }
else -> inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!) else -> inplaceModification_float_value_to_variable(targetVarName, operator, value, target.scope!!)
@ -348,7 +351,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
} }
private fun tryRemoveRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean { private fun tryInplaceModifyWithRemovedRedundantCast(value: TypecastExpression, target: AsmAssignTarget, operator: String): Boolean {
if (target.datatype == value.type) { if (target.datatype == value.type) {
val childIDt = value.expression.inferType(program) val childIDt = value.expression.inferType(program)
val childDt = childIDt.getOrElse { throw AssemblyError("unknown dt") } val childDt = childIDt.getOrElse { throw AssemblyError("unknown dt") }
@ -744,18 +747,18 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name") "^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
"==" -> { "==" -> {
asmgen.out(""" asmgen.out("""
lda #$value lda $name
cmp $name cmp #$value
beq + beq +
lda #0 lda #0
bne ++ beq ++
+ lda #1 + lda #1
+ sta $name""") + sta $name""")
} }
"!=" -> { "!=" -> {
asmgen.out(""" asmgen.out("""
lda #$value lda $name
cmp $name cmp #$value
beq + beq +
lda #1 lda #1
bne ++ bne ++
@ -1066,6 +1069,18 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else else
asmgen.out(" lda #0 | sta $name | sta $name+1") asmgen.out(" lda #0 | sta $name | sta $name+1")
} }
value == 0x00ff -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
}
value == 0xff00 -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
}
value and 255 == 0 -> { value and 255 == 0 -> {
if(asmgen.isTargetCpu(CpuType.CPU65c02)) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name") asmgen.out(" stz $name")
@ -1145,25 +1160,30 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
+""") +""")
else else
asmgen.out(""" asmgen.out("""
ldy #0 ldy #255
lda $otherName lda $otherName
bpl + bpl +
dey ; sign extend iny ; sign extend
+ sty P8ZP_SCRATCH_B1 + eor #255
lda $name
sec sec
sbc $otherName adc $name
sta $name sta $name
lda $name+1 tya
sbc P8ZP_SCRATCH_B1 adc $name+1
sta $name+1""") sta $name+1""")
} }
"*" -> { "*" -> {
if(valueDt==DataType.UBYTE) {
asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1") asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1")
if(asmgen.isTargetCpu(CpuType.CPU65c02)) if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ZP_SCRATCH_W1+1") asmgen.out(" stz P8ZP_SCRATCH_W1+1")
else else
asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1") asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1")
} else {
asmgen.out(" lda $otherName")
asmgen.signExtendAYlsb(valueDt)
asmgen.out(" sta P8ZP_SCRATCH_W1 | sty P8ZP_SCRATCH_W1+1")
}
asmgen.out(""" asmgen.out("""
lda $name lda $name
ldy $name+1 ldy $name+1
@ -1427,29 +1447,28 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta $name+1""") sta $name+1""")
} }
"-" -> { "-" -> {
asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_REG", valueDt, null) asmgen.assignExpressionToVariable(value, "P8ZP_SCRATCH_B1", valueDt, null)
if(valueDt==DataType.UBYTE) if(valueDt==DataType.UBYTE)
asmgen.out(""" asmgen.out("""
lda $name lda $name
sec sec
sbc P8ZP_SCRATCH_REG sbc P8ZP_SCRATCH_B1
sta $name sta $name
bcs + bcs +
dec $name+1 dec $name+1
+""") +""")
else else
asmgen.out(""" asmgen.out("""
ldy #0 ldy #255
lda P8ZP_SCRATCH_REG lda P8ZP_SCRATCH_B1
bpl + bpl +
dey ; sign extend iny ; sign extend
+ sty P8ZP_SCRATCH_B1 + eor #255
lda $name
sec sec
sbc P8ZP_SCRATCH_REG adc $name
sta $name sta $name
lda $name+1 tya
sbc P8ZP_SCRATCH_B1 adc $name+1
sta $name+1""") sta $name+1""")
} }
"*" -> { "*" -> {
@ -1628,7 +1647,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.saveRegisterLocal(CpuRegister.X, scope) asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) { when (operator) {
"**" -> { "**" -> {
if(asmgen.haveFPWR()) { if(asmgen.haveFPWRcall()) {
asmgen.out(""" asmgen.out("""
lda #<$name lda #<$name
ldy #>$name ldy #>$name
@ -1701,11 +1720,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
} }
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: Subroutine) {
val constValueName = asmgen.getFloatAsmConst(value) val constValueName = allocator.getFloatAsmConst(value)
asmgen.saveRegisterLocal(CpuRegister.X, scope) asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) { when (operator) {
"**" -> { "**" -> {
if(asmgen.haveFPWR()) { if(asmgen.haveFPWRcall()) {
asmgen.out(""" asmgen.out("""
lda #<$name lda #<$name
ldy #>$name ldy #>$name
@ -1850,8 +1869,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
val mem = target.memory!! val mem = target.memory!!
when (mem.addressExpression) { when (mem.addressExpression) {
is NumericLiteralValue -> { is NumericLiteral -> {
val addr = (mem.addressExpression as NumericLiteralValue).number.toHex() val addr = (mem.addressExpression as NumericLiteral).number.toHex()
asmgen.out(""" asmgen.out("""
lda $addr lda $addr
beq + beq +
@ -1978,8 +1997,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
TargetStorageKind.MEMORY -> { TargetStorageKind.MEMORY -> {
val memory = target.memory!! val memory = target.memory!!
when (memory.addressExpression) { when (memory.addressExpression) {
is NumericLiteralValue -> { is NumericLiteral -> {
val addr = (memory.addressExpression as NumericLiteralValue).number.toHex() val addr = (memory.addressExpression as NumericLiteral).number.toHex()
asmgen.out(""" asmgen.out("""
lda $addr lda $addr
eor #255 eor #255
@ -2086,40 +2105,38 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when(target.register!!) { //P8ZP_SCRATCH_REG when(target.register!!) { //P8ZP_SCRATCH_REG
RegisterOrPair.AX -> { RegisterOrPair.AX -> {
asmgen.out(""" asmgen.out("""
sta P8ZP_SCRATCH_REG
stx P8ZP_SCRATCH_REG+1
lda #0
sec sec
sbc P8ZP_SCRATCH_REG eor #255
adc #0
pha pha
lda #0 txa
sbc P8ZP_SCRATCH_REG+1 eor #255
adc #0
tax tax
pla""") pla""")
} }
RegisterOrPair.AY -> { RegisterOrPair.AY -> {
asmgen.out(""" asmgen.out("""
sta P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_REG+1
lda #0
sec sec
sbc P8ZP_SCRATCH_REG eor #255
adc #0
pha pha
lda #0 tya
sbc P8ZP_SCRATCH_REG+1 eor #255
adc #0
tay tay
pla""") pla""")
} }
RegisterOrPair.XY -> { RegisterOrPair.XY -> {
asmgen.out(""" asmgen.out("""
stx P8ZP_SCRATCH_REG
sty P8ZP_SCRATCH_REG+1
lda #0
sec sec
sbc P8ZP_SCRATCH_REG txa
eor #255
adc #0
tax tax
lda #0 tya
sbc P8ZP_SCRATCH_REG+1 eor #255
adc #0
tay""") tay""")
} }
in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers") in Cx16VirtualRegisters -> throw AssemblyError("cx16 virtual regs should be variables, not real registers")

View File

@ -0,0 +1,46 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':compilerInterfaces')
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.12"
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
}
// note: there are no unit tests in this module!

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<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="compilerAst" />
<orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
</component>
</module>

View File

@ -0,0 +1,18 @@
package prog8.codegen.experimental6502
import prog8.ast.Program
import prog8.compilerinterface.*
class AsmGen(internal val program: Program,
internal val errors: IErrorReporter,
internal val variables: IVariablesAndConsts,
internal val options: CompilationOptions): IAssemblyGenerator {
override fun compileToAssembly(): IAssemblyProgram? {
println("\n** experimental 65(c)02 code generator **\n")
println("..todo: create assembly code into ${options.outputDir.toAbsolutePath()}..")
return AssemblyProgram("dummy")
}
}

View File

@ -0,0 +1,13 @@
package prog8.codegen.experimental6502
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IAssemblyProgram
internal class AssemblyProgram(override val name: String) : IAssemblyProgram
{
override fun assemble(options: CompilationOptions): Boolean {
println("..todo: assemble code into binary..")
return false
}
}

View File

@ -0,0 +1,46 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm"
}
java {
toolchain {
languageVersion = JavaLanguageVersion.of(javaVersion)
}
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = javaVersion
}
}
dependencies {
implementation project(':compilerInterfaces')
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.12"
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
}
// note: there are no unit tests in this module!

View File

@ -0,0 +1,27 @@
package prog8.codegen.target
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.codegen.target.c128.C128MachineDefinition
import prog8.codegen.target.cbm.CbmMemorySizer
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
class C128Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C128MachineDefinition()
companion object {
const val NAME = "c128"
}
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
}

View File

@ -0,0 +1,27 @@
package prog8.codegen.target
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.codegen.target.c64.C64MachineDefinition
import prog8.codegen.target.cbm.CbmMemorySizer
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
class C64Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = C64MachineDefinition()
companion object {
const val NAME = "c64"
}
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
}

View File

@ -0,0 +1,27 @@
package prog8.codegen.target
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.codegen.target.cbm.CbmMemorySizer
import prog8.codegen.target.cbm.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cbm.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.codegen.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IMemSizer
import prog8.compilerinterface.IStringEncoding
class Cx16Target: ICompilationTarget, IStringEncoding by Encoder, IMemSizer by CbmMemorySizer {
override val name = NAME
override val machine = CX16MachineDefinition()
companion object {
const val NAME = "cx16"
}
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
}

View File

@ -0,0 +1,35 @@
package prog8.codegen.target
import com.github.michaelbull.result.fold
import prog8.ast.base.FatalAstException
import prog8.codegen.target.cbm.IsoEncoding
import prog8.codegen.target.cbm.PetsciiEncoding
import prog8.compilerinterface.Encoding
import prog8.compilerinterface.IStringEncoding
internal object Encoder: IStringEncoding {
override fun encodeString(str: String, encoding: Encoding): List<UByte> { // TODO use Result
val coded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.encodePetscii(str, true)
Encoding.SCREENCODES -> PetsciiEncoding.encodeScreencode(str, true)
Encoding.ISO -> IsoEncoding.encode(str)
else -> throw FatalAstException("unsupported encoding $encoding")
}
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, encoding: Encoding): String { // TODO use Result
val decoded = when(encoding) {
Encoding.PETSCII -> PetsciiEncoding.decodePetscii(bytes, true)
Encoding.SCREENCODES -> PetsciiEncoding.decodeScreencode(bytes, true)
Encoding.ISO -> IsoEncoding.decode(bytes)
else -> throw FatalAstException("unsupported encoding $encoding")
}
return decoded.fold(
failure = { throw it },
success = { it }
)
}
}

View File

@ -1,9 +1,7 @@
package prog8.codegen.target.c128 package prog8.codegen.target.c128
import prog8.ast.base.DataType
import prog8.codegen.target.c64.normal6502instructions import prog8.codegen.target.c64.normal6502instructions
import prog8.codegen.target.cbm.Mflpt5 import prog8.codegen.target.cbm.Mflpt5
import prog8.codegen.target.cbm.viceMonListName
import prog8.compilerinterface.* import prog8.compilerinterface.*
import java.io.IOException import java.io.IOException
import java.nio.file.Path import java.nio.file.Path
@ -58,7 +56,6 @@ class C128MachineDefinition: IMachineDefinition {
} }
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> = emptyMap()
override fun initializeZeropage(compilerOptions: CompilationOptions) { override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C128Zeropage(compilerOptions) zeropage = C128Zeropage(compilerOptions)

View File

@ -1,8 +1,6 @@
package prog8.codegen.target.c64 package prog8.codegen.target.c64
import prog8.ast.base.DataType
import prog8.codegen.target.cbm.Mflpt5 import prog8.codegen.target.cbm.Mflpt5
import prog8.codegen.target.cbm.viceMonListName
import prog8.compilerinterface.* import prog8.compilerinterface.*
import java.io.IOException import java.io.IOException
import java.nio.file.Path import java.nio.file.Path
@ -57,7 +55,6 @@ class C64MachineDefinition: IMachineDefinition {
} }
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0xd000u..0xdfffu
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> = emptyMap()
override fun initializeZeropage(compilerOptions: CompilationOptions) { override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = C64Zeropage(compilerOptions) zeropage = C64Zeropage(compilerOptions)

View File

@ -1,8 +1,10 @@
package prog8.codegen.target.cpu6502.codegen package prog8.codegen.target.cbm
import prog8.ast.base.Cx16VirtualRegisters import prog8.ast.base.Cx16VirtualRegisters
import prog8.ast.base.RegisterOrPair import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.* import prog8.ast.expressions.ArrayIndexedExpression
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.statements.RegisterOrStatusflag import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
@ -44,7 +46,7 @@ internal fun asmsub6502ArgsHaveRegisterClobberRisk(args: List<Expression>,
it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY) it.registerOrPair in listOf(RegisterOrPair.Y, RegisterOrPair.AY, RegisterOrPair.XY)
} }
} }
is FunctionCallExpr -> { is FunctionCallExpression -> {
if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb")) if (expr.target.nameInSource == listOf("lsb") || expr.target.nameInSource == listOf("msb"))
return isClobberRisk(expr.args[0]) return isClobberRisk(expr.args[0])
if (expr.target.nameInSource == listOf("mkword")) if (expr.target.nameInSource == listOf("mkword"))

View File

@ -0,0 +1,31 @@
package prog8.codegen.target.cbm
import prog8.ast.base.*
import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.VarDecl
import prog8.compilerinterface.IMemSizer
internal object CbmMemorySizer: IMemSizer {
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> Mflpt5.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
override fun memorySize(decl: VarDecl): Int {
return when(decl.type) {
VarDeclType.CONST -> 0
VarDeclType.VAR, VarDeclType.MEMORY -> {
when(val dt = decl.datatype) {
in NumericDatatypes -> return memorySize(dt)
in ArrayDatatypes -> decl.arraysize!!.constIndex()!! * memorySize(ArrayToElementTypes.getValue(dt))
DataType.STR -> (decl.value as StringLiteral).value.length + 1
else -> 0
}
}
}
}
}

View File

@ -0,0 +1,27 @@
package prog8.codegen.target.cbm
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import java.io.CharConversionException
import java.nio.charset.Charset
object IsoEncoding {
val charset: Charset = Charset.forName("ISO-8859-15")
fun encode(str: String): Result<List<UByte>, CharConversionException> {
return try {
Ok(str.toByteArray(charset).map { it.toUByte() })
} catch (ce: CharConversionException) {
Err(ce)
}
}
fun decode(bytes: List<UByte>): Result<String, CharConversionException> {
return try {
Ok(String(bytes.map { it.toByte() }.toByteArray(), charset))
} catch (ce: CharConversionException) {
Err(ce)
}
}
}

View File

@ -6,10 +6,10 @@ import com.github.michaelbull.result.Result
import prog8.ast.antlr.escape import prog8.ast.antlr.escape
import java.io.CharConversionException import java.io.CharConversionException
object Petscii { object PetsciiEncoding {
// decoding: from Petscii/Screencodes (0-255) to unicode // decoding: from Petscii/Screencodes (0-255) to unicode
// character tables used from https://github.com/dj51d/cbmcodecs // character tables used from https://github.com/irmen/cbmcodecs2
private val decodingPetsciiLowercase = charArrayOf( private val decodingPetsciiLowercase = charArrayOf(
'\u0000', // 0x00 -> \u0000 '\u0000', // 0x00 -> \u0000
@ -1095,13 +1095,17 @@ object Petscii {
} }
} }
fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): String { fun decodePetscii(petscii: Iterable<UByte>, lowercase: Boolean = false): Result<String, CharConversionException> {
return petscii.map { return try {
Ok(petscii.map {
val code = it.toInt() val code = it.toInt()
if(code<0 || code>= decodingPetsciiLowercase.size) if(code<0 || code>= decodingPetsciiLowercase.size)
throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}") throw CharConversionException("petscii $code out of range 0..${decodingPetsciiLowercase.size-1}")
if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code] if(lowercase) decodingPetsciiLowercase[code] else decodingPetsciiUppercase[code]
}.joinToString("") }.joinToString(""))
} catch(ce: CharConversionException) {
return Err(ce)
}
} }
fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> { fun encodeScreencode(text: String, lowercase: Boolean = false): Result<List<UByte>, CharConversionException> {
@ -1134,13 +1138,17 @@ object Petscii {
} }
} }
fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): String { fun decodeScreencode(screencode: Iterable<UByte>, lowercase: Boolean = false): Result<String, CharConversionException> {
return screencode.map { return try {
Ok(screencode.map {
val code = it.toInt() val code = it.toInt()
if(code<0 || code>= decodingScreencodeLowercase.size) if(code<0 || code>= decodingScreencodeLowercase.size)
throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}") throw CharConversionException("screencode $code out of range 0..${decodingScreencodeLowercase.size-1}")
if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code] if (lowercase) decodingScreencodeLowercase[code] else decodingScreencodeUppercase[code]
}.joinToString("") }.joinToString(""))
} catch(ce: CharConversionException) {
Err(ce)
}
} }
fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> { fun petscii2scr(petscii_code: UByte, inverseVideo: Boolean): Result<UByte, CharConversionException> {

View File

@ -1,8 +1,6 @@
package prog8.codegen.target.cx16 package prog8.codegen.target.cx16
import prog8.ast.base.DataType
import prog8.codegen.target.cbm.Mflpt5 import prog8.codegen.target.cbm.Mflpt5
import prog8.codegen.target.cbm.viceMonListName
import prog8.compilerinterface.* import prog8.compilerinterface.*
import java.io.IOException import java.io.IOException
import java.nio.file.Path import java.nio.file.Path
@ -67,18 +65,6 @@ class CX16MachineDefinition: IMachineDefinition {
} }
override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu override fun isIOAddress(address: UInt): Boolean = address==0u || address==1u || address in 0x9f00u..0x9fffu
override fun getPreallocatedZeropageVars(): Map<String, Pair<UInt, DataType>> {
val vars = mutableMapOf<String, Pair<UInt, DataType>>()
for(reg in 0..15) {
vars["cx16.r${reg}"] = (2+reg*2).toUInt() to DataType.UWORD // cx16.r0 .. cx16.r15
vars["cx16.r${reg}s"] = (2+reg*2).toUInt() to DataType.WORD // cx16.r0s .. cx16.r15s
vars["cx16.r${reg}L"] = (2+reg*2).toUInt() to DataType.UBYTE // cx16.r0L .. cx16.r15L
vars["cx16.r${reg}H"] = (3+reg*2).toUInt() to DataType.UBYTE // cx16.r0H .. cx16.r15H
vars["cx16.r${reg}sL"] = (2+reg*2).toUInt() to DataType.BYTE // cx16.r0sL .. cx16.r15sL
vars["cx16.r${reg}sH"] = (3+reg*2).toUInt() to DataType.BYTE // cx16.r0sH .. cx16.r15sH
}
return vars
}
override fun initializeZeropage(compilerOptions: CompilationOptions) { override fun initializeZeropage(compilerOptions: CompilationOptions) {
zeropage = CX16Zeropage(compilerOptions) zeropage = CX16Zeropage(compilerOptions)

View File

@ -0,0 +1,57 @@
package prog8.codegen.target.cx16
import prog8.ast.GlobalNamespace
import prog8.ast.base.DataType
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.InternalCompilerException
import prog8.compilerinterface.Zeropage
import prog8.compilerinterface.ZeropageType
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
init {
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE))
throw InternalCompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
synchronized(this) {
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
}
removeReservedFromFreePool()
// note: the 16 virtual registers R0-R15 are not regular allocated variables, they're *memory mapped* elsewhere to fixed addresses.
// however, to be able for the compiler to "see" them as zero page variables, we have to register them here as well.
val dummyscope = GlobalNamespace(emptyList())
for(reg in 0..15) {
allocatedVariables[listOf("cx16", "r${reg}")] = ZpAllocation((2+reg*2).toUInt(), DataType.UWORD, 2, dummyscope, null, null) // cx16.r0 .. cx16.r15
allocatedVariables[listOf("cx16", "r${reg}s")] = ZpAllocation((2+reg*2).toUInt(), DataType.WORD, 2, dummyscope, null, null) // cx16.r0s .. cx16.r15s
allocatedVariables[listOf("cx16", "r${reg}L")] = ZpAllocation((2+reg*2).toUInt(), DataType.UBYTE, 1, dummyscope, null, null) // cx16.r0L .. cx16.r15L
allocatedVariables[listOf("cx16", "r${reg}H")] = ZpAllocation((3+reg*2).toUInt(), DataType.UBYTE, 1, dummyscope, null, null) // cx16.r0H .. cx16.r15H
allocatedVariables[listOf("cx16", "r${reg}sL")] = ZpAllocation((2+reg*2).toUInt(), DataType.BYTE, 1, dummyscope, null, null) // cx16.r0sL .. cx16.r15sL
allocatedVariables[listOf("cx16", "r${reg}sH")] = ZpAllocation((3+reg*2).toUInt(), DataType.BYTE, 1, dummyscope, null, null) // cx16.r0sH .. cx16.r15sH
}
}
}
}

View File

@ -1,3 +0,0 @@
package prog8.codegen.target
class AssemblyError(msg: String) : RuntimeException(msg)

View File

@ -1,41 +0,0 @@
package prog8.codegen.target
import com.github.michaelbull.result.fold
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.codegen.target.c128.C128MachineDefinition
import prog8.codegen.target.cbm.Petscii
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
object C128Target: ICompilationTarget {
override val name = "c128"
override val machine = C128MachineDefinition()
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}

View File

@ -1,41 +0,0 @@
package prog8.codegen.target
import com.github.michaelbull.result.fold
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.codegen.target.c64.C64MachineDefinition
import prog8.codegen.target.cbm.Petscii
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.compilerinterface.ICompilationTarget
object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition()
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}

View File

@ -1,45 +0,0 @@
package prog8.codegen.target
import com.github.michaelbull.result.fold
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.Expression
import prog8.ast.statements.RegisterOrStatusflag
import prog8.ast.statements.Subroutine
import prog8.codegen.target.cbm.Petscii
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsEvalOrder
import prog8.codegen.target.cpu6502.codegen.asmsub6502ArgsHaveRegisterClobberRisk
import prog8.codegen.target.cx16.CX16MachineDefinition
import prog8.compilerinterface.ICompilationTarget
object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition()
override fun encodeString(str: String, altEncoding: Boolean): List<UByte> {
val coded = if (altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
return coded.fold(
failure = { throw it },
success = { it }
)
}
override fun decodeString(bytes: List<UByte>, altEncoding: Boolean) =
if (altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmsubArgsEvalOrder(sub: Subroutine): List<Int> =
asmsub6502ArgsEvalOrder(sub)
override fun asmsubArgsHaveRegisterClobberRisk(args: List<Expression>, paramRegisters: List<RegisterOrStatusflag>) =
asmsub6502ArgsHaveRegisterClobberRisk(args, paramRegisters)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes, in PassByReferenceDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
else -> Int.MIN_VALUE
}
}
}

View File

@ -1,41 +0,0 @@
package prog8.codegen.target.cx16
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.InternalCompilerException
import prog8.compilerinterface.Zeropage
import prog8.compilerinterface.ZeropageType
class CX16Zeropage(options: CompilationOptions) : Zeropage(options) {
override val SCRATCH_B1 = 0x7au // temp storage for a single byte
override val SCRATCH_REG = 0x7bu // temp storage for a register, must be B1+1
override val SCRATCH_W1 = 0x7cu // temp storage 1 for a word $7c+$7d
override val SCRATCH_W2 = 0x7eu // temp storage 2 for a word $7e+$7f
init {
if (options.floats && options.zeropage !in arrayOf(ZeropageType.BASICSAFE, ZeropageType.DONTUSE))
throw InternalCompilerException("when floats are enabled, zero page type should be 'basicsafe' or 'dontuse'")
// the addresses 0x02 to 0x21 (inclusive) are taken for sixteen virtual 16-bit api registers.
when (options.zeropage) {
ZeropageType.FULL -> {
free.addAll(0x22u..0xffu)
}
ZeropageType.KERNALSAFE -> {
free.addAll(0x22u..0x7fu)
free.addAll(0xa9u..0xffu)
}
ZeropageType.BASICSAFE -> {
free.addAll(0x22u..0x7fu)
}
ZeropageType.DONTUSE -> {
free.clear() // don't use zeropage at all
}
else -> throw InternalCompilerException("for this machine target, zero page type 'floatsafe' is not available. ${options.zeropage}")
}
removeReservedFromFreePool()
}
}

View File

@ -4,13 +4,14 @@ import prog8.ast.IStatementContainer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException import prog8.ast.expressions.AugmentAssignmentOperators
import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression import prog8.ast.expressions.TypecastExpression
import prog8.ast.expressions.AugmentAssignmentOperators import prog8.ast.getTempVar
import prog8.ast.statements.AssignTarget import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.statements.AssignmentOrigin
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CompilationOptions import prog8.compilerinterface.CompilationOptions
@ -45,7 +46,7 @@ X = BinExpr X = LeftExpr
*/ */
if(binExpr.operator in AugmentAssignmentOperators + listOf("==", "!=") && isSimpleTarget(assignment.target)) { if(binExpr.operator in AugmentAssignmentOperators && isSimpleTarget(assignment.target)) {
if(assignment.target isSameAs binExpr.right) if(assignment.target isSameAs binExpr.right)
return noModifications return noModifications
if(assignment.target isSameAs binExpr.left) { if(assignment.target isSameAs binExpr.left) {
@ -73,17 +74,8 @@ X = BinExpr X = LeftExpr
// ) // )
} }
if(binExpr.operator == "==" || binExpr.operator == "!=") {
// don't split if the operand(s) don't fit the type of the resulting variable
val targetDt = assignment.target.inferType(program)
val leftDt = binExpr.left.inferType(program)
val rightDt = binExpr.right.inferType(program)
if(leftDt isNotAssignableTo targetDt || rightDt isNotAssignableTo targetDt)
return noModifications
}
if(binExpr.right.isSimple) { if(binExpr.right.isSimple) {
val firstAssign = Assignment(assignment.target.copy(), binExpr.left, binExpr.left.position) val firstAssign = Assignment(assignment.target.copy(), binExpr.left, AssignmentOrigin.OPTIMIZER, binExpr.left.position)
val targetExpr = assignment.target.toExpression() val targetExpr = assignment.target.toExpression()
val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position) val augExpr = BinaryExpression(targetExpr, binExpr.operator, binExpr.right, binExpr.right.position)
return listOf( return listOf(
@ -106,17 +98,10 @@ X = BinExpr X = LeftExpr
// we can see if we can unwrap the binary expression by working on a new temporary variable // 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. // (that has the type of the expression), and then finally doing the typecast.
// Once it's outside the typecast, the regular splitting can commence. // Once it's outside the typecast, the regular splitting can commence.
val tempVar = when(val tempDt = origExpr.inferType(program).getOr(DataType.UNDEFINED)) { val tempVar = program.getTempVar(origExpr.inferType(program).getOr(DataType.UNDEFINED))
DataType.UBYTE -> listOf("prog8_lib", "retval_interm_ub")
DataType.BYTE -> listOf("prog8_lib", "retval_interm_b")
DataType.UWORD -> listOf("prog8_lib", "retval_interm_uw")
DataType.WORD -> listOf("prog8_lib", "retval_interm_w")
DataType.FLOAT -> listOf("floats", "tempvar_swap_float")
else -> throw FatalAstException("invalid dt $tempDt")
}
val assignTempVar = Assignment( val assignTempVar = Assignment(
AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position), AssignTarget(IdentifierReference(tempVar, typecast.position), null, null, typecast.position),
typecast.expression, typecast.position typecast.expression, AssignmentOrigin.OPTIMIZER, typecast.position
) )
return listOf( return listOf(
IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer), IAstModification.InsertBefore(assignment, assignTempVar, parent as IStatementContainer),

View File

@ -2,13 +2,13 @@ package prog8.optimizer
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.Expression import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteral
import kotlin.math.pow import kotlin.math.pow
class ConstExprEvaluator { class ConstExprEvaluator {
fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression { fun evaluate(left: NumericLiteral, operator: String, right: NumericLiteral): Expression {
try { try {
return when(operator) { return when(operator) {
"+" -> plus(left, right) "+" -> plus(left, right)
@ -23,12 +23,12 @@ class ConstExprEvaluator {
"and" -> logicaland(left, right) "and" -> logicaland(left, right)
"or" -> logicalor(left, right) "or" -> logicalor(left, right)
"xor" -> logicalxor(left, right) "xor" -> logicalxor(left, right)
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position) "<" -> NumericLiteral.fromBoolean(left < right, left.position)
">" -> NumericLiteralValue.fromBoolean(left > right, left.position) ">" -> NumericLiteral.fromBoolean(left > right, left.position)
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position) "<=" -> NumericLiteral.fromBoolean(left <= right, left.position)
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position) ">=" -> NumericLiteral.fromBoolean(left >= right, left.position)
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position) "==" -> NumericLiteral.fromBoolean(left == right, left.position)
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position) "!=" -> NumericLiteral.fromBoolean(left != right, left.position)
"<<" -> shiftedleft(left, right) "<<" -> shiftedleft(left, right)
">>" -> shiftedright(left, right) ">>" -> shiftedright(left, right)
else -> throw FatalAstException("const evaluation for invalid operator $operator") else -> throw FatalAstException("const evaluation for invalid operator $operator")
@ -38,7 +38,7 @@ class ConstExprEvaluator {
} }
} }
private fun shiftedright(left: NumericLiteralValue, amount: NumericLiteralValue): Expression { private fun shiftedright(left: NumericLiteral, amount: NumericLiteral): Expression {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes) if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left >> $amount", left.position) throw ExpressionError("cannot compute $left >> $amount", left.position)
val result = val result =
@ -46,168 +46,168 @@ class ConstExprEvaluator {
left.number.toInt().ushr(amount.number.toInt()) left.number.toInt().ushr(amount.number.toInt())
else else
left.number.toInt().shr(amount.number.toInt()) left.number.toInt().shr(amount.number.toInt())
return NumericLiteralValue(left.type, result.toDouble(), left.position) return NumericLiteral(left.type, result.toDouble(), left.position)
} }
private fun shiftedleft(left: NumericLiteralValue, amount: NumericLiteralValue): Expression { private fun shiftedleft(left: NumericLiteral, amount: NumericLiteral): Expression {
if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes) if(left.type !in IntegerDatatypes || amount.type !in IntegerDatatypes)
throw ExpressionError("cannot compute $left << $amount", left.position) throw ExpressionError("cannot compute $left << $amount", left.position)
val result = left.number.toInt().shl(amount.number.toInt()) val result = left.number.toInt().shl(amount.number.toInt())
return NumericLiteralValue(left.type, result.toDouble(), left.position) return NumericLiteral(left.type, result.toDouble(), left.position)
} }
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun logicalxor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
val error = "cannot compute $left locical-bitxor $right" val error = "cannot compute $left locical-bitxor $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position) in IntegerDatatypes -> NumericLiteral.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number != 0.0), left.position) DataType.FLOAT -> NumericLiteral.fromBoolean((left.number.toInt() != 0) xor (right.number != 0.0), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
DataType.FLOAT -> when (right.type) { DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number.toInt() != 0), left.position) in IntegerDatatypes -> NumericLiteral.fromBoolean((left.number != 0.0) xor (right.number.toInt() != 0), left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number != 0.0) xor (right.number != 0.0), left.position) DataType.FLOAT -> NumericLiteral.fromBoolean((left.number != 0.0) xor (right.number != 0.0), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun logicalor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
val error = "cannot compute $left locical-or $right" val error = "cannot compute $left locical-or $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position) in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number != 0.0, left.position) DataType.FLOAT -> NumericLiteral.fromBoolean(left.number.toInt() != 0 || right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
DataType.FLOAT -> when (right.type) { DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position) in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number != 0.0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 || right.number != 0.0, left.position) DataType.FLOAT -> NumericLiteral.fromBoolean(left.number != 0.0 || right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun logicaland(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
val error = "cannot compute $left locical-and $right" val error = "cannot compute $left locical-and $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position) in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number != 0.0, left.position) DataType.FLOAT -> NumericLiteral.fromBoolean(left.number.toInt() != 0 && right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
DataType.FLOAT -> when (right.type) { DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position) in IntegerDatatypes -> NumericLiteral.fromBoolean(left.number != 0.0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number != 0.0 && right.number != 0.0, left.position) DataType.FLOAT -> NumericLiteral.fromBoolean(left.number != 0.0 && right.number != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun bitwisexor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun bitwisexor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) { if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) { if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position) return NumericLiteral(DataType.UBYTE, (left.number.toInt() xor (right.number.toInt() and 255)).toDouble(), left.position)
} }
} else if(left.type== DataType.UWORD) { } else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) { if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position) return NumericLiteral(DataType.UWORD, (left.number.toInt() xor right.number.toInt()).toDouble(), left.position)
} }
} }
throw ExpressionError("cannot calculate $left ^ $right", left.position) throw ExpressionError("cannot calculate $left ^ $right", left.position)
} }
private fun bitwiseor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun bitwiseor(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) { if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) { if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position) return NumericLiteral(DataType.UBYTE, (left.number.toInt() or (right.number.toInt() and 255)).toDouble(), left.position)
} }
} else if(left.type== DataType.UWORD) { } else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) { if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() or right.number.toInt()).toDouble(), left.position) return NumericLiteral(DataType.UWORD, (left.number.toInt() or right.number.toInt()).toDouble(), left.position)
} }
} }
throw ExpressionError("cannot calculate $left | $right", left.position) throw ExpressionError("cannot calculate $left | $right", left.position)
} }
private fun bitwiseand(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun bitwiseand(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
if(left.type== DataType.UBYTE) { if(left.type== DataType.UBYTE) {
if(right.type in IntegerDatatypes) { if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position) return NumericLiteral(DataType.UBYTE, (left.number.toInt() and (right.number.toInt() and 255)).toDouble(), left.position)
} }
} else if(left.type== DataType.UWORD) { } else if(left.type== DataType.UWORD) {
if(right.type in IntegerDatatypes) { if(right.type in IntegerDatatypes) {
return NumericLiteralValue(DataType.UWORD, (left.number.toInt() and right.number.toInt()).toDouble(), left.position) return NumericLiteral(DataType.UWORD, (left.number.toInt() and right.number.toInt()).toDouble(), left.position)
} }
} }
throw ExpressionError("cannot calculate $left & $right", left.position) throw ExpressionError("cannot calculate $left & $right", left.position)
} }
private fun power(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun power(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
val error = "cannot calculate $left ** $right" val error = "cannot calculate $left ** $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position) in IntegerDatatypes -> NumericLiteral.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number), left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
DataType.FLOAT -> when (right.type) { DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number.toInt()), left.position) in IntegerDatatypes -> NumericLiteral(DataType.FLOAT, left.number.pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.pow(right.number), left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.pow(right.number), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun plus(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
val error = "cannot add $left and $right" val error = "cannot add $left and $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() + right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number, left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() + right.number, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
DataType.FLOAT -> when (right.type) { DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number + right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteral(DataType.FLOAT, left.number + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number + right.number, left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number + right.number, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun minus(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
val error = "cannot subtract $left and $right" val error = "cannot subtract $left and $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() - right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number, left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() - right.number, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
DataType.FLOAT -> when (right.type) { DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number - right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteral(DataType.FLOAT, left.number - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number - right.number, left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number - right.number, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
} }
private fun multiply(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun multiply(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
val error = "cannot multiply ${left.type} and ${right.type}" val error = "cannot multiply ${left.type} and ${right.type}"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalInteger(left.number.toInt() * right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteral.optimalInteger(left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number, left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number.toInt() * right.number, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
DataType.FLOAT -> when (right.type) { DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number * right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteral(DataType.FLOAT, left.number * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number * right.number, left.position) DataType.FLOAT -> NumericLiteral(DataType.FLOAT, left.number * right.number, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
@ -217,29 +217,29 @@ class ConstExprEvaluator {
private fun divideByZeroError(pos: Position): Unit = private fun divideByZeroError(pos: Position): Unit =
throw ExpressionError("division by zero", pos) throw ExpressionError("division by zero", pos)
private fun divide(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun divide(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
val error = "cannot divide $left by $right" val error = "cannot divide $left by $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt() val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteralValue.optimalInteger(result, left.position) NumericLiteral.optimalInteger(result, left.position)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number==0.0) divideByZeroError(right.position) if(right.number==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number, left.position) NumericLiteral(DataType.FLOAT, left.number.toInt() / right.number, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
DataType.FLOAT -> when (right.type) { DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number / right.number.toInt(), left.position) NumericLiteral(DataType.FLOAT, left.number / right.number.toInt(), left.position)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number ==0.0) divideByZeroError(right.position) if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number / right.number, left.position) NumericLiteral(DataType.FLOAT, left.number / right.number, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
@ -247,28 +247,28 @@ class ConstExprEvaluator {
} }
} }
private fun remainder(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun remainder(left: NumericLiteral, right: NumericLiteral): NumericLiteral {
val error = "cannot compute remainder of $left by $right" val error = "cannot compute remainder of $left by $right"
return when (left.type) { return when (left.type) {
in IntegerDatatypes -> when (right.type) { in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position) NumericLiteral.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number ==0.0) divideByZeroError(right.position) if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number, left.position) NumericLiteral(DataType.FLOAT, left.number.toInt() % right.number, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
DataType.FLOAT -> when (right.type) { DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number % right.number.toInt(), left.position) NumericLiteral(DataType.FLOAT, left.number % right.number.toInt(), left.position)
} }
DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number ==0.0) divideByZeroError(right.position) if(right.number ==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number % right.number, left.position) NumericLiteral(DataType.FLOAT, left.number % right.number, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }

View File

@ -35,19 +35,19 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
// Compile-time constant sub expressions will be evaluated on the spot. // Compile-time constant sub expressions will be evaluated on the spot.
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5 // For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
val subexpr = expr.expression val subexpr = expr.expression
if (subexpr is NumericLiteralValue) { if (subexpr is NumericLiteral) {
// accept prefixed literal values (such as -3, not true) // accept prefixed literal values (such as -3, not true)
return when (expr.operator) { return when (expr.operator) {
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent)) "+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
"-" -> when (subexpr.type) { "-" -> when (subexpr.type) {
in IntegerDatatypes -> { in IntegerDatatypes -> {
listOf(IAstModification.ReplaceNode(expr, listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalInteger(-subexpr.number.toInt(), subexpr.position), NumericLiteral.optimalInteger(-subexpr.number.toInt(), subexpr.position),
parent)) parent))
} }
DataType.FLOAT -> { DataType.FLOAT -> {
listOf(IAstModification.ReplaceNode(expr, listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.FLOAT, -subexpr.number, subexpr.position), NumericLiteral(DataType.FLOAT, -subexpr.number, subexpr.position),
parent)) parent))
} }
else -> throw ExpressionError("can only take negative of int or float", subexpr.position) else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
@ -55,29 +55,29 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
"~" -> when (subexpr.type) { "~" -> when (subexpr.type) {
DataType.BYTE -> { DataType.BYTE -> {
listOf(IAstModification.ReplaceNode(expr, listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position), NumericLiteral(DataType.BYTE, subexpr.number.toInt().inv().toDouble(), subexpr.position),
parent)) parent))
} }
DataType.UBYTE -> { DataType.UBYTE -> {
listOf(IAstModification.ReplaceNode(expr, listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position), NumericLiteral(DataType.UBYTE, (subexpr.number.toInt().inv() and 255).toDouble(), subexpr.position),
parent)) parent))
} }
DataType.WORD -> { DataType.WORD -> {
listOf(IAstModification.ReplaceNode(expr, listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position), NumericLiteral(DataType.WORD, subexpr.number.toInt().inv().toDouble(), subexpr.position),
parent)) parent))
} }
DataType.UWORD -> { DataType.UWORD -> {
listOf(IAstModification.ReplaceNode(expr, listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position), NumericLiteral(DataType.UWORD, (subexpr.number.toInt().inv() and 65535).toDouble(), subexpr.position),
parent)) parent))
} }
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position) else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
} }
"not" -> { "not" -> {
listOf(IAstModification.ReplaceNode(expr, listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.fromBoolean(subexpr.number == 0.0, subexpr.position), NumericLiteral.fromBoolean(subexpr.number == 0.0, subexpr.position),
parent)) parent))
} }
else -> throw ExpressionError(expr.operator, subexpr.position) else -> throw ExpressionError(expr.operator, subexpr.position)
@ -116,7 +116,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
when (leftExpr.operator) { when (leftExpr.operator) {
"+" -> { "+" -> {
// X + С1 == C2 --> X == C2 - C1 // X + С1 == C2 --> X == C2 - C1
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position) val newRightConst = NumericLiteral(rightconst.type, rightconst.number - leftRightConst.number, rightconst.position)
return listOf( return listOf(
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr), IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
IAstModification.ReplaceNode(expr.right, newRightConst, expr) IAstModification.ReplaceNode(expr.right, newRightConst, expr)
@ -124,7 +124,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
} }
"-" -> { "-" -> {
// X - С1 == C2 --> X == C2 + C1 // X - С1 == C2 --> X == C2 + C1
val newRightConst = NumericLiteralValue(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position) val newRightConst = NumericLiteral(rightconst.type, rightconst.number + leftRightConst.number, rightconst.position)
return listOf( return listOf(
IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr), IAstModification.ReplaceNode(leftExpr, leftExpr.left, expr),
IAstModification.ReplaceNode(expr.right, newRightConst, expr) IAstModification.ReplaceNode(expr.right, newRightConst, expr)
@ -142,16 +142,16 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED) val leftDt = leftconst.inferType(program).getOr(DataType.UNDEFINED)
when (leftconst.number) { when (leftconst.number) {
0.0 -> { 0.0 -> {
val value = NumericLiteralValue(leftDt, 0.0, expr.position) val value = NumericLiteral(leftDt, 0.0, expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent) modifications += IAstModification.ReplaceNode(expr, value, parent)
} }
1.0 -> { 1.0 -> {
val value = NumericLiteralValue(leftDt, 1.0, expr.position) val value = NumericLiteral(leftDt, 1.0, expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent) modifications += IAstModification.ReplaceNode(expr, value, parent)
} }
2.0 -> { 2.0 -> {
if(rightconst!=null) { if(rightconst!=null) {
val value = NumericLiteralValue(leftDt, 2.0.pow(rightconst.number), expr.position) val value = NumericLiteral(leftDt, 2.0.pow(rightconst.number), expr.position)
modifications += IAstModification.ReplaceNode(expr, value, parent) modifications += IAstModification.ReplaceNode(expr, value, parent)
} else { } else {
val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED) val rightDt = expr.right.inferType(program).getOr(DataType.UNDEFINED)
@ -162,7 +162,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
is VarDecl -> parent.datatype is VarDecl -> parent.datatype
else -> leftDt else -> leftDt
} }
val one = NumericLiteralValue(targetDt, 1.0, expr.position) val one = NumericLiteral(targetDt, 1.0, expr.position)
val shift = BinaryExpression(one, "<<", expr.right, expr.position) val shift = BinaryExpression(one, "<<", expr.right, expr.position)
modifications += IAstModification.ReplaceNode(expr, shift, parent) modifications += IAstModification.ReplaceNode(expr, shift, parent)
} }
@ -284,7 +284,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return modifications return modifications
} }
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> { override fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
// because constant folding can result in arrays that are now suddenly capable // because constant folding can result in arrays that are now suddenly capable
// of telling the type of all their elements (for instance, when they contained -2 which // of telling the type of all their elements (for instance, when they contained -2 which
// was a prefix expression earlier), we recalculate the array's datatype. // was a prefix expression earlier), we recalculate the array's datatype.
@ -310,7 +310,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
return noModifications return noModifications
} }
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> { override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
// the args of a fuction are constfolded via recursion already. // the args of a fuction are constfolded via recursion already.
val constvalue = functionCallExpr.constValue(program) val constvalue = functionCallExpr.constValue(program)
return if(constvalue!=null) return if(constvalue!=null)
@ -320,7 +320,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
} }
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> { override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr? { fun adjustRangeDt(rangeFrom: NumericLiteral, targetDt: DataType, rangeTo: NumericLiteral, stepLiteral: NumericLiteral?, range: RangeExpression): RangeExpression? {
val fromCast = rangeFrom.cast(targetDt) val fromCast = rangeFrom.cast(targetDt)
val toCast = rangeTo.cast(targetDt) val toCast = rangeTo.cast(targetDt)
if(!fromCast.isValid || !toCast.isValid) if(!fromCast.isValid || !toCast.isValid)
@ -337,18 +337,18 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
range.step range.step
} }
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position) return RangeExpression(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
} }
// adjust the datatype of a range expression in for loops to the loop variable. // adjust the datatype of a range expression in for loops to the loop variable.
val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications val iterableRange = forLoop.iterable as? RangeExpression ?: return noModifications
val rangeFrom = iterableRange.from as? NumericLiteralValue val rangeFrom = iterableRange.from as? NumericLiteral
val rangeTo = iterableRange.to as? NumericLiteralValue val rangeTo = iterableRange.to as? NumericLiteral
if(rangeFrom==null || rangeTo==null) return noModifications if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program) ?: throw UndefinedSymbolError(forLoop.loopVar) val loopvar = forLoop.loopVar.targetVarDecl(program) ?: throw UndefinedSymbolError(forLoop.loopVar)
val stepLiteral = iterableRange.step as? NumericLiteralValue val stepLiteral = iterableRange.step as? NumericLiteral
when(loopvar.datatype) { when(loopvar.datatype) {
DataType.UBYTE -> { DataType.UBYTE -> {
if(rangeFrom.type!= DataType.UBYTE) { if(rangeFrom.type!= DataType.UBYTE) {
@ -389,7 +389,7 @@ class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
} }
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val numval = decl.value as? NumericLiteralValue val numval = decl.value as? NumericLiteral
if(decl.type== VarDeclType.CONST && numval!=null) { if(decl.type== VarDeclType.CONST && numval!=null) {
val valueDt = numval.inferType(program) val valueDt = numval.inferType(program)
if(valueDt isnot decl.datatype) { if(valueDt isnot decl.datatype) {

View File

@ -25,7 +25,7 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
&& declConstValue.type != decl.datatype) { && declConstValue.type != decl.datatype) {
// avoid silent float roundings // avoid silent float roundings
if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) { if(decl.datatype in IntegerDatatypes && declConstValue.type==DataType.FLOAT) {
errors.err("refused silent rounding of float to avoid loss of precision", decl.value!!.position) errors.err("refused rounding of float to avoid loss of precision", decl.value!!.position)
} else { } else {
// cast the numeric literal to the appropriate datatype of the variable // cast the numeric literal to the appropriate datatype of the variable
val cast = declConstValue.cast(decl.datatype) val cast = declConstValue.cast(decl.datatype)
@ -40,7 +40,7 @@ class VarConstantValueTypeAdjuster(private val program: Program, private val err
return noModifications return noModifications
} }
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> { override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
val from = range.from.constValue(program)?.number val from = range.from.constValue(program)?.number
val to = range.to.constValue(program)?.number val to = range.to.constValue(program)?.number
val step = range.step.constValue(program)?.number val step = range.step.constValue(program)?.number
@ -93,7 +93,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
in NumericDatatypes -> listOf( in NumericDatatypes -> listOf(
IAstModification.ReplaceNode( IAstModification.ReplaceNode(
identifier, identifier,
NumericLiteralValue(cval.type, cval.number, identifier.position), NumericLiteral(cval.type, cval.number, identifier.position),
identifier.parent identifier.parent
) )
) )
@ -118,11 +118,11 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
val arraysize = decl.arraysize val arraysize = decl.arraysize
if(arraysize==null) { if(arraysize==null) {
// for arrays that have no size specifier attempt to deduce the size // for arrays that have no size specifier attempt to deduce the size
val arrayval = decl.value as? ArrayLiteralValue val arrayval = decl.value as? ArrayLiteral
if(arrayval!=null) { if(arrayval!=null) {
return listOf(IAstModification.SetExpression( return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) }, { decl.arraysize = ArrayIndex(it, decl.position) },
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), NumericLiteral.optimalInteger(arrayval.value.size, decl.position),
decl decl
)) ))
} }
@ -132,14 +132,14 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
when(decl.datatype) { when(decl.datatype) {
DataType.FLOAT -> { DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats // vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue val litval = decl.value as? NumericLiteral
if (litval!=null && litval.type in IntegerDatatypes) { if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number, litval.position) val newValue = NumericLiteral(DataType.FLOAT, litval.number, litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl)) return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
} }
} }
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val rangeExpr = decl.value as? RangeExpr val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) { if(rangeExpr!=null) {
// convert the initializer range expression to an actual array // convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.constIndex() val declArraySize = decl.arraysize?.constIndex()
@ -149,18 +149,18 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(constRange!=null) { if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE) val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) { val newValue = if(eltType in ByteDatatypes) {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(), constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position) position = decl.value!!.position)
} else { } else {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(), constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position) position = decl.value!!.position)
} }
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl)) return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
} }
} }
val numericLv = decl.value as? NumericLiteralValue val numericLv = decl.value as? NumericLiteral
if(numericLv!=null && numericLv.type== DataType.FLOAT) if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position) errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return noModifications val size = decl.arraysize?.constIndex() ?: return noModifications
@ -187,13 +187,13 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
else -> {} else -> {}
} }
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>() val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position) val refValue = ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl)) return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
} }
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
val rangeExpr = decl.value as? RangeExpr val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) { if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats // convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex() val declArraySize = decl.arraysize?.constIndex()
@ -201,14 +201,14 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!) errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange() val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) { if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), val newValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(), constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position) position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl)) return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
} }
} }
val numericLv = decl.value as? NumericLiteralValue val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return noModifications val size = decl.arraysize?.constIndex() ?: return noModifications
if(rangeExpr==null && numericLv!=null) { if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the size. // arraysize initializer is a single int, and we know the size.
@ -217,8 +217,8 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
errors.err("float value overflow", numericLv.position) errors.err("float value overflow", numericLv.position)
else { else {
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>() val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position) val refValue = ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl)) return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
} }
} }

View File

@ -8,12 +8,10 @@ import prog8.ast.base.FatalAstException
import prog8.ast.base.IntegerDatatypes import prog8.ast.base.IntegerDatatypes
import prog8.ast.base.NumericDatatypes import prog8.ast.base.NumericDatatypes
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.AnonymousScope import prog8.ast.statements.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.IfElse
import prog8.ast.statements.Jump
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compilerinterface.IErrorReporter
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.pow import kotlin.math.pow
@ -26,7 +24,7 @@ import kotlin.math.pow
*/ */
class ExpressionSimplifier(private val program: Program) : AstWalker() { class ExpressionSimplifier(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet() private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet() private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
@ -34,7 +32,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val mods = mutableListOf<IAstModification>() val mods = mutableListOf<IAstModification>()
// try to statically convert a literal value into one of the desired type // try to statically convert a literal value into one of the desired type
val literal = typecast.expression as? NumericLiteralValue val literal = typecast.expression as? NumericLiteral
if (literal != null) { if (literal != null) {
val newLiteral = literal.cast(typecast.type) val newLiteral = literal.cast(typecast.type)
if (newLiteral.isValid && newLiteral.valueOrZero() !== literal) if (newLiteral.isValid && newLiteral.valueOrZero() !== literal)
@ -144,7 +142,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val x = expr.right val x = expr.right
val y = determineY(x, leftBinExpr) val y = determineY(x, leftBinExpr)
if (y != null) { if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1.0, y.position), y.position) val yPlus1 = BinaryExpression(y, "+", NumericLiteral(leftDt, 1.0, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position) val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent)) return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
} }
@ -154,7 +152,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val x = expr.right val x = expr.right
val y = determineY(x, leftBinExpr) val y = determineY(x, leftBinExpr)
if (y != null) { if (y != null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1.0, y.position), y.position) val yMinus1 = BinaryExpression(y, "-", NumericLiteral(leftDt, 1.0, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yMinus1, x.position) val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent)) return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
} }
@ -166,7 +164,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val x = expr.left val x = expr.left
val y = determineY(x, rightBinExpr) val y = determineY(x, rightBinExpr)
if (y != null) { if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue.optimalInteger(1, y.position), y.position) val yPlus1 = BinaryExpression(y, "+", NumericLiteral.optimalInteger(1, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position) val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent)) return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
} }
@ -177,54 +175,32 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) { if(leftDt!=DataType.FLOAT && expr.operator == ">=" && rightVal?.number == 1.0) {
// for integers: x >= 1 --> x > 0 // for integers: x >= 1 --> x > 0
expr.operator = ">" expr.operator = ">"
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr)) return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
} }
if(expr.operator == ">=" && rightVal?.number == 0.0) { if(expr.operator == ">=" && rightVal?.number == 0.0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) { if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned >= 0 --> true // unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent)) return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(true, expr.position), parent))
} }
} }
if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) { if(leftDt!=DataType.FLOAT && expr.operator == "<" && rightVal?.number == 1.0) {
// for integers: x < 1 --> x <= 0 // for integers: x < 1 --> x <= 0
expr.operator = "<=" expr.operator = "<="
return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteralValue.optimalInteger(0, expr.right.position), expr)) return listOf(IAstModification.ReplaceNode(expr.right, NumericLiteral.optimalInteger(0, expr.right.position), expr))
} }
if(expr.operator == "<" && rightVal?.number == 0.0) { if(expr.operator == "<" && rightVal?.number == 0.0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) { if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned < 0 --> false // unsigned < 0 --> false
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent)) return listOf(IAstModification.ReplaceNode(expr, NumericLiteral.fromBoolean(false, expr.position), parent))
}
when(leftDt) {
DataType.BYTE -> {
// signed < 0 --> signed & $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
DataType.WORD -> {
// signedw < 0 --> msb(signedw) & $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(FunctionCallExpr(IdentifierReference(listOf("msb"), expr.position),
mutableListOf(expr.left),
expr.position
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
else -> {}
} }
} }
// simplify when a term is constant and directly determines the outcome // simplify when a term is constant and directly determines the outcome
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position) val constTrue = NumericLiteral.fromBoolean(true, expr.position)
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position) val constFalse = NumericLiteral.fromBoolean(false, expr.position)
val newExpr: Expression? = when (expr.operator) { val newExpr: Expression? = when (expr.operator) {
"or" -> { "or" -> {
when { when {
@ -255,10 +231,10 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
when { when {
leftVal?.number==0.0 -> expr.right leftVal?.number==0.0 -> expr.right
rightVal?.number==0.0 -> expr.left rightVal?.number==0.0 -> expr.left
rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, rightVal.position) rightIDt.isBytes && rightVal?.number==255.0 -> NumericLiteral(DataType.UBYTE, 255.0, rightVal.position)
rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, rightVal.position) rightIDt.isWords && rightVal?.number==65535.0 -> NumericLiteral(DataType.UWORD, 65535.0, rightVal.position)
leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteralValue(DataType.UBYTE, 255.0, leftVal.position) leftIDt.isBytes && leftVal?.number==255.0 -> NumericLiteral(DataType.UBYTE, 255.0, leftVal.position)
leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteralValue(DataType.UWORD, 65535.0, leftVal.position) leftIDt.isWords && leftVal?.number==65535.0 -> NumericLiteral(DataType.UWORD, 65535.0, leftVal.position)
else -> null else -> null
} }
} }
@ -301,7 +277,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return noModifications return noModifications
} }
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> { override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
if(functionCallExpr.target.nameInSource == listOf("lsb")) { if(functionCallExpr.target.nameInSource == listOf("lsb")) {
val arg = functionCallExpr.args[0] val arg = functionCallExpr.args[0]
if(arg is TypecastExpression) { if(arg is TypecastExpression) {
@ -326,7 +302,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
// useless msb() of byte value that was typecasted to word, replace with 0 // useless msb() of byte value that was typecasted to word, replace with 0
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
functionCallExpr, functionCallExpr,
NumericLiteralValue(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position), NumericLiteral(valueDt.getOr(DataType.UBYTE), 0.0, arg.expression.position),
parent)) parent))
} }
} else { } else {
@ -335,7 +311,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
// useless msb() of byte value, replace with 0 // useless msb() of byte value, replace with 0
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
functionCallExpr, functionCallExpr,
NumericLiteralValue(argDt.getOr(DataType.UBYTE), 0.0, arg.position), NumericLiteral(argDt.getOr(DataType.UBYTE), 0.0, arg.position),
parent)) parent))
} }
} }
@ -345,7 +321,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
} }
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> { override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
val range = containment.iterable as? RangeExpr val range = containment.iterable as? RangeExpression
if(range!=null && range.step.constValue(program)?.number==1.0) { if(range!=null && range.step.constValue(program)?.number==1.0) {
val from = range.from.constValue(program) val from = range.from.constValue(program)
val to = range.to.constValue(program) val to = range.to.constValue(program)
@ -363,6 +339,44 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return noModifications return noModifications
} }
override fun after(pipeExpr: PipeExpression, parent: Node): Iterable<IAstModification> {
val expressions = pipeExpr.expressions
val firstValue = expressions.first()
if(firstValue.isSimple) {
val funcname = expressions[1] as IdentifierReference
val first = FunctionCallExpression(funcname.copy(), mutableListOf(firstValue), firstValue.position)
val newExprs = mutableListOf<Expression>(first)
newExprs.addAll(expressions.drop(2))
return listOf(IAstModification.ReplaceNode(pipeExpr, PipeExpression(newExprs, pipeExpr.position), parent))
}
val singleExpr = expressions.singleOrNull()
if(singleExpr!=null) {
val callExpr = singleExpr as FunctionCallExpression
val call = FunctionCallExpression(callExpr.target, callExpr.args, callExpr.position)
return listOf(IAstModification.ReplaceNode(pipeExpr, call, parent))
}
return noModifications
}
override fun after(pipe: Pipe, parent: Node): Iterable<IAstModification> {
val expressions = pipe.expressions
val firstValue = expressions.first()
if(firstValue.isSimple) {
val funcname = expressions[1] as IdentifierReference
val first = FunctionCallExpression(funcname.copy(), mutableListOf(firstValue), firstValue.position)
val newExprs = mutableListOf<Expression>(first)
newExprs.addAll(expressions.drop(2))
return listOf(IAstModification.ReplaceNode(pipe, Pipe(newExprs, pipe.position), parent))
}
val singleExpr = expressions.singleOrNull()
if(singleExpr!=null) {
val callExpr = singleExpr as FunctionCallExpression
val call = FunctionCallStatement(callExpr.target, callExpr.args, true, callExpr.position)
return listOf(IAstModification.ReplaceNode(pipe, call, parent))
}
return noModifications
}
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? { private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when { return when {
subBinExpr.left isSameAs x -> subBinExpr.right subBinExpr.left isSameAs x -> subBinExpr.right
@ -371,11 +385,11 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
} }
} }
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? { private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
if(expr.left.isSameAs(expr.right)) { if(expr.left.isSameAs(expr.right)) {
// optimize X+X into X *2 // optimize X+X into X *2
expr.operator = "*" expr.operator = "*"
expr.right = NumericLiteralValue.optimalInteger(2, expr.right.position) expr.right = NumericLiteral.optimalInteger(2, expr.right.position)
expr.right.linkParents(expr) expr.right.linkParents(expr)
return expr return expr
} }
@ -386,7 +400,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal) val (expr2, _, rightVal2) = reorderAssociativeWithConstant(expr, leftVal)
if (rightVal2 != null) { if (rightVal2 != null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal2 val rightConst: NumericLiteral = rightVal2
when (rightConst.number) { when (rightConst.number) {
0.0 -> { 0.0 -> {
// left // left
@ -399,17 +413,17 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val rnum = rightVal?.number val rnum = rightVal?.number
if(rnum!=null && rnum<0.0) { if(rnum!=null && rnum<0.0) {
expr.operator = "-" expr.operator = "-"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position) expr.right = NumericLiteral(rightVal.type, -rnum, rightVal.position)
return expr return expr
} }
return null return null
} }
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? { private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
if(expr.left.isSameAs(expr.right)) { if(expr.left.isSameAs(expr.right)) {
// optimize X-X into 0 // optimize X-X into 0
return NumericLiteralValue.optimalInteger(0, expr.position) return NumericLiteral.optimalInteger(0, expr.position)
} }
if (leftVal == null && rightVal == null) if (leftVal == null && rightVal == null)
@ -425,7 +439,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if(rnum<0.0) { if(rnum<0.0) {
expr.operator = "+" expr.operator = "+"
expr.right = NumericLiteralValue(rightVal.type, -rnum, rightVal.position) expr.right = NumericLiteral(rightVal.type, -rnum, rightVal.position)
return expr return expr
} }
} }
@ -443,38 +457,38 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return null return null
} }
private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? { private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
if (leftVal == null && rightVal == null) if (leftVal == null && rightVal == null)
return null return null
if (rightVal != null) { if (rightVal != null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal val rightConst: NumericLiteral = rightVal
when (rightConst.number) { when (rightConst.number) {
-3.0 -> { -3.0 -> {
// -1/(left*left*left) // -1/(left*left*left)
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/", return BinaryExpression(NumericLiteral(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position), BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
expr.position) expr.position)
} }
-2.0 -> { -2.0 -> {
// -1/(left*left) // -1/(left*left)
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/", return BinaryExpression(NumericLiteral(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", expr.left, expr.position), BinaryExpression(expr.left, "*", expr.left, expr.position),
expr.position) expr.position)
} }
-1.0 -> { -1.0 -> {
// -1/left // -1/left
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/", return BinaryExpression(NumericLiteral(DataType.FLOAT, -1.0, expr.position), "/",
expr.left, expr.position) expr.left, expr.position)
} }
0.0 -> { 0.0 -> {
// 1 // 1
return NumericLiteralValue(rightConst.type, 1.0, expr.position) return NumericLiteral(rightConst.type, 1.0, expr.position)
} }
0.5 -> { 0.5 -> {
// sqrt(left) // sqrt(left)
return FunctionCallExpr(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position) return FunctionCallExpression(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
} }
1.0 -> { 1.0 -> {
// left // left
@ -495,15 +509,15 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
when (leftVal.number) { when (leftVal.number) {
-1.0 -> { -1.0 -> {
// -1 // -1
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position) return NumericLiteral(DataType.FLOAT, -1.0, expr.position)
} }
0.0 -> { 0.0 -> {
// 0 // 0
return NumericLiteralValue(leftVal.type, 0.0, expr.position) return NumericLiteral(leftVal.type, 0.0, expr.position)
} }
1.0 -> { 1.0 -> {
//1 //1
return NumericLiteralValue(leftVal.type, 1.0, expr.position) return NumericLiteral(leftVal.type, 1.0, expr.position)
} }
} }
@ -512,7 +526,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return null return null
} }
private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? { private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
if (leftVal == null && rightVal == null) if (leftVal == null && rightVal == null)
return null return null
@ -525,10 +539,10 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
val idt = expr.inferType(program) val idt = expr.inferType(program)
if(!idt.isKnown) if(!idt.isKnown)
throw FatalAstException("unknown dt") throw FatalAstException("unknown dt")
return NumericLiteralValue(idt.getOr(DataType.UNDEFINED), 0.0, expr.position) return NumericLiteral(idt.getOr(DataType.UNDEFINED), 0.0, expr.position)
} else if (cv in powersOfTwo) { } else if (cv in powersOfTwo) {
expr.operator = "&" expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(cv!!.toInt()-1, expr.position) expr.right = NumericLiteral.optimalInteger(cv!!.toInt()-1, expr.position)
return null return null
} }
} }
@ -537,14 +551,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
} }
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? { private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
if (leftVal == null && rightVal == null) if (leftVal == null && rightVal == null)
return null return null
// cannot shuffle assiciativity with division! // cannot shuffle assiciativity with division!
if (rightVal != null) { if (rightVal != null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal val rightConst: NumericLiteral = rightVal
val cv = rightConst.number val cv = rightConst.number
val leftIDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
if (!leftIDt.isKnown) if (!leftIDt.isKnown)
@ -567,25 +581,25 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (leftDt in IntegerDatatypes) { if (leftDt in IntegerDatatypes) {
// divided by a power of two => shift right // divided by a power of two => shift right
val numshifts = log2(cv).toInt() val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(expr.left, ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
in negativePowersOfTwo -> { in negativePowersOfTwo -> {
if (leftDt in IntegerDatatypes) { if (leftDt in IntegerDatatypes) {
// divided by a negative power of two => negate, then shift right // divided by a negative power of two => negate, then shift right
val numshifts = log2(-cv).toInt() val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
} }
if (leftDt == DataType.UBYTE) { if (leftDt == DataType.UBYTE) {
if (abs(rightConst.number) >= 256.0) { if (abs(rightConst.number) >= 256.0) {
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position) return NumericLiteral(DataType.UBYTE, 0.0, expr.position)
} }
} else if (leftDt == DataType.UWORD) { } else if (leftDt == DataType.UWORD) {
if (abs(rightConst.number) >= 65536.0) { if (abs(rightConst.number) >= 65536.0) {
return NumericLiteralValue(DataType.UBYTE, 0.0, expr.position) return NumericLiteral(DataType.UBYTE, 0.0, expr.position)
} }
} }
} }
@ -595,7 +609,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
when (leftVal.number) { when (leftVal.number) {
0.0 -> { 0.0 -> {
// 0 // 0
return NumericLiteralValue(leftVal.type, 0.0, expr.position) return NumericLiteral(leftVal.type, 0.0, expr.position)
} }
} }
} }
@ -603,7 +617,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return null return null
} }
private fun optimizeMultiplication(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? { private fun optimizeMultiplication(expr: BinaryExpression, leftVal: NumericLiteral?, rightVal: NumericLiteral?): Expression? {
if (leftVal == null && rightVal == null) if (leftVal == null && rightVal == null)
return null return null
@ -611,7 +625,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (rightVal2 != null) { if (rightVal2 != null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val leftValue: Expression = expr2.left val leftValue: Expression = expr2.left
val rightConst: NumericLiteralValue = rightVal2 val rightConst: NumericLiteral = rightVal2
when (val cv = rightConst.number) { when (val cv = rightConst.number) {
-1.0 -> { -1.0 -> {
// -left // -left
@ -619,7 +633,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
} }
0.0 -> { 0.0 -> {
// 0 // 0
return NumericLiteralValue(rightConst.type, 0.0, expr.position) return NumericLiteral(rightConst.type, 0.0, expr.position)
} }
1.0 -> { 1.0 -> {
// left // left
@ -629,14 +643,14 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
if (leftValue.inferType(program).isInteger) { if (leftValue.inferType(program).isInteger) {
// times a power of two => shift left // times a power of two => shift left
val numshifts = log2(cv).toInt() val numshifts = log2(cv).toInt()
return BinaryExpression(expr2.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(expr2.left, "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
in negativePowersOfTwo -> { in negativePowersOfTwo -> {
if (leftValue.inferType(program).isInteger) { if (leftValue.inferType(program).isInteger) {
// times a negative power of two => negate, then shift left // times a negative power of two => negate, then shift left
val numshifts = log2(-cv).toInt() val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteral.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
} }
@ -646,7 +660,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return null return null
} }
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression? { private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteral?): Expression? {
if (amountLv == null) if (amountLv == null)
return null return null
@ -660,19 +674,16 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) { when (val targetDt = targetIDt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) { if (amount >= 8) {
return NumericLiteralValue(targetDt, 0.0, expr.position) return NumericLiteral(targetDt, 0.0, expr.position)
} }
} }
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
if (amount >= 16) { if (amount >= 16) {
return NumericLiteralValue(targetDt, 0.0, expr.position) return NumericLiteral(targetDt, 0.0, expr.position)
} else if (amount >= 8) { } else if (amount > 8) {
val lsb = FunctionCallExpr(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position) val lsb = FunctionCallExpression(IdentifierReference(listOf("lsb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) { val shifted = BinaryExpression(lsb, "<<", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position)
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(lsb, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position) return FunctionCallExpression(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteral.optimalInteger(0, expr.position)), expr.position)
}
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position)
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(shifted, NumericLiteralValue.optimalInteger(0, expr.position)), expr.position)
} }
} }
else -> { else -> {
@ -681,7 +692,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return null return null
} }
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression? { private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteral?): Expression? {
if (amountLv == null) if (amountLv == null)
return null return null
@ -695,32 +706,27 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
when (idt.getOr(DataType.UNDEFINED)) { when (idt.getOr(DataType.UNDEFINED)) {
DataType.UBYTE -> { DataType.UBYTE -> {
if (amount >= 8) { if (amount >= 8) {
return NumericLiteralValue.optimalInteger(0, expr.position) return NumericLiteral.optimalInteger(0, expr.position)
} }
} }
DataType.BYTE -> { DataType.BYTE -> {
if (amount > 8) { if (amount > 8) {
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position) expr.right = NumericLiteral.optimalInteger(8, expr.right.position)
return null return null
} }
} }
DataType.UWORD -> { DataType.UWORD -> {
if (amount >= 16) { if (amount >= 16) {
return NumericLiteralValue.optimalInteger(0, expr.position) return NumericLiteral.optimalInteger(0, expr.position)
} }
else if (amount >= 8) { else if (amount > 8) {
val msb = FunctionCallExpr(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position) val msb = FunctionCallExpression(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if (amount == 8) { return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteral.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
// mkword(0, msb(v))
val zero = NumericLiteralValue(DataType.UBYTE, 0.0, expr.position)
return FunctionCallExpr(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(zero, msb), expr.position)
}
return TypecastExpression(BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount - 8, expr.position), expr.position), DataType.UWORD, true, expr.position)
} }
} }
DataType.WORD -> { DataType.WORD -> {
if (amount > 16) { if (amount > 16) {
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position) expr.right = NumericLiteral.optimalInteger(16, expr.right.position)
return null return null
} }
} }
@ -730,7 +736,7 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return null return null
} }
private fun reorderAssociativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteralValue?): BinExprWithConstants { private fun reorderAssociativeWithConstant(expr: BinaryExpression, leftVal: NumericLiteral?): BinExprWithConstants {
if (expr.operator in AssociativeOperators && leftVal != null) { if (expr.operator in AssociativeOperators && leftVal != null) {
// swap left and right so that right is always the constant // swap left and right so that right is always the constant
val tmp = expr.left val tmp = expr.left
@ -741,6 +747,6 @@ class ExpressionSimplifier(private val program: Program) : AstWalker() {
return BinExprWithConstants(expr, leftVal, expr.right.constValue(program)) return BinExprWithConstants(expr, leftVal, expr.right.constValue(program))
} }
private data class BinExprWithConstants(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?) private data class BinExprWithConstants(val expr: BinaryExpression, val leftVal: NumericLiteral?, val rightVal: NumericLiteral?)
} }

View File

@ -57,8 +57,8 @@ fun Program.optimizeStatements(errors: IErrorReporter,
return optimizationCount return optimizationCount
} }
fun Program.simplifyExpressions() : Int { fun Program.simplifyExpressions(errors: IErrorReporter) : Int {
val opti = ExpressionSimplifier(this) val opti = ExpressionSimplifier(this, errors)
opti.visit(this) opti.visit(this)
return opti.applyModifications() return opti.applyModifications()
} }
@ -69,7 +69,7 @@ fun Program.splitBinaryExpressions(options: CompilationOptions, compTarget: ICom
return opti.applyModifications() return opti.applyModifications()
} }
fun getTempVarName(dt: InferredTypes.InferredType): List<String> { fun getTempRegisterName(dt: InferredTypes.InferredType): List<String> {
return when { return when {
// TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable... // TODO assume (hope) cx16.r9 isn't used for anything else during the use of this temporary variable...
dt istype DataType.UBYTE -> listOf("cx16", "r9L") dt istype DataType.UBYTE -> listOf("cx16", "r9L")

View File

@ -1,7 +1,10 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.* import prog8.ast.base.ArrayDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.IntegerDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
@ -17,8 +20,8 @@ class StatementOptimizer(private val program: Program,
private val compTarget: ICompilationTarget private val compTarget: ICompilationTarget
) : AstWalker() { ) : AstWalker() {
override fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> { override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with a simple value, // if the first instruction in the called subroutine is a return statement with a simple value (NOT being a parameter),
// remove the jump altogeter and inline the returnvalue directly. // remove the jump altogeter and inline the returnvalue directly.
fun scopePrefix(variable: IdentifierReference): IdentifierReference { fun scopePrefix(variable: IdentifierReference): IdentifierReference {
@ -37,13 +40,18 @@ class StatementOptimizer(private val program: Program,
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
when(val expr = orig.addressExpression) { when(val expr = orig.addressExpression) {
is NumericLiteralValue -> DirectMemoryRead(expr.copy(), orig.position) is NumericLiteral -> DirectMemoryRead(expr.copy(), orig.position)
else -> return noModifications else -> return noModifications
} }
} }
is IdentifierReference -> scopePrefix(orig) is IdentifierReference -> {
is NumericLiteralValue -> orig.copy() if(orig.targetVarDecl(program)?.origin == VarDeclOrigin.SUBROUTINEPARAM)
is StringLiteralValue -> orig.copy() return noModifications
else
scopePrefix(orig)
}
is NumericLiteral -> orig.copy()
is StringLiteral -> orig.copy()
else -> return noModifications else -> return noModifications
} }
return listOf(IAstModification.ReplaceNode(functionCallExpr, copy, parent)) return listOf(IAstModification.ReplaceNode(functionCallExpr, copy, parent))
@ -52,8 +60,6 @@ class StatementOptimizer(private val program: Program,
return noModifications return noModifications
} }
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionPlaceholder) { if(functionCallStatement.target.targetStatement(program) is BuiltinFunctionPlaceholder) {
val functionName = functionCallStatement.target.nameInSource[0] val functionName = functionCallStatement.target.nameInSource[0]
@ -73,32 +79,38 @@ class StatementOptimizer(private val program: Program,
arg as? IdentifierReference arg as? IdentifierReference
} }
if(stringVar!=null && stringVar.wasStringLiteral(program)) { if(stringVar!=null && stringVar.wasStringLiteral(program)) {
val string = stringVar.targetVarDecl(program)?.value as? StringLiteralValue val string = stringVar.targetVarDecl(program)?.value as? StringLiteral
if(string!=null) { if(string!=null) {
val pos = functionCallStatement.position val pos = functionCallStatement.position
if (string.value.length == 1) { if (string.value.length == 1) {
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0] val firstCharEncoded = compTarget.encodeString(string.value, string.encoding)[0]
val chrout = FunctionCallStatement( val chrout = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos), IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toDouble(), pos)), mutableListOf(NumericLiteral(DataType.UBYTE, firstCharEncoded.toDouble(), pos)),
functionCallStatement.void, pos functionCallStatement.void, pos
) )
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent)) val stringDecl = string.parent as VarDecl
return listOf(
IAstModification.ReplaceNode(functionCallStatement, chrout, parent),
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
)
} else if (string.value.length == 2) { } else if (string.value.length == 2) {
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding) val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.encoding)
val chrout1 = FunctionCallStatement( val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos), IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)), mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[0].toDouble(), pos)),
functionCallStatement.void, pos functionCallStatement.void, pos
) )
val chrout2 = FunctionCallStatement( val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("txt", "chrout"), pos), IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)), mutableListOf(NumericLiteral(DataType.UBYTE, firstTwoCharsEncoded[1].toDouble(), pos)),
functionCallStatement.void, pos functionCallStatement.void, pos
) )
val stringDecl = string.parent as VarDecl
return listOf( return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer), IAstModification.InsertBefore(functionCallStatement, chrout1, parent as IStatementContainer),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent) IAstModification.ReplaceNode(functionCallStatement, chrout2, parent),
IAstModification.Remove(stringDecl, stringDecl.parent as IStatementContainer)
) )
} }
} }
@ -118,9 +130,9 @@ class StatementOptimizer(private val program: Program,
if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) { if(functionCallStatement.target.nameInSource !in listOf(listOf("pop"), listOf("popw")) && functionCallStatement.args.size==1) {
val arg = functionCallStatement.args[0] val arg = functionCallStatement.args[0]
if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) { if(!arg.isSimple && arg !is TypecastExpression && arg !is IFunctionCall) {
val name = getTempVarName(arg.inferType(program)) val name = getTempRegisterName(arg.inferType(program))
val tempvar = IdentifierReference(name, functionCallStatement.position) val tempvar = IdentifierReference(name, functionCallStatement.position)
val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, functionCallStatement.position) val assignTempvar = Assignment(AssignTarget(tempvar.copy(), null, null, functionCallStatement.position), arg, AssignmentOrigin.OPTIMIZER, functionCallStatement.position)
return listOf( return listOf(
IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer), IAstModification.InsertBefore(functionCallStatement, assignTempvar, parent as IStatementContainer),
IAstModification.ReplaceNode(arg, tempvar, functionCallStatement) IAstModification.ReplaceNode(arg, tempvar, functionCallStatement)
@ -177,13 +189,13 @@ class StatementOptimizer(private val program: Program,
} }
} }
val range = forLoop.iterable as? RangeExpr val range = forLoop.iterable as? RangeExpression
if(range!=null) { if(range!=null) {
if (range.size() == 1) { if (range.size() == 1) {
// for loop over a (constant) range of just a single value-- optimize the loop away // for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block // loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position) val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, forLoop.position)) scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), range.from, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements) scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
} }
@ -191,14 +203,14 @@ class StatementOptimizer(private val program: Program,
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program) val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program)
if(iterable!=null) { if(iterable!=null) {
if(iterable.datatype==DataType.STR) { if(iterable.datatype==DataType.STR) {
val sv = iterable.value as StringLiteralValue val sv = iterable.value as StringLiteral
val size = sv.value.length val size = sv.value.length
if(size==1) { if(size==1) {
// loop over string of length 1 -> just assign the single character // loop over string of length 1 -> just assign the single character
val character = compTarget.encodeString(sv.value, sv.altEncoding)[0] val character = compTarget.encodeString(sv.value, sv.encoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character.toDouble(), iterable.position) val byte = NumericLiteral(DataType.UBYTE, character.toDouble(), iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position) val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position)) scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements) scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
} }
@ -207,12 +219,12 @@ class StatementOptimizer(private val program: Program,
val size = iterable.arraysize!!.constIndex() val size = iterable.arraysize!!.constIndex()
if(size==1) { if(size==1) {
// loop over array of length 1 -> just assign the single value // loop over array of length 1 -> just assign the single value
val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number val av = (iterable.value as ArrayLiteral).value[0].constValue(program)?.number
if(av!=null) { if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position) val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment( scope.statements.add(Assignment(
AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteralValue.optimalInteger(av.toInt(), iterable.position), AssignTarget(forLoop.loopVar, null, null, forLoop.position), NumericLiteral.optimalInteger(av.toInt(), iterable.position),
forLoop.position)) AssignmentOrigin.OPTIMIZER, forLoop.position))
scope.statements.addAll(forLoop.body.statements) scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent)) return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
} }
@ -275,14 +287,9 @@ class StatementOptimizer(private val program: Program,
return noModifications return noModifications
} }
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump // NOTE: do NOT remove a jump to the next statement, because this will lead to wrong code when this occurs at the end of a subroutine
val scope = jump.parent as IStatementContainer // if we want to optimize this away, it can be done later at code generation time.
val label = jump.identifier?.targetStatement(program)
if (label != null && scope.statements.indexOf(label) == scope.statements.indexOf(jump) + 1)
return listOf(IAstModification.Remove(jump, scope))
return noModifications
}
override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> { override fun after(gosub: GoSub, parent: Node): Iterable<IAstModification> {
// if the next statement is return with no returnvalue, change into a regular jump if there are no parameters as well. // if the next statement is return with no returnvalue, change into a regular jump if there are no parameters as well.
@ -309,12 +316,12 @@ class StatementOptimizer(private val program: Program,
val op1 = binExpr.operator val op1 = binExpr.operator
val op2 = rExpr.operator val op2 = rExpr.operator
if(rExpr.left is NumericLiteralValue && op2 in AssociativeOperators) { if(rExpr.left is NumericLiteral && op2 in AssociativeOperators) {
// associative operator, make sure the constant numeric value is second (right) // associative operator, make sure the constant numeric value is second (right)
return listOf(IAstModification.SwapOperands(rExpr)) return listOf(IAstModification.SwapOperands(rExpr))
} }
val rNum = (rExpr.right as? NumericLiteralValue)?.number val rNum = (rExpr.right as? NumericLiteral)?.number
if(rNum!=null) { if(rNum!=null) {
if (op1 == "+" || op1 == "-") { if (op1 == "+" || op1 == "-") {
if (op2 == "+") { if (op2 == "+") {
@ -323,7 +330,7 @@ class StatementOptimizer(private val program: Program,
val addConstant = Assignment( val addConstant = Assignment(
assignment.target.copy(), assignment.target.copy(),
BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position), BinaryExpression(binExpr.left.copy(), "+", rExpr.right, rExpr.position),
assignment.position AssignmentOrigin.OPTIMIZER, assignment.position
) )
return listOf( return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent), IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
@ -334,7 +341,7 @@ class StatementOptimizer(private val program: Program,
val subConstant = Assignment( val subConstant = Assignment(
assignment.target.copy(), assignment.target.copy(),
BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position), BinaryExpression(binExpr.left.copy(), "-", rExpr.right, rExpr.position),
assignment.position AssignmentOrigin.OPTIMIZER, assignment.position
) )
return listOf( return listOf(
IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent), IAstModification.ReplaceNode(binExpr, expr2, binExpr.parent),
@ -375,7 +382,7 @@ class StatementOptimizer(private val program: Program,
if(bexpr.right isSameAs assignment.target) { if(bexpr.right isSameAs assignment.target) {
// X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation) // X = value - X --> X = -X ; X += value (to avoid need of stack-evaluation)
val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position) val negation = PrefixExpression("-", bexpr.right.copy(), bexpr.position)
val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), assignment.position) val addValue = Assignment(assignment.target.copy(), BinaryExpression(bexpr.right, "+", bexpr.left, bexpr.position), AssignmentOrigin.OPTIMIZER, assignment.position)
return listOf( return listOf(
IAstModification.ReplaceNode(bexpr, negation, assignment), IAstModification.ReplaceNode(bexpr, negation, assignment),
IAstModification.InsertAfter(assignment, addValue, parent as IStatementContainer) IAstModification.InsertAfter(assignment, addValue, parent as IStatementContainer)
@ -436,6 +443,28 @@ class StatementOptimizer(private val program: Program,
} }
} }
// word = msb(word) , word=lsb(word)
if(assignment.target.inferType(program).isWords) {
var fcall = assignment.value as? FunctionCallExpression
if (fcall == null)
fcall = (assignment.value as? TypecastExpression)?.expression as? FunctionCallExpression
if (fcall != null && (fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))) {
if (fcall.args.single() isSameAs assignment.target) {
return if (fcall.target.nameInSource == listOf("lsb")) {
// optimize word=lsb(word) ==> word &= $00ff
val and255 = BinaryExpression(fcall.args[0], "&", NumericLiteral(DataType.UWORD, 255.0, fcall.position), fcall.position)
val newAssign = Assignment(assignment.target, and255, AssignmentOrigin.OPTIMIZER, fcall.position)
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
} else {
// optimize word=msb(word) ==> word >>= 8
val shift8 = BinaryExpression(fcall.args[0], ">>", NumericLiteral(DataType.UBYTE, 8.0, fcall.position), fcall.position)
val newAssign = Assignment(assignment.target, shift8, AssignmentOrigin.OPTIMIZER, fcall.position)
listOf(IAstModification.ReplaceNode(assignment, newAssign, parent))
}
}
}
}
return noModifications return noModifications
} }
@ -445,16 +474,10 @@ class StatementOptimizer(private val program: Program,
val returnDt = subr.returntypes.single() val returnDt = subr.returntypes.single()
if (returnDt in IntegerDatatypes) { if (returnDt in IntegerDatatypes) {
// first assign to intermediary variable, then return that // first assign to intermediary variable, then return that
val returnVarName = "retval_interm_" + when(returnDt) { val returnVarName = program.getTempVar(returnDt)
DataType.UBYTE -> "ub" val returnValueIntermediary = IdentifierReference(returnVarName, returnStmt.position)
DataType.BYTE -> "b"
DataType.UWORD -> "uw"
DataType.WORD -> "w"
else -> "<undefined>"
}
val returnValueIntermediary = IdentifierReference(listOf("prog8_lib", returnVarName), returnStmt.position)
val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position) val tgt = AssignTarget(returnValueIntermediary, null, null, returnStmt.position)
val assign = Assignment(tgt, value, returnStmt.position) val assign = Assignment(tgt, value, AssignmentOrigin.OPTIMIZER, returnStmt.position)
val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position) val returnReplacement = Return(returnValueIntermediary.copy(), returnStmt.position)
return listOf( return listOf(
IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer), IAstModification.InsertBefore(returnStmt, assign, parent as IStatementContainer),
@ -464,11 +487,15 @@ class StatementOptimizer(private val program: Program,
return null return null
} }
if(returnStmt.value is BinaryExpression) { // TODO decision when to use intermediary variable to calculate returnvalue seems a bit arbitrary...
val mod = returnViaIntermediaryVar(returnStmt.value!!) val returnvalue = returnStmt.value
if (returnvalue!=null) {
if (returnvalue is BinaryExpression || (returnvalue is TypecastExpression && !returnvalue.expression.isSimple)) {
val mod = returnViaIntermediaryVar(returnvalue)
if(mod!=null) if(mod!=null)
return mod return mod
} }
}
return noModifications return noModifications
} }

View File

@ -3,6 +3,7 @@ package prog8.optimizer
import prog8.ast.* import prog8.ast.*
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.base.defaultZero
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
@ -69,6 +70,7 @@ class UnusedCodeRemover(private val program: Program,
if(callgraph.unused(block)) { if(callgraph.unused(block)) {
if(block.statements.any{ it !is VarDecl || it.type==VarDeclType.VAR}) if(block.statements.any{ it !is VarDecl || it.type==VarDeclType.VAR})
errors.warn("removing unused block '${block.name}'", block.position) errors.warn("removing unused block '${block.name}'", block.position)
program.removeInternedStringsFromRemovedBlock(block)
return listOf(IAstModification.Remove(block, parent as IStatementContainer)) return listOf(IAstModification.Remove(block, parent as IStatementContainer))
} }
} }
@ -92,6 +94,7 @@ class UnusedCodeRemover(private val program: Program,
} }
if(!subroutine.definingModule.isLibrary) if(!subroutine.definingModule.isLibrary)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position) errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
program.removeInternedStringsFromRemovedSubroutine(subroutine)
return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer)) return listOf(IAstModification.Remove(subroutine, parent as IStatementContainer))
} }
} }
@ -101,42 +104,43 @@ class UnusedCodeRemover(private val program: Program,
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type==VarDeclType.VAR) { if(decl.type==VarDeclType.VAR) {
val forceOutput = "force_output" in decl.definingBlock.options() val block = decl.definingBlock
if (!forceOutput && !decl.autogeneratedDontRemove && !decl.sharedWithAsm && !decl.definingBlock.isInLibrary) { val forceOutput = "force_output" in block.options()
if (!forceOutput && decl.origin==VarDeclOrigin.USERCODE && !decl.sharedWithAsm) {
val usages = callgraph.usages(decl) val usages = callgraph.usages(decl)
if (usages.isEmpty()) { if (usages.isEmpty()) {
// if(!decl.definingModule.isLibrary)
errors.warn("removing unused variable '${decl.name}'", decl.position) errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent as IStatementContainer)) return listOf(IAstModification.Remove(decl, parent as IStatementContainer))
} else {
// if all usages are just an assignment to this vardecl,
// and it is in regular RAM, then remove the var as well including all assignments
val assignTargets = usages.mapNotNull {
it.parent as? AssignTarget
}.filter {
!it.isIOAddress(compTarget.machine)
} }
if(assignTargets.size==usages.size) { else {
if(usages.size==1) {
val singleUse = usages[0].parent
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) errors.warn("removing unused variable '${decl.name}'", decl.position)
val assignmentsToRemove = assignTargets.map { it.parent to it.parent.parent as IStatementContainer}.toSet() return listOf(
return assignmentsToRemove.map { IAstModification.Remove(decl, parent as IStatementContainer),
IAstModification.Remove(it.first, it.second) IAstModification.Remove(assignment, assignment.parent as IStatementContainer)
} + listOf(
IAstModification.Remove(decl, parent as IStatementContainer)
) )
} }
} }
} }
} }
}
}
return noModifications return noModifications
} }
private fun deduplicateAssignments(statements: List<Statement>, scope: IStatementContainer): List<IAstModification> { private fun deduplicateAssignments(statements: List<Statement>, scope: IStatementContainer): List<IAstModification> {
// removes 'duplicate' assignments that assign the same target directly after another // removes 'duplicate' assignments that assign the same target directly after another, unless it is a function call
val linesToRemove = mutableListOf<Assignment>() val linesToRemove = mutableListOf<Assignment>()
val modifications = mutableListOf<IAstModification>() val modifications = mutableListOf<IAstModification>()
fun substituteZeroInBinexpr(expr: BinaryExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) { fun substituteZeroInBinexpr(expr: BinaryExpression, zero: NumericLiteral, assign1: Assignment, assign2: Assignment) {
if(expr.left isSameAs assign2.target) { if(expr.left isSameAs assign2.target) {
// X = X <oper> Right // X = X <oper> Right
linesToRemove.add(assign1) linesToRemove.add(assign1)
@ -187,7 +191,7 @@ class UnusedCodeRemover(private val program: Program,
} }
} }
fun substituteZeroInPrefixexpr(expr: PrefixExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) { fun substituteZeroInPrefixexpr(expr: PrefixExpression, zero: NumericLiteral, assign1: Assignment, assign2: Assignment) {
if(expr.expression isSameAs assign2.target) { if(expr.expression isSameAs assign2.target) {
linesToRemove.add(assign1) linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode( modifications.add(IAstModification.ReplaceNode(
@ -196,7 +200,7 @@ class UnusedCodeRemover(private val program: Program,
} }
} }
fun substituteZeroInTypecast(expr: TypecastExpression, zero: NumericLiteralValue, assign1: Assignment, assign2: Assignment) { fun substituteZeroInTypecast(expr: TypecastExpression, zero: NumericLiteral, assign1: Assignment, assign2: Assignment) {
if(expr.expression isSameAs assign2.target) { if(expr.expression isSameAs assign2.target) {
linesToRemove.add(assign1) linesToRemove.add(assign1)
modifications.add(IAstModification.ReplaceNode( modifications.add(IAstModification.ReplaceNode(
@ -219,7 +223,7 @@ class UnusedCodeRemover(private val program: Program,
val cvalue1 = assign1.value.constValue(program) val cvalue1 = assign1.value.constValue(program)
if(cvalue1!=null && cvalue1.number==0.0 && assign2.target.isSameAs(assign1.target, program) && assign2.isAugmentable) { if(cvalue1!=null && cvalue1.number==0.0 && assign2.target.isSameAs(assign1.target, program) && assign2.isAugmentable) {
val value2 = assign2.value val value2 = assign2.value
val zero = VarDecl.defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position) val zero = defaultZero(value2.inferType(program).getOr(DataType.UNDEFINED), value2.position)
when(value2) { when(value2) {
is BinaryExpression -> substituteZeroInBinexpr(value2, zero, assign1, assign2) is BinaryExpression -> substituteZeroInBinexpr(value2, zero, assign1, assign2)
is PrefixExpression -> substituteZeroInPrefixexpr(value2, zero, assign1, assign2) is PrefixExpression -> substituteZeroInPrefixexpr(value2, zero, assign1, assign2)
@ -234,8 +238,11 @@ class UnusedCodeRemover(private val program: Program,
is PrefixExpression, is PrefixExpression,
is BinaryExpression, is BinaryExpression,
is TypecastExpression, is TypecastExpression,
is FunctionCallExpr -> { /* don't remove */ } is FunctionCallExpression -> { /* don't remove */ }
else -> linesToRemove.add(assign1) else -> {
if(assign1.value !is IFunctionCall)
linesToRemove.add(assign1)
}
} }
} }
} }

View File

@ -30,7 +30,9 @@ dependencies {
implementation project(':compilerInterfaces') implementation project(':compilerInterfaces')
implementation project(':codeOptimizers') implementation project(':codeOptimizers')
implementation project(':compilerAst') implementation project(':compilerAst')
implementation project(':codeGeneration') implementation project(':codeGenTargets')
implementation project(':codeGenCpu6502')
implementation project(':codeGenExperimental6502')
implementation 'org.antlr:antlr4-runtime:4.9.2' implementation 'org.antlr:antlr4-runtime:4.9.2'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect" // implementation "org.jetbrains.kotlin:kotlin-reflect"

View File

@ -16,9 +16,11 @@
<orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" /> <orderEntry type="library" name="michael.bull.kotlin.result.jvm" level="project" />
<orderEntry type="module" module-name="codeOptimizers" /> <orderEntry type="module" module-name="codeOptimizers" />
<orderEntry type="module" module-name="compilerInterfaces" /> <orderEntry type="module" module-name="compilerInterfaces" />
<orderEntry type="module" module-name="codeGeneration" /> <orderEntry type="module" module-name="codeGenTargets" />
<orderEntry type="library" name="io.kotest.assertions.core.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" /> <orderEntry type="library" name="io.kotest.runner.junit5.jvm" level="project" />
<orderEntry type="library" name="antlr.antlr4" level="project" /> <orderEntry type="library" name="antlr.antlr4" level="project" />
<orderEntry type="module" module-name="codeGenCpu6502" />
<orderEntry type="module" module-name="codeGenExperimental6502" />
</component> </component>
</module> </module>

View File

@ -10,8 +10,6 @@ floats {
const float PI = 3.141592653589793 const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586 const float TWOPI = 6.283185307179586
float tempvar_swap_float ; used for some swap() operations
; ---- ROM float functions ---- ; ---- ROM float functions ----

View File

@ -555,6 +555,7 @@ sys {
} }
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) { asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
; note: can't be inlined because is called from asm as well
%asm {{ %asm {{
ldx cx16.r0 ldx cx16.r0
stx P8ZP_SCRATCH_W1 ; source in ZP stx P8ZP_SCRATCH_W1 ; source in ZP

View File

@ -248,15 +248,25 @@ pop_float_fac1 .proc
.pend .pend
copy_float .proc copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1, ; -- copies the 5 bytes of the mflt value pointed to by P8ZP_SCRATCH_W1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y. ; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta _target+1 sta P8ZP_SCRATCH_W2
sty _target+2 sty P8ZP_SCRATCH_W2+1
ldy #4 ldy #0
_loop lda (P8ZP_SCRATCH_W1),y lda (P8ZP_SCRATCH_W1),y
_target sta $ffff,y ; modified sta (P8ZP_SCRATCH_W2),y
dey iny
bpl _loop lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
iny
lda (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W2),y
rts rts
.pend .pend
@ -678,3 +688,54 @@ set_array_float .proc
.pend .pend
equal_zero .proc
jsr floats.pop_float_fac1
jsr floats.SIGN
beq _true
bne _false
_true lda #1
sta P8ESTACK_LO,x
dex
rts
_false lda #0
sta P8ESTACK_LO,x
dex
rts
.pend
notequal_zero .proc
jsr floats.pop_float_fac1
jsr floats.SIGN
bne equal_zero._true
beq equal_zero._false
.pend
greater_zero .proc
jsr floats.pop_float_fac1
jsr floats.SIGN
beq equal_zero._false
bpl equal_zero._true
jmp equal_zero._false
.pend
less_zero .proc
jsr floats.pop_float_fac1
jsr floats.SIGN
bmi equal_zero._true
jmp equal_zero._false
.pend
greaterequal_zero .proc
jsr floats.pop_float_fac1
jsr floats.SIGN
bpl equal_zero._true
jmp equal_zero._false
.pend
lessequal_zero .proc
jsr floats.pop_float_fac1
jsr floats.SIGN
beq equal_zero._true
bmi equal_zero._true
jmp equal_zero._false
.pend

View File

@ -163,7 +163,7 @@ graphics {
lda addr+1 lda addr+1
sta P8ZP_SCRATCH_W1+1 sta P8ZP_SCRATCH_W1+1
ldy separate_pixels ldy separate_pixels
lda _filled_right,y lda hline_filled_right,y
eor #255 eor #255
ldy #0 ldy #0
ora (P8ZP_SCRATCH_W1),y ora (P8ZP_SCRATCH_W1),y
@ -207,18 +207,18 @@ _modified stx $ffff ; modified
_zero ldx P8ZP_SCRATCH_REG _zero ldx P8ZP_SCRATCH_REG
ldy separate_pixels ldy separate_pixels
beq _zero2 beq hline_zero2
lda _modified+1 lda _modified+1
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
lda _modified+2 lda _modified+2
sta P8ZP_SCRATCH_W1+1 sta P8ZP_SCRATCH_W1+1
lda _filled_right,y lda hline_filled_right,y
ldy #0 ldy #0
ora (P8ZP_SCRATCH_W1),y ora (P8ZP_SCRATCH_W1),y
sta (P8ZP_SCRATCH_W1),y sta (P8ZP_SCRATCH_W1),y
jmp _zero2 jmp hline_zero2
_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110 hline_filled_right .byte 0, %10000000, %11000000, %11100000, %11110000, %11111000, %11111100, %11111110
_zero2 hline_zero2
}} }}
} }
} }

View File

@ -520,6 +520,7 @@ sys {
} }
asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) { asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
; note: can't be inlined because is called from asm as well
%asm {{ %asm {{
ldx cx16.r0 ldx cx16.r0
stx P8ZP_SCRATCH_W1 ; source in ZP stx P8ZP_SCRATCH_W1 ; source in ZP

View File

@ -7,7 +7,7 @@ conv {
; ----- number conversions to decimal strings ---- ; ----- number conversions to decimal strings ----
str string_out = "????????????????" ; result buffer for the string conversion routines str @shared string_out = "????????????????" ; result buffer for the string conversion routines
asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) { asmsub str_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total) ; ---- convert the ubyte in A in decimal string form, with left padding 0s (3 positions total)

View File

@ -34,4 +34,42 @@ cx16diskio {
sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> uword { sub load_size(ubyte startbank, uword startaddress, uword endaddress) -> uword {
return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress return $2000 * (cx16.getrambank() - startbank) + endaddress - startaddress
} }
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command VLOAD "filename",device,bank,address
; loads a file into Vera's video memory in the given bank:address, returns success in A
%asm {{
; -- load a file into video ram
phx
pha
tya
tax
lda #1
ldy #0
jsr c64.SETLFS
lda cx16.r0
ldy cx16.r0+1
jsr prog8_lib.strlen
tya
ldx cx16.r0
ldy cx16.r0+1
jsr c64.SETNAM
pla
clc
adc #2
ldx cx16.r1
ldy cx16.r1+1
stz P8ZP_SCRATCH_B1
jsr c64.LOAD
bcs +
inc P8ZP_SCRATCH_B1
+ jsr c64.CLRCHN
lda #1
jsr c64.CLOSE
plx
lda P8ZP_SCRATCH_B1
rts
}}
}
} }

View File

@ -13,8 +13,6 @@ floats {
const float PI = 3.141592653589793 const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586 const float TWOPI = 6.283185307179586
float tempvar_swap_float ; used for some swap() operations
; ---- ROM float functions ---- ; ---- ROM float functions ----

View File

@ -369,7 +369,7 @@ _done
set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode set_both_strides(13) ; 160 increment = 1 line in 640 px 4c mode
color &= 3 color &= 3
color <<= gfx2.plot.shift4c[lsb(x) & 3] color <<= gfx2.plot.shift4c[lsb(x) & 3]
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3] ubyte @shared mask = gfx2.plot.mask4c[lsb(x) & 3]
repeat height { repeat height {
%asm {{ %asm {{
lda cx16.VERA_DATA0 lda cx16.VERA_DATA0
@ -545,9 +545,9 @@ _done
} }
sub plot(uword @zp x, uword y, ubyte color) { sub plot(uword @zp x, uword y, ubyte color) {
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1] ubyte[8] @shared bits = [128, 64, 32, 16, 8, 4, 2, 1]
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100] ubyte[4] @shared mask4c = [%00111111, %11001111, %11110011, %11111100]
ubyte[4] shift4c = [6,4,2,0] ubyte[4] @shared shift4c = [6,4,2,0]
when active_mode { when active_mode {
1 -> { 1 -> {

View File

@ -307,13 +307,25 @@ romsub $ff7d = primm()
romsub $ff44 = macptr() clobbers(A,X,Y) romsub $ff44 = macptr() clobbers(A,X,Y)
romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y) romsub $ff47 = enter_basic(ubyte cold_or_warm @Pc) clobbers(A,X,Y)
romsub $ff68 = mouse_config(ubyte shape @A, ubyte scale @X) clobbers (A, X, Y) romsub $ff68 = mouse_config(ubyte shape @A, ubyte scale @X) clobbers (A, X, Y)
romsub $ff6b = mouse_get(ubyte zpdataptr @X) clobbers(A) romsub $ff6b = mouse_get(ubyte zpdataptr @X) -> ubyte @A
romsub $ff71 = mouse_scan() clobbers(A, X, Y) romsub $ff71 = mouse_scan() clobbers(A, X, Y)
romsub $ff53 = joystick_scan() clobbers(A, X, Y) romsub $ff53 = joystick_scan() clobbers(A, X, Y)
romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y romsub $ff56 = joystick_get(ubyte joynr @A) -> ubyte @A, ubyte @X, ubyte @Y
romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y) romsub $ff4d = clock_set_date_time(uword yearmonth @R0, uword dayhours @R1, uword minsecs @R2, ubyte jiffies @R3) clobbers(A, X, Y)
romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time() romsub $ff50 = clock_get_date_time() clobbers(A, X, Y) -> uword @R0, uword @R1, uword @R2, ubyte @R3 ; result registers see clock_set_date_time()
asmsub mouse_pos() -> ubyte @A {
; -- short wrapper around mouse_get() kernal routine:
; -- gets the position of the mouse cursor in cx16.r0 and cx16.r1 (x/y coordinate), returns mouse button status.
%asm {{
phx
ldx #cx16.r0
jsr cx16.mouse_get
plx
rts
}}
}
; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...: ; It's not documented what registers are clobbered, so we assume the worst for all following kernal routines...:
@ -521,45 +533,6 @@ asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A)
}} }}
} }
asmsub vload(str name @R0, ubyte device @Y, ubyte bank @A, uword address @R1) -> ubyte @A {
; -- like the basic command VLOAD "filename",device,bank,address
; loads a file into video memory in the given bank:address, returns success in A
; !! NOTE !! the V38 ROMs contain a bug in the LOAD code that makes the load address not work correctly,
; it works fine when loading from local filesystem
%asm {{
; -- load a file into video ram
phx
pha
tya
tax
lda #1
ldy #0
jsr c64.SETLFS
lda cx16.r0
ldy cx16.r0+1
jsr prog8_lib.strlen
tya
ldx cx16.r0
ldy cx16.r0+1
jsr c64.SETNAM
pla
clc
adc #2
ldx cx16.r1
ldy cx16.r1+1
stz P8ZP_SCRATCH_B1
jsr c64.LOAD
bcs +
inc P8ZP_SCRATCH_B1
+ jsr c64.CLRCHN
lda #1
jsr c64.CLOSE
plx
lda P8ZP_SCRATCH_B1
rts
}}
}
inline asmsub joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX { inline asmsub joystick_get2(ubyte joynr @A) clobbers(Y) -> uword @AX {
; convenience routine to get the joystick state without requiring inline assembly that deals with the multiple return values. ; convenience routine to get the joystick state without requiring inline assembly that deals with the multiple return values.
; Also disables interrupts to avoid the IRQ race condition mentioned here: https://github.com/commanderx16/x16-rom/issues/203 ; Also disables interrupts to avoid the IRQ race condition mentioned here: https://github.com/commanderx16/x16-rom/issues/203
@ -848,11 +821,12 @@ sys {
}} }}
} }
inline asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) { asmsub memcopy(uword source @R0, uword target @R1, uword count @AY) clobbers(A,X,Y) {
; note: can't be inlined because is called from asm as well
%asm {{ %asm {{
sta cx16.r2 sta cx16.r2
sty cx16.r2+1 sty cx16.r2+1
jsr cx16.memory_copy jmp cx16.memory_copy
}} }}
} }

View File

@ -169,13 +169,26 @@ sub color2 (ubyte txtcol, ubyte bgcol) {
} }
sub lowercase() { sub lowercase() {
cx16.screen_set_charset(3, 0) ; lowercase charset c64.CHROUT($0e)
; this is not 100% compatible: cx16.screen_set_charset(3, 0) ; lowercase petscii charset
} }
sub uppercase() { sub uppercase() {
cx16.screen_set_charset(2, 0) ; uppercase charset c64.CHROUT($8e)
; this is not 100% compatible: cx16.screen_set_charset(2, 0) ; uppercase petscii charset
} }
sub iso() {
c64.CHROUT($0f)
; This doesn't enable it completely: cx16.screen_set_charset(1, 0) ; iso charset
}
sub iso_off() {
; -- you have to call this first when switching back from iso charset to regular charset.
c64.CHROUT($8f)
}
asmsub scroll_left() clobbers(A, Y) { asmsub scroll_left() clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left ; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself ; contents of the rightmost column are unchanged, you should clear/refill this yourself

View File

@ -76,7 +76,8 @@ io_error:
sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte { sub list_files(ubyte drivenumber, uword pattern_ptr, uword name_ptrs, ubyte max_names) -> ubyte {
; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested. ; -- fill the array 'name_ptrs' with (pointers to) the names of the files requested.
uword names_buffer = memory("filenames", 512) const uword names_buf_size = 800
uword names_buffer = memory("filenames", names_buf_size, 0)
uword buffer_start = names_buffer uword buffer_start = names_buffer
ubyte files_found = 0 ubyte files_found = 0
if lf_start_list(drivenumber, pattern_ptr) { if lf_start_list(drivenumber, pattern_ptr) {
@ -87,7 +88,7 @@ io_error:
name_ptrs++ name_ptrs++
names_buffer += string.copy(diskio.list_filename, names_buffer) + 1 names_buffer += string.copy(diskio.list_filename, names_buffer) + 1
files_found++ files_found++
if names_buffer - buffer_start > 512-18 if names_buffer - buffer_start > names_buf_size-18
break break
if files_found == max_names if files_found == max_names
break break
@ -242,18 +243,18 @@ close_end:
void c64.CHKIN(11) ; use #11 as input channel again void c64.CHKIN(11) ; use #11 as input channel again
%asm {{ %asm {{
lda bufferpointer lda bufferpointer
sta _in_buffer+1 sta m_in_buffer+1
lda bufferpointer+1 lda bufferpointer+1
sta _in_buffer+2 sta m_in_buffer+2
}} }}
repeat num_bytes { repeat num_bytes {
%asm {{ %asm {{
jsr c64.CHRIN jsr c64.CHRIN
sta cx16.r5 sta cx16.r5
_in_buffer sta $ffff m_in_buffer sta $ffff
inc _in_buffer+1 inc m_in_buffer+1
bne + bne +
inc _in_buffer+2 inc m_in_buffer+2
+ inc list_blocks + inc list_blocks
bne + bne +
inc list_blocks+1 inc list_blocks+1
@ -406,7 +407,7 @@ io_error:
sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte { sub save(ubyte drivenumber, uword filenameptr, uword address, uword size) -> ubyte {
c64.SETNAM(string.length(filenameptr), filenameptr) c64.SETNAM(string.length(filenameptr), filenameptr)
c64.SETLFS(1, drivenumber, 0) c64.SETLFS(1, drivenumber, 0)
uword end_address = address + size uword @shared end_address = address + size
first_byte = 0 ; result var reuse first_byte = 0 ; result var reuse
%asm {{ %asm {{

View File

@ -4,6 +4,8 @@
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0 ; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
read_byte_from_address_on_stack .proc read_byte_from_address_on_stack .proc
; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged) ; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged)
lda P8ESTACK_LO+1,x lda P8ESTACK_LO+1,x
@ -683,8 +685,106 @@ shiftright_b .proc
.pend .pend
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start equalzero_b .proc
lda P8ESTACK_LO+1,x
beq _true
bne _false
_true lda #1
sta P8ESTACK_LO+1,x
rts
_false lda #0
sta P8ESTACK_LO+1,x
rts
.pend
equalzero_w .proc
lda P8ESTACK_LO+1,x
ora P8ESTACK_HI+1,x
beq equalzero_b._true
bne equalzero_b._false
.pend
notequalzero_b .proc
lda P8ESTACK_LO+1,x
beq equalzero_b._false
bne equalzero_b._true
.pend
notequalzero_w .proc
lda P8ESTACK_LO+1,x
ora P8ESTACK_HI+1,x
beq equalzero_b._false
bne equalzero_b._true
.pend
lesszero_b .proc
lda P8ESTACK_LO+1,x
bmi equalzero_b._true
jmp equalzero_b._false
.pend
lesszero_w .proc
lda P8ESTACK_HI+1,x
bmi equalzero_b._true
jmp equalzero_b._false
.pend
greaterzero_ub .proc
lda P8ESTACK_LO+1,x
bne equalzero_b._true
beq equalzero_b._false
.pend
greaterzero_sb .proc
lda P8ESTACK_LO+1,x
beq equalzero_b._false
bpl equalzero_b._true
bmi equalzero_b._false
.pend
greaterzero_uw .proc
lda P8ESTACK_LO+1,x
ora P8ESTACK_HI+1,x
bne equalzero_b._true
beq equalzero_b._false
.pend
greaterzero_sw .proc
lda P8ESTACK_HI+1,x
bmi equalzero_b._false
ora P8ESTACK_LO+1,x
beq equalzero_b._false
bne equalzero_b._true
.pend
lessequalzero_sb .proc
lda P8ESTACK_LO+1,x
bmi equalzero_b._true
beq equalzero_b._true
bne equalzero_b._false
.pend
lessequalzero_sw .proc
lda P8ESTACK_HI+1,x
bmi equalzero_b._true
ora P8ESTACK_LO+1,x
beq equalzero_b._true
bne equalzero_b._false
.pend
greaterequalzero_sb .proc
lda P8ESTACK_LO+1,x
bpl equalzero_b._true
bmi equalzero_b._false
.pend
greaterequalzero_sw .proc
lda P8ESTACK_HI+1,x
bmi equalzero_b._false
ora P8ESTACK_LO+1,x
beq equalzero_b._true
bne equalzero_b._false
.pend
memcopy16_up .proc memcopy16_up .proc
@ -1021,17 +1121,15 @@ strcmp_mem .proc
; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y. ; Returns -1,0,1 in A, depeding on the ordering. Clobbers Y.
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1 sty P8ZP_SCRATCH_W1+1
_loop ldy #0 ldy #0
lda (P8ZP_SCRATCH_W1),y _loop lda (P8ZP_SCRATCH_W1),y
bne + bne +
lda (P8ZP_SCRATCH_W2),y lda (P8ZP_SCRATCH_W2),y
bne _return_minusone bne _return_minusone
beq _return beq _return
+ lda (P8ZP_SCRATCH_W2),y + cmp (P8ZP_SCRATCH_W2),y
sec bcc _return_minusone
sbc (P8ZP_SCRATCH_W1),y bne _return_one
bmi _return_one
bne _return_minusone
inc P8ZP_SCRATCH_W1 inc P8ZP_SCRATCH_W1
bne + bne +
inc P8ZP_SCRATCH_W1+1 inc P8ZP_SCRATCH_W1+1
@ -1058,20 +1156,6 @@ sign_extend_stack_byte .proc
rts rts
.pend .pend
sign_extend_AY_byte .proc
; -- sign extend the (signed) byte in AY to full 16 bits
pha
and #$80
beq +
ldy #$ff
pla
rts
+ ldy #0
pla
rts
.pend
strlen .proc strlen .proc
; -- returns the number of bytes in the string in AY, in Y. ; -- returns the number of bytes in the string in AY, in Y.
sta P8ZP_SCRATCH_W1 sta P8ZP_SCRATCH_W1

View File

@ -6,24 +6,6 @@ prog8_lib {
%asminclude "library:prog8_lib.asm" %asminclude "library:prog8_lib.asm"
%asminclude "library:prog8_funcs.asm" %asminclude "library:prog8_funcs.asm"
; to store intermediary expression results for return values:
; NOTE: these variables are used in the StatementReorderer and StatementOptimizer
uword @zp retval_interm_uw
word @zp retval_interm_w
ubyte @zp retval_interm_ub
byte @zp retval_interm_b
word retval_interm_w2
byte retval_interm_b2
; prog8 "hooks" to be able to access the temporary scratch variables
; YOU SHOULD NOT USE THESE IN USER CODE - THESE ARE MEANT FOR INTERNAL COMPILER USE
; NOTE: the assembly code generator will match these names and not generate
; new variables/memdefs for them, rather, they'll point to the scratch variables directly.
&ubyte P8ZP_SCRATCH_REG = $ff
&byte P8ZP_SCRATCH_B1 = $ff
&uword P8ZP_SCRATCH_W1 = $ff
&word P8ZP_SCRATCH_W2 = $ff
asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A { asmsub pattern_match(str string @AY, str pattern @R0) clobbers(Y) -> ubyte @A {
%asm {{ %asm {{

View File

@ -130,11 +130,11 @@ _startloop dey
}} }}
} }
asmsub find(uword string @R0, ubyte character @A) -> uword @AY { asmsub find(uword string @R0, ubyte character @A) -> ubyte @A, ubyte @Pc {
; Locates the first position of the given character in the string, ; Locates the first position of the given character in the string,
; returns the string starting with this character or $0000 if the character is not found. ; returns Carry set if found + index in A, or Carry clear if not found.
%asm {{ %asm {{
; need to copy the the cx16 virtual registers to zeropage to be compatible with C64... ; need to copy the the cx16 virtual registers to zeropage to make this run on C64...
sta P8ZP_SCRATCH_B1 sta P8ZP_SCRATCH_B1
lda cx16.r0 lda cx16.r0
ldy cx16.r0+1 ldy cx16.r0+1
@ -147,18 +147,11 @@ _startloop dey
beq _found beq _found
iny iny
bne - bne -
_notfound lda #0 _notfound clc
ldy #0 rts
_found tya
sec
rts rts
_found sty P8ZP_SCRATCH_B1
ldy P8ZP_SCRATCH_W1+1
lda P8ZP_SCRATCH_W1
clc
adc P8ZP_SCRATCH_B1
bcc +
iny
+ rts
}} }}
} }

View File

@ -1 +1 @@
7.6 7.8

View File

@ -3,11 +3,11 @@ package prog8
import kotlinx.cli.* import kotlinx.cli.*
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.codegen.target.C128Target import prog8.codegen.target.C128Target
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import prog8.compiler.CompilationResult import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import java.io.File import java.io.File
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Path import java.nio.file.Path
@ -43,7 +43,8 @@ private fun compileMain(args: Array<String>): Boolean {
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation") val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results") val quietAssembler by cli.option(ArgType.Boolean, fullName = "quietasm", description = "don't print assembler output results")
val asmListfile by cli.option(ArgType.Boolean, fullName = "asmlist", description = "make the assembler produce a listing file as well") val asmListfile by cli.option(ArgType.Boolean, fullName = "asmlist", description = "make the assembler produce a listing file as well")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.name}', '${C128Target.name}', '${Cx16Target.name}')").default(C64Target.name) val experimentalCodegen by cli.option(ArgType.Boolean, fullName = "expericodegen", description = "use experimental codegen")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler (one of '${C64Target.NAME}', '${C128Target.NAME}', '${Cx16Target.NAME}')").default(C64Target.NAME)
val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator) val sourceDirs by cli.option(ArgType.String, fullName="srcdirs", description = "list of extra paths, separated with ${File.pathSeparator}, to search in for imported modules").multiple().delimiter(File.pathSeparator)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999) val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
@ -70,7 +71,7 @@ private fun compileMain(args: Array<String>): Boolean {
if(srcdirs.firstOrNull()!=".") if(srcdirs.firstOrNull()!=".")
srcdirs.add(0, ".") srcdirs.add(0, ".")
if (compilationTarget !in setOf(C64Target.name, C128Target.name, Cx16Target.name)) { if (compilationTarget !in setOf(C64Target.NAME, C128Target.NAME, Cx16Target.NAME)) {
System.err.println("Invalid compilation target: $compilationTarget") System.err.println("Invalid compilation target: $compilationTarget")
return false return false
} }
@ -93,6 +94,7 @@ private fun compileMain(args: Array<String>): Boolean {
slowCodegenWarnings == true, slowCodegenWarnings == true,
quietAssembler == true, quietAssembler == true,
asmListfile == true, asmListfile == true,
experimentalCodegen == true,
compilationTarget, compilationTarget,
srcdirs, srcdirs,
outputPath outputPath
@ -143,6 +145,7 @@ private fun compileMain(args: Array<String>): Boolean {
slowCodegenWarnings == true, slowCodegenWarnings == true,
quietAssembler == true, quietAssembler == true,
asmListfile == true, asmListfile == true,
experimentalCodegen == true,
compilationTarget, compilationTarget,
srcdirs, srcdirs,
outputPath outputPath

View File

@ -1,25 +1,28 @@
package prog8.compiler package prog8.compiler
import com.github.michaelbull.result.* import com.github.michaelbull.result.onFailure
import prog8.ast.AstToSourceTextConverter import prog8.ast.AstToSourceTextConverter
import prog8.ast.IBuiltinFunctions import prog8.ast.IBuiltinFunctions
import prog8.ast.IStatementContainer
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.Expression import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.ast.statements.VarDecl
import prog8.ast.walk.IAstVisitor
import prog8.codegen.target.C128Target import prog8.codegen.target.C128Target
import prog8.compiler.astprocessing.*
import prog8.codegen.target.C64Target import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target import prog8.codegen.target.Cx16Target
import prog8.codegen.target.cpu6502.codegen.AsmGen import prog8.compiler.astprocessing.*
import prog8.compilerinterface.* import prog8.compilerinterface.*
import prog8.optimizer.* import prog8.optimizer.*
import prog8.parser.ParseError import prog8.parser.ParseError
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.nameWithoutExtension import kotlin.io.path.nameWithoutExtension
import kotlin.math.round
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -37,6 +40,7 @@ class CompilerArguments(val filepath: Path,
val slowCodegenWarnings: Boolean, val slowCodegenWarnings: Boolean,
val quietAssembler: Boolean, val quietAssembler: Boolean,
val asmListfile: Boolean, val asmListfile: Boolean,
val experimentalCodegen: Boolean,
val compilationTarget: String, val compilationTarget: String,
val sourceDirs: List<String> = emptyList(), val sourceDirs: List<String> = emptyList(),
val outputDir: Path = Path(""), val outputDir: Path = Path(""),
@ -52,9 +56,9 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
val compTarget = val compTarget =
when(args.compilationTarget) { when(args.compilationTarget) {
C64Target.name -> C64Target C64Target.NAME -> C64Target()
C128Target.name -> C128Target C128Target.NAME -> C128Target()
Cx16Target.name -> Cx16Target Cx16Target.NAME -> Cx16Target()
else -> throw IllegalArgumentException("invalid compilation target") else -> throw IllegalArgumentException("invalid compilation target")
} }
@ -62,6 +66,9 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
val totalTime = measureTimeMillis { val totalTime = measureTimeMillis {
// import main module and everything it needs // import main module and everything it needs
val (programresult, compilationOptions, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs) val (programresult, compilationOptions, imported) = parseImports(args.filepath, args.errors, compTarget, args.sourceDirs)
print("Parsed ${args.filepath}")
ModuleImporter.ansiEraseRestOfLine(true)
with(compilationOptions) { with(compilationOptions) {
slowCodegenWarnings = args.slowCodegenWarnings slowCodegenWarnings = args.slowCodegenWarnings
optimize = args.optimize optimize = args.optimize
@ -69,6 +76,8 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
dontReinitGlobals = args.dontReinitGlobals dontReinitGlobals = args.dontReinitGlobals
asmQuiet = args.quietAssembler asmQuiet = args.quietAssembler
asmListfile = args.asmListfile asmListfile = args.asmListfile
experimentalCodegen = args.experimentalCodegen
outputDir = args.outputDir.normalize()
} }
program = programresult program = programresult
importedFiles = imported importedFiles = imported
@ -91,7 +100,7 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
// printProgram(program) // printProgram(program)
if (args.writeAssembly) { if (args.writeAssembly) {
when (val result = writeAssembly(program, args.errors, args.outputDir, compilationOptions)) { when (val result = writeAssembly(program, args.errors, compilationOptions)) {
is WriteAssemblyResult.Ok -> programName = result.filename is WriteAssemblyResult.Ok -> programName = result.filename
is WriteAssemblyResult.Fail -> { is WriteAssemblyResult.Fail -> {
System.err.println(result.error) System.err.println(result.error)
@ -102,34 +111,35 @@ fun compileProgram(args: CompilerArguments): CompilationResult {
} }
System.out.flush() System.out.flush()
System.err.flush() System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.") val seconds = totalTime/1000.0
println("\nTotal compilation+assemble time: ${round(seconds*100.0)/100.0} sec.")
return CompilationResult(true, program, programName, compTarget, importedFiles) return CompilationResult(true, program, programName, compTarget, importedFiles)
} catch (px: ParseError) { } catch (px: ParseError) {
System.err.print("\u001b[91m") // bright red System.err.print("\n\u001b[91m") // bright red
System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim()) System.err.println("${px.position.toClickableStr()} parse error: ${px.message}".trim())
System.err.print("\u001b[0m") // reset System.err.print("\u001b[0m") // reset
} catch (ac: AbortCompilation) { } catch (ac: AbortCompilation) {
if(!ac.message.isNullOrEmpty()) { if(!ac.message.isNullOrEmpty()) {
System.err.print("\u001b[91m") // bright red System.err.print("\n\u001b[91m") // bright red
System.err.println(ac.message) System.err.println(ac.message)
System.err.print("\u001b[0m") // reset System.err.print("\u001b[0m") // reset
} }
} catch (nsf: NoSuchFileException) { } catch (nsf: NoSuchFileException) {
System.err.print("\u001b[91m") // bright red System.err.print("\n\u001b[91m") // bright red
System.err.println("File not found: ${nsf.message}") System.err.println("File not found: ${nsf.message}")
System.err.print("\u001b[0m") // reset System.err.print("\u001b[0m") // reset
} catch (ax: AstException) { } catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red System.err.print("\n\u001b[91m") // bright red
System.err.println(ax.toString()) System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset System.err.print("\u001b[0m") // reset
} catch (x: Exception) { } catch (x: Exception) {
print("\u001b[91m") // bright red print("\n\u001b[91m") // bright red
println("\n* internal error *") println("\n* internal error *")
print("\u001b[0m") // reset print("\u001b[0m") // reset
System.out.flush() System.out.flush()
throw x throw x
} catch (x: NotImplementedError) { } catch (x: NotImplementedError) {
print("\u001b[91m") // bright red print("\n\u001b[91m") // bright red
println("\n* internal error: missing feature/code *") println("\n* internal error: missing feature/code *")
print("\u001b[0m") // reset print("\u001b[0m") // reset
System.out.flush() System.out.flush()
@ -146,7 +156,7 @@ private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuilt
override val names = functions.keys override val names = functions.keys
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet() override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteralValue? { override fun constValue(name: String, args: List<Expression>, position: Position): NumericLiteral? {
val func = BuiltinFunctions[name] val func = BuiltinFunctions[name]
if(func!=null) { if(func!=null) {
val exprfunc = func.constExpressionFunc val exprfunc = func.constExpressionFunc
@ -174,7 +184,7 @@ fun parseImports(filepath: Path,
errors: IErrorReporter, errors: IErrorReporter,
compTarget: ICompilationTarget, compTarget: ICompilationTarget,
sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> { sourceDirs: List<String>): Triple<Program, CompilationOptions, List<Path>> {
println("Compiler target: ${compTarget.name}. Parsing...") println("Compiler target: ${compTarget.name}")
val bf = BuiltinFunctionsFacade(BuiltinFunctions) val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget) val program = Program(filepath.nameWithoutExtension, bf, compTarget, compTarget)
bf.program = program bf.program = program
@ -226,7 +236,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
// error will be printed by the astchecker // error will be printed by the astchecker
} }
if (zpType == ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) { if (zpType == ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.NAME) {
System.err.println("Warning: zp option floatsafe changed to basicsafe for cx16 target") System.err.println("Warning: zp option floatsafe changed to basicsafe for cx16 target")
zpType = ZeropageType.BASICSAFE zpType = ZeropageType.BASICSAFE
} }
@ -235,6 +245,7 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
.asSequence() .asSequence()
.filter { it is Directive && it.directive == "%zpreserved" } .filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args } .map { (it as Directive).args }
.filter { it.size==2 && it[0].int!=null && it[1].int!=null }
.map { it[0].int!!..it[1].int!! } .map { it[0].int!!..it[1].int!! }
.toList() .toList()
@ -265,9 +276,9 @@ fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget
private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) { private fun processAst(program: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings // perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...") println("Analyzing code...")
program.preprocessAst(program, errors) program.preprocessAst(errors)
program.checkIdentifiers(errors, program, compilerOptions) program.checkIdentifiers(errors, compilerOptions)
errors.report() errors.report()
// TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR // TODO: turning char literals into UBYTEs via an encoding should really happen in code gen - but for that we'd need DataType.CHAR
// ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value. // ...but what do we gain from this? We can leave it as it is now: where a char literal is no more than syntactic sugar for an UBYTE value.
@ -282,11 +293,11 @@ private fun processAst(program: Program, errors: IErrorReporter, compilerOptions
errors.report() errors.report()
program.addTypecasts(errors, compilerOptions) program.addTypecasts(errors, compilerOptions)
errors.report() errors.report()
program.variousCleanups(program, errors) program.variousCleanups(errors, compilerOptions)
errors.report() errors.report()
program.checkValid(errors, compilerOptions) program.checkValid(errors, compilerOptions)
errors.report() errors.report()
program.checkIdentifiers(errors, program, compilerOptions) program.checkIdentifiers(errors, compilerOptions)
errors.report() errors.report()
} }
@ -300,7 +311,7 @@ private fun optimizeAst(program: Program, compilerOptions: CompilationOptions, e
while (true) { while (true) {
// keep optimizing expressions and statements until no more steps remain // keep optimizing expressions and statements until no more steps remain
val optsDone1 = program.simplifyExpressions() val optsDone1 = program.simplifyExpressions(errors)
val optsDone2 = program.splitBinaryExpressions(compilerOptions, compTarget) val optsDone2 = program.splitBinaryExpressions(compilerOptions, compTarget)
val optsDone3 = program.optimizeStatements(errors, functions, compTarget) val optsDone3 = program.optimizeStatements(errors, functions, compTarget)
program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away program.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
@ -315,7 +326,7 @@ private fun postprocessAst(program: Program, errors: IErrorReporter, compilerOpt
program.desugaring(errors) program.desugaring(errors)
program.addTypecasts(errors, compilerOptions) program.addTypecasts(errors, compilerOptions)
errors.report() errors.report()
program.variousCleanups(program, errors) program.variousCleanups(errors, compilerOptions)
val callGraph = CallGraph(program) val callGraph = CallGraph(program)
callGraph.checkRecursiveCalls(errors) callGraph.checkRecursiveCalls(errors)
errors.report() errors.report()
@ -332,37 +343,56 @@ private sealed class WriteAssemblyResult {
private fun writeAssembly(program: Program, private fun writeAssembly(program: Program,
errors: IErrorReporter, errors: IErrorReporter,
outputDir: Path,
compilerOptions: CompilationOptions compilerOptions: CompilationOptions
): WriteAssemblyResult { ): WriteAssemblyResult {
// asm generation directly from the Ast // asm generation directly from the Ast
program.processAstBeforeAsmGeneration(compilerOptions, errors) compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val variables = VariableExtractor().extractVars(program)
program.processAstBeforeAsmGeneration(compilerOptions, variables, errors)
errors.report() errors.report()
// 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)
// println("*********** AST RIGHT BEFORE ASM GENERATION *************") // println("*********** AST RIGHT BEFORE ASM GENERATION *************")
// printProgram(program) // printProgram(program)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions) val assembly = asmGeneratorFor(program, errors, variables, compilerOptions).compileToAssembly()
val assembly = asmGeneratorFor(compilerOptions.compTarget,
program,
errors,
compilerOptions.compTarget.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
errors.report() errors.report()
return if(assembly.valid && errors.noErrors()) { return if(assembly!=null && errors.noErrors()) {
val assemblerReturnStatus = assembly.assemble(compilerOptions) if(assembly.assemble(compilerOptions)) {
if(assemblerReturnStatus!=0)
WriteAssemblyResult.Fail("assembler step failed with return code $assemblerReturnStatus")
else {
WriteAssemblyResult.Ok(assembly.name) WriteAssemblyResult.Ok(assembly.name)
} } else
WriteAssemblyResult.Fail("assembler step failed")
} else { } else {
WriteAssemblyResult.Fail("compiler failed with errors") WriteAssemblyResult.Fail("compiler failed with errors")
} }
} }
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) { fun printProgram(program: Program) {
println() println()
val printer = AstToSourceTextConverter(::print, program) val printer = AstToSourceTextConverter(::print, program)
@ -370,15 +400,15 @@ fun printProgram(program: Program) {
println() println()
} }
internal fun asmGeneratorFor( internal fun asmGeneratorFor(program: Program, errors: IErrorReporter, variables: IVariablesAndConsts, options: CompilationOptions): IAssemblyGenerator
compTarget: ICompilationTarget,
program: Program,
errors: IErrorReporter,
zp: Zeropage,
options: CompilationOptions,
outputDir: Path
): IAssemblyGenerator
{ {
// at the moment we only have one code generation backend (for 6502 and 65c02) if(options.experimentalCodegen) {
return AsmGen(program, errors, zp, options, compTarget, outputDir) if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
return prog8.codegen.experimental6502.AsmGen(program, errors, variables, options)
} else {
if (options.compTarget.machine.cpu in arrayOf(CpuType.CPU6502, CpuType.CPU65c02))
return prog8.codegen.cpu6502.AsmGen(program, errors, variables, options)
}
throw NotImplementedError("no asm generator for cpu ${options.compTarget.machine.cpu}")
} }

View File

@ -39,11 +39,8 @@ class ModuleImporter(private val program: Program,
else -> candidates.first() // when more candiates, pick the one from the first location else -> candidates.first() // when more candiates, pick the one from the first location
} }
val logMsg = "importing '${filePath.nameWithoutExtension}' (from file $srcPath)" val source = SourceCode.File(srcPath)
println(logMsg) return Ok(importModule(source))
val module = importModule(SourceCode.File(srcPath))
return Ok(module)
} }
fun importLibraryModule(name: String): Module? { fun importLibraryModule(name: String): Module? {
@ -54,6 +51,7 @@ class ModuleImporter(private val program: Program,
} }
private fun importModule(src: SourceCode) : Module { private fun importModule(src: SourceCode) : Module {
printImportingMessage(src.name, src.origin)
val moduleAst = Prog8Parser.parseModule(src) val moduleAst = Prog8Parser.parseModule(src)
program.addModule(moduleAst) program.addModule(moduleAst)
@ -91,7 +89,6 @@ class ModuleImporter(private val program: Program,
val importedModule = val importedModule =
moduleResourceSrc.fold( moduleResourceSrc.fold(
success = { success = {
println("importing '$moduleName' (from internal ${it.origin})")
importModule(it) importModule(it)
}, },
failure = { failure = {
@ -99,7 +96,6 @@ class ModuleImporter(private val program: Program,
val moduleSrc = getModuleFromFile(moduleName, importingModule) val moduleSrc = getModuleFromFile(moduleName, importingModule)
moduleSrc.fold( moduleSrc.fold(
success = { success = {
println("importing '$moduleName' (from file ${it.origin})")
importModule(it) importModule(it)
}, },
failure = { failure = {
@ -152,4 +148,18 @@ class ModuleImporter(private val program: Program,
return Err(NoSuchFileException(File("name"))) 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()
}
}
} }

View File

@ -1,9 +1,6 @@
package prog8.compiler.astprocessing package prog8.compiler.astprocessing
import prog8.ast.INameScope import prog8.ast.*
import prog8.ast.IStatementContainer
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
@ -13,6 +10,9 @@ import java.io.CharConversionException
import java.io.File import java.io.File
import kotlin.io.path.Path import kotlin.io.path.Path
/**
* Semantic analysis.
*/
internal class AstChecker(private val program: Program, internal class AstChecker(private val program: Program,
private val errors: IErrorReporter, private val errors: IErrorReporter,
private val compilerOptions: CompilationOptions private val compilerOptions: CompilationOptions
@ -88,7 +88,7 @@ internal class AstChecker(private val program: Program,
override fun visit(forLoop: ForLoop) { override fun visit(forLoop: ForLoop) {
fun checkUnsignedLoopDownto0(range: RangeExpr?) { fun checkUnsignedLoopDownto0(range: RangeExpression?) {
if(range==null) if(range==null)
return return
val step = range.step.constValue(program)?.number ?: 1.0 val step = range.step.constValue(program)?.number ?: 1.0
@ -100,7 +100,7 @@ internal class AstChecker(private val program: Program,
} }
val iterableDt = forLoop.iterable.inferType(program).getOr(DataType.BYTE) val iterableDt = forLoop.iterable.inferType(program).getOr(DataType.BYTE)
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) { if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpression) {
errors.err("can only loop over an iterable type", forLoop.position) errors.err("can only loop over an iterable type", forLoop.position)
} else { } else {
val loopvar = forLoop.loopVar.targetVarDecl(program) val loopvar = forLoop.loopVar.targetVarDecl(program)
@ -112,14 +112,14 @@ internal class AstChecker(private val program: Program,
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR) if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position) errors.err("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpr) checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
} }
DataType.UWORD -> { DataType.UWORD -> {
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR && if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW) iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position) errors.err("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)
checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpr) checkUnsignedLoopDownto0(forLoop.iterable as? RangeExpression)
} }
DataType.BYTE -> { DataType.BYTE -> {
if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B) if(iterableDt!= DataType.BYTE && iterableDt!= DataType.ARRAY_B)
@ -140,10 +140,10 @@ internal class AstChecker(private val program: Program,
} }
if(errors.noErrors()) { if(errors.noErrors()) {
// check loop range values // check loop range values
val range = forLoop.iterable as? RangeExpr val range = forLoop.iterable as? RangeExpression
if(range!=null) { if(range!=null) {
val from = range.from as? NumericLiteralValue val from = range.from as? NumericLiteral
val to = range.to as? NumericLiteralValue val to = range.to as? NumericLiteral
if(from != null) if(from != null)
checkValueTypeAndRange(loopvar.datatype, from) checkValueTypeAndRange(loopvar.datatype, from)
else if(range.from.inferType(program) isnot loopvar.datatype) else if(range.from.inferType(program) isnot loopvar.datatype)
@ -208,8 +208,7 @@ internal class AstChecker(private val program: Program,
is Label, is Label,
is VarDecl, is VarDecl,
is InlineAssembly, is InlineAssembly,
is IStatementContainer, is IStatementContainer -> true
is Nop -> true
is Assignment -> { is Assignment -> {
val target = statement.target.identifier!!.targetStatement(program) val target = statement.target.identifier!!.targetStatement(program)
target === statement.previousSibling() // an initializer assignment is okay target === statement.previousSibling() // an initializer assignment is okay
@ -454,6 +453,10 @@ internal class AstChecker(private val program: Program,
errors.err("typecasting a float value in-place makes no sense", assignment.value.position) errors.err("typecasting a float value in-place makes no sense", assignment.value.position)
} }
val numvalue = assignment.value.constValue(program)
if(numvalue!=null && targetDt.isKnown)
checkValueTypeAndRange(targetDt.getOr(DataType.UNDEFINED), numvalue)
super.visit(assignment) super.visit(assignment)
} }
@ -488,23 +491,15 @@ internal class AstChecker(private val program: Program,
} }
} }
// target type check is already done at the Assignment:
// val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR)
// if(targetDt in IterableDatatypes)
// errors.err("cannot assign to a string or array type", assignTarget.position)
if (assignment is Assignment) { if (assignment is Assignment) {
val targetDatatype = assignTarget.inferType(program) val targetDatatype = assignTarget.inferType(program)
if (targetDatatype.isKnown) { if (targetDatatype.isKnown) {
val constVal = assignment.value.constValue(program) val constVal = assignment.value.constValue(program)
if (constVal != null) { if(constVal==null) {
checkValueTypeAndRange(targetDatatype.getOr(DataType.BYTE), constVal)
} else {
val sourceDatatype = assignment.value.inferType(program) val sourceDatatype = assignment.value.inferType(program)
if (sourceDatatype.isUnknown) { if (sourceDatatype.isUnknown) {
if (assignment.value !is FunctionCallExpr) if (assignment.value !is FunctionCallExpression)
errors.err("assignment value is invalid or has no proper datatype", assignment.value.position) errors.err("assignment value is invalid or has no proper datatype, maybe forgot '&' (address-of)", assignment.value.position)
} else { } else {
checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE), checkAssignmentCompatible(targetDatatype.getOr(DataType.BYTE),
sourceDatatype.getOr(DataType.BYTE), assignment.value) sourceDatatype.getOr(DataType.BYTE), assignment.value)
@ -534,14 +529,6 @@ internal class AstChecker(private val program: Program,
err("const modifier can only be used on numeric types (byte, word, float)") err("const modifier can only be used on numeric types (byte, word, float)")
} }
// @zp can only occur on integers
if(decl.datatype !in IntegerDatatypes) {
if(decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)
errors.warn("this datatype can't be placed in zeropage", decl.position)
if(decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
errors.err("this datatype can't be placed in zeropage", decl.position)
}
// FLOATS enabled? // FLOATS enabled?
if(!compilerOptions.floats && decl.datatype.oneOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY) if(!compilerOptions.floats && decl.datatype.oneOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!= VarDeclType.MEMORY)
err("floating point used, but that is not enabled via options") err("floating point used, but that is not enabled via options")
@ -554,11 +541,11 @@ internal class AstChecker(private val program: Program,
err("array variable is missing a size specification or an initialization value") err("array variable is missing a size specification or an initialization value")
return return
} }
if(decl.value is NumericLiteralValue) { if(decl.value is NumericLiteral) {
err("unsized array declaration cannot use a single literal initialization value") err("unsized array declaration cannot use a single literal initialization value")
return return
} }
if(decl.value is RangeExpr) if(decl.value is RangeExpression)
throw FatalAstException("range expressions in vardecls should have been converted into array values during constFolding $decl") throw FatalAstException("range expressions in vardecls should have been converted into array values during constFolding $decl")
} }
@ -568,16 +555,16 @@ internal class AstChecker(private val program: Program,
null -> { null -> {
// a vardecl without an initial value, don't bother with it // a vardecl without an initial value, don't bother with it
} }
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value") is RangeExpression -> throw FatalAstException("range expression should have been converted to a true array value")
is StringLiteralValue -> { is StringLiteral -> {
checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue) checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteral)
} }
is ArrayLiteralValue -> { is ArrayLiteral -> {
val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue) val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteral)
checkValueTypeAndRangeArray(decl.datatype, arraySpec, decl.value as ArrayLiteralValue) checkValueTypeAndRangeArray(decl.datatype, arraySpec, decl.value as ArrayLiteral)
} }
is NumericLiteralValue -> { is NumericLiteral -> {
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue) checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteral)
} }
else -> { else -> {
if(decl.type==VarDeclType.CONST) { if(decl.type==VarDeclType.CONST) {
@ -605,7 +592,7 @@ internal class AstChecker(private val program: Program,
else -> {} else -> {}
} }
} }
val numvalue = decl.value as? NumericLiteralValue val numvalue = decl.value as? NumericLiteral
if(numvalue!=null) { if(numvalue!=null) {
if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) { if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
err("memory address must be valid integer 0..\$ffff") err("memory address must be valid integer 0..\$ffff")
@ -655,10 +642,11 @@ internal class AstChecker(private val program: Program,
if(parameter==null) if(parameter==null)
err("string var must be initialized with a string literal") err("string var must be initialized with a string literal")
} }
else if (decl.type==VarDeclType.VAR && decl.value !is StringLiteralValue)
err("string var can only be initialized with a string literal")
} }
if(compilerOptions.zeropage==ZeropageType.DONTUSE && decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE)
err("zeropage usage has been disabled by options")
super.visit(decl) super.visit(decl)
} }
@ -695,6 +683,8 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur at module level") err("this directive may only occur at module level")
if(directive.args.size!=2 || directive.args[0].int==null || directive.args[1].int==null) if(directive.args.size!=2 || directive.args[0].int==null || directive.args[1].int==null)
err("requires two addresses (start, end)") err("requires two addresses (start, end)")
if(directive.args[0].int!! > 255u || directive.args[1].int!! > 255u)
err("start and end addresss must be in Zeropage so 0..255")
} }
"%address" -> { "%address" -> {
if(directive.parent !is Module) if(directive.parent !is Module)
@ -763,7 +753,7 @@ internal class AstChecker(private val program: Program,
errors.err("included file not found: $filename", directive.position) errors.err("included file not found: $filename", directive.position)
} }
override fun visit(array: ArrayLiteralValue) { override fun visit(array: ArrayLiteral) {
if(array.type.isKnown) { if(array.type.isKnown) {
if (!compilerOptions.floats && array.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) { if (!compilerOptions.floats && array.type.oneOf(DataType.FLOAT, DataType.ARRAY_F)) {
errors.err("floating point used, but that is not enabled via options", array.position) errors.err("floating point used, but that is not enabled via options", array.position)
@ -777,11 +767,11 @@ internal class AstChecker(private val program: Program,
val decl = e.targetVarDecl(program)!! val decl = e.targetVarDecl(program)!!
return decl.datatype in PassByReferenceDatatypes return decl.datatype in PassByReferenceDatatypes
} }
return e is StringLiteralValue return e is StringLiteral
} }
if(array.parent is VarDecl) { if(array.parent is VarDecl) {
if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) }) if (!array.value.all { it is NumericLiteral || it is AddressOf || isPassByReferenceElement(it) })
errors.err("array literal for variable initialization contains non-constant elements", array.position) errors.err("array literal for variable initialization contains non-constant elements", array.position)
} else if(array.parent is ForLoop) { } else if(array.parent is ForLoop) {
if (!array.value.all { it.constValue(program) != null }) if (!array.value.all { it.constValue(program) != null })
@ -793,7 +783,7 @@ internal class AstChecker(private val program: Program,
override fun visit(char: CharLiteral) { override fun visit(char: CharLiteral) {
try { // just *try* if it can be encoded, don't actually do it try { // just *try* if it can be encoded, don't actually do it
compilerOptions.compTarget.encodeString(char.value.toString(), char.altEncoding) compilerOptions.compTarget.encodeString(char.value.toString(), char.encoding)
} catch (cx: CharConversionException) { } catch (cx: CharConversionException) {
errors.err(cx.message ?: "can't encode character", char.position) errors.err(cx.message ?: "can't encode character", char.position)
} }
@ -801,11 +791,13 @@ internal class AstChecker(private val program: Program,
super.visit(char) super.visit(char)
} }
override fun visit(string: StringLiteralValue) { override fun visit(string: StringLiteral) {
checkValueTypeAndRangeString(DataType.STR, string) checkValueTypeAndRangeString(DataType.STR, string)
try { // just *try* if it can be encoded, don't actually do it try { // just *try* if it can be encoded, don't actually do it
compilerOptions.compTarget.encodeString(string.value, string.altEncoding) val bytes = compilerOptions.compTarget.encodeString(string.value, string.encoding)
if(0u in bytes)
errors.warn("a character in the string encodes into the 0-byte, which will terminate the string prematurely", string.position)
} catch (cx: CharConversionException) { } catch (cx: CharConversionException) {
errors.err(cx.message ?: "can't encode string", string.position) errors.err(cx.message ?: "can't encode string", string.position)
} }
@ -845,6 +837,13 @@ internal class AstChecker(private val program: Program,
val leftDt = leftIDt.getOr(DataType.UNDEFINED) val leftDt = leftIDt.getOr(DataType.UNDEFINED)
val rightDt = rightIDt.getOr(DataType.UNDEFINED) val rightDt = rightIDt.getOr(DataType.UNDEFINED)
if(expr.operator=="+" || expr.operator=="-") {
if(leftDt == DataType.STR || rightDt == DataType.STR || leftDt in ArrayDatatypes || rightDt in ArrayDatatypes) {
errors.err("missing & (address-of) on the string operand", expr.position)
return
}
}
when(expr.operator){ when(expr.operator){
"/", "%" -> { "/", "%" -> {
val constvalRight = expr.right.constValue(program) val constvalRight = expr.right.constValue(program)
@ -881,8 +880,15 @@ internal class AstChecker(private val program: Program,
errors.err("left operand is not numeric or str", expr.left.position) errors.err("left operand is not numeric or str", expr.left.position)
if(rightDt!in NumericDatatypes && rightDt != DataType.STR) if(rightDt!in NumericDatatypes && rightDt != DataType.STR)
errors.err("right operand is not numeric or str", expr.right.position) errors.err("right operand is not numeric or str", expr.right.position)
if(leftDt!=rightDt) if(leftDt!=rightDt) {
if(leftDt==DataType.STR && rightDt in IntegerDatatypes) {
// only exception allowed: str * constvalue
if(expr.right.constValue(program)!=null)
errors.err("can only use string repeat with a constant number value", expr.left.position)
} else {
errors.err("left and right operands aren't the same type", expr.left.position) errors.err("left and right operands aren't the same type", expr.left.position)
}
}
if(expr.operator !in ComparisonOperators) { if(expr.operator !in ComparisonOperators) {
if (leftDt == DataType.STR && rightDt == DataType.STR || leftDt in ArrayDatatypes && rightDt in ArrayDatatypes) { if (leftDt == DataType.STR && rightDt == DataType.STR || leftDt in ArrayDatatypes && rightDt in ArrayDatatypes) {
@ -900,10 +906,13 @@ internal class AstChecker(private val program: Program,
if(!typecast.expression.inferType(program).isKnown) if(!typecast.expression.inferType(program).isKnown)
errors.err("this expression doesn't return a value", typecast.expression.position) errors.err("this expression doesn't return a value", typecast.expression.position)
if(typecast.expression is NumericLiteral)
errors.err("can't cast the value to the requested target type", typecast.expression.position)
super.visit(typecast) super.visit(typecast)
} }
override fun visit(range: RangeExpr) { override fun visit(range: RangeExpression) {
fun err(msg: String) { fun err(msg: String) {
errors.err(msg, range.position) errors.err(msg, range.position)
} }
@ -936,7 +945,7 @@ internal class AstChecker(private val program: Program,
} }
} }
override fun visit(functionCallExpr: FunctionCallExpr) { override fun visit(functionCallExpr: FunctionCallExpression) {
// this function call is (part of) an expression, which should be in a statement somewhere. // this function call is (part of) an expression, which should be in a statement somewhere.
val stmtOfExpression = findParentNode<Statement>(functionCallExpr) val stmtOfExpression = findParentNode<Statement>(functionCallExpr)
?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}") ?: throw FatalAstException("cannot determine statement scope of function call expression at ${functionCallExpr.position}")
@ -1081,16 +1090,14 @@ internal class AstChecker(private val program: Program,
var ident: IdentifierReference? = null var ident: IdentifierReference? = null
if(arg.value is IdentifierReference) if(arg.value is IdentifierReference)
ident = arg.value as IdentifierReference ident = arg.value as IdentifierReference
else if(arg.value is FunctionCallExpr) { else if(arg.value is FunctionCallExpression) {
val fcall = arg.value as FunctionCallExpr val fcall = arg.value as FunctionCallExpression
if(fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb")) if(fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))
ident = fcall.args[0] as? IdentifierReference ident = fcall.args[0] as? IdentifierReference
} }
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) { if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
var regname = ident.nameInSource[1].uppercase() var regname = ident.nameInSource[1].uppercase()
if(regname.endsWith('L')) if(regname.endsWith('L') || regname.endsWith('H') || regname.endsWith('s'))
regname=regname.substring(0, regname.length-1)
if(regname.endsWith('s'))
regname=regname.substring(0, regname.length-1) regname=regname.substring(0, regname.length-1)
val reg = RegisterOrPair.valueOf(regname) val reg = RegisterOrPair.valueOf(regname)
val same = params.filter { it.value.registerOrPair==reg } val same = params.filter { it.value.registerOrPair==reg }
@ -1146,9 +1153,9 @@ internal class AstChecker(private val program: Program,
if(index!=null && (index<0 || index>=arraysize)) if(index!=null && (index<0 || index>=arraysize))
errors.err("array index out of bounds", arrayIndexedExpression.indexer.position) errors.err("array index out of bounds", arrayIndexedExpression.indexer.position)
} else if(target.datatype == DataType.STR) { } else if(target.datatype == DataType.STR) {
if(target.value is StringLiteralValue) { if(target.value is StringLiteral) {
// check string lengths for non-memory mapped strings // check string lengths for non-memory mapped strings
val stringLen = (target.value as StringLiteralValue).value.length val stringLen = (target.value as StringLiteral).value.length
val index = arrayIndexedExpression.indexer.constIndex() val index = arrayIndexedExpression.indexer.constIndex()
if (index != null && (index < 0 || index >= stringLen)) if (index != null && (index < 0 || index >= stringLen))
errors.err("index out of bounds", arrayIndexedExpression.indexer.position) errors.err("index out of bounds", arrayIndexedExpression.indexer.position)
@ -1208,38 +1215,153 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(containment: ContainmentCheck) { override fun visit(containment: ContainmentCheck) {
if(!containment.iterable.inferType(program).isIterable) val elementDt = containment.element.inferType(program)
errors.err("value set for containment check must be an iterable type", containment.iterable.position) val iterableDt = containment.iterable.inferType(program)
if(containment.parent is BinaryExpression) if(containment.parent is BinaryExpression)
errors.err("containment check is currently not supported in complex expressions", containment.position) errors.err("containment check is currently not supported in complex expressions", containment.position)
// TODO check that iterable contains the same types as the element that is searched if(iterableDt.isIterable) {
val iterableEltDt = ArrayToElementTypes.getValue(iterableDt.getOr(DataType.UNDEFINED))
val invalidDt = if (elementDt.isBytes) {
iterableEltDt !in ByteDatatypes
} else if (elementDt.isWords) {
iterableEltDt !in WordDatatypes
} else {
false
}
if (invalidDt)
errors.err("element datatype doesn't match iterable datatype", containment.position)
} else {
errors.err("value set for containment check must be an iterable type", containment.iterable.position)
}
super.visit(containment) super.visit(containment)
} }
override fun visit(pipe: PipeExpression) {
processPipe(pipe.expressions, pipe)
val last = pipe.expressions.last() as IdentifierReference
val target = last.targetStatement(program)!!
when(target) {
is BuiltinFunctionPlaceholder -> {
if(BuiltinFunctions.getValue(target.name).known_returntype==null)
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
}
is Subroutine -> {
if(target.returntypes.isEmpty())
errors.err("invalid pipe expression; last term doesn't return a value", last.position)
else if(target.returntypes.size!=1)
errors.err("invalid pipe expression; last term doesn't return a single value", last.position)
}
else -> errors.err("invalid pipe expression; last term doesn't return a value", last.position)
}
super.visit(pipe)
}
override fun visit(pipe: Pipe) {
processPipe(pipe.expressions, pipe)
super.visit(pipe)
}
private fun processPipe(expressions: List<Expression>, scope: Node) {
// first expression is just any expression producing a value
// all other expressions should be the name of a unary function that returns a single value
// the last expression should be the name of a unary function whose return value we don't care about.
if (expressions.size < 2) {
errors.err("pipe is missing one or more expressions", scope.position)
} else {
// invalid size and other issues will be handled by the ast checker later.
var valueDt = expressions[0].inferType(program).getOrElse {
throw FatalAstException("invalid dt ${expressions[0]} @ ${scope.position}")
}
for(expr in expressions.drop(1)) { // just keep the first expression value as-is
val functionName = expr as? IdentifierReference
val function = functionName?.targetStatement(program)
if(functionName!=null && function!=null) {
when (function) {
is BuiltinFunctionPlaceholder -> {
val func = BuiltinFunctions.getValue(function.name)
if(func.parameters.size!=1)
errors.err("can only use unary function", expr.position)
else if(func.known_returntype==null && expr !== expressions.last())
errors.err("function must return a single value", expr.position)
val paramDts = func.parameters.firstOrNull()?.possibleDatatypes
if(paramDts!=null && !paramDts.any { valueDt isAssignableTo it })
errors.err("pipe value datatype $valueDt incompatible withfunction argument ${paramDts.toList()}", functionName.position)
valueDt = func.known_returntype!!
}
is Subroutine -> {
if(function.parameters.size!=1)
errors.err("can only use unary function", expr.position)
else if(function.returntypes.size!=1 && expr !== expressions.last())
errors.err("function must return a single value", expr.position)
val paramDt = function.parameters.firstOrNull()?.type
if(paramDt!=null && !(valueDt isAssignableTo paramDt))
errors.err("pipe value datatype $valueDt incompatible with function argument $paramDt", functionName.position)
if(function.returntypes.isNotEmpty())
valueDt = function.returntypes.single()
}
else -> {
throw FatalAstException("weird function")
}
}
} else {
if(expr is IFunctionCall)
errors.err("use only the name of the function, not a call", expr.position)
else
errors.err("can only use unary function", expr.position)
}
}
}
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? { private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
when (val targetStatement = target.targetStatement(program)) { when (val targetStatement = target.targetStatement(program)) {
is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement is Label, is Subroutine, is BuiltinFunctionPlaceholder -> return targetStatement
null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position) is VarDecl -> {
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", statement.position) if(statement is Jump) {
if (targetStatement.datatype == DataType.UWORD)
return targetStatement
else
errors.err("wrong address variable datatype, expected uword", target.position)
}
else
errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position)
}
null -> errors.err("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", target.position)
else -> errors.err("cannot call that: ${target.nameInSource.joinToString(".")}", target.position)
} }
return null return null
} }
private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteralValue) : Boolean { private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteral) : Boolean {
return if (targetDt == DataType.STR) { return if (targetDt == DataType.STR) {
if (value.value.length > 255) { when {
value.value.length > 255 -> {
errors.err("string length must be 0-255", value.position) errors.err("string length must be 0-255", value.position)
false false
} }
else value.value.isEmpty() -> {
true val decl = value.parent as? VarDecl
if(decl!=null && (decl.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || decl.zeropage==ZeropageWish.PREFER_ZEROPAGE)) {
errors.err("string in Zeropage must be non-empty", value.position)
false
}
else true
}
else -> true
}
} }
else false else false
} }
private fun checkValueTypeAndRangeArray(targetDt: DataType, arrayspec: ArrayIndex, value: ArrayLiteralValue) : Boolean { private fun checkValueTypeAndRangeArray(targetDt: DataType, arrayspec: ArrayIndex, value: ArrayLiteral) : Boolean {
fun err(msg: String) : Boolean { fun err(msg: String) : Boolean {
errors.err(msg, value.position) errors.err(msg, value.position)
return false return false
@ -1316,7 +1438,7 @@ internal class AstChecker(private val program: Program,
} }
} }
private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteralValue) : Boolean { private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteral) : Boolean {
fun err(msg: String) : Boolean { fun err(msg: String) : Boolean {
errors.err(msg, value.position) errors.err(msg, value.position)
return false return false
@ -1360,10 +1482,10 @@ internal class AstChecker(private val program: Program,
return true return true
} }
private fun checkArrayValues(value: ArrayLiteralValue, type: DataType): Boolean { private fun checkArrayValues(value: ArrayLiteral, type: DataType): Boolean {
val array = value.value.map { val array = value.value.map {
when (it) { when (it) {
is NumericLiteralValue -> it.number.toInt() is NumericLiteral -> it.number.toInt()
is AddressOf -> it.identifier.hashCode() and 0xffff is AddressOf -> it.identifier.hashCode() and 0xffff
is TypecastExpression -> { is TypecastExpression -> {
val constVal = it.expression.constValue(program) val constVal = it.expression.constValue(program)
@ -1403,7 +1525,7 @@ internal class AstChecker(private val program: Program,
sourceValue: Expression) : Boolean { sourceValue: Expression) : Boolean {
val position = sourceValue.position val position = sourceValue.position
if(sourceValue is RangeExpr) if(sourceValue is RangeExpression)
errors.err("can't assign a range value to something else", position) errors.err("can't assign a range value to something else", position)
val result = when(targetDatatype) { val result = when(targetDatatype) {

View File

@ -5,29 +5,36 @@ import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.expressions.CharLiteral import prog8.ast.expressions.CharLiteral
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.ast.statements.VarDeclOrigin
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compilerinterface.CompilationOptions import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IErrorReporter import prog8.compilerinterface.IErrorReporter
import prog8.compilerinterface.IStringEncoding import prog8.compilerinterface.IStringEncoding
import prog8.compilerinterface.IVariablesAndConsts
internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) { internal fun Program.checkValid(errors: IErrorReporter, compilerOptions: CompilationOptions) {
// semantic analysis to see if the program is valid.
val parentChecker = ParentNodeChecker() val parentChecker = ParentNodeChecker()
parentChecker.visit(this) parentChecker.visit(this)
val checker = AstChecker(this, errors, compilerOptions) val checker = AstChecker(this, errors, compilerOptions)
checker.visit(this) checker.visit(this)
} }
internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, errors: IErrorReporter) { internal fun Program.processAstBeforeAsmGeneration(compilerOptions: CompilationOptions, variables: IVariablesAndConsts, errors: IErrorReporter) {
val fixer = BeforeAsmGenerationAstChanger(this, compilerOptions, errors) val fixer = BeforeAsmAstChanger(this, compilerOptions, variables, errors)
fixer.visit(this) fixer.visit(this)
while(errors.noErrors() && fixer.applyModifications()>0) { while(errors.noErrors() && fixer.applyModifications()>0) {
fixer.visit(this) fixer.visit(this)
} }
val cleaner = BeforeAsmTypecastCleaner(this, errors)
cleaner.visit(this)
while(errors.noErrors() && cleaner.applyModifications()>0) {
cleaner.visit(this)
}
} }
internal fun Program.reorderStatements(errors: IErrorReporter, options: CompilationOptions) { internal fun Program.reorderStatements(errors: IErrorReporter, options: CompilationOptions) {
@ -46,7 +53,7 @@ internal fun Program.charLiteralsToUByteLiterals(enc: IStringEncoding) {
override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> { override fun after(char: CharLiteral, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
char, char,
NumericLiteralValue(DataType.UBYTE, enc.encodeString(char.value.toString(), char.altEncoding)[0].toDouble(), char.position), NumericLiteral(DataType.UBYTE, enc.encodeString(char.value.toString(), char.encoding)[0].toDouble(), char.position),
parent parent
)) ))
} }
@ -72,17 +79,17 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this) fixer.visit(this)
} }
internal fun Program.preprocessAst(program: Program, errors: IErrorReporter) { internal fun Program.preprocessAst(errors: IErrorReporter) {
val transforms = AstPreprocessor(program, errors) val transforms = AstPreprocessor(this, errors)
transforms.visit(this) transforms.visit(this)
var mods = transforms.applyModifications() var mods = transforms.applyModifications()
while(mods>0) while(mods>0)
mods = transforms.applyModifications() mods = transforms.applyModifications()
} }
internal fun Program.checkIdentifiers(errors: IErrorReporter, program: Program, options: CompilationOptions) { internal fun Program.checkIdentifiers(errors: IErrorReporter, options: CompilationOptions) {
val checker2 = AstIdentifiersChecker(errors, program, options.compTarget) val checker2 = AstIdentifiersChecker(errors, this, options.compTarget)
checker2.visit(this) checker2.visit(this)
if(errors.noErrors()) { if(errors.noErrors()) {
@ -96,8 +103,8 @@ internal fun Program.checkIdentifiers(errors: IErrorReporter, program: Program,
} }
} }
internal fun Program.variousCleanups(program: Program, errors: IErrorReporter) { internal fun Program.variousCleanups(errors: IErrorReporter, options: CompilationOptions) {
val process = VariousCleanups(program, errors) val process = VariousCleanups(this, errors, options)
process.visit(this) process.visit(this)
if(errors.noErrors()) if(errors.noErrors())
process.applyModifications() process.applyModifications()
@ -135,7 +142,7 @@ internal fun Program.moveMainAndStartToFirst() {
internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean { internal fun IdentifierReference.isSubroutineParameter(program: Program): Boolean {
val vardecl = this.targetVarDecl(program) val vardecl = this.targetVarDecl(program)
if(vardecl!=null && vardecl.autogeneratedDontRemove) { if(vardecl!=null && vardecl.origin==VarDeclOrigin.SUBROUTINEPARAM) {
return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true return vardecl.definingSubroutine?.parameters?.any { it.name==vardecl.name } == true
} }
return false return false

View File

@ -4,11 +4,12 @@ import prog8.ast.IFunctionCall
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.FunctionCallExpr import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.StringLiteralValue import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.BuiltinFunctions import prog8.compilerinterface.BuiltinFunctions
import prog8.compilerinterface.Encoding
import prog8.compilerinterface.ICompilationTarget import prog8.compilerinterface.ICompilationTarget
import prog8.compilerinterface.IErrorReporter import prog8.compilerinterface.IErrorReporter
@ -121,14 +122,14 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
super.visit(label) super.visit(label)
} }
override fun visit(string: StringLiteralValue) { override fun visit(string: StringLiteral) {
if (string.value.length > 255) if (string.value.length > 255)
errors.err("string literal length max is 255", string.position) errors.err("string literal length max is 255", string.position)
super.visit(string) super.visit(string)
} }
override fun visit(functionCallExpr: FunctionCallExpr) = visitFunctionCall(functionCallExpr) override fun visit(functionCallExpr: FunctionCallExpression) = visitFunctionCall(functionCallExpr)
override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement) override fun visit(functionCallStatement: FunctionCallStatement) = visitFunctionCall(functionCallStatement)
private fun visitFunctionCall(call: IFunctionCall) { private fun visitFunctionCall(call: IFunctionCall) {
@ -146,7 +147,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
errors.err("invalid number of arguments", pos) errors.err("invalid number of arguments", pos)
} }
if(func.name=="memory") { if(func.name=="memory") {
val name = call.args[0] as? StringLiteralValue val name = call.args[0] as? StringLiteral
if(name!=null) { if(name!=null) {
val processed = name.value.map { val processed = name.value.map {
if(it.isLetterOrDigit()) if(it.isLetterOrDigit())
@ -154,7 +155,7 @@ internal class AstIdentifiersChecker(private val errors: IErrorReporter,
else else
'_' '_'
}.joinToString("") }.joinToString("")
call.args[0] = StringLiteralValue(processed, false, name.position) call.args[0] = StringLiteral(processed, Encoding.PETSCII, name.position)
call.args[0].linkParents(call as Node) call.args[0].linkParents(call as Node)
} }
} }

View File

@ -2,7 +2,9 @@ package prog8.compiler.astprocessing
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.NumericDatatypes
import prog8.ast.base.SyntaxError
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
@ -12,10 +14,10 @@ import prog8.compilerinterface.IErrorReporter
class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWalker() { class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWalker() {
override fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> { override fun after(range: RangeExpression, parent: Node): Iterable<IAstModification> {
// has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes // has to be done before the constant folding, otherwise certain checks there will fail on invalid range sizes
val modifications = mutableListOf<IAstModification>() val modifications = mutableListOf<IAstModification>()
if(range.from !is NumericLiteralValue) { if(range.from !is NumericLiteral) {
try { try {
val constval = range.from.constValue(program) val constval = range.from.constValue(program)
if (constval != null) if (constval != null)
@ -24,7 +26,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
// syntax errors will be reported later // syntax errors will be reported later
} }
} }
if(range.to !is NumericLiteralValue) { if(range.to !is NumericLiteral) {
try { try {
val constval = range.to.constValue(program) val constval = range.to.constValue(program)
if(constval!=null) if(constval!=null)
@ -33,7 +35,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
// syntax errors will be reported later // syntax errors will be reported later
} }
} }
if(range.step !is NumericLiteralValue) { if(range.step !is NumericLiteral) {
try { try {
val constval = range.step.constValue(program) val constval = range.step.constValue(program)
if(constval!=null) if(constval!=null)
@ -49,7 +51,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
// move vardecls in Anonymous scope up to the containing subroutine // move vardecls in Anonymous scope up to the containing subroutine
// and add initialization assignment in its place if needed // and add initialization assignment in its place if needed
val vars = scope.statements.filterIsInstance<VarDecl>() val vars = scope.statements.asSequence().filterIsInstance<VarDecl>()
val parentscope = scope.definingScope val parentscope = scope.definingScope
if(vars.any() && parentscope !== parent) { if(vars.any() && parentscope !== parent) {
val movements = mutableListOf<IAstModification>() val movements = mutableListOf<IAstModification>()
@ -62,7 +64,7 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
} else { } else {
if(decl.value!=null && decl.datatype in NumericDatatypes) { if(decl.value!=null && decl.datatype in NumericDatatypes) {
val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position) val target = AssignTarget(IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, decl.value!!, decl.position) val assign = Assignment(target, decl.value!!, AssignmentOrigin.VARINIT, decl.position)
replacements.add(IAstModification.ReplaceNode(decl, assign, scope)) replacements.add(IAstModification.ReplaceNode(decl, assign, scope))
decl.value = null decl.value = null
decl.allowInitializeWithZero = false decl.allowInitializeWithZero = false
@ -85,4 +87,16 @@ class AstPreprocessor(val program: Program, val errors: IErrorReporter) : AstWal
} }
return noModifications return noModifications
} }
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val nextAssignment = decl.nextSibling() as? Assignment
if(nextAssignment!=null && nextAssignment.origin!=AssignmentOrigin.VARINIT) {
// check if it's a proper initializer assignment for the variable
if(decl.value==null && nextAssignment.target.identifier?.targetVarDecl(program)===decl) {
if(!nextAssignment.value.referencesIdentifier(nextAssignment.target.identifier!!.nameInSource))
nextAssignment.origin = AssignmentOrigin.VARINIT
}
}
return noModifications
}
} }

View File

@ -6,8 +6,11 @@ import prog8.ast.base.DataType
import prog8.ast.expressions.ArrayIndexedExpression import prog8.ast.expressions.ArrayIndexedExpression
import prog8.ast.expressions.BinaryExpression import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.DirectMemoryRead import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.StringLiteralValue import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.* import prog8.ast.statements.AssignTarget
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
@ -21,7 +24,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
val namesInSub = symbolsInSub.map{ it.first }.toSet() val namesInSub = symbolsInSub.map{ it.first }.toSet()
if(subroutine.asmAddress==null) { if(subroutine.asmAddress==null) {
if(!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty()) { if(!subroutine.isAsmSubroutine && subroutine.parameters.isNotEmpty()) {
val vars = subroutine.statements.filterIsInstance<VarDecl>().map { it.name }.toSet() val vars = subroutine.statements.asSequence().filterIsInstance<VarDecl>().map { it.name }.toSet()
if(!vars.containsAll(subroutine.parameters.map{it.name})) { if(!vars.containsAll(subroutine.parameters.map{it.name})) {
return subroutine.parameters return subroutine.parameters
.filter { it.name !in namesInSub } .filter { it.name !in namesInSub }
@ -37,8 +40,8 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
} }
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> { override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftStr = expr.left as? StringLiteralValue val leftStr = expr.left as? StringLiteral
val rightStr = expr.right as? StringLiteralValue val rightStr = expr.right as? StringLiteral
if(expr.operator == "+") { if(expr.operator == "+") {
val concatenatedString = concatString(expr) val concatenatedString = concatString(expr)
if(concatenatedString!=null) if(concatenatedString!=null)
@ -49,7 +52,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
val amount = expr.right.constValue(program) val amount = expr.right.constValue(program)
if(amount!=null) { if(amount!=null) {
val string = leftStr.value.repeat(amount.number.toInt()) val string = leftStr.value.repeat(amount.number.toInt())
val strval = StringLiteralValue(string, leftStr.altEncoding, expr.position) val strval = StringLiteral(string, leftStr.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, strval, parent)) return listOf(IAstModification.ReplaceNode(expr, strval, parent))
} }
} }
@ -57,7 +60,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
val amount = expr.right.constValue(program) val amount = expr.right.constValue(program)
if(amount!=null) { if(amount!=null) {
val string = rightStr.value.repeat(amount.number.toInt()) val string = rightStr.value.repeat(amount.number.toInt())
val strval = StringLiteralValue(string, rightStr.altEncoding, expr.position) val strval = StringLiteral(string, rightStr.encoding, expr.position)
return listOf(IAstModification.ReplaceNode(expr, strval, parent)) return listOf(IAstModification.ReplaceNode(expr, strval, parent))
} }
} }
@ -70,9 +73,9 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent) return replacePointerVarIndexWithMemreadOrMemwrite(program, arrayIndexedExpression, parent)
} }
private fun concatString(expr: BinaryExpression): StringLiteralValue? { private fun concatString(expr: BinaryExpression): StringLiteral? {
val rightStrval = expr.right as? StringLiteralValue val rightStrval = expr.right as? StringLiteral
val leftStrval = expr.left as? StringLiteralValue val leftStrval = expr.left as? StringLiteral
return when { return when {
expr.operator!="+" -> null expr.operator!="+" -> null
expr.left is BinaryExpression && rightStrval!=null -> { expr.left is BinaryExpression && rightStrval!=null -> {
@ -80,17 +83,17 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
if(subStrVal==null) if(subStrVal==null)
null null
else else
StringLiteralValue("${subStrVal.value}${rightStrval.value}", subStrVal.altEncoding, rightStrval.position) StringLiteral("${subStrVal.value}${rightStrval.value}", subStrVal.encoding, rightStrval.position)
} }
expr.right is BinaryExpression && leftStrval!=null -> { expr.right is BinaryExpression && leftStrval!=null -> {
val subStrVal = concatString(expr.right as BinaryExpression) val subStrVal = concatString(expr.right as BinaryExpression)
if(subStrVal==null) if(subStrVal==null)
null null
else else
StringLiteralValue("${leftStrval.value}${subStrVal.value}", subStrVal.altEncoding, leftStrval.position) StringLiteral("${leftStrval.value}${subStrVal.value}", subStrVal.encoding, leftStrval.position)
} }
leftStrval!=null && rightStrval!=null -> { leftStrval!=null && rightStrval!=null -> {
StringLiteralValue("${leftStrval.value}${rightStrval.value}", leftStrval.altEncoding, leftStrval.position) StringLiteral("${leftStrval.value}${rightStrval.value}", leftStrval.encoding, leftStrval.position)
} }
else -> null else -> null
} }

View File

@ -1,35 +1,20 @@
package prog8.compiler package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall import prog8.ast.*
import prog8.ast.IStatementContainer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
import prog8.compiler.astprocessing.isSubroutineParameter
import prog8.codegen.target.AssemblyError
import prog8.compilerinterface.* import prog8.compilerinterface.*
import prog8.optimizer.getTempVarName import prog8.optimizer.getTempRegisterName
internal class BeforeAsmAstChanger(val program: Program,
internal class BeforeAsmGenerationAstChanger(val program: Program, private val options: CompilationOptions, private val options: CompilationOptions,
private val errors: IErrorReporter) : AstWalker() { private val variables: IVariablesAndConsts,
private val errors: IErrorReporter
private val subroutineVariables = mutableMapOf<Subroutine, MutableList<Pair<String, VarDecl>>>() ) : AstWalker() {
private fun rememberSubroutineVar(decl: VarDecl) {
val sub = decl.definingSubroutine ?: return
var varsList = subroutineVariables[sub]
if(varsList==null) {
varsList = mutableListOf()
subroutineVariables[sub] = varsList
}
varsList.add(decl.name to decl)
}
override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> { override fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> {
throw FatalAstException("break should have been replaced by goto $breakStmt") throw FatalAstException("break should have been replaced by goto $breakStmt")
@ -48,13 +33,28 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
val subs = block.statements.filterIsInstance<Subroutine>() val subs = block.statements.filterIsInstance<Subroutine>()
block.statements.removeAll(subs) block.statements.removeAll(subs)
block.statements.addAll(subs) block.statements.addAll(subs)
// 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 return noModifications
} }
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type==VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes) if(!options.dontReinitGlobals) {
if (decl.type == VarDeclType.VAR && decl.value != null && decl.datatype in NumericDatatypes)
throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl") throw FatalAstException("vardecls for variables, with initial numerical value, should have been rewritten as plain vardecl + assignment $decl")
rememberSubroutineVar(decl) }
return noModifications return noModifications
} }
@ -79,20 +79,28 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
// A = <something-without-A> <associativeoperator> <otherthing-with-A> // A = <something-without-A> <associativeoperator> <otherthing-with-A>
// use the other part of the expression to split. // use the other part of the expression to split.
val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("unknown dt") } val sourceDt = binExpr.right.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError("unknown dt") }, sourceDt, implicit=true) val (_, right) = binExpr.right.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
val assignRight = Assignment(assignment.target, right, assignment.position) "unknown dt"
)
}, sourceDt, implicit=true)
val assignRight = Assignment(assignment.target, right, AssignmentOrigin.BEFOREASMGEN, assignment.position)
return listOf( return listOf(
IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer), IAstModification.InsertBefore(assignment, assignRight, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr), IAstModification.ReplaceNode(binExpr.right, binExpr.left, binExpr),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
)
} }
} else { } else {
val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("unknown dt") } val sourceDt = binExpr.left.inferType(program).getOrElse { throw AssemblyError("unknown dt") }
val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError("unknown dt") }, sourceDt, implicit=true) val (_, left) = binExpr.left.typecastTo(assignment.target.inferType(program).getOrElse { throw AssemblyError(
val assignLeft = Assignment(assignment.target, left, assignment.position) "unknown dt"
)
}, sourceDt, implicit=true)
val assignLeft = Assignment(assignment.target, left, AssignmentOrigin.BEFOREASMGEN, assignment.position)
return listOf( return listOf(
IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer), IAstModification.InsertBefore(assignment, assignLeft, parent as IStatementContainer),
IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)) IAstModification.ReplaceNode(binExpr.left, assignment.target.toExpression(), binExpr)
)
} }
} }
} }
@ -107,27 +115,17 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
} }
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val firstDeclarations = mutableMapOf<String, VarDecl>() val mods = mutableListOf<IAstModification>()
val rememberedSubroutineVars = subroutineVariables.getOrDefault(subroutine, mutableListOf())
for(decl in rememberedSubroutineVars) {
val existing = firstDeclarations[decl.first]
if(existing!=null && existing !== decl.second) {
errors.err("variable ${decl.first} already defined in subroutine ${subroutine.name} at ${existing.position}", decl.second.position)
} else {
firstDeclarations[decl.first] = decl.second
}
}
rememberedSubroutineVars.clear()
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine. // add the implicit return statement at the end (if it's not there yet), but only if it's not a kernal routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations. // and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>() if (!subroutine.isAsmSubroutine && !subroutine.inline) {
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null && !subroutine.inline) {
if(subroutine.statements.isEmpty() || if(subroutine.statements.isEmpty() ||
(subroutine.amountOfRtsInAsm() == 0 (subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return && subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine)) { && subroutine.statements.last() !is Subroutine
&& subroutine.statements.last() !is Return)) {
val returnStmt = Return(null, subroutine.position)
mods += IAstModification.InsertLast(returnStmt, subroutine) mods += IAstModification.InsertLast(returnStmt, subroutine)
} }
} }
@ -141,75 +139,51 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
if(outerScope !is Block if(outerScope !is Block
&& (prevStmt !is Jump) && (prevStmt !is Jump)
&& prevStmt !is Subroutine && prevStmt !is Subroutine
&& prevStmt !is Return) { && prevStmt !is Return
) {
val returnStmt = Return(null, subroutine.position)
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope) mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope)
} }
} }
if (!subroutine.inline) {
if (subroutine.isAsmSubroutine && subroutine.asmAddress==null && subroutine.amountOfRtsInAsm() == 0) {
// make sure the NOT INLINED asm subroutine actually has a rts at the end
// (non-asm routines get a Return statement as needed, above)
mods += IAstModification.InsertLast(InlineAssembly(" rts\n", Position.DUMMY), subroutine)
}
}
return mods return mods
} }
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove redundant typecasts (outside of expressions)
// such as casting byte<->ubyte, word<->uword
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of,
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
if(typecast.parent !is Expression) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
val identifier = typecast.expression as? IdentifierReference
if(identifier!=null) {
return if(identifier.isSubroutineParameter(program)) {
listOf(IAstModification.ReplaceNode(
typecast,
typecast.expression,
parent
))
} else {
listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(identifier, typecast.position),
parent
))
}
} else if(typecast.expression is IFunctionCall) {
return listOf(IAstModification.ReplaceNode(
typecast,
typecast.expression,
parent
))
}
} else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
}
}
return noModifications
}
override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> { override fun after(ifElse: IfElse, parent: Node): Iterable<IAstModification> {
val prefixExpr = ifElse.condition as? PrefixExpression val prefixExpr = ifElse.condition as? PrefixExpression
if(prefixExpr!=null && prefixExpr.operator=="not") { if(prefixExpr!=null && prefixExpr.operator=="not") {
// if not x -> if x==0 // if not x -> if x==0
val booleanExpr = BinaryExpression(prefixExpr.expression, "==", NumericLiteralValue.optimalInteger(0, ifElse.condition.position), ifElse.condition.position) val booleanExpr = BinaryExpression(
prefixExpr.expression,
"==",
NumericLiteral.optimalInteger(0, ifElse.condition.position),
ifElse.condition.position
)
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse)) return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
} }
val binExpr = ifElse.condition as? BinaryExpression val binExpr = ifElse.condition as? BinaryExpression
if(binExpr==null || binExpr.operator !in ComparisonOperators) { if(binExpr==null || binExpr.operator !in ComparisonOperators) {
// if x -> if x!=0, if x+5 -> if x+5 != 0 // if x -> if x!=0, if x+5 -> if x+5 != 0
val booleanExpr = BinaryExpression(ifElse.condition, "!=", NumericLiteralValue.optimalInteger(0, ifElse.condition.position), ifElse.condition.position) val booleanExpr = BinaryExpression(
ifElse.condition,
"!=",
NumericLiteral.optimalInteger(0, ifElse.condition.position),
ifElse.condition.position
)
return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse)) return listOf(IAstModification.ReplaceNode(ifElse.condition, booleanExpr, ifElse))
} }
if((binExpr.left as? NumericLiteralValue)?.number==0.0 && if((binExpr.left as? NumericLiteral)?.number==0.0 &&
(binExpr.right as? NumericLiteralValue)?.number!=0.0) (binExpr.right as? NumericLiteral)?.number!=0.0)
throw FatalAstException("0==X should have been swapped to if X==0") throw FatalAstException("0==X should have been swapped to if X==0")
// simplify the conditional expression, introduce simple assignments if required. // simplify the conditional expression, introduce simple assignments if required.
@ -219,11 +193,19 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
val modifications = mutableListOf<IAstModification>() val modifications = mutableListOf<IAstModification>()
if(simplify.rightVarAssignment!=null) { if(simplify.rightVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr) modifications += IAstModification.ReplaceNode(binExpr.right, simplify.rightOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(ifElse, simplify.rightVarAssignment, parent as IStatementContainer) modifications += IAstModification.InsertBefore(
ifElse,
simplify.rightVarAssignment,
parent as IStatementContainer
)
} }
if(simplify.leftVarAssignment!=null) { if(simplify.leftVarAssignment!=null) {
modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr) modifications += IAstModification.ReplaceNode(binExpr.left, simplify.leftOperandReplacement!!, binExpr)
modifications += IAstModification.InsertBefore(ifElse, simplify.leftVarAssignment, parent as IStatementContainer) modifications += IAstModification.InsertBefore(
ifElse,
simplify.leftVarAssignment,
parent as IStatementContainer
)
} }
return modifications return modifications
@ -240,9 +222,9 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
// TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off, // TODO: somehow figure out if the expr will result in stack-evaluation STILL after being split off,
// in that case: do *not* split it off but just keep it as it is (otherwise code size increases) // in that case: do *not* split it off but just keep it as it is (otherwise code size increases)
// TODO: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
// TODO: this should be replaced by a general expression-evaluation optimization step. // TODO: this should be replaced by a general expression-evaluation optimization step.
// the actual conditional expression in the statement should be no more than VARIABLE <COMPARISON-OPERATOR> SIMPLE-EXPRESSION // the actual conditional expression in the statement should be no more than VARIABLE <COMPARISON-OPERATOR> SIMPLE-EXPRESSION
// NOTE: do NOT move this to an earler ast transform phase (such as StatementReorderer or StatementOptimizer) - it WILL result in larger code.
var leftAssignment: Assignment? = null var leftAssignment: Assignment? = null
var leftOperandReplacement: Expression? = null var leftOperandReplacement: Expression? = null
@ -260,27 +242,23 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
} }
if(separateLeftExpr) { if(separateLeftExpr) {
val name = getTempVarName(leftDt) val name = getTempRegisterName(leftDt)
leftOperandReplacement = IdentifierReference(name, expr.position) leftOperandReplacement = IdentifierReference(name, expr.position)
leftAssignment = Assignment( leftAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position), AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
expr.left, expr.left,
expr.position AssignmentOrigin.BEFOREASMGEN, expr.position
) )
} }
if(separateRightExpr) { if(separateRightExpr) {
val name = when { val name = program.getTempVar(rightDt.getOrElse { throw FatalAstException("invalid dt") }, true)
rightDt istype DataType.UBYTE -> listOf("prog8_lib","retval_interm_ub") val tempvardecl = program.toplevelModule.lookup(name) as VarDecl
rightDt istype DataType.UWORD -> listOf("prog8_lib","retval_interm_uw") variables.addIfUnknown(tempvardecl.definingBlock, tempvardecl)
rightDt istype DataType.BYTE -> listOf("prog8_lib","retval_interm_b2")
rightDt istype DataType.WORD -> listOf("prog8_lib","retval_interm_w2")
else -> throw AssemblyError("invalid dt")
}
rightOperandReplacement = IdentifierReference(name, expr.position) rightOperandReplacement = IdentifierReference(name, expr.position)
rightAssignment = Assignment( rightAssignment = Assignment(
AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position), AssignTarget(IdentifierReference(name, expr.position), null, null, expr.position),
expr.right, expr.right,
expr.position AssignmentOrigin.BEFOREASMGEN, expr.position
) )
} }
return CondExprSimplificationResult( return CondExprSimplificationResult(
@ -299,13 +277,13 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
if(dt1 in ByteDatatypes) { if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes) if(dt2 in ByteDatatypes)
return noModifications return noModifications
val (replaced, cast) = arg1.typecastTo(if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true) val (replaced, cast) = arg1.typecastTo(if(dt1== DataType.UBYTE) DataType.UWORD else DataType.WORD, dt1, true)
if(replaced) if(replaced)
return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement)) return listOf(IAstModification.ReplaceNode(arg1, cast, functionCallStatement))
} else { } else {
if(dt2 in WordDatatypes) if(dt2 in WordDatatypes)
return noModifications return noModifications
val (replaced, cast) = arg2.typecastTo(if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true) val (replaced, cast) = arg2.typecastTo(if(dt2== DataType.UBYTE) DataType.UWORD else DataType.WORD, dt2, true)
if(replaced) if(replaced)
return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement)) return listOf(IAstModification.ReplaceNode(arg2, cast, functionCallStatement))
} }
@ -323,7 +301,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
val index = arrayIndexedExpression.indexer.indexExpr val index = arrayIndexedExpression.indexer.indexExpr
if(index !is NumericLiteralValue && index !is IdentifierReference) { if(index !is NumericLiteral && index !is IdentifierReference) {
// replace complex indexing expression with a temp variable to hold the computed index first // replace complex indexing expression with a temp variable to hold the computed index first
return getAutoIndexerVarFor(arrayIndexedExpression) return getAutoIndexerVarFor(arrayIndexedExpression)
} }
@ -337,11 +315,11 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
val complexArrayIndexedExpressions = mutableListOf<ArrayIndexedExpression>() val complexArrayIndexedExpressions = mutableListOf<ArrayIndexedExpression>()
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) { override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
val ix = arrayIndexedExpression.indexer.indexExpr val ix = arrayIndexedExpression.indexer.indexExpr
if(ix !is NumericLiteralValue && ix !is IdentifierReference) if(ix !is NumericLiteral && ix !is IdentifierReference)
complexArrayIndexedExpressions.add(arrayIndexedExpression) complexArrayIndexedExpressions.add(arrayIndexedExpression)
} }
override fun visit(branch: Branch) {} override fun visit(branch: ConditionalBranch) {}
override fun visit(forLoop: ForLoop) {} override fun visit(forLoop: ForLoop) {}
@ -371,11 +349,19 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, private val o
val modifications = mutableListOf<IAstModification>() val modifications = mutableListOf<IAstModification>()
val statement = expr.containingStatement val statement = expr.containingStatement
val dt = expr.indexer.indexExpr.inferType(program) val dt = expr.indexer.indexExpr.inferType(program)
val tempvar = if(dt.isBytes) listOf("prog8_lib","retval_interm_ub") else listOf("prog8_lib","retval_interm_b") val tempvar = program.getTempVar(dt.getOrElse { throw FatalAstException("invalid dt") })
val tempvardecl = program.toplevelModule.lookup(tempvar) as VarDecl
variables.addIfUnknown(tempvardecl.definingBlock, tempvardecl)
val target = AssignTarget(IdentifierReference(tempvar, expr.indexer.position), null, null, expr.indexer.position) val target = AssignTarget(IdentifierReference(tempvar, expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position) val assign = Assignment(target, expr.indexer.indexExpr, AssignmentOrigin.BEFOREASMGEN, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer)) modifications.add(IAstModification.InsertBefore(statement, assign, statement.parent as IStatementContainer))
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer)) modifications.add(
IAstModification.ReplaceNode(
expr.indexer.indexExpr,
target.identifier!!.copy(),
expr.indexer
)
)
return modifications return modifications
} }

View File

@ -0,0 +1,76 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.PassByReferenceDatatypes
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.TypecastExpression
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compilerinterface.IErrorReporter
internal class BeforeAsmTypecastCleaner(val program: Program,
private val errors: IErrorReporter
) : AstWalker() {
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove redundant typecasts (outside of expressions)
// such as casting byte<->ubyte, word<->uword or even redundant casts (sourcetype = target type).
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of,
// UNLESS it's a str parameter in the containing subroutine - then we remove the typecast altogether
val sourceDt = typecast.expression.inferType(program).getOr(DataType.UNDEFINED)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes
) {
if(typecast.parent !is Expression) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
if(typecast.type==sourceDt)
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type== DataType.UWORD) {
val identifier = typecast.expression as? IdentifierReference
if(identifier!=null) {
return if(identifier.isSubroutineParameter(program)) {
listOf(
IAstModification.ReplaceNode(
typecast,
typecast.expression,
parent
)
)
} else {
listOf(
IAstModification.ReplaceNode(
typecast,
AddressOf(identifier, typecast.position),
parent
)
)
}
} else if(typecast.expression is IFunctionCall) {
return listOf(
IAstModification.ReplaceNode(
typecast,
typecast.expression,
parent
)
)
}
} else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
}
}
return noModifications
}
}

View File

@ -6,11 +6,14 @@ import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.ParentSentinel import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.* import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.PrefixExpression
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compilerinterface.* import prog8.compilerinterface.IErrorReporter
internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() { internal class CodeDesugarer(val program: Program, private val errors: IErrorReporter) : AstWalker() {
@ -117,7 +120,7 @@ _after:
override fun before(functionCallStatement: FunctionCallStatement, parent: Node) = override fun before(functionCallStatement: FunctionCallStatement, parent: Node) =
before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position) before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
override fun before(functionCallExpr: FunctionCallExpr, parent: Node) = override fun before(functionCallExpr: FunctionCallExpression, parent: Node) =
before(functionCallExpr as IFunctionCall, parent, functionCallExpr.position) before(functionCallExpr as IFunctionCall, parent, functionCallExpr.position)
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> { private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
@ -129,16 +132,9 @@ _after:
if(functionCall.target.nameInSource==listOf("poke")) { if(functionCall.target.nameInSource==listOf("poke")) {
// poke(a, v) is synonymous with @(a) = v // poke(a, v) is synonymous with @(a) = v
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position) val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
val assign = Assignment(tgt, functionCall.args[1], position) val assign = Assignment(tgt, functionCall.args[1], AssignmentOrigin.OPTIMIZER, position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent)) return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
} }
return noModifications return noModifications
} }
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
if(expr.operator=="in") {
println("IN-TEST: $expr\n in: $parent")
}
return noModifications
}
} }

View File

@ -1,12 +1,13 @@
package prog8.compiler.astprocessing package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.expressions.ArrayLiteralValue import prog8.ast.expressions.ArrayLiteral
import prog8.ast.expressions.ContainmentCheck import prog8.ast.expressions.ContainmentCheck
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.ast.statements.WhenChoice import prog8.ast.statements.WhenChoice
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
@ -15,9 +16,19 @@ import prog8.ast.walk.IAstModification
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() { internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> { override fun after(string: StringLiteral, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice && string.parent !is ContainmentCheck) { if(string.parent !is VarDecl
&& string.parent !is WhenChoice
&& (string.parent !is ContainmentCheck || string.value.length>ContainmentCheck.max_inlined_string_length)) {
// replace the literal string by an identifier reference to the interned string // replace the literal string by an identifier reference to the interned string
val parentFunc = (string.parent as? IFunctionCall)?.target
if(parentFunc!=null) {
if(parentFunc.nameInSource.size==1 && parentFunc.nameInSource[0]=="memory") {
// memory() builtin function just uses the string as a label name
return noModifications
}
}
val scopedName = program.internString(string) val scopedName = program.internString(string)
val identifier = IdentifierReference(scopedName, string.position) val identifier = IdentifierReference(scopedName, string.position)
return listOf(IAstModification.ReplaceNode(string, identifier, parent)) return listOf(IAstModification.ReplaceNode(string, identifier, parent))
@ -25,7 +36,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
return noModifications return noModifications
} }
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> { override fun after(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
val vardecl = array.parent as? VarDecl val vardecl = array.parent as? VarDecl
if(vardecl!=null) { if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess from the vardecl type) // adjust the datatype of the array (to an educated guess from the vardecl type)
@ -36,7 +47,7 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl)) return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
} }
} else { } else {
if(array.parent is ContainmentCheck) if(array.parent is ContainmentCheck && array.value.size<ContainmentCheck.max_inlined_string_length)
return noModifications return noModifications
val arrayDt = array.guessDatatype(program) val arrayDt = array.guessDatatype(program)

View File

@ -17,7 +17,7 @@ internal class ParentNodeChecker: AstWalker() {
return noModifications return noModifications
} }
override fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> { override fun before(array: ArrayLiteral, parent: Node): Iterable<IAstModification> {
if(array.parent!==parent) if(array.parent!==parent)
throw FatalAstException("parent node mismatch at $array") throw FatalAstException("parent node mismatch at $array")
return noModifications return noModifications
@ -47,7 +47,7 @@ internal class ParentNodeChecker: AstWalker() {
return noModifications return noModifications
} }
override fun before(branch: Branch, parent: Node): Iterable<IAstModification> { override fun before(branch: ConditionalBranch, parent: Node): Iterable<IAstModification> {
if(branch.parent!==parent) if(branch.parent!==parent)
throw FatalAstException("parent node mismatch at $branch") throw FatalAstException("parent node mismatch at $branch")
return noModifications return noModifications
@ -149,7 +149,7 @@ internal class ParentNodeChecker: AstWalker() {
return noModifications return noModifications
} }
override fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> { override fun before(numLiteral: NumericLiteral, parent: Node): Iterable<IAstModification> {
if(numLiteral.parent!==parent) if(numLiteral.parent!==parent)
throw FatalAstException("parent node mismatch at $numLiteral") throw FatalAstException("parent node mismatch at $numLiteral")
return noModifications return noModifications
@ -161,7 +161,7 @@ internal class ParentNodeChecker: AstWalker() {
return noModifications return noModifications
} }
override fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> { override fun before(range: RangeExpression, parent: Node): Iterable<IAstModification> {
if(range.parent!==parent) if(range.parent!==parent)
throw FatalAstException("parent node mismatch at $range") throw FatalAstException("parent node mismatch at $range")
return noModifications return noModifications
@ -185,7 +185,7 @@ internal class ParentNodeChecker: AstWalker() {
return noModifications return noModifications
} }
override fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> { override fun before(string: StringLiteral, parent: Node): Iterable<IAstModification> {
if(string.parent!==parent) if(string.parent!==parent)
throw FatalAstException("parent node mismatch at $string") throw FatalAstException("parent node mismatch at $string")
return noModifications return noModifications
@ -221,7 +221,7 @@ internal class ParentNodeChecker: AstWalker() {
return noModifications return noModifications
} }
override fun before(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> { override fun before(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
if(functionCallExpr.parent!==parent) if(functionCallExpr.parent!==parent)
throw FatalAstException("parent node mismatch at $functionCallExpr") throw FatalAstException("parent node mismatch at $functionCallExpr")
return noModifications return noModifications
@ -233,12 +233,6 @@ internal class ParentNodeChecker: AstWalker() {
return noModifications return noModifications
} }
override fun before(nop: Nop, parent: Node): Iterable<IAstModification> {
if(nop.parent!==parent)
throw FatalAstException("parent node mismatch at $nop")
return noModifications
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> { override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
if(scope.parent!==parent) if(scope.parent!==parent)
throw FatalAstException("parent node mismatch at $scope") throw FatalAstException("parent node mismatch at $scope")

View File

@ -13,7 +13,7 @@ Too bad, because the code is very small
//import prog8.ast.Program //import prog8.ast.Program
//import prog8.ast.base.Position //import prog8.ast.base.Position
//import prog8.ast.expressions.BinaryExpression //import prog8.ast.expressions.BinaryExpression
//import prog8.ast.expressions.NumericLiteralValue //import prog8.ast.expressions.NumericLiteral
//import kotlin.reflect.KClass //import kotlin.reflect.KClass
//import kotlin.reflect.KVisibility //import kotlin.reflect.KVisibility
//import kotlin.reflect.full.declaredMemberProperties //import kotlin.reflect.full.declaredMemberProperties
@ -59,9 +59,9 @@ Too bad, because the code is very small
// //
//fun main() { //fun main() {
// val ast = BinaryExpression( // val ast = BinaryExpression(
// NumericLiteralValue.optimalInteger(100, Position.DUMMY), // NumericLiteral.optimalInteger(100, Position.DUMMY),
// "+", // "+",
// NumericLiteralValue.optimalInteger(200, Position.DUMMY), // NumericLiteral.optimalInteger(200, Position.DUMMY),
// Position.DUMMY // Position.DUMMY
// ) // )
// //

View File

@ -31,7 +31,7 @@ internal class StatementReorderer(val program: Program,
val (blocks, other) = module.statements.partition { it is Block } val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: UInt.MAX_VALUE }).toMutableList() module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: UInt.MAX_VALUE }).toMutableList()
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" } val mainBlock = module.statements.asSequence().filterIsInstance<Block>().firstOrNull { it.name=="main" }
if(mainBlock!=null && mainBlock.address==null) { if(mainBlock!=null && mainBlock.address==null) {
module.statements.remove(mainBlock) module.statements.remove(mainBlock)
module.statements.add(0, mainBlock) module.statements.add(0, mainBlock)
@ -44,36 +44,30 @@ internal class StatementReorderer(val program: Program,
private val declsProcessedWithInitAssignment = mutableSetOf<VarDecl>() private val declsProcessedWithInitAssignment = mutableSetOf<VarDecl>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> { override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if(decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) { if (decl.type == VarDeclType.VAR) {
if (decl.datatype in NumericDatatypes) {
if(decl !in declsProcessedWithInitAssignment) { if(decl !in declsProcessedWithInitAssignment) {
declsProcessedWithInitAssignment.add(decl) declsProcessedWithInitAssignment.add(decl)
if (decl.value == null) { if (decl.value == null) {
if (!decl.autogeneratedDontRemove && decl.allowInitializeWithZero) { if (decl.origin==VarDeclOrigin.USERCODE && decl.allowInitializeWithZero) {
// A numeric vardecl without an initial value is initialized with zero, // A numeric vardecl without an initial value is initialized with zero,
// unless there's already an assignment below it, that initializes the value (or a for loop that uses it as loopvar). // unless there's already an assignment below it, that initializes the value (or a for loop that uses it as loopvar).
// This allows you to restart the program and have the same starting values of the variables // This allows you to restart the program and have the same starting values of the variables
// So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0' // So basically consider 'ubyte xx' as a short form for 'ubyte xx; xx=0'
decl.value = null decl.value = null
if(decl.name.startsWith("retval_interm_") && decl.definingScope.name=="prog8_lib") { if(decl.name.startsWith("tempvar_") && decl.definingScope.name=="prog8_lib") {
// no need to zero out the special internal returnvalue intermediates. // no need to zero out the special internal temporary variables.
return noModifications return noModifications
} }
val nextStmt = decl.nextSibling() if(decl.findInitializer(program)!=null)
val nextAssign = nextStmt as? Assignment return noModifications // an initializer assignment for a vardecl is already here
if(nextAssign!=null && !nextAssign.isAugmentable) { val nextFor = decl.nextSibling() as? ForLoop
val target = nextAssign.target.identifier?.targetStatement(program)
if(target === decl) {
// an initializer assignment for a vardecl is already here
return noModifications
}
}
val nextFor = nextStmt as? ForLoop
val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name) val hasNextForWithThisLoopvar = nextFor?.loopVar?.nameInSource==listOf(decl.name)
if (!hasNextForWithThisLoopvar) { if (!hasNextForWithThisLoopvar) {
// Add assignment to initialize with zero // Add assignment to initialize with zero
// Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later. // Note: for block-level vars, this will introduce assignments in the block scope. These have to be dealt with correctly later.
val identifier = IdentifierReference(listOf(decl.name), decl.position) val identifier = IdentifierReference(listOf(decl.name), decl.position)
val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), decl.position) val assignzero = Assignment(AssignTarget(identifier, null, null, decl.position), decl.zeroElementValue(), AssignmentOrigin.VARINIT, decl.position)
return listOf(IAstModification.InsertAfter( return listOf(IAstModification.InsertAfter(
decl, assignzero, parent as IStatementContainer decl, assignzero, parent as IStatementContainer
)) ))
@ -85,7 +79,7 @@ internal class StatementReorderer(val program: Program,
// So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99' // So basically consider 'ubyte xx=99' as a short form for 'ubyte xx; xx=99'
val pos = decl.value!!.position val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos) val identifier = IdentifierReference(listOf(decl.name), pos)
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, pos) val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, AssignmentOrigin.VARINIT, pos)
decl.value = null decl.value = null
return listOf(IAstModification.InsertAfter( return listOf(IAstModification.InsertAfter(
decl, assign, parent as IStatementContainer decl, assign, parent as IStatementContainer
@ -93,6 +87,25 @@ internal class StatementReorderer(val program: Program,
} }
} }
} }
else if(decl.datatype in ArrayDatatypes) {
// only if the initializer expression is a reference to another array, split it into a separate assignment.
// this is so that it later can be changed into a memcopy.
// (that code only triggers on regular assignment, not on variable initializers)
val ident = decl.value as? IdentifierReference
if(ident!=null) {
val target = ident.targetVarDecl(program)
if(target!=null && target.isArray) {
val pos = decl.value!!.position
val identifier = IdentifierReference(listOf(decl.name), pos)
val assign = Assignment(AssignTarget(identifier, null, null, pos), decl.value!!, AssignmentOrigin.VARINIT, pos)
decl.value = null
return listOf(IAstModification.InsertAfter(
decl, assign, parent as IStatementContainer
))
}
}
}
}
return noModifications return noModifications
} }
@ -118,7 +131,7 @@ internal class StatementReorderer(val program: Program,
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> { override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.name=="start" && parent is Block) { if(subroutine.name=="start" && parent is Block) {
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") { if(parent.statements.asSequence().filterIsInstance<Subroutine>().first().name!="start") {
return listOf( return listOf(
IAstModification.Remove(subroutine, parent), IAstModification.Remove(subroutine, parent),
IAstModification.InsertFirst(subroutine, parent) IAstModification.InsertFirst(subroutine, parent)
@ -150,14 +163,24 @@ internal class StatementReorderer(val program: Program,
varsChanges += varsChanges +=
if(stringParamsByNames.isNotEmpty()) { if(stringParamsByNames.isNotEmpty()) {
subroutine.statements subroutine.statements
.asSequence()
.filterIsInstance<VarDecl>() .filterIsInstance<VarDecl>()
.filter { it.subroutineParameter!=null && it.name in stringParamsByNames } .filter { it.subroutineParameter!=null && it.name in stringParamsByNames }
.map { .map {
val newvar = VarDecl(it.type, DataType.UWORD, it.zeropage, null, it.name, null, false, true, it.sharedWithAsm, stringParamsByNames.getValue(it.name), it.position) val newvar = VarDecl(it.type, it.origin, DataType.UWORD,
it.zeropage,
null,
it.name,
null,
false,
it.sharedWithAsm,
stringParamsByNames.getValue(it.name),
it.position
)
IAstModification.ReplaceNode(it, newvar, subroutine) IAstModification.ReplaceNode(it, newvar, subroutine)
} }
} }
else emptyList() else emptySequence()
} }
return modifications + parameterChanges + varsChanges return modifications + parameterChanges + varsChanges
@ -233,7 +256,7 @@ internal class StatementReorderer(val program: Program,
// generating the wrong results later // generating the wrong results later
fun wrapped(expr: Expression): Expression = fun wrapped(expr: Expression): Expression =
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0.0, expr.position), expr.position) BinaryExpression(expr, "!=", NumericLiteral(DataType.UBYTE, 0.0, expr.position), expr.position)
fun isLogicalExpr(expr: Expression?): Boolean { fun isLogicalExpr(expr: Expression?): Boolean {
if(expr is BinaryExpression && expr.operator in (LogicalOperators + ComparisonOperators)) if(expr is BinaryExpression && expr.operator in (LogicalOperators + ComparisonOperators))
@ -283,7 +306,7 @@ internal class StatementReorderer(val program: Program,
val targetType = assignment.target.inferType(program) val targetType = assignment.target.inferType(program)
if(targetType.isArray && valueType.isArray) { if(targetType.isArray && valueType.isArray) {
if (assignment.value is ArrayLiteralValue) { if (assignment.value is ArrayLiteral) {
errors.err("cannot assign array literal here, use separate assignment per element", assignment.position) errors.err("cannot assign array literal here, use separate assignment per element", assignment.position)
} else { } else {
return copyArrayValue(assignment) return copyArrayValue(assignment)
@ -346,8 +369,10 @@ internal class StatementReorderer(val program: Program,
val identifier = assign.target.identifier!! val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program)!! val targetVar = identifier.targetVarDecl(program)!!
if(targetVar.arraysize==null) if(targetVar.arraysize==null) {
errors.err("array has no defined size", assign.position) errors.err("array has no defined size", assign.position)
return noModifications
}
if(assign.value !is IdentifierReference) { if(assign.value !is IdentifierReference) {
errors.err("invalid array value to assign to other array", assign.value.position) errors.err("invalid array value to assign to other array", assign.value.position)
@ -367,11 +392,13 @@ internal class StatementReorderer(val program: Program,
if(!errors.noErrors()) if(!errors.noErrors())
return noModifications return noModifications
val numelements = targetVar.arraysize!!.constIndex()!!
val eltsize = program.memsizer.memorySize(ArrayToElementTypes.getValue(sourceVar.datatype))
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position), val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
mutableListOf( mutableListOf(
AddressOf(sourceIdent, assign.position), AddressOf(sourceIdent, assign.position),
AddressOf(identifier, assign.position), AddressOf(identifier, assign.position),
NumericLiteralValue.optimalInteger(targetVar.arraysize!!.constIndex()!!, assign.position) NumericLiteral.optimalInteger(numelements*eltsize, assign.position)
), ),
true, true,
assign.position assign.position
@ -382,18 +409,30 @@ internal class StatementReorderer(val program: Program,
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> { override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
val function = functionCallStatement.target.targetStatement(program)!! val function = functionCallStatement.target.targetStatement(program)!!
checkUnusedReturnValues(functionCallStatement, function, program, errors) checkUnusedReturnValues(functionCallStatement, function, program, errors)
return replaceCallByGosub(functionCallStatement, parent, program, options)
}
}
internal fun replaceCallByGosub(functionCallStatement: FunctionCallStatement,
parent: Node,
program: Program,
options: CompilationOptions): Iterable<IAstModification> {
val function = functionCallStatement.target.targetStatement(program)!!
if(function is Subroutine) { if(function is Subroutine) {
if(function.inline) if(function.inline)
return noModifications return emptyList()
return if(function.isAsmSubroutine) return if(function.isAsmSubroutine)
replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent) replaceCallAsmSubStatementWithGosub(function, functionCallStatement, parent, options)
else else
replaceCallSubStatementWithGosub(function, functionCallStatement, parent) replaceCallSubStatementWithGosub(function, functionCallStatement, parent, program)
}
return noModifications
} }
return emptyList()
}
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node, program: Program): Iterable<IAstModification> {
val noModifications = emptyList<IAstModification>()
private fun replaceCallSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(function.parameters.isEmpty()) { if(function.parameters.isEmpty()) {
// 0 params -> just GoSub // 0 params -> just GoSub
return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent)) return listOf(IAstModification.ReplaceNode(call, GoSub(null, call.target, null, call.position), parent))
@ -422,14 +461,16 @@ internal class StatementReorderer(val program: Program,
if(argumentValue is IdentifierReference) if(argumentValue is IdentifierReference)
argumentValue = AddressOf(argumentValue, argumentValue.position) argumentValue = AddressOf(argumentValue, argumentValue.position)
} }
Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, argumentValue.position) Assignment(AssignTarget(paramIdentifier, null, null, argumentValue.position), argumentValue, AssignmentOrigin.PARAMETERASSIGN, argumentValue.position)
} }
val scope = AnonymousScope(assignParams.toMutableList(), call.position) val scope = AnonymousScope(assignParams.toMutableList(), call.position)
scope.statements += GoSub(null, call.target, null, call.position) scope.statements += GoSub(null, call.target, null, call.position)
return listOf(IAstModification.ReplaceNode(call, scope, parent)) return listOf(IAstModification.ReplaceNode(call, scope, parent))
} }
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node, options: CompilationOptions): Iterable<IAstModification> {
val noModifications = emptyList<IAstModification>()
private fun replaceCallAsmSubStatementWithGosub(function: Subroutine, call: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(function.parameters.isEmpty()) { if(function.parameters.isEmpty()) {
// 0 params -> just GoSub // 0 params -> just GoSub
val scope = AnonymousScope(mutableListOf(), call.position) val scope = AnonymousScope(mutableListOf(), call.position)
@ -470,17 +511,17 @@ internal class StatementReorderer(val program: Program,
} }
return listOf(IAstModification.ReplaceNode(call, scope, parent)) return listOf(IAstModification.ReplaceNode(call, scope, parent))
} }
} }
private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement { private fun popCall(targetName: List<String>, dt: DataType, position: Position): FunctionCallStatement {
return FunctionCallStatement( return FunctionCallStatement(
IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position), IdentifierReference(listOf(if(dt in ByteDatatypes) "pop" else "popw"), position),
mutableListOf(IdentifierReference(targetName, position)), mutableListOf(IdentifierReference(targetName, position)),
true, position true, position
) )
} }
private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement { private fun pushCall(value: Expression, dt: DataType, position: Position): FunctionCallStatement {
val pushvalue = when(dt) { val pushvalue = when(dt) {
DataType.UBYTE, DataType.UWORD -> value DataType.UBYTE, DataType.UWORD -> value
in PassByReferenceDatatypes -> value in PassByReferenceDatatypes -> value
@ -494,6 +535,4 @@ internal class StatementReorderer(val program: Program,
mutableListOf(pushvalue), mutableListOf(pushvalue),
true, position true, position
) )
}
} }

View File

@ -33,11 +33,9 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(valueDt isNotAssignableTo decl.datatype) if(valueDt isNotAssignableTo decl.datatype)
return noModifications return noModifications
return listOf(IAstModification.ReplaceNode( val modifications = mutableListOf<IAstModification>()
declValue, addTypecastOrCastedValueModification(modifications, declValue, decl.datatype, decl)
TypecastExpression(declValue, decl.datatype, true, declValue.position), return modifications
decl
))
} }
} }
return noModifications return noModifications
@ -55,7 +53,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val value = if(rightDt.isBytes) 256+leftCv.number else 65536+leftCv.number val value = if(rightDt.isBytes) 256+leftCv.number else 65536+leftCv.number
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
expr.left, expr.left,
NumericLiteralValue(rightDt.getOr(DataType.UNDEFINED), value, expr.left.position), NumericLiteral(rightDt.getOr(DataType.UNDEFINED), value, expr.left.position),
expr)) expr))
} }
val rightCv = expr.right.constValue(program) val rightCv = expr.right.constValue(program)
@ -63,21 +61,43 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val value = if(leftDt.isBytes) 256+rightCv.number else 65536+rightCv.number val value = if(leftDt.isBytes) 256+rightCv.number else 65536+rightCv.number
return listOf(IAstModification.ReplaceNode( return listOf(IAstModification.ReplaceNode(
expr.right, expr.right,
NumericLiteralValue(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position), NumericLiteral(leftDt.getOr(DataType.UNDEFINED), value, expr.right.position),
expr)) expr))
} }
if(leftDt istype DataType.BYTE && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
// cast left to unsigned
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
if(leftDt istype DataType.WORD && rightDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
// cast left to unsigned
val cast = TypecastExpression(expr.left, rightDt.getOr(DataType.UNDEFINED), true, expr.left.position)
return listOf(IAstModification.ReplaceNode(expr.left, cast, expr))
}
if(rightDt istype DataType.BYTE && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
// cast right to unsigned
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
}
if(rightDt istype DataType.WORD && leftDt.oneOf(DataType.UBYTE, DataType.UWORD)) {
// cast right to unsigned
val cast = TypecastExpression(expr.right, leftDt.getOr(DataType.UNDEFINED), true, expr.right.position)
return listOf(IAstModification.ReplaceNode(expr.right, cast, expr))
}
} }
// determine common datatype and add typecast as required to make left and right equal types // determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.operator, expr.right) val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.getOr(DataType.UNDEFINED), rightDt.getOr(DataType.UNDEFINED), expr.left, expr.right)
if(toFix!=null) { if(toFix!=null) {
return when { val modifications = mutableListOf<IAstModification>()
toFix===expr.left -> listOf(IAstModification.ReplaceNode( when {
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr)) toFix===expr.left -> addTypecastOrCastedValueModification(modifications, expr.left, commonDt, expr)
toFix===expr.right -> listOf(IAstModification.ReplaceNode( toFix===expr.right -> addTypecastOrCastedValueModification(modifications, expr.right, commonDt, expr)
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
else -> throw FatalAstException("confused binary expression side") else -> throw FatalAstException("confused binary expression side")
} }
return modifications
} }
} }
return noModifications return noModifications
@ -95,12 +115,11 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if(valuetype in IterableDatatypes && targettype==DataType.UWORD) if(valuetype in IterableDatatypes && targettype==DataType.UWORD)
// special case, don't typecast STR/arrays to UWORD, we support those assignments "directly" // special case, don't typecast STR/arrays to UWORD, we support those assignments "directly"
return noModifications return noModifications
return listOf(IAstModification.ReplaceNode( val modifications = mutableListOf<IAstModification>()
assignment.value, addTypecastOrCastedValueModification(modifications, assignment.value, targettype, assignment)
TypecastExpression(assignment.value, targettype, true, assignment.value.position), return modifications
assignment))
} else { } else {
fun castLiteral(cvalue2: NumericLiteralValue): List<IAstModification.ReplaceNode> { fun castLiteral(cvalue2: NumericLiteral): List<IAstModification.ReplaceNode> {
val cast = cvalue2.cast(targettype) val cast = cvalue2.cast(targettype)
return if(cast.isValid) return if(cast.isValid)
listOf(IAstModification.ReplaceNode(assignment.value, cast.valueOrZero(), assignment)) listOf(IAstModification.ReplaceNode(assignment.value, cast.valueOrZero(), assignment))
@ -135,7 +154,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
return afterFunctionCallArgs(functionCallStatement) return afterFunctionCallArgs(functionCallStatement)
} }
override fun after(functionCallExpr: FunctionCallExpr, parent: Node): Iterable<IAstModification> { override fun after(functionCallExpr: FunctionCallExpression, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCallExpr) return afterFunctionCallArgs(functionCallExpr)
} }
@ -154,25 +173,22 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if (argtype isAssignableTo requiredType) { if (argtype isAssignableTo requiredType) {
// don't need a cast for pass-by-reference types that are assigned to UWORD // don't need a cast for pass-by-reference types that are assigned to UWORD
if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes) if(requiredType!=DataType.UWORD || argtype !in PassByReferenceDatatypes)
modifications += IAstModification.ReplaceNode( addTypecastOrCastedValueModification(modifications, pair.second, requiredType, call as Node)
call.args[index],
TypecastExpression(pair.second, requiredType, true, pair.second.position),
call as Node)
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) { } else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// We allow STR/ARRAY values in place of UWORD parameters. // We allow STR/ARRAY values in place of UWORD parameters.
// Take their address instead, UNLESS it's a str parameter in the containing subroutine // Take their address instead, UNLESS it's a str parameter in the containing subroutine
val identifier = pair.second as? IdentifierReference val identifier = pair.second as? IdentifierReference
if(identifier?.isSubroutineParameter(program)==false) { if(identifier?.isSubroutineParameter(program)==false) {
modifications += IAstModification.ReplaceNode( modifications += IAstModification.ReplaceNode(
call.args[index], identifier,
AddressOf(identifier, pair.second.position), AddressOf(identifier, pair.second.position),
call as Node) call as Node)
} }
} else if(pair.second is NumericLiteralValue) { } else if(pair.second is NumericLiteral) {
val cast = (pair.second as NumericLiteralValue).cast(requiredType) val cast = (pair.second as NumericLiteral).cast(requiredType)
if(cast.isValid) if(cast.isValid)
modifications += IAstModification.ReplaceNode( modifications += IAstModification.ReplaceNode(
call.args[index], pair.second,
cast.valueOrZero(), cast.valueOrZero(),
call as Node) call as Node)
} }
@ -189,10 +205,7 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
if (pair.first.possibleDatatypes.all { argtype != it }) { if (pair.first.possibleDatatypes.all { argtype != it }) {
for (possibleType in pair.first.possibleDatatypes) { for (possibleType in pair.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) { if (argtype isAssignableTo possibleType) {
modifications += IAstModification.ReplaceNode( addTypecastOrCastedValueModification(modifications, pair.second, possibleType, call as Node)
call.args[index],
TypecastExpression(pair.second, possibleType, true, pair.second.position),
call as Node)
break break
} }
else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) { else if(DataType.UWORD in pair.first.possibleDatatypes && argtype in PassByReferenceDatatypes) {
@ -226,29 +239,36 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
else else
errors.err("integer implicitly converted to float but floating point is not enabled via options", typecast.position) errors.err("integer implicitly converted to float but floating point is not enabled via options", typecast.position)
} }
return noModifications return noModifications
} }
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> { override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword // make sure the memory address is an uword
val modifications = mutableListOf<IAstModification>()
val dt = memread.addressExpression.inferType(program) val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) { if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero() val castedValue = (memread.addressExpression as? NumericLiteral)?.cast(DataType.UWORD)?.valueOrZero()
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position) if(castedValue!=null)
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread)) modifications += IAstModification.ReplaceNode(memread.addressExpression, castedValue, memread)
else
addTypecastOrCastedValueModification(modifications, memread.addressExpression, DataType.UWORD, memread)
} }
return noModifications return modifications
} }
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> { override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword // make sure the memory address is an uword
val modifications = mutableListOf<IAstModification>()
val dt = memwrite.addressExpression.inferType(program) val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) { if(dt.isKnown && dt.getOr(DataType.UWORD)!=DataType.UWORD) {
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)?.valueOrZero() val castedValue = (memwrite.addressExpression as? NumericLiteral)?.cast(DataType.UWORD)?.valueOrZero()
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position) if(castedValue!=null)
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite)) modifications += IAstModification.ReplaceNode(memwrite.addressExpression, castedValue, memwrite)
else
addTypecastOrCastedValueModification(modifications, memwrite.addressExpression, DataType.UWORD, memwrite)
} }
return noModifications return modifications
} }
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> { override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
@ -260,18 +280,37 @@ class TypecastsAdder(val program: Program, val options: CompilationOptions, val
val subReturnType = subroutine.returntypes.first() val subReturnType = subroutine.returntypes.first()
if (returnValue.inferType(program) istype subReturnType) if (returnValue.inferType(program) istype subReturnType)
return noModifications return noModifications
if (returnValue is NumericLiteralValue) { if (returnValue is NumericLiteral) {
val cast = returnValue.cast(subroutine.returntypes.single()) val cast = returnValue.cast(subroutine.returntypes.single())
if(cast.isValid) if(cast.isValid)
returnStmt.value = cast.valueOrZero() returnStmt.value = cast.valueOrZero()
} else { } else {
return listOf(IAstModification.ReplaceNode( val modifications = mutableListOf<IAstModification>()
returnValue, addTypecastOrCastedValueModification(modifications, returnValue, subReturnType, returnStmt)
TypecastExpression(returnValue, subReturnType, true, returnValue.position), return modifications
returnStmt))
} }
} }
} }
return noModifications return noModifications
} }
private fun addTypecastOrCastedValueModification(
modifications: MutableList<IAstModification>,
expressionToCast: Expression,
requiredType: DataType,
parent: Node
) {
val sourceDt = expressionToCast.inferType(program).getOr(DataType.UNDEFINED)
if(sourceDt == requiredType)
return
if(expressionToCast is NumericLiteral && expressionToCast.type!=DataType.FLOAT) { // refuse to automatically truncate floats
val castedValue = expressionToCast.cast(requiredType)
if (castedValue.isValid) {
modifications += IAstModification.ReplaceNode(expressionToCast, castedValue.valueOrZero(), parent)
return
}
}
val cast = TypecastExpression(expressionToCast, requiredType, true, expressionToCast.position)
modifications += IAstModification.ReplaceNode(expressionToCast, cast, parent)
}
} }

View File

@ -0,0 +1,191 @@
package prog8.compiler.astprocessing
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.walk.IAstVisitor
import prog8.compilerinterface.IVariablesAndConsts
internal class VariableExtractor: IAstVisitor {
private val allBlockVars = mutableMapOf<Block, MutableSet<VarDecl>>()
private val allBlockConsts = mutableMapOf<Block, MutableSet<VarDecl>>()
private val allBlockMemoryvars = mutableMapOf<Block, MutableSet<VarDecl>>()
private val allSubroutineVars = mutableMapOf<Subroutine, MutableSet<VarDecl>>()
private val allSubroutineConsts = mutableMapOf<Subroutine, MutableSet<VarDecl>>()
private val allSubroutineMemoryvars = mutableMapOf<Subroutine, MutableSet<VarDecl>>()
fun extractVars(program: Program): IVariablesAndConsts {
this.visit(program)
return VariablesAndConsts(
allBlockVars, allBlockConsts, allBlockMemoryvars,
allSubroutineVars, allSubroutineConsts, allSubroutineMemoryvars)
}
override fun visit(decl: VarDecl) {
val scope=decl.definingScope
when (decl.type) {
VarDeclType.VAR -> {
when (scope) {
is Block -> {
val decls = allBlockVars[scope] ?: mutableSetOf()
decls.add(decl)
allBlockVars[scope] = decls
}
is Subroutine -> {
val decls = allSubroutineVars[scope] ?: mutableSetOf()
decls.add(decl)
allSubroutineVars[scope] = decls
}
else -> {
throw FatalAstException("var can only occur in subroutine or block scope")
}
}
}
VarDeclType.CONST -> {
when(scope) {
is Block -> {
val decls = allBlockConsts[scope] ?: mutableSetOf()
decls.add(decl)
allBlockConsts[scope] = decls
}
is Subroutine -> {
val decls = allSubroutineConsts[scope] ?: mutableSetOf()
decls.add(decl)
allSubroutineConsts[scope] = decls
}
else -> {
throw FatalAstException("var can only occur in subroutine or block scope")
}
}
}
VarDeclType.MEMORY -> {
when(scope) {
is Block -> {
val decls = allBlockMemoryvars[scope] ?: mutableSetOf()
decls.add(decl)
allBlockMemoryvars[scope] = decls
}
is Subroutine -> {
val decls = allSubroutineMemoryvars[scope] ?: mutableSetOf()
decls.add(decl)
allSubroutineMemoryvars[scope] = decls
}
else -> {
throw FatalAstException("var can only occur in subroutine or block scope")
}
}
}
else -> {
throw FatalAstException("invalid var type")
}
}
super.visit(decl)
}
}
internal class VariablesAndConsts (
astBlockVars: Map<Block, Set<VarDecl>>,
astBlockConsts: Map<Block, Set<VarDecl>>,
astBlockMemvars: Map<Block, Set<VarDecl>>,
astSubroutineVars: Map<Subroutine, Set<VarDecl>>,
astSubroutineConsts: Map<Subroutine, Set<VarDecl>>,
astSubroutineMemvars: Map<Subroutine, Set<VarDecl>>
) : IVariablesAndConsts
{
override val blockVars: Map<Block, Set<IVariablesAndConsts.StaticVariable>>
override val blockConsts: Map<Block, Set<IVariablesAndConsts.ConstantNumberSymbol>>
override val blockMemvars: Map<Block, Set<IVariablesAndConsts.MemoryMappedVariable>>
override val subroutineVars: Map<Subroutine, Set<IVariablesAndConsts.StaticVariable>>
override val subroutineConsts: Map<Subroutine, Set<IVariablesAndConsts.ConstantNumberSymbol>>
override val subroutineMemvars: Map<Subroutine, Set<IVariablesAndConsts.MemoryMappedVariable>>
private val bv = astBlockVars.keys.associateWith { mutableSetOf<IVariablesAndConsts.StaticVariable>() }.toMutableMap()
private val bc = astBlockConsts.keys.associateWith { mutableSetOf<IVariablesAndConsts.ConstantNumberSymbol>() }
private val bmv = astBlockMemvars.keys.associateWith { mutableSetOf<IVariablesAndConsts.MemoryMappedVariable>() }
private val sv = astSubroutineVars.keys.associateWith { mutableSetOf<IVariablesAndConsts.StaticVariable>() }
private val sc = astSubroutineConsts.keys.associateWith { mutableSetOf<IVariablesAndConsts.ConstantNumberSymbol>() }
private val smv = astSubroutineMemvars.keys.associateWith { mutableSetOf<IVariablesAndConsts.MemoryMappedVariable>() }
init {
astBlockVars.forEach { (block, decls) ->
val vars = bv.getValue(block)
vars.addAll(decls.map { toStatic(it) })
}
astBlockConsts.forEach { (block, decls) ->
bc.getValue(block).addAll(
decls.map {
IVariablesAndConsts.ConstantNumberSymbol(
it.datatype,
it.scopedName,
(it.value as NumericLiteral).number,
it.position
)
})
}
astBlockMemvars.forEach { (block, decls) ->
val vars = bmv.getValue(block)
for(decl in decls) {
// make sure the 'stubs' for the scratch variables in zeropage are not included as normal variables
if(!decl.name.startsWith("P8ZP_SCRATCH_")) {
vars.add(
IVariablesAndConsts.MemoryMappedVariable(
decl.datatype,
decl.scopedName,
(decl.value as NumericLiteral).number.toUInt(),
decl.position
)
)
}
}
}
astSubroutineVars.forEach { (sub, decls) ->
val vars = sv.getValue(sub)
vars.addAll(decls.map { toStatic(it) })
}
astSubroutineConsts.forEach { (sub, decls) ->
sc.getValue(sub).addAll(
decls.map {
IVariablesAndConsts.ConstantNumberSymbol(
it.datatype,
it.scopedName,
(it.value as NumericLiteral).number,
it.position
)
})
}
astSubroutineMemvars.forEach { (sub, decls) ->
smv.getValue(sub).addAll(
decls.map {
IVariablesAndConsts.MemoryMappedVariable(
it.datatype,
it.scopedName,
(it.value as NumericLiteral).number.toUInt(),
it.position
)
})
}
blockVars = bv
blockConsts = bc
blockMemvars = bmv
subroutineVars = sv
subroutineConsts = sc
subroutineMemvars = smv
}
private fun toStatic(decl: VarDecl) =
IVariablesAndConsts.StaticVariable(decl.datatype, decl.scopedName, decl.definingScope, decl.value, decl.arraysize?.constIndex(), decl.zeropage, decl.position)
override fun addIfUnknown(definingBlock: Block, variable: VarDecl) {
var blockvars = bv[definingBlock]
if(blockvars==null) {
blockvars = mutableSetOf()
bv[definingBlock] = blockvars
}
blockvars.add(toStatic(variable))
}
}

View File

@ -1,5 +1,6 @@
package prog8.compiler.astprocessing package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.IStatementContainer import prog8.ast.IStatementContainer
import prog8.ast.Node import prog8.ast.Node
import prog8.ast.Program import prog8.ast.Program
@ -7,17 +8,16 @@ import prog8.ast.base.ArrayDatatypes
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Assignment
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.walk.AstWalker import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification import prog8.ast.walk.IAstModification
import prog8.compilerinterface.CompilationOptions
import prog8.compilerinterface.IErrorReporter import prog8.compilerinterface.IErrorReporter
internal class VariousCleanups(val program: Program, val errors: IErrorReporter): AstWalker() { internal class VariousCleanups(val program: Program, val errors: IErrorReporter, val options: CompilationOptions): AstWalker() {
override fun before(nop: Nop, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nop, parent as IStatementContainer))
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> { override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is IStatementContainer) return if(parent is IStatementContainer)
@ -38,8 +38,8 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
} }
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> { override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) { if(typecast.expression is NumericLiteral) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type) val value = (typecast.expression as NumericLiteral).cast(typecast.type)
if(value.isValid) if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent)) return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
} }
@ -62,7 +62,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> { override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val nextAssign = assignment.nextSibling() as? Assignment val nextAssign = assignment.nextSibling() as? Assignment
if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) { if(nextAssign!=null && nextAssign.target.isSameAs(assignment.target, program)) {
if(nextAssign.value isSameAs assignment.value) if(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 listOf(IAstModification.Remove(assignment, parent as IStatementContainer))
} }
@ -98,7 +98,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
val leftBinExpr = expr.left as? BinaryExpression val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression val rightBinExpr = expr.right as? BinaryExpression
if(leftBinExpr!=null && leftBinExpr.operator=="==" && rightBinExpr!=null && rightBinExpr.operator=="==") { if(leftBinExpr!=null && leftBinExpr.operator=="==" && rightBinExpr!=null && rightBinExpr.operator=="==") {
if(leftBinExpr.right is NumericLiteralValue && rightBinExpr.right is NumericLiteralValue) { if(leftBinExpr.right is NumericLiteral && rightBinExpr.right is NumericLiteral) {
if(leftBinExpr.left isSameAs rightBinExpr.left) if(leftBinExpr.left isSameAs rightBinExpr.left)
errors.warn("consider using 'in' or 'when' to test for multiple values", expr.position) errors.warn("consider using 'in' or 'when' to test for multiple values", expr.position)
} }
@ -130,7 +130,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> { override fun after(containment: ContainmentCheck, parent: Node): Iterable<IAstModification> {
// replace trivial containment checks with just false or a single comparison // replace trivial containment checks with just false or a single comparison
fun replaceWithEquals(value: NumericLiteralValue): Iterable<IAstModification> { fun replaceWithEquals(value: NumericLiteral): Iterable<IAstModification> {
errors.warn("containment could be written as just a single comparison", containment.position) errors.warn("containment could be written as just a single comparison", containment.position)
val equals = BinaryExpression(containment.element, "==", value, containment.position) val equals = BinaryExpression(containment.element, "==", value, containment.position)
return listOf(IAstModification.ReplaceNode(containment, equals, parent)) return listOf(IAstModification.ReplaceNode(containment, equals, parent))
@ -138,7 +138,7 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
fun replaceWithFalse(): Iterable<IAstModification> { fun replaceWithFalse(): Iterable<IAstModification> {
errors.warn("condition is always false", containment.position) errors.warn("condition is always false", containment.position)
return listOf(IAstModification.ReplaceNode(containment, NumericLiteralValue.fromBoolean(false, containment.position), parent)) return listOf(IAstModification.ReplaceNode(containment, NumericLiteral.fromBoolean(false, containment.position), parent))
} }
fun checkArray(array: Array<Expression>): Iterable<IAstModification> { fun checkArray(array: Array<Expression>): Iterable<IAstModification> {
@ -152,51 +152,55 @@ internal class VariousCleanups(val program: Program, val errors: IErrorReporter)
return noModifications return noModifications
} }
fun checkString(stringVal: StringLiteralValue): Iterable<IAstModification> { fun checkString(stringVal: StringLiteral): Iterable<IAstModification> {
if(stringVal.value.isEmpty()) if(stringVal.value.isEmpty())
return replaceWithFalse() return replaceWithFalse()
if(stringVal.value.length==1) { if(stringVal.value.length==1) {
val string = program.encoding.encodeString(stringVal.value, stringVal.altEncoding) val string = program.encoding.encodeString(stringVal.value, stringVal.encoding)
return replaceWithEquals(NumericLiteralValue(DataType.UBYTE, string[0].toDouble(), stringVal.position)) return replaceWithEquals(NumericLiteral(DataType.UBYTE, string[0].toDouble(), stringVal.position))
} }
return noModifications return noModifications
} }
when(containment.iterable) { when(containment.iterable) {
is ArrayLiteralValue -> { is ArrayLiteral -> {
val array = (containment.iterable as ArrayLiteralValue).value val array = (containment.iterable as ArrayLiteral).value
return checkArray(array) return checkArray(array)
} }
is IdentifierReference -> { is IdentifierReference -> {
val variable = (containment.iterable as IdentifierReference).targetVarDecl(program)!! val variable = (containment.iterable as IdentifierReference).targetVarDecl(program)!!
when(variable.datatype) { when(variable.datatype) {
DataType.STR -> { DataType.STR -> {
val stringVal = (variable.value as StringLiteralValue) val stringVal = (variable.value as StringLiteral)
return checkString(stringVal) return checkString(stringVal)
} }
in ArrayDatatypes -> { in ArrayDatatypes -> {
val array = (variable.value as ArrayLiteralValue).value val array = (variable.value as ArrayLiteral).value
return checkArray(array) return checkArray(array)
} }
else -> {} else -> {}
} }
} }
is RangeExpr -> { is RangeExpression -> {
val constValues = (containment.iterable as RangeExpr).toConstantIntegerRange() val constValues = (containment.iterable as RangeExpression).toConstantIntegerRange()
if(constValues!=null) { if(constValues!=null) {
if (constValues.isEmpty()) if (constValues.isEmpty())
return replaceWithFalse() return replaceWithFalse()
if (constValues.count()==1) if (constValues.count()==1)
return replaceWithEquals(NumericLiteralValue.optimalNumeric(constValues.first, containment.position)) return replaceWithEquals(NumericLiteral.optimalNumeric(constValues.first, containment.position))
} }
} }
is StringLiteralValue -> { is StringLiteral -> {
val stringVal = containment.iterable as StringLiteralValue val stringVal = containment.iterable as StringLiteral
return checkString(stringVal) return checkString(stringVal)
} }
else -> {} else -> {}
} }
return noModifications return noModifications
} }
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return replaceCallByGosub(functionCallStatement, parent, program, options)
}
} }

View File

@ -4,7 +4,7 @@ import prog8.ast.IFunctionCall
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.expressions.Expression import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCallExpr import prog8.ast.expressions.FunctionCallExpression
import prog8.ast.expressions.TypecastExpression import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor import prog8.ast.walk.IAstVisitor
@ -13,7 +13,7 @@ import prog8.compilerinterface.InternalCompilerException
internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor { internal class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCallExpr: FunctionCallExpr) { override fun visit(functionCallExpr: FunctionCallExpression) {
val error = checkTypes(functionCallExpr as IFunctionCall, program) val error = checkTypes(functionCallExpr as IFunctionCall, program)
if(error!=null) if(error!=null)
throw InternalCompilerException(error) throw InternalCompilerException(error)

View File

@ -2,13 +2,6 @@ package prog8tests
import com.github.michaelbull.result.getErrorOrElse import com.github.michaelbull.result.getErrorOrElse
import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.getOrElse
import prog8.ast.Program
import prog8.ast.internedStringsModuleName
import prog8.compiler.ModuleImporter
import prog8.compilerinterface.IErrorReporter
import prog8.parser.ParseError
import prog8.parser.SourceCode
import kotlin.io.path.*
import io.kotest.assertions.fail import io.kotest.assertions.fail
import io.kotest.assertions.throwables.shouldThrow import io.kotest.assertions.throwables.shouldThrow
import io.kotest.assertions.withClue import io.kotest.assertions.withClue
@ -16,11 +9,14 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldBeIn import io.kotest.matchers.collections.shouldBeIn
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldContain
import prog8.ast.Program
import prog8.ast.internedStringsModuleName
import prog8.compiler.ModuleImporter
import prog8.compilerinterface.IErrorReporter
import prog8.parser.ParseError
import prog8.parser.SourceCode
import prog8tests.helpers.* import prog8tests.helpers.*
import prog8tests.helpers.DummyFunctions import kotlin.io.path.*
import prog8tests.helpers.DummyMemsizer
import prog8tests.helpers.DummyStringEncoder
import prog8tests.helpers.ErrorReporterForTests
class TestModuleImporter: FunSpec({ class TestModuleImporter: FunSpec({
@ -71,7 +67,7 @@ class TestModuleImporter: FunSpec({
val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString val searchIn = Path(".", "$srcPathRel").invariantSeparatorsPathString
val importer = makeImporter(null, searchIn) val importer = makeImporter(null, searchIn)
shouldThrow<AccessDeniedException> { importer.importModule(srcPathRel) } shouldThrow<FileSystemException> { importer.importModule(srcPathRel) }
.let { .let {
withClue(".file should be normalized") { withClue(".file should be normalized") {
"${it.file}" shouldBe "${it.file.normalize()}" "${it.file}" shouldBe "${it.file.normalize()}"
@ -82,7 +78,7 @@ class TestModuleImporter: FunSpec({
} }
program.modules.size shouldBe 1 program.modules.size shouldBe 1
shouldThrow<AccessDeniedException> { importer.importModule(srcPathAbs) } shouldThrow<FileSystemException> { importer.importModule(srcPathAbs) }
.let { .let {
withClue(".file should be normalized") { withClue(".file should be normalized") {
"${it.file}" shouldBe "${it.file.normalize()}" "${it.file}" shouldBe "${it.file.normalize()}"

View File

@ -1,14 +1,6 @@
package prog8tests package prog8tests
import io.kotest.core.config.AbstractProjectConfig import io.kotest.core.config.AbstractProjectConfig
import io.kotest.core.listeners.Listener
import io.kotest.core.listeners.TestListener
import io.kotest.core.spec.Spec
import io.kotest.extensions.system.NoSystemErrListener
import io.kotest.extensions.system.NoSystemOutListener
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import kotlin.math.max
object ProjectConfig : AbstractProjectConfig() { object ProjectConfig : AbstractProjectConfig() {
override val parallelism = 2 // max(2, Runtime.getRuntime().availableProcessors() / 2) override val parallelism = 2 // max(2, Runtime.getRuntime().availableProcessors() / 2)

View File

@ -5,6 +5,7 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain import io.kotest.matchers.string.shouldContain
import prog8.codegen.target.C64Target import prog8.codegen.target.C64Target
import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
@ -24,10 +25,76 @@ class TestAstChecks: FunSpec({
} }
""" """
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true) val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target, true, text, writeAssembly = true, errors=errors).assertSuccess() compileText(C64Target(), true, text, writeAssembly = true, errors=errors).assertSuccess()
errors.errors.size shouldBe 0 errors.errors.size shouldBe 0
errors.warnings.size shouldBe 2 errors.warnings.size shouldBe 2
errors.warnings[0] shouldContain "converted to float" errors.warnings[0] shouldContain "converted to float"
errors.warnings[1] shouldContain "converted to float" errors.warnings[1] shouldContain "converted to float"
} }
test("can't assign label or subroutine without using address-of") {
val text = """
main {
sub start() {
label:
uword @shared addr
addr = label
addr = thing
addr = &label
addr = &thing
}
sub thing() {
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target(), true, text, writeAssembly = true, errors=errors).assertFailure()
errors.errors.size shouldBe 2
errors.warnings.size shouldBe 0
errors.errors[0] shouldContain ":7:28) assignment value is invalid"
errors.errors[1] shouldContain ":8:28) assignment value is invalid"
}
test("can't do str or array expression without using address-of") {
val text = """
%import textio
main {
sub start() {
ubyte[] array = [1,2,3,4]
str s1 = "test"
ubyte ff = 1
txt.print(s1+ff)
txt.print(array+ff)
txt.print_uwhex(s1+ff, true)
txt.print_uwhex(array+ff, true)
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = false, errors=errors).assertFailure()
errors.errors.filter { it.contains("missing &") }.size shouldBe 4
}
test("str or array expression with address-of") {
val text = """
%import textio
main {
sub start() {
ubyte[] array = [1,2,3,4]
str s1 = "test"
ubyte ff = 1
txt.print(&s1+ff)
txt.print(&array+ff)
txt.print_uwhex(&s1+ff, true)
txt.print_uwhex(&array+ff, true)
; also good:
ff = (s1 == "derp")
ff = (s1 != "derp")
}
}
"""
compileText(C64Target(), false, text, writeAssembly = false).assertSuccess()
}
}) })

View File

@ -2,15 +2,18 @@ package prog8tests
import io.kotest.assertions.withClue import io.kotest.assertions.withClue
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.maps.shouldContainKey import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual
import io.kotest.matchers.maps.shouldNotContainKey import io.kotest.matchers.maps.shouldNotContainKey
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import prog8.ast.Program
import prog8.ast.statements.Block import prog8.ast.statements.Block
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.codegen.target.C64Target import prog8.codegen.target.C64Target
import prog8.compilerinterface.CallGraph import prog8.compilerinterface.CallGraph
import prog8tests.helpers.assertSuccess import prog8.parser.Prog8Parser.parseModule
import prog8tests.helpers.compileText import prog8.parser.SourceCode
import prog8tests.helpers.*
class TestCallgraph: FunSpec({ class TestCallgraph: FunSpec({
test("testGraphForEmptySubs") { test("testGraphForEmptySubs") {
@ -23,7 +26,7 @@ class TestCallgraph: FunSpec({
} }
} }
""" """
val result = compileText(C64Target, false, sourcecode).assertSuccess() val result = compileText(C64Target(), false, sourcecode).assertSuccess()
val graph = CallGraph(result.program) val graph = CallGraph(result.program)
graph.imports.size shouldBe 1 graph.imports.size shouldBe 1
@ -52,7 +55,7 @@ class TestCallgraph: FunSpec({
} }
} }
test("testGraphForEmptyButReferencedSub") { test("reference to empty sub") {
val sourcecode = """ val sourcecode = """
%import string %import string
main { main {
@ -64,7 +67,7 @@ class TestCallgraph: FunSpec({
} }
} }
""" """
val result = compileText(C64Target, false, sourcecode).assertSuccess() val result = compileText(C64Target(), false, sourcecode).assertSuccess()
val graph = CallGraph(result.program) val graph = CallGraph(result.program)
graph.imports.size shouldBe 1 graph.imports.size shouldBe 1
@ -82,17 +85,144 @@ class TestCallgraph: FunSpec({
val startSub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="start"} val startSub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="start"}
val emptySub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="empty"} val emptySub = mainBlock.statements.filterIsInstance<Subroutine>().single{it.name=="empty"}
withClue("start 'calls' (references) empty") { graph.calls shouldNotContainKey startSub
graph.calls shouldContainKey startSub graph.calledBy shouldNotContainKey emptySub
}
withClue("empty doesn't call anything") { withClue("empty doesn't call anything") {
graph.calls shouldNotContainKey emptySub graph.calls shouldNotContainKey emptySub
} }
withClue("empty gets 'called'") {
graph.calledBy shouldContainKey emptySub
}
withClue( "start doesn't get called (except as entrypoint ofc.)") { withClue( "start doesn't get called (except as entrypoint ofc.)") {
graph.calledBy shouldNotContainKey startSub graph.calledBy shouldNotContainKey startSub
} }
} }
test("allIdentifiers separates for different positions of the IdentifierReferences") {
val sourcecode = """
main {
sub start() {
uword x1 = &empty
uword x2 = &empty
empty()
}
sub empty() {
%asm {{
nop
}}
}
}
"""
val result = compileText(C64Target(), false, sourcecode).assertSuccess()
val graph = CallGraph(result.program)
graph.allIdentifiers.size shouldBeGreaterThanOrEqual 9
val empties = graph.allIdentifiers.keys.filter { it.nameInSource==listOf("empty") }
empties.size shouldBe 3
empties[0].position.line shouldBe 4
empties[1].position.line shouldBe 5
empties[2].position.line shouldBe 6
}
test("checking block and subroutine names usage in assembly code") {
val source = """
main {
sub start() {
%asm {{
lda #<blockname
lda #<blockname.subroutine
correctlabel:
nop
}}
}
}
blockname {
sub subroutine() {
@(1000) = 0
}
sub correctlabel() {
@(1000) = 0
}
}
; all block and subroutines below should NOT be found in asm because they're only substrings of the names in there
locknam {
sub rout() {
@(1000) = 0
}
sub orrectlab() {
@(1000) = 0
}
}"""
val module = parseModule(SourceCode.Text(source))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val callgraph = CallGraph(program)
val blockMain = program.allBlocks.single { it.name=="main" }
val blockBlockname = program.allBlocks.single { it.name=="blockname" }
val blockLocknam = program.allBlocks.single { it.name=="locknam" }
val subStart = blockMain.statements.filterIsInstance<Subroutine>().single { it.name == "start" }
val subSubroutine = blockBlockname.statements.filterIsInstance<Subroutine>().single { it.name == "subroutine" }
val subCorrectlabel = blockBlockname.statements.filterIsInstance<Subroutine>().single { it.name == "correctlabel" }
val subRout = blockLocknam.statements.filterIsInstance<Subroutine>().single { it.name == "rout" }
val subOrrectlab = blockLocknam.statements.filterIsInstance<Subroutine>().single { it.name == "orrectlab" }
callgraph.unused(blockMain) shouldBe false
callgraph.unused(blockBlockname) shouldBe false
callgraph.unused(blockLocknam) shouldBe true
callgraph.unused(subStart) shouldBe false
callgraph.unused(subSubroutine) shouldBe false
callgraph.unused(subCorrectlabel) shouldBe false
callgraph.unused(subRout) shouldBe true
callgraph.unused(subOrrectlab) shouldBe true
}
test("recursion detection") {
val source="""
main {
sub start() {
recurse1()
}
sub recurse1() {
recurse2()
}
sub recurse2() {
start()
}
}"""
val module = parseModule(SourceCode.Text(source))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val callgraph = CallGraph(program)
val errors = ErrorReporterForTests()
callgraph.checkRecursiveCalls(errors)
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 4
errors.warnings[0] shouldContain "contains recursive subroutine calls"
errors.warnings[1] shouldContain "start at"
errors.warnings[2] shouldContain "recurse1 at"
errors.warnings[3] shouldContain "recurse2 at"
}
test("no recursion warning if reference isn't a call") {
val source="""
main {
sub start() {
recurse1()
}
sub recurse1() {
recurse2()
}
sub recurse2() {
uword @shared address = &start
}
}"""
val module = parseModule(SourceCode.Text(source))
val program = Program("test", DummyFunctions, DummyMemsizer, DummyStringEncoder)
program.addModule(module)
val callgraph = CallGraph(program)
val errors = ErrorReporterForTests()
callgraph.checkRecursiveCalls(errors)
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 0
}
}) })

View File

@ -9,9 +9,9 @@ import prog8.ast.IFunctionCall
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteral
import prog8.ast.statements.Assignment
import prog8.codegen.target.Cx16Target import prog8.codegen.target.Cx16Target
import prog8.compilerinterface.Encoding
import prog8tests.helpers.assertSuccess import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText import prog8tests.helpers.compileText
@ -24,7 +24,7 @@ import prog8tests.helpers.compileText
class TestCompilerOnCharLit: FunSpec({ class TestCompilerOnCharLit: FunSpec({
test("testCharLitAsRomsubArg") { test("testCharLitAsRomsubArg") {
val platform = Cx16Target val platform = Cx16Target()
val result = compileText(platform, false, """ val result = compileText(platform, false, """
main { main {
romsub ${"$"}FFD2 = chrout(ubyte ch @ A) romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
@ -39,15 +39,15 @@ class TestCompilerOnCharLit: FunSpec({
val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0] val funCall = startSub.statements.filterIsInstance<IFunctionCall>()[0]
withClue("char literal should have been replaced by ubyte literal") { withClue("char literal should have been replaced by ubyte literal") {
funCall.args[0] shouldBe instanceOf<NumericLiteralValue>() funCall.args[0] shouldBe instanceOf<NumericLiteral>()
} }
val arg = funCall.args[0] as NumericLiteralValue val arg = funCall.args[0] as NumericLiteral
arg.type shouldBe DataType.UBYTE arg.type shouldBe DataType.UBYTE
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble() arg.number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0].toDouble()
} }
test("testCharVarAsRomsubArg") { test("testCharVarAsRomsubArg") {
val platform = Cx16Target val platform = Cx16Target()
val result = compileText(platform, false, """ val result = compileText(platform, false, """
main { main {
romsub ${"$"}FFD2 = chrout(ubyte ch @ A) romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
@ -76,18 +76,18 @@ class TestCompilerOnCharLit: FunSpec({
withClue("initializer value should have been moved to separate assignment"){ withClue("initializer value should have been moved to separate assignment"){
decl.value shouldBe null decl.value shouldBe null
} }
val assignInitialValue = decl.nextSibling() as Assignment val assignInitialValue = decl.findInitializer(program)!!
assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch") assignInitialValue.target.identifier!!.nameInSource shouldBe listOf("ch")
withClue("char literal should have been replaced by ubyte literal") { withClue("char literal should have been replaced by ubyte literal") {
assignInitialValue.value shouldBe instanceOf<NumericLiteralValue>() assignInitialValue.value shouldBe instanceOf<NumericLiteral>()
} }
val initializerValue = assignInitialValue.value as NumericLiteralValue val initializerValue = assignInitialValue.value as NumericLiteral
initializerValue.type shouldBe DataType.UBYTE initializerValue.type shouldBe DataType.UBYTE
initializerValue.number shouldBe platform.encodeString("\n", false)[0].toDouble() initializerValue.number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0].toDouble()
} }
test("testCharConstAsRomsubArg") { test("testCharConstAsRomsubArg") {
val platform = Cx16Target val platform = Cx16Target()
val result = compileText(platform, false, """ val result = compileText(platform, false, """
main { main {
romsub ${"$"}FFD2 = chrout(ubyte ch @ A) romsub ${"$"}FFD2 = chrout(ubyte ch @ A)
@ -108,10 +108,10 @@ class TestCompilerOnCharLit: FunSpec({
val decl = arg.targetVarDecl(program)!! val decl = arg.targetVarDecl(program)!!
decl.type shouldBe VarDeclType.CONST decl.type shouldBe VarDeclType.CONST
decl.datatype shouldBe DataType.UBYTE decl.datatype shouldBe DataType.UBYTE
(decl.value as NumericLiteralValue).number shouldBe platform.encodeString("\n", false)[0] (decl.value as NumericLiteral).number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0]
} }
is NumericLiteralValue -> { is NumericLiteral -> {
arg.number shouldBe platform.encodeString("\n", false)[0].toDouble() arg.number shouldBe platform.encodeString("\n", Encoding.PETSCII)[0].toDouble()
} }
else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail else -> fail("invalid arg type") // funCall.args[0] shouldBe instanceOf<IdentifierReference>() // make test fail
} }

View File

@ -1,14 +1,13 @@
package prog8tests package prog8tests
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import prog8.compiler.CompilationResult import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target
import prog8.compilerinterface.ICompilationTarget import prog8.compilerinterface.ICompilationTarget
import prog8tests.helpers.* import prog8tests.helpers.*
import prog8tests.helpers.assertSuccess
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.absolute import kotlin.io.path.absolute
import kotlin.io.path.exists import kotlin.io.path.exists
@ -32,6 +31,7 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
slowCodegenWarnings = false, slowCodegenWarnings = false,
quietAssembler = true, quietAssembler = true,
asmListfile = false, asmListfile = false,
experimentalCodegen = false,
compilationTarget = target.name, compilationTarget = target.name,
outputDir = outputDir outputDir = outputDir
) )
@ -40,10 +40,10 @@ private fun compileTheThing(filepath: Path, optimize: Boolean, target: ICompilat
private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> { private fun prepareTestFiles(source: String, optimize: Boolean, target: ICompilationTarget): Pair<String, Path> {
val searchIn = mutableListOf(examplesDir) val searchIn = mutableListOf(examplesDir)
if (target == Cx16Target) { if (target is Cx16Target) {
searchIn.add(0, assumeDirectory(examplesDir, "cx16")) searchIn.add(0, assumeDirectory(examplesDir, "cx16"))
} }
val filepath = searchIn val filepath = searchIn.asSequence()
.map { it.resolve("$source.p8") } .map { it.resolve("$source.p8") }
.map { it.normalize().absolute() } .map { it.normalize().absolute() }
.map { workingDir.relativize(it) } .map { workingDir.relativize(it) }
@ -72,9 +72,10 @@ class TestCompilerOnExamplesC64: FunSpec({
onlyC64.forEach { onlyC64.forEach {
val (source, optimize) = it val (source, optimize) = it
val (displayName, filepath) = prepareTestFiles(source, optimize, C64Target) val target = C64Target()
val (displayName, filepath) = prepareTestFiles(source, optimize, target)
test(displayName) { test(displayName) {
compileTheThing(filepath, optimize, C64Target).assertSuccess() compileTheThing(filepath, optimize, target).assertSuccess()
} }
} }
}) })
@ -103,9 +104,10 @@ class TestCompilerOnExamplesCx16: FunSpec({
onlyCx16.forEach { onlyCx16.forEach {
val (source, optimize) = it val (source, optimize) = it
val (displayName, filepath) = prepareTestFiles(source, optimize, Cx16Target) val target = Cx16Target()
val (displayName, filepath) = prepareTestFiles(source, optimize, target)
test(displayName) { test(displayName) {
compileTheThing(filepath, optimize, Cx16Target).assertSuccess() compileTheThing(filepath, optimize, target).assertSuccess()
} }
} }
}) })
@ -141,13 +143,15 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
bothCx16AndC64.forEach { bothCx16AndC64.forEach {
val (source, optimize) = it val (source, optimize) = it
val (displayNameC64, filepathC64) = prepareTestFiles(source, optimize, C64Target) val c64target = C64Target()
val (displayNameCx16, filepathCx16) = prepareTestFiles(source, optimize, Cx16Target) val cx16target = Cx16Target()
val (displayNameC64, filepathC64) = prepareTestFiles(source, optimize, c64target)
val (displayNameCx16, filepathCx16) = prepareTestFiles(source, optimize, cx16target)
test(displayNameC64) { test(displayNameC64) {
compileTheThing(filepathC64, optimize, C64Target).assertSuccess() compileTheThing(filepathC64, optimize, c64target).assertSuccess()
} }
test(displayNameCx16) { test(displayNameCx16) {
compileTheThing(filepathCx16, optimize, Cx16Target).assertSuccess() compileTheThing(filepathCx16, optimize, cx16target).assertSuccess()
} }
} }
}) })

View File

@ -6,7 +6,7 @@ import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe import io.kotest.matchers.shouldNotBe
import prog8.ast.expressions.AddressOf import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue import prog8.ast.expressions.StringLiteral
import prog8.ast.statements.FunctionCallStatement import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Label import prog8.ast.statements.Label
import prog8.codegen.target.Cx16Target import prog8.codegen.target.Cx16Target
@ -27,7 +27,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8") val filepath = assumeReadableFile(fixturesDir, "importFromSameFolder.p8")
assumeReadableFile(fixturesDir, "foo_bar.p8") assumeReadableFile(fixturesDir, "foo_bar.p8")
val platform = Cx16Target val platform = Cx16Target()
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name) val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
.assertSuccess() .assertSuccess()
@ -36,7 +36,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val strLits = startSub.statements val strLits = startSub.statements
.filterIsInstance<FunctionCallStatement>() .filterIsInstance<FunctionCallStatement>()
.map { it.args[0] as IdentifierReference } .map { it.args[0] as IdentifierReference }
.map { it.targetVarDecl(program)!!.value as StringLiteralValue } .map { it.targetVarDecl(program)!!.value as StringLiteral }
strLits[0].value shouldBe "main.bar" strLits[0].value shouldBe "main.bar"
strLits[1].value shouldBe "foo.bar" strLits[1].value shouldBe "foo.bar"
@ -50,7 +50,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8") val filepath = assumeReadableFile(fixturesDir, "asmIncludeFromSameFolder.p8")
assumeReadableFile(fixturesDir, "foo_bar.asm") assumeReadableFile(fixturesDir, "foo_bar.asm")
val platform = Cx16Target val platform = Cx16Target()
val result = compileFile(platform, optimize = false, fixturesDir, filepath.name) val result = compileFile(platform, optimize = false, fixturesDir, filepath.name)
.assertSuccess() .assertSuccess()
@ -60,7 +60,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
.filterIsInstance<FunctionCallStatement>() .filterIsInstance<FunctionCallStatement>()
.map { it.args[0] } .map { it.args[0] }
val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteralValue val str0 = (args[0] as IdentifierReference).targetVarDecl(program)!!.value as StringLiteral
str0.value shouldBe "main.bar" str0.value shouldBe "main.bar"
str0.definingScope.name shouldBe "main" str0.definingScope.name shouldBe "main"
@ -76,7 +76,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8") val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonExisting.p8")
assumeNotExists(fixturesDir, "i_do_not_exist.bin") assumeNotExists(fixturesDir, "i_do_not_exist.bin")
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir) compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir)
.assertFailure() .assertFailure()
} }
@ -84,7 +84,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8") val p8Path = assumeReadableFile(fixturesDir, "asmBinaryNonReadable.p8")
assumeDirectory(fixturesDir, "subFolder") assumeDirectory(fixturesDir, "subFolder")
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir) compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir)
.assertFailure() .assertFailure()
} }
@ -94,7 +94,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
) )
tests.forEach { tests.forEach {
val (where, p8Str, binStr) = it val (where, p8Str, _) = it
test("%asmbinary from ${where}folder") { test("%asmbinary from ${where}folder") {
val p8Path = assumeReadableFile(fixturesDir, p8Str) val p8Path = assumeReadableFile(fixturesDir, p8Str)
// val binPath = assumeReadableFile(fixturesDir, binStr) // val binPath = assumeReadableFile(fixturesDir, binStr)
@ -104,7 +104,7 @@ class TestCompilerOnImportsAndIncludes: FunSpec({
outputDir.normalize().toAbsolutePath() shouldNotBe workingDir.normalize().toAbsolutePath() outputDir.normalize().toAbsolutePath() shouldNotBe workingDir.normalize().toAbsolutePath()
} }
compileFile(Cx16Target, false, p8Path.parent, p8Path.name, outputDir) compileFile(Cx16Target(), false, p8Path.parent, p8Path.name, outputDir)
.assertSuccess( .assertSuccess(
"argument to assembler directive .binary " + "argument to assembler directive .binary " +
"should be relative to the generated .asm file (in output dir), " + "should be relative to the generated .asm file (in output dir), " +

View File

@ -7,16 +7,16 @@ import io.kotest.matchers.string.shouldContain
import io.kotest.matchers.types.instanceOf import io.kotest.matchers.types.instanceOf
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.* import prog8.ast.expressions.ArrayLiteral
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteral
import prog8.ast.expressions.RangeExpression
import prog8.ast.statements.ForLoop import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.codegen.target.C64Target import prog8.codegen.target.C64Target
import prog8.codegen.target.Cx16Target import prog8.codegen.target.Cx16Target
import prog8.compilerinterface.Encoding
import prog8tests.helpers.* import prog8tests.helpers.*
import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertFailure
import prog8tests.helpers.assertSuccess
import prog8tests.helpers.compileText
/** /**
@ -27,11 +27,11 @@ import prog8tests.helpers.compileText
class TestCompilerOnRanges: FunSpec({ class TestCompilerOnRanges: FunSpec({
test("testUByteArrayInitializerWithRange_char_to_char") { test("testUByteArrayInitializerWithRange_char_to_char") {
val platform = Cx16Target val platform = Cx16Target()
val result = compileText(platform, false, """ val result = compileText(platform, false, """
main { main {
sub start() { sub start() {
ubyte[] cs = @'a' to 'z' ; values are computed at compile time ubyte[] cs = sc:'a' to 'z' ; values are computed at compile time
cs[0] = 23 ; keep optimizer from removing it cs[0] = 23 ; keep optimizer from removing it
} }
} }
@ -41,11 +41,11 @@ class TestCompilerOnRanges: FunSpec({
val startSub = program.entrypoint val startSub = program.entrypoint
val decl = startSub val decl = startSub
.statements.filterIsInstance<VarDecl>()[0] .statements.filterIsInstance<VarDecl>()[0]
val rhsValues = (decl.value as ArrayLiteralValue) val rhsValues = (decl.value as ArrayLiteral)
.value // Array<Expression> .value // Array<Expression>
.map { (it as NumericLiteralValue).number.toInt() } .map { (it as NumericLiteral).number.toInt() }
val expectedStart = platform.encodeString("a", true)[0].toInt() val expectedStart = platform.encodeString("a", Encoding.SCREENCODES)[0].toInt()
val expectedEnd = platform.encodeString("z", false)[0].toInt() val expectedEnd = platform.encodeString("z", Encoding.PETSCII)[0].toInt()
val expectedStr = "$expectedStart .. $expectedEnd" val expectedStr = "$expectedStart .. $expectedEnd"
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}" val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
@ -58,9 +58,9 @@ class TestCompilerOnRanges: FunSpec({
} }
test("testFloatArrayInitializerWithRange_char_to_char") { test("testFloatArrayInitializerWithRange_char_to_char") {
val platform = C64Target val platform = C64Target()
val result = compileText(platform, optimize = false, """ val result = compileText(platform, optimize = false, """
%option enable_floats %import floats
main { main {
sub start() { sub start() {
float[] cs = 'a' to 'z' ; values are computed at compile time float[] cs = 'a' to 'z' ; values are computed at compile time
@ -73,11 +73,11 @@ class TestCompilerOnRanges: FunSpec({
val startSub = program.entrypoint val startSub = program.entrypoint
val decl = startSub val decl = startSub
.statements.filterIsInstance<VarDecl>()[0] .statements.filterIsInstance<VarDecl>()[0]
val rhsValues = (decl.value as ArrayLiteralValue) val rhsValues = (decl.value as ArrayLiteral)
.value // Array<Expression> .value // Array<Expression>
.map { (it as NumericLiteralValue).number.toInt() } .map { (it as NumericLiteral).number.toInt() }
val expectedStart = platform.encodeString("a", false)[0].toInt() val expectedStart = platform.encodeString("a", Encoding.PETSCII)[0].toInt()
val expectedEnd = platform.encodeString("z", false)[0].toInt() val expectedEnd = platform.encodeString("z", Encoding.PETSCII)[0].toInt()
val expectedStr = "$expectedStart .. $expectedEnd" val expectedStr = "$expectedStart .. $expectedEnd"
val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}" val actualStr = "${rhsValues.first()} .. ${rhsValues.last()}"
@ -92,8 +92,8 @@ class TestCompilerOnRanges: FunSpec({
context("floatArrayInitializerWithRange") { context("floatArrayInitializerWithRange") {
val combos = cartesianProduct( val combos = cartesianProduct(
listOf("", "42", "41"), // sizeInDecl listOf("", "42", "41"), // sizeInDecl
listOf("%option enable_floats", ""), // optEnableFloats listOf("%import floats", ""), // optEnableFloats
listOf(Cx16Target, C64Target), // platform listOf(Cx16Target(), C64Target()), // platform
listOf(false, true) // optimize listOf(false, true) // optimize
) )
@ -128,12 +128,12 @@ class TestCompilerOnRanges: FunSpec({
} }
test("testForLoopWithRange_char_to_char") { test("testForLoopWithRange_char_to_char") {
val platform = Cx16Target val platform = Cx16Target()
val result = compileText(platform, optimize = true, """ val result = compileText(platform, optimize = true, """
main { main {
sub start() { sub start() {
ubyte i ubyte i
for i in @'a' to 'f' { for i in sc:'a' to 'f' {
i += i ; keep optimizer from removing it i += i ; keep optimizer from removing it
} }
} }
@ -145,10 +145,10 @@ class TestCompilerOnRanges: FunSpec({
val iterable = startSub val iterable = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
.map { it.iterable }[0] .map { it.iterable }[0]
val rangeExpr = iterable as RangeExpr val rangeExpr = iterable as RangeExpression
val expectedStart = platform.encodeString("a", true)[0].toInt() val expectedStart = platform.encodeString("a", Encoding.SCREENCODES)[0].toInt()
val expectedEnd = platform.encodeString("f", false)[0].toInt() val expectedEnd = platform.encodeString("f", Encoding.PETSCII)[0].toInt()
val expectedStr = "$expectedStart .. $expectedEnd" val expectedStr = "$expectedStart .. $expectedEnd"
val intProgression = rangeExpr.toConstantIntegerRange() val intProgression = rangeExpr.toConstantIntegerRange()
@ -162,7 +162,7 @@ class TestCompilerOnRanges: FunSpec({
} }
test("testForLoopWithRange_bool_to_bool") { test("testForLoopWithRange_bool_to_bool") {
val platform = Cx16Target val platform = Cx16Target()
val result = compileText(platform, optimize = true, """ val result = compileText(platform, optimize = true, """
main { main {
sub start() { sub start() {
@ -179,7 +179,7 @@ class TestCompilerOnRanges: FunSpec({
val rangeExpr = startSub val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
.map { it.iterable } .map { it.iterable }
.filterIsInstance<RangeExpr>()[0] .filterIsInstance<RangeExpression>()[0]
rangeExpr.size() shouldBe 2 rangeExpr.size() shouldBe 2
val intProgression = rangeExpr.toConstantIntegerRange() val intProgression = rangeExpr.toConstantIntegerRange()
@ -188,7 +188,7 @@ class TestCompilerOnRanges: FunSpec({
} }
test("testForLoopWithRange_ubyte_to_ubyte") { test("testForLoopWithRange_ubyte_to_ubyte") {
val platform = Cx16Target val platform = Cx16Target()
val result = compileText(platform, optimize = true, """ val result = compileText(platform, optimize = true, """
main { main {
sub start() { sub start() {
@ -205,7 +205,7 @@ class TestCompilerOnRanges: FunSpec({
val rangeExpr = startSub val rangeExpr = startSub
.statements.filterIsInstance<ForLoop>() .statements.filterIsInstance<ForLoop>()
.map { it.iterable } .map { it.iterable }
.filterIsInstance<RangeExpr>()[0] .filterIsInstance<RangeExpression>()[0]
rangeExpr.size() shouldBe 9 rangeExpr.size() shouldBe 9
val intProgression = rangeExpr.toConstantIntegerRange() val intProgression = rangeExpr.toConstantIntegerRange()
@ -215,7 +215,7 @@ class TestCompilerOnRanges: FunSpec({
test("testForLoopWithRange_str_downto_str") { test("testForLoopWithRange_str_downto_str") {
val errors = ErrorReporterForTests() val errors = ErrorReporterForTests()
compileText(Cx16Target, true, """ compileText(Cx16Target(), true, """
main { main {
sub start() { sub start() {
ubyte i ubyte i
@ -231,7 +231,7 @@ class TestCompilerOnRanges: FunSpec({
} }
test("testForLoopWithIterable_str") { test("testForLoopWithIterable_str") {
val result = compileText(Cx16Target, false, """ val result = compileText(Cx16Target(), false, """
main { main {
sub start() { sub start() {
ubyte i ubyte i
@ -253,17 +253,17 @@ class TestCompilerOnRanges: FunSpec({
} }
test("testRangeExprNumericSize") { test("testRangeExprNumericSize") {
val expr = RangeExpr( val expr = RangeExpression(
NumericLiteralValue.optimalInteger(10, Position.DUMMY), NumericLiteral.optimalInteger(10, Position.DUMMY),
NumericLiteralValue.optimalInteger(20, Position.DUMMY), NumericLiteral.optimalInteger(20, Position.DUMMY),
NumericLiteralValue.optimalInteger(2, Position.DUMMY), NumericLiteral.optimalInteger(2, Position.DUMMY),
Position.DUMMY) Position.DUMMY)
expr.size() shouldBe 6 expr.size() shouldBe 6
expr.toConstantIntegerRange() expr.toConstantIntegerRange()
} }
test("range with negative step should be constvalue") { test("range with negative step should be constvalue") {
val result = compileText(C64Target, false, """ val result = compileText(C64Target(), false, """
main { main {
sub start() { sub start() {
ubyte[] array = 100 to 50 step -2 ubyte[] array = 100 to 50 step -2
@ -275,15 +275,15 @@ class TestCompilerOnRanges: FunSpec({
""").assertSuccess() """).assertSuccess()
val statements = result.program.entrypoint.statements val statements = result.program.entrypoint.statements
val array = (statements[0] as VarDecl).value val array = (statements[0] as VarDecl).value
array shouldBe instanceOf<ArrayLiteralValue>() array shouldBe instanceOf<ArrayLiteral>()
(array as ArrayLiteralValue).value.size shouldBe 26 (array as ArrayLiteral).value.size shouldBe 26
val forloop = (statements.dropLast(1).last() as ForLoop) val forloop = (statements.dropLast(1).last() as ForLoop)
forloop.iterable shouldBe instanceOf<RangeExpr>() forloop.iterable shouldBe instanceOf<RangeExpression>()
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY) (forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.UBYTE, -2.0, Position.DUMMY)
} }
test("range with start/end variables should be ok") { test("range with start/end variables should be ok") {
val result = compileText(C64Target, false, """ val result = compileText(C64Target(), false, """
main { main {
sub start() { sub start() {
byte from = 100 byte from = 100
@ -296,13 +296,13 @@ class TestCompilerOnRanges: FunSpec({
""").assertSuccess() """).assertSuccess()
val statements = result.program.entrypoint.statements val statements = result.program.entrypoint.statements
val forloop = (statements.dropLast(1).last() as ForLoop) val forloop = (statements.dropLast(1).last() as ForLoop)
forloop.iterable shouldBe instanceOf<RangeExpr>() forloop.iterable shouldBe instanceOf<RangeExpression>()
(forloop.iterable as RangeExpr).step shouldBe NumericLiteralValue(DataType.UBYTE, -2.0, Position.DUMMY) (forloop.iterable as RangeExpression).step shouldBe NumericLiteral(DataType.UBYTE, -2.0, Position.DUMMY)
} }
test("for statement on all possible iterable expressions") { test("for statement on all possible iterable expressions") {
compileText(C64Target, false, """ compileText(C64Target(), false, """
main { main {
sub start() { sub start() {
ubyte xx ubyte xx
@ -343,7 +343,7 @@ class TestCompilerOnRanges: FunSpec({
} }
test("if containment check on all possible iterable expressions") { test("if containment check on all possible iterable expressions") {
compileText(C64Target, false, """ compileText(C64Target(), false, """
main { main {
sub start() { sub start() {
ubyte xx ubyte xx
@ -404,7 +404,7 @@ class TestCompilerOnRanges: FunSpec({
} }
test("containment check in expressions") { test("containment check in expressions") {
compileText(C64Target, false, """ compileText(C64Target(), false, """
main { main {
sub start() { sub start() {
ubyte xx ubyte xx

View File

@ -1,12 +1,11 @@
package prog8tests package prog8tests
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
import prog8.codegen.target.Cx16Target
import prog8.compiler.CompilationResult import prog8.compiler.CompilationResult
import prog8.compiler.CompilerArguments import prog8.compiler.CompilerArguments
import prog8.compiler.compileProgram import prog8.compiler.compileProgram
import prog8.codegen.target.Cx16Target
import prog8tests.helpers.* import prog8tests.helpers.*
import prog8tests.helpers.assertSuccess
import java.nio.file.Path import java.nio.file.Path
import kotlin.io.path.absolute import kotlin.io.path.absolute
import kotlin.io.path.createTempFile import kotlin.io.path.createTempFile
@ -46,7 +45,8 @@ class TestCompilerOptionSourcedirs: FunSpec({
slowCodegenWarnings = false, slowCodegenWarnings = false,
quietAssembler = true, quietAssembler = true,
asmListfile = false, asmListfile = false,
compilationTarget = Cx16Target.name, experimentalCodegen = false,
compilationTarget = Cx16Target.NAME,
sourceDirs, sourceDirs,
outputDir outputDir
) )

View File

@ -5,9 +5,9 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldStartWith import io.kotest.matchers.string.shouldStartWith
import prog8.ast.internedStringsModuleName import prog8.ast.internedStringsModuleName
import prog8.codegen.target.C64Target
import prog8.compiler.determineCompilationOptions import prog8.compiler.determineCompilationOptions
import prog8.compiler.parseImports import prog8.compiler.parseImports
import prog8.codegen.target.C64Target
import prog8.compilerinterface.ZeropageType import prog8.compilerinterface.ZeropageType
import prog8tests.helpers.ErrorReporterForTests import prog8tests.helpers.ErrorReporterForTests
import prog8tests.helpers.assertSuccess import prog8tests.helpers.assertSuccess
@ -18,7 +18,7 @@ import prog8tests.helpers.outputDir
class TestImportedModulesOrderAndOptions: FunSpec({ class TestImportedModulesOrderAndOptions: FunSpec({
test("testImportedModuleOrderAndMainModuleCorrect") { test("testImportedModuleOrderAndMainModuleCorrect") {
val result = compileText(C64Target, false, """ val result = compileText(C64Target(), false, """
%import textio %import textio
%import floats %import floats
@ -36,7 +36,7 @@ main {
} }
withClue("module order in parse tree") { withClue("module order in parse tree") {
moduleNames.drop(1) shouldBe listOf( moduleNames.drop(1) shouldBe listOf(
"prog8_interned_strings", internedStringsModuleName,
"textio", "textio",
"syslib", "syslib",
"conv", "conv",
@ -49,7 +49,7 @@ main {
} }
test("testCompilationOptionsCorrectFromMain") { test("testCompilationOptionsCorrectFromMain") {
val result = compileText(C64Target, false, """ val result = compileText(C64Target(), false, """
%import textio %import textio
%import floats %import floats
%zeropage dontuse %zeropage dontuse
@ -62,7 +62,7 @@ main {
} }
""").assertSuccess() """).assertSuccess()
result.program.toplevelModule.name shouldStartWith "on_the_fly_test" result.program.toplevelModule.name shouldStartWith "on_the_fly_test"
val options = determineCompilationOptions(result.program, C64Target) val options = determineCompilationOptions(result.program, C64Target())
options.floats shouldBe true options.floats shouldBe true
options.zeropage shouldBe ZeropageType.DONTUSE options.zeropage shouldBe ZeropageType.DONTUSE
options.noSysInit shouldBe true options.noSysInit shouldBe true
@ -85,7 +85,7 @@ main {
val filenameBase = "on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16) val filenameBase = "on_the_fly_test_" + sourceText.hashCode().toUInt().toString(16)
val filepath = outputDir.resolve("$filenameBase.p8") val filepath = outputDir.resolve("$filenameBase.p8")
filepath.toFile().writeText(sourceText) filepath.toFile().writeText(sourceText)
val (program, options, importedfiles) = parseImports(filepath, errors, C64Target, emptyList()) val (program, options, importedfiles) = parseImports(filepath, errors, C64Target(), emptyList())
program.toplevelModule.name shouldBe filenameBase program.toplevelModule.name shouldBe filenameBase
withClue("all imports other than the test source must have been internal resources library files") { withClue("all imports other than the test source must have been internal resources library files") {

Some files were not shown because too many files have changed in this diff Show More