Compare commits

...

182 Commits
v6.0 ... v6.4

Author SHA1 Message Date
4cae2c56ec implemented last remaining codegen for word-byte division and remainders. 2021-03-25 22:03:36 +01:00
d840975054 remove unreached error checks 2021-03-25 21:47:05 +01:00
1b14da6c03 compiler warning instead of crash when attempting to assign invalid array value to other array 2021-03-24 22:01:22 +01:00
292640b17a asmgen: string values cannot be typecasted 2021-03-24 21:49:33 +01:00
112a7b09f2 added codegen for expression that needs the status-flag register result as a value on the stack 2021-03-24 21:42:27 +01:00
863ec9ce8a Merge pull request #26 from Elektron72/vim-syntax
Add support for built-in functions to Vim syntax file (and other fixes)
2021-03-24 20:50:53 +01:00
2eb346a205 Add support for built-ins to Vim syntax file
This commit adds support for highlighting built-in functions and
variables to the Vim syntax file.
2021-03-23 19:53:20 -04:00
8092355acb Add syntax sync to Vim syntax file
This will make the highlighting slightly slower, but will fix issues
with assembly not being highlighted properly.
2021-03-23 19:41:34 -04:00
e7ef2ed31b todo 2021-03-23 23:48:53 +01:00
af4de6d2fc replacing complex array indexer expressions moved to BeforeAsmGeneration + use cx16 virtualregister instead of adding temp variables for this 2021-03-23 23:44:14 +01:00
69f73dd779 Add void operator to Vim syntax file 2021-03-23 18:12:52 -04:00
9706b46012 credits 2021-03-23 02:50:16 +01:00
6d75dd3bb8 Merge pull request #25 from Elektron72/vim-syntax
Add Vim syntax highlighting file
2021-03-23 01:29:57 +01:00
bd295ffc99 array indexer complexity is now dealt with in the asm-generator only 2021-03-22 19:40:57 +01:00
07ce3e3c9d Add Vim syntax highlighting file
The readme file in syntax-files/Vim/ was also modified to give simple
installation instructions.
2021-03-22 12:13:20 -04:00
cbc3e37a89 stuff 2021-03-22 02:29:59 +01:00
3626828ceb decided 2021-03-22 01:45:19 +01:00
24b77fb5a5 comments. 2021-03-21 21:10:29 +01:00
1505fe686a updated vtui example 2021-03-21 20:40:35 +01:00
0991131fa8 don't stript unused asmsub definitions 2021-03-21 19:55:21 +01:00
2e928bd3c2 fix compiler crash for certain str argument to asm functions 2021-03-21 18:39:39 +01:00
ca868ae19e added cx16.vload() (like the VLOAD basic instruction) 2021-03-20 02:39:53 +01:00
3e286dd14c move test 2021-03-18 19:34:54 +01:00
11247d52b1 fix bugs in word <= and >= comparisons 2021-03-18 19:20:48 +01:00
1dbc902513 fix bugs in uword <= and >= comparisons 2021-03-18 18:41:41 +01:00
330e691b78 wip 2021-03-18 02:43:08 +01:00
6780d4f562 fix bug in uword > comparison 2021-03-18 02:21:21 +01:00
b30b8b7368 fix bug in float < and > comparisons 2021-03-18 01:41:54 +01:00
3df182b8c3 created extensive comparison test suite 2021-03-18 00:50:13 +01:00
7f21d89fea moved test programs to test folder in compiler module 2021-03-17 20:15:16 +01:00
2b267b4ba1 IDE syntax 2021-03-17 19:36:37 +01:00
ef64881528 busy creating extensive comparison test suite 2021-03-17 19:35:22 +01:00
9a6bd760bd fixed issues in uword '>' 2021-03-16 23:40:32 +01:00
00b9766aea fixed issues in word '>' 2021-03-16 23:22:58 +01:00
6381d2b6ac improve word '<', word (u)word '<=' , uword '>=' codegen 2021-03-16 18:15:47 +01:00
d2ab5f230d example TODOs 2021-03-16 01:09:25 +01:00
824b41d457 improve word '>' and '>=' codegen 2021-03-16 00:48:03 +01:00
b5523c7077 don't optimize with inlining too aggressively (code bloat) 2021-03-16 00:33:15 +01:00
eb3594b18c revert to just using comparison expressions in graphics code (we're optimizing these now!) 2021-03-16 00:11:55 +01:00
852d85d010 improve uword '<' and '>' codegen 2021-03-16 00:03:51 +01:00
5e0aef04fe improve (u)byte '>=' codegen 2021-03-15 23:20:16 +01:00
a00c693f93 improve (u)byte '<=' codegen 2021-03-15 23:17:04 +01:00
c943da1448 improve ubyte '<' and '>' codegen 2021-03-15 23:12:52 +01:00
b630fae580 refactor byte '==', '!=', '<' and '>' codegen 2 2021-03-15 23:08:30 +01:00
38e40084f1 refactor byte '==', '!=', '<' and '>' codegen 2021-03-15 22:47:18 +01:00
bf23ad78e6 improve byte '<' and '>' codegen 2021-03-15 22:26:00 +01:00
ded1d19737 improve '==' and '!=' codegen 2021-03-15 19:29:32 +01:00
496a3b0d2c todo 2021-03-15 18:56:25 +01:00
6922333755 add a cmp(x,y) function that returns no value but only sets the status bits based off the comparison (can be used with a conditional jump afterwards) 2021-03-13 15:11:22 +01:00
a00c39e9cf compiler error instead of crash when using functioncall without returnvalue 2021-03-12 19:31:04 +01:00
1c1da8e38e additional optimization to the bresenham line routines 2021-03-10 18:49:40 +01:00
50a306f492 line drawing fixes 2021-03-09 22:11:30 +01:00
6995ee2d17 fix cx16 bresenham line inaccuracy 2021-03-09 22:04:19 +01:00
6c60ea9cac allocate even more c64 zeropage locations for floats 2021-03-09 21:47:36 +01:00
2431ed811a don't remove typecasts in asmsub argument lists 2021-03-09 21:29:48 +01:00
6bd205c02a fix c64 bresenham line inaccuracy 2021-03-09 21:07:55 +01:00
62ec77e148 ver 2021-03-08 23:35:52 +01:00
9120e1de88 fix ubyte/uword to float conversion crashes on Commander X16 2021-03-08 23:21:52 +01:00
60e169bd87 added optimized integer square (x*x) routine 2021-03-08 23:08:47 +01:00
e4bca5fe47 version 2021-03-06 23:07:30 +01:00
a1729b65ab fix min(), max(), sum(), abs() 2021-03-06 22:57:22 +01:00
2950d26c8e array and struct value assignments now via memcopy instead of assignment per element 2021-03-06 22:10:03 +01:00
4f8d4a9585 use memcopy to assign arrays 2021-03-06 19:01:16 +01:00
d787795759 simplified 2021-03-06 15:43:23 +01:00
cf74e73e27 IDEA syntax colors 2021-03-06 15:23:58 +01:00
2770254fd9 removed inline assembly from bobs demo 2021-03-06 14:31:26 +01:00
de04bd8cfa added more convenient number-to-string functions to conv library 2021-03-06 13:47:27 +01:00
076a547f91 added more convenient number-to-string functions to conv library 2021-03-06 13:34:57 +01:00
dffd0a2706 added fastrnd8() with the old rnd() generator code in it, new code for rnd() uses the much better rndw() generator now. 2021-03-05 22:49:14 +01:00
6c66f86103 todo 2021-03-05 21:07:35 +01:00
26502c949a add unlimited bobs example 2021-03-05 19:05:13 +01:00
8dfe510883 avoid compiler crash when evaluating const expressions fails due to things like integer out of bounds 2021-03-04 01:32:02 +01:00
96ba9f5902 spelling correction 2021-03-04 01:31:29 +01:00
3a6ba0ab71 added 'kefrenbars' example 2021-03-03 01:09:18 +01:00
32d894d6b6 optimized repeat loop for word counts 2021-02-28 21:22:46 +01:00
543efa4299 attempt 2 at optimizing repeats 2021-02-28 21:02:17 +01:00
eba0708099 Revert "optimized repeat loop for word counts"
This reverts commit 51e6bf0d
2021-02-28 20:29:28 +01:00
51e6bf0d45 optimized repeat loop for word counts 2021-02-28 17:34:18 +01:00
07b5c44a54 preparing to optimize 16 bit repeat loop 2021-02-28 17:13:15 +01:00
9fe32c1c34 codegen uses 'bra' on 65c02 instead of 'jmp' 2021-02-28 16:46:08 +01:00
0e0278c84a for loops now use 'bra' if available 2021-02-28 16:35:59 +01:00
dea775a9cd package refactor 2021-02-28 16:29:15 +01:00
7e3e18a5c7 deal with 'bra' better on 65c02 2021-02-28 16:20:03 +01:00
8e3ebc84f0 readme 2021-02-28 15:40:04 +01:00
e6079dfd71 don't always use pha/pla in pointer expression code 2021-02-27 16:21:46 +01:00
2b435fe6a5 vtui example updated to vtui 0.6 2021-02-27 03:30:21 +01:00
4e640b11fd added kernal bank switch trick to rasterbars 2021-02-26 01:16:06 +01:00
8b1e1e68fa switch to Kotlin's new JVM IR compilation 2021-02-26 01:10:00 +01:00
fd11927708 optimized highres 4c position calc a bit 2021-02-26 00:43:51 +01:00
cd500fee8c wording 2021-02-25 00:52:27 +01:00
1bd32c0f19 added animal guessing game example 2021-02-24 22:58:16 +01:00
7aefca3de0 target 2021-02-24 00:17:52 +01:00
f275ed96ea optimized palette.set_color() 2021-02-24 00:01:27 +01:00
d14dac3872 got rid of final traces of heapid, fixed compiler warnings 2021-02-24 00:01:04 +01:00
b0213b0565 vtui lib 2021-02-23 23:31:32 +01:00
c677f0a875 fixed string interning to also consider the alt-encoding 2021-02-23 23:27:44 +01:00
6e65cb2c0a added sounds to cx16 tehtriz 2021-02-23 01:29:45 +01:00
e65c5402d7 added cx16 rasterbars example 2021-02-22 02:11:44 +01:00
334f86480a added irq routines for cx16 2021-02-22 00:48:41 +01:00
0e62f5b759 don't remove subroutines in a block marked with "force_output" 2021-02-21 23:25:26 +01:00
edf9a500d3 kernel -> kernal 2021-02-21 22:48:06 +01:00
001d01fdaf slight tweak to 64tass .cpu to enable wdc65c02 variant on cx16 with its extra opcodes 2021-02-21 22:45:23 +01:00
a95677564e changed system irq/rasterirq setting routines 2021-02-21 22:23:50 +01:00
4aca8bb8df also track subroutines in the callgraph that only get their address taken 2021-02-21 22:09:49 +01:00
5540482888 compiler error for duplicate when choice labels 2021-02-21 21:26:15 +01:00
00d735249b fix pointer write outside zeropage 2021-02-21 16:22:44 +01:00
b5289511ba don't remove empty when choice from the list of choices! 2021-02-21 15:11:19 +01:00
b6ded8501f added 'align_word' and 'align_page' block options to control block start address alignment in the assembler 2021-02-21 01:24:44 +01:00
781915d2cf reducing dependencies 2021-02-20 17:54:33 +01:00
f4cef3eaf2 reducing dependencies 2021-02-20 17:19:54 +01:00
d23c2eed86 test 2021-02-20 16:58:24 +01:00
15695a304e start address of blocks without explicit memory address, is now word-aligned in memory 2021-02-20 03:06:00 +01:00
6319269976 underscore '_' is now also mapped to petscii, to the graphical symbol 2021-02-20 02:55:06 +01:00
0ed3d951a7 don't require carry parameter Pc to asmsubs to be last 2021-02-20 02:27:57 +01:00
2aa39757b4 reduce dependencies on global compilationtarget 2021-02-19 19:02:29 +01:00
39d32a3600 refactor cpuCheck 2021-02-19 18:48:12 +01:00
219d17de34 reduce dependencies on global compilaiontarget 2021-02-19 18:33:54 +01:00
9bb5b454e4 reduce dependencies on global compilaiontarget 2021-02-18 23:44:26 +01:00
2412f8c531 added cx16 vtui example 2021-02-18 23:16:38 +01:00
8701d684e6 added cx16 vtui example 2021-02-18 03:45:06 +01:00
b543cc34cd no longer warn about removing unused asmsubs 2021-02-18 01:52:56 +01:00
791dbbab9b fixed block label itself not getting the correct memory address in the assembly
fixed %asmbinary relative path issues
2021-02-18 01:28:33 +01:00
ac0b1da3fc machinedefinition doesn't import system libs itself anymore 2021-02-18 00:43:32 +01:00
2f97aedc3c fixed invalid removal of string tag from memory() 2021-02-16 23:58:31 +01:00
ab544ee965 improved string constant interning; no longer output duplicate strings in the Ast 2021-02-16 23:43:38 +01:00
fa527f8624 restored optimization of txt.print() with strings of lengths 1 or 2 2021-02-16 23:37:11 +01:00
92ee0aefee docs: replaced old invalid c64scr names with txt 2021-02-16 23:28:35 +01:00
99759ae853 enhanced tehtriz blocks to have light edges 2021-02-15 17:48:10 +01:00
81930312ff added textio.setcc2() on commanderX16 to enable setting fg+bg colors. 2021-02-15 17:47:48 +01:00
194fbcdd91 todos 2021-02-15 04:41:33 +01:00
1e3930aae2 fix bug in evaluating logical expressions if one of the operands was not boolean 1 or 0 2021-02-14 18:29:05 +01:00
62dda4d891 fix asm bug in conv.any2uword 2021-02-14 17:13:56 +01:00
2b870fb9f7 get rid of compiled examples. Just compile them yourself... 2021-02-14 17:13:29 +01:00
53f0318187 version 6.1 2021-02-14 00:07:45 +01:00
5e6e711f33 optimize pokew() 2021-02-14 00:05:57 +01:00
78af2cd4dc optimize peekw() 2021-02-13 23:52:08 +01:00
02cb237623 added poke() and pokew() builtin functions 2021-02-13 23:16:50 +01:00
cc0f19653e added peek() and peekw() builtin functions 2021-02-13 22:38:39 +01:00
4fff150c7b fixed mkword() bug 2021-02-13 22:00:13 +01:00
f6136891cc optimized for loop over const bytes, fixed downto 1 2021-02-13 13:46:02 +01:00
1e22170302 added graphical starmaps to textelite 2021-02-11 00:23:36 +01:00
bdda6f502a textelite output cleanups and alignments 2021-02-10 23:19:07 +01:00
1bbd77fddb added txt.column() 2021-02-10 22:47:49 +01:00
321fdd10d1 ported tehtriz to Cx16 2021-02-10 21:55:14 +01:00
9867dfcdeb ported tehtriz to Cx16 2021-02-10 21:44:35 +01:00
7c09ac632c got rid of the --longOptionNames in the cli argparser 2021-02-10 21:26:46 +01:00
3502f65332 reducing dependencies 2021-02-09 19:03:21 +01:00
628390c3b5 reducing dependencies 2021-02-09 18:56:47 +01:00
bc37097df2 reducing dependencies 2021-02-09 18:49:25 +01:00
7d98275763 reducing dependencies 2021-02-09 02:06:27 +01:00
d6ffb549f6 reducing dependencies 2021-02-09 01:47:05 +01:00
bcd0db984d reducing ast dependencies - moved ErrorReporter back to compiler module 2021-02-09 01:15:31 +01:00
d9244f22c2 reducing ast dependencies - separate Ast compilation module 2021-02-09 01:06:11 +01:00
c97d76dbf2 reducing ast dependencies 2021-02-09 00:05:56 +01:00
9e05e97d7f reducing ast dependencies 2021-02-07 19:38:20 +01:00
1070dedd7c todo 2021-02-07 19:08:47 +01:00
ccd1516637 reducing ast dependencies 2021-02-07 18:44:38 +01:00
f1f51a01c6 reducing ast dependencies 2021-02-07 18:34:55 +01:00
be75b8dbe5 reducing ast dependencies 2021-02-07 07:05:00 +01:00
02fae0e722 reducing ast dependencies 2021-02-07 06:50:59 +01:00
e35b739579 reducing ast dependencies 2021-02-07 06:39:08 +01:00
34aa6cc8a2 compiler checks for conflicting register usage in sub arguments vs target parameter registers 2021-02-07 05:25:50 +01:00
d7a6b20028 todo 2021-02-07 01:14:10 +01:00
eb2d5bb1f8 fix bank arg error in gfx2.position 2021-02-06 16:58:17 +01:00
cefef3d1be todo 2021-02-06 15:22:31 +01:00
cc96ab7a9b assignment source now also treats cx16.r[0-15] as registers
no longer create useless assignment code for r0=r0
2021-02-06 13:01:45 +01:00
49ea31c0a4 fix shift signed word right 2021-02-06 01:23:31 +01:00
f1478d776b fix vertical line highres 4color 2021-02-05 18:09:21 +01:00
40e4cfb686 amiga 2021-02-04 17:47:52 +01:00
76f459ee95 amiga 2021-02-02 23:09:03 +01:00
c478718019 fixed and optimized horiz_line for highres 4c 2021-02-01 22:03:10 +01:00
c27248a58b amiga 2021-01-29 23:52:29 +01:00
51bc539468 added palette.set_rgb() 2021-01-29 02:46:07 +01:00
2395863e7e asmsubs: fix clobbering and optimize register usage for loading the arguments 2021-01-29 01:52:49 +01:00
69c459c8ac gfx2 highres 4colors 2021-01-28 22:28:14 +01:00
c8855b2b10 better error msg 2021-01-27 02:40:56 +01:00
a910c0fddb gfx2 highres 4colors 2021-01-27 02:31:20 +01:00
fd55611cac asmsubs: don't use stack for simple lsb/msb/mkword arguments 2021-01-27 01:41:55 +01:00
52f6be2bb0 gfx2: changed screen mode numbering to a more intuitive sequence 2021-01-26 18:17:20 +01:00
857f930dc2 amiga 2021-01-26 00:09:42 +01:00
dd2c436dc6 tweaked repeat 2021-01-25 23:39:54 +01:00
9f047ba752 palette.set_monochrome() now has 2 arguments: screen and draw color RGB values 2021-01-24 04:15:15 +01:00
188 changed files with 8586 additions and 6015 deletions

View File

@ -3,14 +3,11 @@
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="100" isEnabled="false" name="JavaScript" />
<language isEnabled="false" name="Groovy" />
<language isEnabled="false" name="Style Sheets" />
<language minSize="70" name="Kotlin" />
<language isEnabled="false" name="TypeScript" />
<language isEnabled="false" name="ActionScript" />
<language isEnabled="false" name="Groovy" />
</Languages>
</inspection_tool>
<inspection_tool class="PyInterpreterInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" />
<option name="processLiterals" value="true" />

2
.idea/kotlinc.xml generated
View File

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

1
.idea/modules.xml generated
View File

@ -3,6 +3,7 @@
<component name="ProjectModuleManager">
<modules>
<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$/dbusCompilerService/dbusCompilerService.iml" filepath="$PROJECT_DIR$/dbusCompilerService/dbusCompilerService.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />

View File

@ -23,13 +23,14 @@ https://prog8.readthedocs.io/
What does Prog8 provide?
------------------------
- big reduction of source code length over raw assembly
- reduction of source code length over raw assembly
- modularity, symbol scoping, subroutines
- various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string and array variables and string sharing
- subroutines with an input- and output parameter signature
- no stack frame allocations because parameters and local variables are automatically allocated statically
- constant folding in expressions and other high-level program optimizations
- subroutines with input parameters and result values
- high-level program optimizations
- small program boilerplate/compilersupport overhead
- sane variable initialization, programs can be restarted again just fine after exiting to basic
- conditional branches
- floating point operations (requires the C64 Basic ROM routines for this)
- 'when' statement to provide a concise jump table alternative to if/elseif chains
@ -38,8 +39,10 @@ What does Prog8 provide?
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- fast execution speed due to compilation to native assembly code
- variables are allocated statically
- inline assembly allows you to have full control when every cycle or byte matters
- supports the sixteen 'virtual' 16-bit registers R0 .. R15 from the Commander X16, and provides them also on the C64.
- encode strings and characters into petscii or screencodes as desired (C64/Cx16)
*Rapid edit-compile-run-debug cycle:*
@ -52,7 +55,7 @@ What does Prog8 provide?
- "c64": Commodore-64 (6510 CPU = almost a 6502)
- "cx16": [CommanderX16](https://www.commanderx16.com) (65c02 CPU)
- If you only use standard kernel and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!
- If you only use standard kernal and prog8 library routines, it is possible to compile the *exact same program* for both machines (just change the compiler target flag)!

View File

@ -1,7 +1,7 @@
plugins {
id 'java'
id 'application'
id "org.jetbrains.kotlin.jvm" version "1.4.21"
id "org.jetbrains.kotlin.jvm" version "1.4.30"
id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '6.1.0'
}
@ -18,14 +18,12 @@ repositories {
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':parser')
implementation project(':compilerAst')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.antlr:antlr4-runtime:4.8'
implementation 'org.jetbrains.kotlinx:kotlinx-cli:0.3.1'
// implementation 'net.razorvine:ksim65:1.8'
// implementation "com.github.hypfvieh:dbus-java:3.2.4"
implementation project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
@ -36,6 +34,7 @@ dependencies {
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
@ -44,6 +43,7 @@ compileKotlin {
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}

View File

@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="Python" name="Python">
<configuration sdkName="Python 3.9" />
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
@ -11,9 +16,9 @@
<orderEntry type="jdk" jdkName="11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm" level="project" />
<orderEntry type="library" name="antlr-runtime-4.9" level="project" />
<orderEntry type="module" module-name="compilerAst" />
<orderEntry type="library" name="Python 3.9 interpreter library" level="application" />
</component>
</module>

View File

@ -428,7 +428,9 @@ var_fac1_greater_f .proc
cmp #1
beq +
lda #0
+ rts
rts
+ lda #1
rts
.pend
var_fac1_greatereq_f .proc

View File

@ -2,7 +2,7 @@
%import textio
; bitmap pixel graphics module for the C64
; only black/white monchrome 320x200 for now
; only black/white monochrome 320x200 for now
; assumes bitmap screen memory is $2000-$3fff
graphics {
@ -39,30 +39,28 @@ graphics {
swap(x1, x2)
swap(y1, y2)
}
word @zp dx = x2-x1 as word
word @zp dy = y2-y1
word @zp dx = (x2 as word)-x1
word @zp dy = (y2 as word)-y1
if dx==0 {
vertical_line(x1, y1, abs(dy)+1 as ubyte)
vertical_line(x1, y1, abs(dy) as ubyte +1)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, y1, abs(dx)+1 as uword)
horizontal_line(x1, y1, abs(dx) as uword +1)
return
}
; TODO rewrite the rest in optimized assembly
word @zp d = 0
ubyte positive_ix = true
if dx < 0 {
dx = -dx
positive_ix = false
}
dx *= 2
dy *= 2
word @zp dx2 = dx*2
word @zp dy2 = dy*2
internal_plotx = x1
if dx >= dy {
@ -72,10 +70,10 @@ graphics {
if internal_plotx==x2
return
internal_plotx++
d += dy
d += dy2
if d > dx {
y1++
d -= dx
d -= dx2
}
}
} else {
@ -84,10 +82,10 @@ graphics {
if internal_plotx==x2
return
internal_plotx--
d += dy
d += dy2
if d > dx {
y1++
d -= dx
d -= dx2
}
}
}
@ -99,10 +97,10 @@ graphics {
if y1 == y2
return
y1++
d += dx
d += dx2
if d > dy {
internal_plotx++
d -= dy
d -= dy2
}
}
} else {
@ -111,10 +109,10 @@ graphics {
if y1 == y2
return
y1++
d += dx
d += dx2
if d > dy {
internal_plotx--
d -= dy
d -= dy2
}
}
}

View File

@ -11,7 +11,7 @@ c64 {
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STATUS = $90 ; kernel status variable for I/O
&ubyte STATUS = $90 ; kernal status variable for I/O
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
@ -178,7 +178,7 @@ c64 {
; ---- C64 ROM kernal routines ----
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use txt.print instead)
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
@ -202,7 +202,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
@ -211,8 +211,8 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
@ -294,6 +294,12 @@ asmsub init_system() {
}}
}
asmsub init_system_phase2() {
%asm {{
rts ; no phase 2 steps on the C64
}}
}
asmsub disable_runstop_and_charsetswitch() clobbers(A) {
%asm {{
lda #$80
@ -304,27 +310,13 @@ asmsub disable_runstop_and_charsetswitch() clobbers(A) {
}}
}
asmsub set_irqvec_excl() clobbers(A) {
%asm {{
sei
lda #<_irq_handler
sta c64.CINV
lda #>_irq_handler
sta c64.CINV+1
cli
rts
_irq_handler jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
asmsub set_irqvec() clobbers(A) {
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei
lda #<_irq_handler
sta c64.CINV
@ -333,9 +325,23 @@ asmsub set_irqvec() clobbers(A) {
cli
rts
_irq_handler jsr _irq_handler_init
jsr irq.irq
_modified jsr $ffff ; modified
jsr _irq_handler_end
jmp c64.IRQDFRT ; continue with normal kernel irq routine
lda _use_kernal
bne +
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
@ -388,7 +394,7 @@ IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irqvec() clobbers(A) {
asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda #<c64.IRQDFRT
@ -404,8 +410,15 @@ asmsub restore_irqvec() clobbers(A) {
}}
}
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta set_irq._use_kernal
lda cx16.r0
ldy cx16.r0+1
sei
jsr _setup_raster_irq
lda #<_raster_irq_handler
@ -416,12 +429,21 @@ asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFRT
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
lda set_irq._use_kernal
bne +
; end irq processing - don't use kernal's irq handling
pla
tay
pla
tax
pla
rti
+ jmp c64.IRQDFRT ; continue with kernal irq routine
_setup_raster_irq
pha
@ -445,28 +467,6 @@ _setup_raster_irq
}}
}
asmsub set_rasterirq_excl(uword rasterpos @ AY) clobbers(A) {
%asm {{
sei
jsr set_rasterirq._setup_raster_irq
lda #<_raster_irq_handler
sta c64.CINV
lda #>_raster_irq_handler
sta c64.CINV+1
cli
rts
_raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff
sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFEND ; end irq processing - don't call kernel
}}
}
; ---- end of C64 specific system utility routines ----
}

View File

@ -31,6 +31,16 @@ sub spc() {
txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
sec
jsr c64.PLOT
tay
clc
jmp c64.PLOT
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
@ -576,7 +586,7 @@ _colormod sta $ffff ; modified
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{
stx P8ZP_SCRATCH_REG
tax

View File

@ -7,239 +7,213 @@ conv {
; ----- number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
str string_out = "????????????????" ; result buffer for the string conversion routines
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)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
rts
phx
jsr conv.ubyte2decimal
sty string_out
sta string_out+1
stx string_out+2
lda #0
sta string_out+3
plx
rts
}}
}
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
asmsub str_ub (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in decimal string form, without left padding 0s
%asm {{
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
;HexToDec99
; start in A
; end with A = 10's, decOnes (also in X)
;HexToDec255
; start in A
; end with Y = 100's, A = 10's, decOnes (also in X)
;HexToDec999
; start with A = high byte, Y = low byte
; end with Y = 100's, A = 10's, decOnes (also in X)
; requires 1 extra temp register on top of decOnes, could combine
; these two if HexToDec65535 was eliminated...
;HexToDec65535
; start with A/Y (low/high) as 16 bit value
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
ASCII_0_OFFSET = $30
temp = P8ZP_SCRATCH_B1 ; byte in zeropage
hexHigh = P8ZP_SCRATCH_W1 ; byte in zeropage
hexLow = P8ZP_SCRATCH_W1+1 ; byte in zeropage
HexToDec65535; SUBROUTINE
sty hexHigh ;3 @9
sta hexLow ;3 @12
tya
tax ;2 @14
lsr a ;2 @16
lsr a ;2 @18 integer divide 1024 (result 0-63)
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
;at this point we have a number 1-65 that we have to times by 24,
;add to original sum, and Mod 1024 to get a remainder 0-999
sta temp ;3 @25
asl a ;2 @27
adc temp ;3 @30 x3
tay ;2 @32
lsr a ;2 @34
lsr a ;2 @36
lsr a ;2 @38
lsr a ;2 @40
lsr a ;2 @42
tax ;2 @44
tya ;2 @46
asl a ;2 @48
asl a ;2 @50
asl a ;2 @52
clc ;2 @54
adc hexLow ;3 @57
sta hexLow ;3 @60
txa ;2 @62
adc hexHigh ;3 @65
sta hexHigh ;3 @68
ror a ;2 @70
lsr a ;2 @72
tay ;2 @74 integer divide 1,000 (result 0-65)
lsr a ;2 @76 split the 1,000 and 10,000 digit
tax ;2 @78
lda ShiftedBcdTab,x ;4 @82
tax ;2 @84
rol a ;2 @86
and #$0F ;2 @88
ora #ASCII_0_OFFSET
sta decThousands ;3 @91
txa ;2 @93
lsr a ;2 @95
lsr a ;2 @97
lsr a ;2 @99
ora #ASCII_0_OFFSET
sta decTenThousands ;3 @102
lda hexLow ;3 @105
cpy temp ;3 @108
bmi _doSubtract ;2³ @110/111
beq _useZero ;2³ @112/113
adc #23 + 24 ;2 @114
_doSubtract
sbc #23 ;2 @116
sta hexLow ;3 @119
_useZero
lda hexHigh ;3 @122
sbc #0 ;2 @124
Start100s
and #$03 ;2 @126
tax ;2 @128 0,1,2,3
cmp #2 ;2 @130
rol a ;2 @132 0,2,5,7
ora #ASCII_0_OFFSET
tay ;2 @134 Y = Hundreds digit
lda hexLow ;3 @137
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
bcs hex_doSub200 ;2³ @143/144
hex_try200
cmp #200 ;2 @145
bcc hex_try100 ;2³ @147/148
hex_doSub200
iny ;2 @149
iny ;2 @151
sbc #200 ;2 @153
hex_try100
cmp #100 ;2 @155
bcc HexToDec99 ;2³ @157/158
iny ;2 @159
sbc #100 ;2 @161
HexToDec99; SUBROUTINE
lsr a ;2 @163
tax ;2 @165
lda ShiftedBcdTab,x ;4 @169
tax ;2 @171
rol a ;2 @173
and #$0F ;2 @175
ora #ASCII_0_OFFSET
sta decOnes ;3 @178
txa ;2 @180
lsr a ;2 @182
lsr a ;2 @184
lsr a ;2 @186
ora #ASCII_0_OFFSET
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
sty decHundreds
sta decTens
ldx decOnes
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
HexToDec999; SUBROUTINE
sty hexLow ;3 @9
jmp Start100s ;3 @12
Mod100Tab
.byte 0,56,12,56+12
ShiftedBcdTab
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
decTenThousands .byte 0
decThousands .byte 0
decHundreds .byte 0
decTens .byte 0
decOnes .byte 0
.byte 0 ; zero-terminate the decimal output string
}}
}
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{
cmp #0
bpl +
eor #255
clc
adc #1
+ jmp ubyte2decimal
}}
}
asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
%asm {{
stx P8ZP_SCRATCH_REG
phx
ldy #0
sty P8ZP_SCRATCH_B1
jsr conv.ubyte2decimal
_output_byte_digits
; hundreds?
cpy #'0'
beq +
pha
and #$0f
tax
ldy _hex_digits,x
tya
ldy P8ZP_SCRATCH_B1
sta string_out,y
pla
lsr a
lsr a
lsr a
lsr a
tax
lda _hex_digits,x
ldx P8ZP_SCRATCH_REG
rts
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
inc P8ZP_SCRATCH_B1
; tens?
+ ldy P8ZP_SCRATCH_B1
cmp #'0'
beq +
sta string_out,y
iny
+ ; ones.
txa
sta string_out,y
iny
lda #0
sta string_out,y
plx
rts
}}
}
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
asmsub str_b (byte value @ A) clobbers(A,Y) {
; ---- convert the byte in A in decimal string form, without left padding 0s
%asm {{
sta P8ZP_SCRATCH_REG
tya
jsr ubyte2hex
sta output
sty output+1
lda P8ZP_SCRATCH_REG
jsr ubyte2hex
sta output+2
sty output+3
rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
phx
ldy #0
sty P8ZP_SCRATCH_B1
cmp #0
bpl +
pha
lda #'-'
sta string_out
inc P8ZP_SCRATCH_B1
pla
+ jsr conv.byte2decimal
bra str_ub._output_byte_digits
}}
}
asmsub str_ubhex (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in hex string form
%asm {{
jsr conv.ubyte2hex
sta string_out
sty string_out+1
lda #0
sta string_out+2
rts
}}
}
asmsub str_ubbin (ubyte value @ A) clobbers(A,Y) {
; ---- convert the ubyte in A in binary string form
%asm {{
sta P8ZP_SCRATCH_B1
ldy #0
sty string_out+8
ldy #7
- lsr P8ZP_SCRATCH_B1
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out,y
dey
bpl -
rts
}}
}
asmsub str_uwbin (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in binary string form
%asm {{
sta P8ZP_SCRATCH_REG
tya
jsr str_ubbin
ldy #0
sty string_out+16
ldy #7
- lsr P8ZP_SCRATCH_REG
bcc +
lda #'1'
bne _digit
+ lda #'0'
_digit sta string_out+8,y
dey
bpl -
rts
}}
}
asmsub str_uwhex (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in hexadecimal string form (4 digits)
%asm {{
pha
tya
jsr conv.ubyte2hex
sta string_out
sty string_out+1
pla
jsr conv.ubyte2hex
sta string_out+2
sty string_out+3
lda #0
sta string_out+4
rts
}}
}
asmsub str_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, with left padding 0s (5 positions total)
%asm {{
phx
jsr conv.uword2decimal
ldy #0
- lda conv.uword2decimal.decTenThousands,y
sta string_out,y
beq +
iny
bne -
+ plx
rts
}}
}
asmsub str_uw (uword value @ AY) clobbers(A,Y) {
; ---- convert the uword in A/Y in decimal string form, without left padding 0s
%asm {{
phx
jsr conv.uword2decimal
ldx #0
_output_digits
ldy #0
- lda conv.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _gotdigit
iny
bne -
_gotdigit sta string_out,x
inx
iny
lda conv.uword2decimal.decTenThousands,y
bne _gotdigit
_end lda #0
sta string_out,x
plx
rts
_allzero lda #'0'
sta string_out,x
inx
bne _end
}}
}
asmsub str_w (word value @ AY) clobbers(A,Y) {
; ---- convert the (signed) word in A/Y in decimal string form, without left padding 0's
%asm {{
cpy #0
bpl str_uw
phx
pha
lda #'-'
sta string_out
tya
eor #255
tay
pla
eor #255
clc
adc #1
bcc +
iny
+ jsr conv.uword2decimal
ldx #1
bne str_uw._output_digits
}}
}
@ -257,7 +231,7 @@ asmsub any2uword(str string @AY) clobbers(Y) -> ubyte @A {
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1)
lda (P8ZP_SCRATCH_W1),y
ldy P8ZP_SCRATCH_W1+1
cmp #'$'
beq _hex
@ -520,4 +494,243 @@ _stop
}}
}
; ----- low level number conversions to decimal strings ----
asmsub ubyte2decimal (ubyte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
rts
}}
}
asmsub uword2decimal (uword value @AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
%asm {{
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
;HexToDec99
; start in A
; end with A = 10's, decOnes (also in X)
;HexToDec255
; start in A
; end with Y = 100's, A = 10's, decOnes (also in X)
;HexToDec999
; start with A = high byte, Y = low byte
; end with Y = 100's, A = 10's, decOnes (also in X)
; requires 1 extra temp register on top of decOnes, could combine
; these two if HexToDec65535 was eliminated...
;HexToDec65535
; start with A/Y (low/high) as 16 bit value
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
ASCII_0_OFFSET = $30
temp = P8ZP_SCRATCH_B1 ; byte in zeropage
hexHigh = P8ZP_SCRATCH_W1 ; byte in zeropage
hexLow = P8ZP_SCRATCH_W1+1 ; byte in zeropage
HexToDec65535; SUBROUTINE
sty hexHigh ;3 @9
sta hexLow ;3 @12
tya
tax ;2 @14
lsr a ;2 @16
lsr a ;2 @18 integer divide 1024 (result 0-63)
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
;at this point we have a number 1-65 that we have to times by 24,
;add to original sum, and Mod 1024 to get a remainder 0-999
sta temp ;3 @25
asl a ;2 @27
adc temp ;3 @30 x3
tay ;2 @32
lsr a ;2 @34
lsr a ;2 @36
lsr a ;2 @38
lsr a ;2 @40
lsr a ;2 @42
tax ;2 @44
tya ;2 @46
asl a ;2 @48
asl a ;2 @50
asl a ;2 @52
clc ;2 @54
adc hexLow ;3 @57
sta hexLow ;3 @60
txa ;2 @62
adc hexHigh ;3 @65
sta hexHigh ;3 @68
ror a ;2 @70
lsr a ;2 @72
tay ;2 @74 integer divide 1,000 (result 0-65)
lsr a ;2 @76 split the 1,000 and 10,000 digit
tax ;2 @78
lda ShiftedBcdTab,x ;4 @82
tax ;2 @84
rol a ;2 @86
and #$0F ;2 @88
ora #ASCII_0_OFFSET
sta decThousands ;3 @91
txa ;2 @93
lsr a ;2 @95
lsr a ;2 @97
lsr a ;2 @99
ora #ASCII_0_OFFSET
sta decTenThousands ;3 @102
lda hexLow ;3 @105
cpy temp ;3 @108
bmi _doSubtract ;2³ @110/111
beq _useZero ;2³ @112/113
adc #23 + 24 ;2 @114
_doSubtract
sbc #23 ;2 @116
sta hexLow ;3 @119
_useZero
lda hexHigh ;3 @122
sbc #0 ;2 @124
Start100s
and #$03 ;2 @126
tax ;2 @128 0,1,2,3
cmp #2 ;2 @130
rol a ;2 @132 0,2,5,7
ora #ASCII_0_OFFSET
tay ;2 @134 Y = Hundreds digit
lda hexLow ;3 @137
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
bcs hex_doSub200 ;2³ @143/144
hex_try200
cmp #200 ;2 @145
bcc hex_try100 ;2³ @147/148
hex_doSub200
iny ;2 @149
iny ;2 @151
sbc #200 ;2 @153
hex_try100
cmp #100 ;2 @155
bcc HexToDec99 ;2³ @157/158
iny ;2 @159
sbc #100 ;2 @161
HexToDec99; SUBROUTINE
lsr a ;2 @163
tax ;2 @165
lda ShiftedBcdTab,x ;4 @169
tax ;2 @171
rol a ;2 @173
and #$0F ;2 @175
ora #ASCII_0_OFFSET
sta decOnes ;3 @178
txa ;2 @180
lsr a ;2 @182
lsr a ;2 @184
lsr a ;2 @186
ora #ASCII_0_OFFSET
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
sty decHundreds
sta decTens
ldx decOnes
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
HexToDec999; SUBROUTINE
sty hexLow ;3 @9
jmp Start100s ;3 @12
Mod100Tab
.byte 0,56,12,56+12
ShiftedBcdTab
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
decTenThousands .byte 0
decThousands .byte 0
decHundreds .byte 0
decTens .byte 0
decOnes .byte 0
.byte 0 ; zero-terminate the decimal output string
}}
}
asmsub byte2decimal (byte value @A) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{
cmp #0
bpl +
eor #255
clc
adc #1
+ jmp ubyte2decimal
}}
}
asmsub ubyte2hex (ubyte value @A) -> ubyte @A, ubyte @Y {
; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
%asm {{
stx P8ZP_SCRATCH_REG
pha
and #$0f
tax
ldy _hex_digits,x
pla
lsr a
lsr a
lsr a
lsr a
tax
lda _hex_digits,x
ldx P8ZP_SCRATCH_REG
rts
_hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
}}
}
asmsub uword2hex (uword value @AY) clobbers(A,Y) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{
sta P8ZP_SCRATCH_REG
tya
jsr ubyte2hex
sta output
sty output+1
lda P8ZP_SCRATCH_REG
jsr ubyte2hex
sta output+2
sty output+3
rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
}}
}
}

View File

@ -79,10 +79,10 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
; ---- unsigned 16 bit word in A/Y (lo/hi) to fac1
%asm {{
phx
sta P8ZP_SCRATCH_W2
sta _tmp
sty P8ZP_SCRATCH_B1
tya
ldy P8ZP_SCRATCH_W2
ldy _tmp
jsr GIVAYF ; load it as signed... correct afterwards
lda P8ZP_SCRATCH_B1
bpl +
@ -91,6 +91,7 @@ asmsub GIVUAYFAY (uword value @ AY) clobbers(A,X,Y) {
jsr FADD
+ plx
rts
_tmp .byte 0
_flt65536 .byte 145,0,0,0,0 ; 65536.0
}}
}
@ -128,6 +129,14 @@ asmsub GETADRAY () clobbers(X) -> uword @ AY {
}}
}
asmsub FREADUY (ubyte value @Y) {
; -- 8 bit unsigned Y -> float in fac1
%asm {{
lda #0
jmp GIVAYF
}}
}
sub print_f (float value) {
; ---- prints the floating point value (without a newline).
%asm {{

View File

@ -1,33 +1,42 @@
%target cx16
; Bitmap pixel graphics module for the CommanderX16
; Bitmap pixel graphics routines for the CommanderX16
; Custom routines to use the full-screen 640x480 and 320x240 screen modes.
; This only works on the Cx16. No text layer is currently shown, text can be drawn as part of the bitmap itself.
; Note: for compatible graphics code that words on C64 too, use the "graphics" module instead.
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.
; (These modes are not supported by the documented GRAPH_xxxx kernal routines)
;
; No text layer is currently shown, text can be drawn as part of the bitmap itself.
; Note: for similar graphics routines that also work on the C-64, use the "graphics" module instead.
; Note: for color palette manipulation, use the "palette" module or write Vera registers yourself.
; Note: this library implements code for various resolutions and color depths. This takes up memory.
; If you're memory constrained you should probably not use this built-in library,
; but make a copy in your project only containing the code for the required resolution.
;
;
; SCREEN MODE LIST:
; mode 0 = reset back to default text mode
; mode 1 = bitmap 320 x 240 monochrome
; mode 2 = bitmap 320 x 240 x 4c (TODO not yet implemented)
; mode 3 = bitmap 320 x 240 x 16c (TODO not yet implemented)
; mode 4 = bitmap 320 x 240 x 256c
; mode 5 = bitmap 640 x 480 monochrome
; mode 6 = bitmap 640 x 480 x 4c
; higher color dephts in highres are not supported due to lack of VRAM
; TODO can we make a FB vector table and emulation routines for the Cx16s' GRAPH_init() call? to replace the builtin 320x200 fb driver?
gfx2 {
; read-only control variables:
ubyte active_mode = 255
ubyte active_mode = 0
uword width = 0
uword height = 0
ubyte bpp = 0
ubyte monochrome_dont_stipple_flag = false ; set to false to enable stippling mode in monochrome displaymodes
sub screen_mode(ubyte mode) {
; mode 0 = bitmap 320 x 240 x 1c monochrome
; mode 1 = bitmap 320 x 240 x 256c
; mode 128 = bitmap 640 x 480 x 1c monochrome
; ...other modes?
; copy the lower-case charset to the upper part of the vram, so we can use it later to plot text
when mode {
0 -> {
; 320 x 240 x 1c
1 -> {
; lores monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
@ -38,8 +47,9 @@ gfx2 {
height = 240
bpp = 1
}
1 -> {
; 320 x 240 x 256c
; TODO modes 2, 3 not yet implemented
4 -> {
; lores 256c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 64
cx16.VERA_DC_VSCALE = 64
@ -50,8 +60,8 @@ gfx2 {
height = 240
bpp = 8
}
128 -> {
; 640 x 480 x 1c
5 -> {
; highres monochrome
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
@ -62,13 +72,26 @@ gfx2 {
height = 480
bpp = 1
}
255 -> {
6 -> {
; highres 4c
cx16.VERA_DC_VIDEO = (cx16.VERA_DC_VIDEO & %11001111) | %00100000 ; enable only layer 1
cx16.VERA_DC_HSCALE = 128
cx16.VERA_DC_VSCALE = 128
cx16.VERA_L1_CONFIG = %00000101
cx16.VERA_L1_MAPBASE = 0
cx16.VERA_L1_TILEBASE = %00000001
width = 640
height = 480
bpp = 2
}
else -> {
; back to default text mode and colors
cx16.VERA_CTRL = %10000000 ; reset VERA and palette
c64.CINT() ; back to text mode
width = 0
height = 0
bpp = 0
mode = 0
}
}
@ -81,21 +104,28 @@ gfx2 {
monochrome_stipple(false)
position(0, 0)
when active_mode {
0 -> {
; 320 x 240 x 1c
1 -> {
; lores monochrome
repeat 240/2/8
cs_innerloop640()
}
1 -> {
; 320 x 240 x 256c
; TODO mode 2, 3
4 -> {
; lores 256c
repeat 240/2
cs_innerloop640()
}
128 -> {
; 640 x 480 x 1c
5 -> {
; highres monochrome
repeat 480/8
cs_innerloop640()
}
6 -> {
; highres 4c
repeat 480/4
cs_innerloop640()
}
; modes 7 and 8 not supported due to lack of VRAM
}
position(0, 0)
}
@ -130,35 +160,13 @@ gfx2 {
if length==0
return
when active_mode {
1 -> {
; 8bpp mode
position(x, y)
%asm {{
lda color
phx
ldx length+1
beq +
ldy #0
- sta cx16.VERA_DATA0
iny
bne -
dex
bne -
+ ldy length ; remaining
beq +
- sta cx16.VERA_DATA0
dey
bne -
+ plx
}}
}
0, 128 -> {
; 1 bpp mode
1, 5 -> {
; monochrome modes, either resolution
ubyte separate_pixels = (8-lsb(x)) & 7
if separate_pixels as uword > length
separate_pixels = lsb(length)
repeat separate_pixels {
; this could be optimized by setting this byte in 1 go but probably not worth it due to code size
; TODO optimize this by writing a masked byte in 1 go
plot(x, y, color)
x++
}
@ -200,116 +208,226 @@ _loop lda length
_done
}}
repeat separate_pixels {
; this could be optimized by setting this byte in 1 go but probably not worth it due to code size
; TODO optimize this by writing a masked byte in 1 go
plot(x, y, color)
x++
}
}
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) ; vera auto-increment off again
cx16.VERA_ADDR_H &= %00000111 ; vera auto-increment off again
}
4 -> {
; lores 256c
position(x, y)
%asm {{
lda color
phx
ldx length+1
beq +
ldy #0
- sta cx16.VERA_DATA0
iny
bne -
dex
bne -
+ ldy length ; remaining
beq +
- sta cx16.VERA_DATA0
dey
bne -
+ plx
}}
}
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
color &= 3
ubyte[4] colorbits
ubyte ii
for ii in 3 downto 0 {
colorbits[ii] = color
color <<= 2
}
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
%asm {{
lda cx16.VERA_ADDR_H
and #%00000111 ; no auto advance
sta cx16.VERA_ADDR_H
stz cx16.VERA_CTRL ; setup vera addr 0
lda cx16.r1
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
phx
ldx x
}}
repeat length {
%asm {{
txa
and #3
tay
lda cx16.VERA_DATA0
and gfx2.plot.mask4c,y
ora colorbits,y
sta cx16.VERA_DATA0
cpy #%00000011 ; next vera byte?
bne +
inc cx16.VERA_ADDR_L
bne +
inc cx16.VERA_ADDR_M
+ bne +
inc cx16.VERA_ADDR_H
+ inx ; next pixel
}}
}
%asm {{
plx
}}
}
}
}
sub vertical_line(uword x, uword y, uword height, ubyte color) {
position(x,y)
if active_mode==1 {
; set vera auto-increment to 320 pixel increment (=next line)
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) | (14<<4)
%asm {{
ldy height
beq +
lda color
- sta cx16.VERA_DATA0
dey
bne -
+
}}
return
}
; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) ; no auto advance
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if active_mode>=128
cx16.r14 = 640/8
else
cx16.r14 = 320/8
if color {
if monochrome_dont_stipple_flag {
repeat height {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera ptr to go to the next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
}}
when active_mode {
1, 5 -> {
; monochrome, either resolution
; note for the 1 bpp modes we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
cx16.r15 = gfx2.plot.bits[x as ubyte & 7] ; bitmask
if active_mode>=5
cx16.r14 = 640/8
else
cx16.r14 = 320/8
if color {
if monochrome_dont_stipple_flag {
repeat height {
%asm {{
lda cx16.VERA_DATA0
ora cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera ptr to go to the next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
}}
}
} else {
; stippling.
height = (height+1)/2 ; TODO is the line sometimes 1 pixel too long now because of rounding?
%asm {{
lda x
eor y
and #1
bne +
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera ptr to go to the next line for correct stipple pattern
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
+
asl cx16.r14
ldy height
beq +
- lda cx16.VERA_DATA0
ora cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera data ptr to go to the next-next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
dey
bne -
+
}}
}
} else {
cx16.r15 = ~cx16.r15
repeat height {
%asm {{
lda cx16.VERA_DATA0
and cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera data ptr to go to the next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; the bitmap size is small enough to not have to deal with the _H part:
; lda cx16.VERA_ADDR_H
; adc #0
; sta cx16.VERA_ADDR_H
}}
}
}
} else {
; stippling.
height = (height+1)/2
}
4 -> {
; lores 256c
; set vera auto-increment to 320 pixel increment (=next line)
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | (14<<4)
%asm {{
lda x
eor y
and #1
bne +
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera ptr to go to the next line for correct stipple pattern
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
+
asl cx16.r14
ldy height
beq +
- lda cx16.VERA_DATA0
ora cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera data ptr to go to the next-next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
lda color
- sta cx16.VERA_DATA0
dey
bne -
+
}}
}
} else {
cx16.r15 = ~cx16.r15
repeat height {
%asm {{
lda cx16.VERA_DATA0
and cx16.r15
sta cx16.VERA_DATA0
lda cx16.VERA_ADDR_L
clc
adc cx16.r14 ; advance vera data ptr to go to the next line
sta cx16.VERA_ADDR_L
lda cx16.VERA_ADDR_M
adc #0
sta cx16.VERA_ADDR_M
; lda cx16.VERA_ADDR_H ; the bitmap size is small enough to not have to deal with the _H part.
; adc #0
; sta cx16.VERA_ADDR_H
}}
6 -> {
; highres 4c
; note for this mode we can't use vera's auto increment mode because we have to 'or' the pixel data in place.
; TODO use TWO vera adress pointers simultaneously one for reading, one for writing, so auto-increment IS possible
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
; TODO optimize this vertical line loop in pure assembly
color &= 3
color <<= gfx2.plot.shift4c[lsb(x) & 3]
ubyte mask = gfx2.plot.mask4c[lsb(x) & 3]
repeat height {
ubyte value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask | color
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
%asm {{
; 24 bits add 160 (640/4)
clc
lda cx16.r0
adc #640/4
sta cx16.r0
lda cx16.r0+1
adc #0
sta cx16.r0+1
bcc +
inc cx16.r1
+
}}
}
}
}
}
sub line(uword @zp x1, uword @zp y1, uword @zp x2, uword @zp y2, ubyte color) {
@ -320,42 +438,41 @@ _done
swap(x1, x2)
swap(y1, y2)
}
word @zp dx = x2-x1 as word
word @zp dy = y2-y1 as word
word @zp dx = (x2 as word)-x1
word @zp dy = (y2 as word)-y1
if dx==0 {
vertical_line(x1, y1, abs(dy)+1 as uword, color)
vertical_line(x1, y1, abs(dy) as uword +1, color)
return
}
if dy==0 {
if x1>x2
x1=x2
horizontal_line(x1, y1, abs(dx)+1 as uword, color)
horizontal_line(x1, y1, abs(dx) as uword +1, color)
return
}
; TODO rewrite the rest in optimized assembly (or reuse GRAPH_draw_line if we can get the FB replacement vector layer working)
word @zp d = 0
ubyte positive_ix = true
cx16.r13 = true ; 'positive_ix'
if dx < 0 {
dx = -dx
positive_ix = false
cx16.r13 = false
}
dx *= 2
dy *= 2
word @zp dx2 = dx*2
word @zp dy2 = dy*2
cx16.r14 = x1 ; internal plot X
if dx >= dy {
if positive_ix {
if cx16.r13 {
repeat {
plot(cx16.r14, y1, color)
if cx16.r14==x2
return
cx16.r14++
d += dy
d += dy2
if d > dx {
y1++
d -= dx
d -= dx2
}
}
} else {
@ -364,25 +481,25 @@ _done
if cx16.r14==x2
return
cx16.r14--
d += dy
d += dy2
if d > dx {
y1++
d -= dx
d -= dx2
}
}
}
}
else {
if positive_ix {
if cx16.r13 {
repeat {
plot(cx16.r14, y1, color)
if y1 == y2
return
y1++
d += dx
d += dx2
if d > dy {
cx16.r14++
d -= dy
d -= dy2
}
}
} else {
@ -391,10 +508,10 @@ _done
if y1 == y2
return
y1++
d += dx
d += dx2
if d > dy {
cx16.r14--
d -= dy
d -= dy2
}
}
}
@ -468,11 +585,14 @@ _done
sub plot(uword @zp x, uword y, ubyte color) {
ubyte[8] bits = [128, 64, 32, 16, 8, 4, 2, 1]
ubyte[4] mask4c = [%00111111, %11001111, %11110011, %11111100]
ubyte[4] shift4c = [6,4,2,0]
uword addr
ubyte value
when active_mode {
0 -> {
1 -> {
; lores monochrome
%asm {{
lda x
eor y
@ -490,7 +610,16 @@ _done
}
}
}
128 -> {
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
cx16.vpoke(lsb(cx16.r1), cx16.r0, color)
; activate vera auto-increment mode so next_pixel() can be used after this
cx16.VERA_ADDR_H = cx16.VERA_ADDR_H & %00000111 | %00010000
color = cx16.VERA_DATA0
}
5 -> {
; highres monochrome
%asm {{
lda x
eor y
@ -508,30 +637,44 @@ _done
}
}
}
1 -> {
void addr_mul_320_add_24(y, x) ; 24 bits result is in r0 and r1L
value = lsb(cx16.r1)
cx16.vpoke(value, cx16.r0, color)
; activate vera auto-increment mode so next_pixel() can be used after this
cx16.VERA_ADDR_H = (cx16.VERA_ADDR_H & %00000111) | %00010000
color = cx16.VERA_DATA0
6 -> {
; highres 4c
; TODO also mostly usable for lores 4c?
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
color &= 3
color <<= shift4c[lsb(x) & 3]
; TODO optimize the vera memory manipulation in pure assembly
cx16.VERA_ADDR_H &= %00000111 ; no auto advance
value = cx16.vpeek(lsb(cx16.r1), cx16.r0) & mask4c[lsb(x) & 3] | color
cx16.vpoke(lsb(cx16.r1), cx16.r0, value)
}
}
}
sub position(uword @zp x, uword y) {
ubyte bank
when active_mode {
0 -> {
1 -> {
; lores monochrome
cx16.r0 = y*(320/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
128 -> {
; TODO modes 2,3
4 -> {
; lores 256c
void addr_mul_24_for_lores_256c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 0, 1)
}
5 -> {
; highres monochrome
cx16.r0 = y*(640/8) + x/8
cx16.vaddr(0, cx16.r0, 0, 1)
}
1 -> {
void addr_mul_320_add_24(y, x) ; 24 bits result is in r0 and r1L
ubyte bank = lsb(cx16.r1)
6 -> {
; highres 4c
void addr_mul_24_for_highres_4c(y, x) ; 24 bits result is in r0 and r1L (highest byte)
bank = lsb(cx16.r1)
cx16.vaddr(bank, cx16.r0, 0, 1)
}
}
@ -541,6 +684,7 @@ _done
; -- sets the next pixel byte to the graphics chip.
; for 8 bpp screens this will plot 1 pixel.
; for 1 bpp screens it will plot 8 pixels at once (color = bit pattern).
; for 2 bpp screens it will plot 4 pixels at once (color = bit pattern).
%asm {{
sta cx16.VERA_DATA0
}}
@ -550,6 +694,7 @@ _done
; -- sets the next bunch of pixels from a prepared array of bytes.
; for 8 bpp screens this will plot 1 pixel per byte.
; for 1 bpp screens it will plot 8 pixels at once (colors are the bit patterns per byte).
; for 2 bpp screens it will plot 4 pixels at once (colors are the bit patterns per byte).
%asm {{
phx
sta P8ZP_SCRATCH_W1
@ -614,13 +759,13 @@ _done
sub text(uword @zp x, uword y, ubyte color, uword sctextptr) {
; -- Write some text at the given pixel position. The text string must be in screencode encoding (not petscii!).
; You must also have called text_charset() first to select and prepare the character set to use.
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to mulitples of 8 !
; NOTE: in monochrome (1bpp) screen modes, x position is currently constrained to mulitples of 8 ! TODO allow per-pixel horizontal positioning
uword chardataptr
when active_mode {
0, 128 -> {
; 1-bitplane modes
1, 5 -> {
; monochrome mode, either resolution
cx16.r2 = 40
if active_mode>=128
if active_mode>=5
cx16.r2 = 80
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
@ -660,12 +805,13 @@ _done
sctextptr++
}
}
1 -> {
; 320 x 240 x 256c
4 -> {
; lores 256c
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
cx16.vaddr(charset_bank, chardataptr, 1, 1)
repeat 8 {
; TODO rewrite this inner loop fully in assembly
position(x,y)
y++
%asm {{
@ -689,6 +835,28 @@ _done
sctextptr++
}
}
6 -> {
; hires 4c
while @(sctextptr) {
chardataptr = charset_addr + (@(sctextptr) as uword)*8
repeat 8 {
; TODO rewrite this inner loop fully in assembly
ubyte charbits = cx16.vpeek(charset_bank, chardataptr)
repeat 8 {
charbits <<= 1
if_cs
plot(x, y, color)
x++
}
x-=8
chardataptr++
y++
}
x+=8
y-=8
sctextptr++
}
}
}
}
@ -709,48 +877,98 @@ _done
}}
}
asmsub addr_mul_320_add_24(uword address @R0, uword value @AY) clobbers(A) -> uword @R0, ubyte @R1 {
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
sta P8ZP_SCRATCH_B1
lda cx16.r0+1
sta cx16.r1
sta P8ZP_SCRATCH_REG
lda cx16.r0
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
sta cx16.r0
lda P8ZP_SCRATCH_B1
clc
adc P8ZP_SCRATCH_REG
sta cx16.r0+1
bcc +
inc cx16.r1
+ ; now add the value to this 24-bits number
lda cx16.r0
clc
adc P8ZP_SCRATCH_W1
sta cx16.r0
lda cx16.r0+1
adc P8ZP_SCRATCH_W1+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ lda cx16.r1
rts
}}
}
asmsub addr_mul_24_for_highres_4c(uword yy @R2, uword xx @R3) clobbers(A, Y) -> uword @R0, uword @R1 {
; yy * 160 + xx/4 (24 bits calculation)
; 24 bits result is in r0 and r1L (highest byte)
%asm {{
ldy #5
- asl cx16.r2
rol cx16.r2+1
dey
bne -
lda cx16.r2
sta cx16.r0
lda cx16.r2+1
sta cx16.r0+1
asl cx16.r0
rol cx16.r0+1
asl cx16.r0
rol cx16.r0+1
; xx >>= 2 (xx=R3)
lsr cx16.r3+1
ror cx16.r3
lsr cx16.r3+1
ror cx16.r3
; add r2 and xx (r3) to r0 (24-bits)
stz cx16.r1
clc
lda cx16.r0
adc cx16.r2
sta cx16.r0
lda cx16.r0+1
adc cx16.r2+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ clc
lda cx16.r0
adc cx16.r3
sta cx16.r0
lda cx16.r0+1
adc cx16.r3+1
sta cx16.r0+1
bcc +
inc cx16.r1
+
rts
}}
}
asmsub addr_mul_24_for_lores_256c(uword yy @R0, uword xx @AY) clobbers(A) -> uword @R0, ubyte @R1 {
; yy * 320 + xx (24 bits calculation)
%asm {{
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda cx16.r0
sta P8ZP_SCRATCH_B1
lda cx16.r0+1
sta cx16.r1
sta P8ZP_SCRATCH_REG
lda cx16.r0
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
asl a
rol P8ZP_SCRATCH_REG
sta cx16.r0
lda P8ZP_SCRATCH_B1
clc
adc P8ZP_SCRATCH_REG
sta cx16.r0+1
bcc +
inc cx16.r1
+ ; now add the value to this 24-bits number
lda cx16.r0
clc
adc P8ZP_SCRATCH_W1
sta cx16.r0
lda cx16.r0+1
adc P8ZP_SCRATCH_W1+1
sta cx16.r0+1
bcc +
inc cx16.r1
+ lda cx16.r1
rts
}}
}
}

View File

@ -4,7 +4,7 @@
; Bitmap pixel graphics module for the CommanderX16
; wraps the graphics functions that are in ROM.
; only black/white monchrome 320x200 for now. (i.e. truncated at the bottom)
; only black/white monochrome 320x200 for now. (i.e. truncated at the bottom)
; For full-screen 640x480 or 320x240 graphics, use the "gfx2" module instead. (but that is Cx16-specific)
; Note: there is no color palette manipulation here, you have to do that yourself or use the "palette" module.

View File

@ -9,34 +9,46 @@ palette {
ubyte c
sub set_color(ubyte index, uword color) {
cx16.vpoke(1, $fa00+index*2, lsb(color))
cx16.vpoke(1, $fa01+index*2, msb(color))
vera_palette_ptr = $fa00+index*2
cx16.vpoke(1, vera_palette_ptr, lsb(color))
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, msb(color))
}
sub set_rgb4(uword palletteptr, uword num_colors) {
sub set_rgb4(uword palette_bytes_ptr, uword num_colors) {
; 2 bytes per color entry, the Vera uses this, but the R/GB bytes order is swapped
vera_palette_ptr = $fa00
repeat num_colors {
cx16.vpoke(1, vera_palette_ptr+1, @(palletteptr))
palletteptr++
cx16.vpoke(1, vera_palette_ptr, @(palletteptr))
palletteptr++
cx16.vpoke(1, vera_palette_ptr+1, @(palette_bytes_ptr))
palette_bytes_ptr++
cx16.vpoke(1, vera_palette_ptr, @(palette_bytes_ptr))
palette_bytes_ptr++
vera_palette_ptr+=2
}
}
sub set_rgb8(uword palletteptr, uword num_colors) {
sub set_rgb(uword palette_words_ptr, uword num_colors) {
; 1 word per color entry (in little endian format so $gb0r)
vera_palette_ptr = $fa00
repeat num_colors*2 {
cx16.vpoke(1, vera_palette_ptr, @(palette_words_ptr))
palette_words_ptr++
vera_palette_ptr++
}
}
sub set_rgb8(uword palette_bytes_ptr, uword num_colors) {
; 3 bytes per color entry, adjust color depth from 8 to 4 bits per channel.
vera_palette_ptr = $fa00
ubyte red
ubyte greenblue
repeat num_colors {
red = @(palletteptr) >> 4
palletteptr++
greenblue = @(palletteptr) & %11110000
palletteptr++
greenblue |= @(palletteptr) >> 4 ; add Blue
palletteptr++
red = @(palette_bytes_ptr) >> 4
palette_bytes_ptr++
greenblue = @(palette_bytes_ptr) & %11110000
palette_bytes_ptr++
greenblue |= @(palette_bytes_ptr) >> 4 ; add Blue
palette_bytes_ptr++
cx16.vpoke(1, vera_palette_ptr, greenblue)
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, red)
@ -44,16 +56,16 @@ palette {
}
}
sub set_monochrome() {
sub set_monochrome(uword screencolorRGB, uword drawcolorRGB) {
vera_palette_ptr = $fa00
cx16.vpoke(1, vera_palette_ptr, 0)
cx16.vpoke(1, vera_palette_ptr, lsb(screencolorRGB)) ; G,B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, 0)
cx16.vpoke(1, vera_palette_ptr, msb(screencolorRGB)) ; R
vera_palette_ptr++
repeat 255 {
cx16.vpoke(1, vera_palette_ptr, 255)
cx16.vpoke(1, vera_palette_ptr, lsb(drawcolorRGB)) ; G,B
vera_palette_ptr++
cx16.vpoke(1, vera_palette_ptr, 255)
cx16.vpoke(1, vera_palette_ptr, msb(drawcolorRGB)) ; R
vera_palette_ptr++
}
}
@ -88,7 +100,7 @@ palette {
$666, ; 12 = medium grey
$9D8, ; 13 = light green
$65B, ; 14 = light blue
$999 ; 15 = light grey
$999 ; 15 = light grey
]
uword[] C64_colorpalette_pepto = [ ; # this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
@ -107,7 +119,7 @@ palette {
$777, ; 12 = medium grey
$af9, ; 13 = light green
$76e, ; 14 = light blue
$bbb ; 15 = light grey
$bbb ; 15 = light grey
]
uword[] C64_colorpalette_light = [ ; this is a lighter palette

View File

@ -35,7 +35,7 @@ romsub $FFAE = UNLSN() clobbers(A) ; command serial
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte secondary @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(X,Y) -> ubyte @Pc, ubyte @A ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
@ -44,8 +44,8 @@ romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(X, Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, uword @ XY ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFE1 = STOP() clobbers(X) -> ubyte @ Pz, ubyte @ A ; (via 808 ($328)) check the STOP key (and some others in A)
@ -102,10 +102,11 @@ asmsub MEMTOP2() -> ubyte @A {
cx16 {
; 65c02 hardware vectors:
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; irq and hardware vectors:
&uword CINV = $0314 ; IRQ vector (in ram)
&uword NMI_VEC = $FFFA ; 65c02 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 65c02 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 65c02 interrupt vector, determined by the kernal if banked in
; the sixteen virtual 16-bit registers
@ -349,75 +350,115 @@ asmsub vaddr(ubyte bank @A, uword address @R0, ubyte addrsel @R1, byte autoIncrO
}
asmsub vpoke(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
; -- write a single byte to VERA's video memory
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
sty cx16.VERA_DATA0
rts
}}
; -- write a single byte to VERA's video memory
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
sty cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_or(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
; -- or a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
ora cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
; -- or a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
ora cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_and(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers(A) {
; -- and a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
and cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
; -- and a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
and cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
asmsub vpoke_xor(ubyte bank @A, uword address @R0, ubyte value @Y) clobbers (A) {
; -- xor a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
; -- xor a single byte to the value already in the VERA's video memory at that location
; note: inefficient when writing multiple sequential bytes!
%asm {{
stz cx16.VERA_CTRL
and #1
sta cx16.VERA_ADDR_H
lda cx16.r0
sta cx16.VERA_ADDR_L
lda cx16.r0+1
sta cx16.VERA_ADDR_M
tya
eor cx16.VERA_DATA0
sta cx16.VERA_DATA0
rts
}}
}
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
}}
}
sub FB_set_pixels_from_buf(uword buffer, uword count) {
%asm {{
; -- This is replacement code for the normal FB_set_pixels subroutine in ROM
@ -453,7 +494,7 @@ _loop ldy #0
}
; ---- system stuff -----
asmsub init_system() {
asmsub init_system() {
; Initializes the machine to a sane starting state.
; Called automatically by the loader program logic.
%asm {{
@ -461,7 +502,7 @@ asmsub init_system() {
cld
;stz $00
;stz $01
;stz d1prb ; select rom bank 0
;stz d1prb ; select rom bank 0 (enable kernal)
lda #$80
sta VERA_CTRL
jsr c64.IOINIT
@ -485,8 +526,177 @@ asmsub init_system() {
}}
}
asmsub init_system_phase2() {
%asm {{
sei
lda cx16.CINV
sta restore_irq._orig_irqvec
lda cx16.CINV+1
sta restore_irq._orig_irqvec+1
cli
rts
}}
}
asmsub set_irq(uword handler @AY, ubyte useKernal @Pc) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda #0
adc #0
sta _use_kernal
sei
lda #<_irq_handler
sta cx16.CINV
lda #>_irq_handler
sta cx16.CINV+1
lda cx16.VERA_IEN
ora #%00000001 ; enable the vsync irq
sta cx16.VERA_IEN
cli
rts
_irq_handler jsr _irq_handler_init
_modified jsr $ffff ; modified
jsr _irq_handler_end
lda _use_kernal
bne +
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #1
sta cx16.VERA_ISR ; clear Vera Vsync irq status
ply
plx
pla
rti
+ jmp (restore_irq._orig_irqvec) ; continue with normal kernal irq routine
_use_kernal .byte 0
_irq_handler_init
; save all zp scratch registers and the X register as these might be clobbered by the irq routine
stx IRQ_X_REG
lda P8ZP_SCRATCH_B1
sta IRQ_SCRATCH_ZPB1
lda P8ZP_SCRATCH_REG
sta IRQ_SCRATCH_ZPREG
lda P8ZP_SCRATCH_W1
sta IRQ_SCRATCH_ZPWORD1
lda P8ZP_SCRATCH_W1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda P8ZP_SCRATCH_W2
sta IRQ_SCRATCH_ZPWORD2
lda P8ZP_SCRATCH_W2+1
sta IRQ_SCRATCH_ZPWORD2+1
; stack protector; make sure we don't clobber the top of the evaluation stack
dex
dex
dex
dex
dex
dex
cld
rts
_irq_handler_end
; restore all zp scratch registers and the X register
lda IRQ_SCRATCH_ZPB1
sta P8ZP_SCRATCH_B1
lda IRQ_SCRATCH_ZPREG
sta P8ZP_SCRATCH_REG
lda IRQ_SCRATCH_ZPWORD1
sta P8ZP_SCRATCH_W1
lda IRQ_SCRATCH_ZPWORD1+1
sta P8ZP_SCRATCH_W1+1
lda IRQ_SCRATCH_ZPWORD2
sta P8ZP_SCRATCH_W2
lda IRQ_SCRATCH_ZPWORD2+1
sta P8ZP_SCRATCH_W2+1
ldx IRQ_X_REG
rts
IRQ_X_REG .byte 0
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irq() clobbers(A) {
%asm {{
sei
lda _orig_irqvec
sta cx16.CINV
lda _orig_irqvec+1
sta cx16.CINV+1
lda cx16.VERA_IEN
and #%11110000 ; disable all Vera IRQs
ora #%00000001 ; enable only the vsync Irq
sta cx16.VERA_IEN
cli
rts
_orig_irqvec .word 0
}}
}
asmsub set_rasterirq(uword handler @AY, uword rasterpos @R0) clobbers(A) {
%asm {{
sta _modified+1
sty _modified+2
lda cx16.r0
ldy cx16.r0+1
sei
lda cx16.VERA_IEN
and #%11110000 ; clear other IRQs
ora #%00000010 ; enable the line (raster) irq
sta cx16.VERA_IEN
lda cx16.r0
ldy cx16.r0+1
jsr set_rasterline
lda #<_raster_irq_handler
sta cx16.CINV
lda #>_raster_irq_handler
sta cx16.CINV+1
cli
rts
_raster_irq_handler
jsr set_irq._irq_handler_init
_modified jsr $ffff ; modified
jsr set_irq._irq_handler_end
; end irq processing - don't use kernal's irq handling
lda cx16.VERA_ISR
ora #%00000010
sta cx16.VERA_ISR ; clear Vera line irq status
ply
plx
pla
rti
}}
}
asmsub set_rasterline(uword line @AY) {
%asm {{
sta cx16.VERA_IRQ_LINE_L
lda cx16.VERA_IEN
and #%01111111
sta cx16.VERA_IEN
tya
lsr a
ror a
and #%10000000
ora cx16.VERA_IEN
sta cx16.VERA_IEN
rts
}}
}
}
sys {
; ------- lowlevel system routines --------

View File

@ -31,6 +31,16 @@ sub spc() {
txt.chrout(' ')
}
asmsub column(ubyte col @A) clobbers(A, X, Y) {
; ---- set the cursor on the given column (starting with 0) on the current line
%asm {{
sec
jsr c64.PLOT
tay
clc
jmp c64.PLOT
}}
}
asmsub fill_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
; ---- fill the character screen with the given fill character and character color.
@ -410,7 +420,7 @@ _print_byte_digits
jsr c64.CHROUT
pla
jsr c64.CHROUT
jmp _ones
bra _ones
+ pla
cmp #'0'
beq _ones
@ -433,7 +443,7 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
jsr c64.CHROUT
+ pla
jsr conv.byte2decimal
jmp print_ub._print_byte_digits
bra print_ub._print_byte_digits
}}
}
@ -484,7 +494,7 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
jsr print_ubbin
pla
clc
jmp print_ubbin
bra print_ubbin
}}
}
@ -497,7 +507,7 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
jsr print_ubhex
pla
clc
jmp print_ubhex
bra print_ubhex
}}
}
@ -560,7 +570,7 @@ asmsub print_w (word value @ AY) clobbers(A,Y) {
adc #1
bcc +
iny
+ jmp print_uw
+ bra print_uw
}}
}
@ -588,95 +598,126 @@ asmsub input_chars (uword buffer @ AY) clobbers(A) -> ubyte @ Y {
asmsub setchr (ubyte col @X, ubyte row @Y, ubyte character @A) clobbers(A) {
; ---- sets the character in the screen matrix at the given position
%asm {{
pha
txa
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
pha
txa
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
}}
}
asmsub getchr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- get the character in the screen matrix at the given location
%asm {{
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
asl a
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
}}
}
asmsub setclr (ubyte col @X, ubyte row @Y, ubyte color @A) clobbers(A) {
; ---- set the color in A on the screen matrix at the given position
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color!
%asm {{
pha
txa
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
pha
txa
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
pla
sta cx16.VERA_DATA0
rts
}}
}
asmsub getclr (ubyte col @A, ubyte row @Y) -> ubyte @ A {
; ---- get the color in the screen color matrix at the given location
%asm {{
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
asl a
ina
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
sta cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
rts
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte charcolor) {
; ---- set char+color at the given position on the screen
; note: color handling is the same as on the C64: it only sets the foreground color.
; use setcc2 if you want Cx-16 specific feature of setting both Bg+Fg colors.
%asm {{
phx
lda column
asl a
tax
ldy row
lda charcolor
and #$0f
sta P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
and #$f0
ora P8ZP_SCRATCH_B1
sta cx16.VERA_DATA0
plx
rts
phx
lda column
asl a
tax
ldy row
lda charcolor
and #$0f
sta P8ZP_SCRATCH_B1
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda cx16.VERA_DATA0
and #$f0
ora P8ZP_SCRATCH_B1
sta cx16.VERA_DATA0
plx
rts
}}
}
sub setcc2 (ubyte column, ubyte row, ubyte char, ubyte colors) {
; ---- set char+color at the given position on the screen
; note: on the CommanderX16 this allows you to set both Fg and Bg colors;
; use the high nybble in A to set the Bg color!
%asm {{
phx
lda column
asl a
tax
ldy row
stz cx16.VERA_CTRL
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda char
sta cx16.VERA_DATA0
inx
stz cx16.VERA_ADDR_H
stx cx16.VERA_ADDR_L
sty cx16.VERA_ADDR_M
lda colors
sta cx16.VERA_DATA0
plx
rts
}}
}
asmsub plot (ubyte col @ Y, ubyte row @ A) clobbers(A) {
; ---- safe wrapper around PLOT kernel routine, to save the X register.
; ---- safe wrapper around PLOT kernal routine, to save the X register.
%asm {{
phx
tax

View File

@ -244,8 +244,8 @@ randseed .proc
.pend
randbyte .proc
; -- 8-bit pseudo random number generator into A
fast_randbyte .proc
; -- fast but bad 8-bit pseudo random number generator into A
lda _seed
beq _eor
asl a
@ -263,6 +263,10 @@ _seed .byte $3a
.pend
randbyte .proc
; -- 8 bit pseudo random number generator into A (by just reusing randword)
jmp randword
.pend
randword .proc
; -- 16 bit pseudo random number generator into AY
@ -1262,7 +1266,7 @@ mul_word_100 .proc
.pend
mul_word_320 .proc
; AY = A * 256 + A * 64 (msb doesn't matter)
; AY = A * 256 + A * 64 (msb in Y doesn't matter)
sta P8ZP_SCRATCH_B1
ldy #0
sty P8ZP_SCRATCH_REG
@ -1537,3 +1541,71 @@ _negative lsr a
rts
.pend
square .proc
; -- calculate square root of signed word in AY, result in AY
; routine by Lee Davsion, source: http://6502.org/source/integers/square.htm
; using this routine is about twice as fast as doing a regular multiplication.
;
; Calculates the 16 bit unsigned integer square of the signed 16 bit integer in
; Numberl/Numberh. The result is always in the range 0 to 65025 and is held in
; Squarel/Squareh
;
; The maximum input range is only +/-255 and no checking is done to ensure that
; this is so.
;
; This routine is useful if you are trying to draw circles as for any circle
;
; x^2+y^2=r^2 where x and y are the co-ordinates of any point on the circle and
; r is the circle radius
numberl = P8ZP_SCRATCH_W1 ; number to square low byte
numberh = P8ZP_SCRATCH_W1+1 ; number to square high byte
squarel = P8ZP_SCRATCH_W2 ; square low byte
squareh = P8ZP_SCRATCH_W2+1 ; square high byte
tempsq = P8ZP_SCRATCH_B1 ; temp byte for intermediate result
sta numberl
sty numberh
stx P8ZP_SCRATCH_REG
lda #$00 ; clear a
sta squarel ; clear square low byte
; (no need to clear the high byte, it gets shifted out)
lda numberl ; get number low byte
ldx numberh ; get number high byte
bpl _nonneg ; if +ve don't negate it
; else do a two's complement
eor #$ff ; invert
sec ; +1
adc #$00 ; and add it
_nonneg:
sta tempsq ; save abs(number)
ldx #$08 ; set bit count
_nextr2bit:
asl squarel ; low byte *2
rol squareh ; high byte *2+carry from low
asl a ; shift number byte
bcc _nosqadd ; don't do add if c = 0
tay ; save a
clc ; clear carry for add
lda tempsq ; get number
adc squarel ; add number^2 low byte
sta squarel ; save number^2 low byte
lda #$00 ; clear a
adc squareh ; add number^2 high byte
sta squareh ; save number^2 high byte
tya ; get a back
_nosqadd:
dex ; decrement bit count
bne _nextr2bit ; go do next bit
lda squarel
ldy squareh
ldx P8ZP_SCRATCH_REG
rts
.pend

View File

@ -387,6 +387,14 @@ func_sqrt16_into_A .proc
rts
.pend
func_fastrnd8_stack .proc
; -- put a random ubyte on the estack (using fast but bad RNG)
jsr math.fast_randbyte
sta P8ESTACK_LO,x
dex
rts
.pend
func_rnd_stack .proc
; -- put a random ubyte on the estack
jsr math.randbyte
@ -432,6 +440,7 @@ func_min_ub_stack .proc
func_min_b_into_A .proc
; -- min(barray) -> A. (array in P8ZP_SCRATCH_W1, num elements in A)
tay
dey
lda #127
sta P8ZP_SCRATCH_B1
- lda (P8ZP_SCRATCH_W1),y
@ -548,6 +557,7 @@ func_min_w_stack .proc
func_max_ub_into_A .proc
; -- max(ubarray) -> A (array in P8ZP_SCRATCH_W1, num elements in A)
tay
dey
lda #0
sta P8ZP_SCRATCH_B1
- lda (P8ZP_SCRATCH_W1),y
@ -1078,3 +1088,28 @@ _loop_hi ldy _index_first
.pend
func_peekw .proc
; -- read the word value on the address in AY
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
tay
pla
rts
.pend
func_pokew .proc
; -- store the word value in AY in the address in P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_REG
ldy #0
sta (P8ZP_SCRATCH_W1),y
iny
lda P8ZP_SCRATCH_REG
sta (P8ZP_SCRATCH_W1),y
rts
.pend

View File

@ -1071,3 +1071,15 @@ sign_extend_AY_byte .proc
pla
rts
.pend
strlen .proc
; -- returns the number of bytes in the string in AY, in Y.
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
ldy #0
- lda (P8ZP_SCRATCH_W1),y
beq +
iny
bne -
+ rts
.pend

View File

@ -1 +1 @@
6.0
6.4

View File

@ -1,12 +1,14 @@
package prog8
import kotlinx.cli.*
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
import kotlinx.cli.multiple
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
import prog8.compiler.compileProgram
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.CompilationTarget
import prog8.parser.ParsingFailedError
import java.nio.file.FileSystems
import java.nio.file.Path
@ -32,14 +34,14 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
private fun compileMain(args: Array<String>) {
val cli = ArgParser("prog8compiler")
val startEmulator by cli.option(ArgType.Boolean, shortName="emu", description = "auto-start emulator after successful compilation")
val outputDir by cli.option(ArgType.String, shortName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.option(ArgType.Boolean, shortName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, shortName = "noopt", description = "don't perform any optimizations")
val watchMode by cli.option(ArgType.Boolean, shortName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, shortName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.option(ArgType.String, shortName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val cli = ArgParser("prog8compiler", prefixStyle = ArgParser.OptionPrefixStyle.JVM)
val startEmulator by cli.option(ArgType.Boolean, fullName = "emu", description = "auto-start emulator after successful compilation")
val outputDir by cli.option(ArgType.String, fullName = "out", description = "directory for output files instead of current directory").default(".")
val dontWriteAssembly by cli.option(ArgType.Boolean, fullName = "noasm", description="don't create assembly code")
val dontOptimize by cli.option(ArgType.Boolean, fullName = "noopt", description = "don't perform any optimizations")
val watchMode by cli.option(ArgType.Boolean, fullName = "watch", description = "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val slowCodegenWarnings by cli.option(ArgType.Boolean, fullName = "slowwarn", description="show debug warnings about slow/problematic assembly code generation")
val compilationTarget by cli.option(ArgType.String, fullName = "target", description = "target output of the compiler, currently '${C64Target.name}' and '${Cx16Target.name}' available").default(C64Target.name)
val moduleFiles by cli.argument(ArgType.String, fullName = "modules", description = "main module file(s) to compile").multiple(999)
try {
@ -114,7 +116,7 @@ private fun compileMain(args: Array<String>) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else {
CompilationTarget.instance.machine.launchEmulator(compilationResult.programName)
compilationResult.compTarget.machine.launchEmulator(compilationResult.programName)
}
}
}

View File

@ -1,22 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.statements.Directive
internal class ImportedModuleDirectiveRemover: AstWalker() {
/**
* Most global directives don't apply for imported modules, so remove them
*/
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
private val noModifications = emptyList<IAstModification>()
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent as INameScope))
}
return noModifications
}
}

View File

@ -1,44 +0,0 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.NopStatement
internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
into.statements.remove(scope)
}
}
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
return noModifications
}
}

View File

@ -5,12 +5,14 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.target.ICompilationTarget
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
@ -37,7 +39,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// But it can only be done if the target variable IS NOT OCCURRING AS AN OPERAND ITSELF.
if(!assignment.isAugmentable
&& assignment.target.identifier != null
&& assignment.target.isInRegularRAM(program.namespace)) {
&& compTarget.isInRegularRAM(assignment.target, program)) {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null && binExpr.operator !in comparisonOperators) {
if (binExpr.left !is BinaryExpression) {
@ -106,7 +108,7 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
}
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel 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.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
@ -153,16 +155,6 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
// The only place for now where we can do this is for:
// asmsub register pair parameter.
if(typecast.type in WordDatatypes) {
val fcall = typecast.parent as? IFunctionCall
if (fcall != null) {
val sub = fcall.target.targetStatement(program.namespace) as? Subroutine
if (sub != null && sub.isAsmSubroutine) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
}
if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
if(typecast.expression is IdentifierReference) {
@ -215,4 +207,93 @@ internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: E
}
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource==listOf("cmp")) {
// if the datatype of the arguments of cmp() are different, cast the byte one to word.
val arg1 = functionCallStatement.args[0]
val arg2 = functionCallStatement.args[1]
val dt1 = arg1.inferType(program).typeOrElse(DataType.STRUCT)
val dt2 = arg2.inferType(program).typeOrElse(DataType.STRUCT)
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes)
return noModifications
val cast1 = TypecastExpression(arg1, if(dt1==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
return listOf(IAstModification.ReplaceNode(arg1, cast1, functionCallStatement))
} else {
if(dt2 in WordDatatypes)
return noModifications
val cast2 = TypecastExpression(arg2, if(dt2==DataType.UBYTE) DataType.UWORD else DataType.WORD, true, functionCallStatement.position)
return listOf(IAstModification.ReplaceNode(arg2, cast2, functionCallStatement))
}
}
return noModifications
}
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val containingStatement = getContainingStatement(arrayIndexedExpression)
if(getComplexArrayIndexedExpressions(containingStatement).size > 1) {
errors.err("it's not possible to use more than one complex array indexing expression in a single statement; break it up via a temporary variable for instance", containingStatement.position)
return noModifications
}
val index = arrayIndexedExpression.indexer.indexExpr
if(index !is NumericLiteralValue && index !is IdentifierReference) {
// replace complex indexing expression with a temp variable to hold the computed index first
return getAutoIndexerVarFor(arrayIndexedExpression)
}
return noModifications
}
private fun getComplexArrayIndexedExpressions(stmt: Statement): List<ArrayIndexedExpression> {
class Searcher : IAstVisitor {
val complexArrayIndexedExpressions = mutableListOf<ArrayIndexedExpression>()
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
val ix = arrayIndexedExpression.indexer.indexExpr
if(ix !is NumericLiteralValue && ix !is IdentifierReference)
complexArrayIndexedExpressions.add(arrayIndexedExpression)
}
override fun visit(branchStatement: BranchStatement) {}
override fun visit(forLoop: ForLoop) {}
override fun visit(ifStatement: IfStatement) {
ifStatement.condition.accept(this)
}
override fun visit(untilLoop: UntilLoop) {
untilLoop.condition.accept(this)
}
}
val searcher = Searcher()
stmt.accept(searcher)
return searcher.complexArrayIndexedExpressions
}
private fun getContainingStatement(expression: Expression): Statement {
var node: Node = expression
while(node !is Statement)
node = node.parent
return node
}
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val statement = expr.containingStatement()
// replace the indexer with just the variable (simply use a cx16 virtual register r9, that we HOPE is not used for other things in the expression...)
// assign the indexing expression to the helper variable, but only if that hasn't been done already
val target = AssignTarget(IdentifierReference(listOf("cx16", "r9"), expr.indexer.position), null, null, expr.indexer.position)
val assign = Assignment(target, expr.indexer.indexExpr, expr.indexer.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
modifications.add(IAstModification.ReplaceNode(expr.indexer.indexExpr, target.identifier!!.copy(), expr.indexer))
return modifications
}
}

View File

@ -1,9 +1,30 @@
package prog8.compiler
import prog8.ast.AstToSourceCode
import prog8.ast.IBuiltinFunctions
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.Position
import prog8.ast.expressions.Expression
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.Directive
import prog8.compiler.astprocessing.*
import prog8.compiler.functions.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.ICompilationTarget
import prog8.compiler.target.asmGeneratorFor
import prog8.optimizer.*
import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.moduleName
import java.io.File
import java.io.InputStream
import java.nio.file.Path
import kotlin.math.abs
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
enum class OutputType {
RAW,
@ -28,7 +49,8 @@ data class CompilationOptions(val output: OutputType,
val zeropage: ZeropageType,
val zpReserved: List<IntRange>,
val floats: Boolean,
val noSysInit: Boolean) {
val noSysInit: Boolean,
val compTarget: ICompilationTarget) {
var slowCodegenWarnings = false
var optimize = false
}
@ -36,26 +58,274 @@ data class CompilationOptions(val output: OutputType,
class CompilerException(message: String?) : Exception(message)
fun Number.toHex(): String {
// 0..15 -> "0".."15"
// 16..255 -> "$10".."$ff"
// 256..65536 -> "$0100".."$ffff"
// negative values are prefixed with '-'.
val integer = this.toInt()
if(integer<0)
return '-' + abs(integer).toHex()
return when (integer) {
in 0 until 16 -> integer.toString()
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
else -> throw CompilerException("number too large for 16 bits $this")
class CompilationResult(val success: Boolean,
val programAst: Program,
val programName: String,
val compTarget: ICompilationTarget,
val importedFiles: List<Path>)
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
compilationTarget: String,
outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
val compTarget =
when(compilationTarget) {
C64Target.name -> C64Target
Cx16Target.name -> Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors, compTarget)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(programAst, errors, BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, compTarget, importedFiles)
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
print("\u001b[0m") // reset
System.out.flush()
throw x
} catch (x: NotImplementedError) {
print("\u001b[91m") // bright red
println("\n* internal error: missing feature/code *")
print("\u001b[0m") // reset
System.out.flush()
throw x
}
val failedProgram = Program("failed", mutableListOf(), BuiltinFunctionsFacade(BuiltinFunctions), compTarget)
return CompilationResult(false, failedProgram, programName, compTarget, emptyList())
}
private class BuiltinFunctionsFacade(functions: Map<String, FSignature>): IBuiltinFunctions {
lateinit var program: Program
override val names = functions.keys
override val purefunctionNames = functions.filter { it.value.pure }.map { it.key }.toSet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? {
val func = BuiltinFunctions[name]
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null) {
return try {
exprfunc(args, position, program, memsizer)
} catch(x: NotConstArgumentException) {
// const-evaluating the builtin function call failed.
null
} catch(x: CannotEvaluateException) {
// const-evaluating the builtin function call failed.
null
}
}
else if(func.known_returntype==null)
return null // builtin function $name can't be used here because it doesn't return a value
}
return null
}
override fun returnType(name: String, args: MutableList<Expression>) =
builtinFunctionReturnType(name, args, program)
}
private fun parseImports(filepath: Path, errors: IErrorReporter, compTarget: ICompilationTarget): Triple<Program, CompilationOptions, List<Path>> {
val compilationTargetName = compTarget.name
println("Compiler target: $compilationTargetName. Parsing...")
val importer = ModuleImporter()
val bf = BuiltinFunctionsFacade(BuiltinFunctions)
val programAst = Program(moduleName(filepath.fileName), mutableListOf(), bf, compTarget)
bf.program = programAst
importer.importModule(programAst, filepath, compTarget, compilationTargetName)
errors.report()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst, compTarget)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// depending on the machine and compiler options we may have to include some libraries
for(lib in compTarget.machine.importLibs(compilerOptions, compilationTargetName))
importer.importLibraryModule(programAst, lib, compTarget, compilationTargetName)
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math", compTarget, compilationTargetName)
importer.importLibraryModule(programAst, "prog8_lib", compTarget, compilationTargetName)
errors.report()
return Triple(programAst, compilerOptions, importedFiles)
}
private fun determineCompilationOptions(program: Program, compTarget: ICompilationTarget): CompilationOptions {
val mainModule = program.mainModule
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && compTarget.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
System.err.println("invalid output type $outputType")
exitProcess(1)
}
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, noSysInit,
compTarget
)
}
private fun processAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${compilerOptions.compTarget.name}...")
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
errors.report()
programAst.constantFold(errors, compilerOptions.compTarget)
errors.report()
programAst.reorderStatements(errors)
errors.report()
programAst.addTypecasts(errors)
errors.report()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget)
errors.report()
programAst.checkIdentifiers(errors, compilerOptions.compTarget)
errors.report()
}
private fun optimizeAst(programAst: Program, errors: IErrorReporter, functions: IBuiltinFunctions, compTarget: ICompilationTarget) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions(compTarget)
val optsDone3 = programAst.optimizeStatements(errors, functions, compTarget, ::loadAsmIncludeFile)
programAst.constantFold(errors, compTarget) // because simplified statements and expressions can result in more constants that can be folded away
errors.report()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
val remover = UnusedCodeRemover(programAst, errors, compTarget, ::loadAsmIncludeFile)
remover.visit(programAst)
remover.applyModifications()
errors.report()
}
private fun postprocessAst(programAst: Program, errors: IErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
errors.report()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors, compilerOptions.compTarget) // check if final tree is still valid
errors.report()
val callGraph = CallGraph(programAst, ::loadAsmIncludeFile)
callGraph.checkRecursiveCalls(errors)
errors.report()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private fun writeAssembly(programAst: Program,
errors: IErrorReporter,
outputDir: Path,
compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast
programAst.processAstBeforeAsmGeneration(errors, compilerOptions.compTarget)
errors.report()
// printAst(programAst)
compilerOptions.compTarget.machine.initializeZeropage(compilerOptions)
val assembly = asmGeneratorFor(compilerOptions.compTarget,
programAst,
errors,
compilerOptions.compTarget.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
assembly.assemble(compilerOptions)
errors.report()
return assembly.name
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}
fun loadAsmIncludeFile(filename: String, source: Path): String {
return if (filename.startsWith("library:")) {
val resource = tryGetEmbeddedResource(filename.substring(8))
?: throw IllegalArgumentException("library file '$filename' not found")
?: throw IllegalArgumentException("library file '$filename' not found")
resource.bufferedReader().use { it.readText() }
} else {
// first try in the isSameAs folder as where the containing file was imported from

View File

@ -1,9 +1,18 @@
package prog8.ast.base
package prog8.compiler
import prog8.ast.base.Position
import prog8.parser.ParsingFailedError
class ErrorReporter {
interface IErrorReporter {
fun err(msg: String, position: Position)
fun warn(msg: String, position: Position)
fun isEmpty(): Boolean
fun report()
}
internal class ErrorReporter: IErrorReporter {
private enum class MessageSeverity {
WARNING,
ERROR
@ -13,10 +22,14 @@ class ErrorReporter {
private val messages = mutableListOf<CompilerMessage>()
private val alreadyReportedMessages = mutableSetOf<String>()
fun err(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
fun warn(msg: String, position: Position) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
override fun err(msg: String, position: Position) {
messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
}
override fun warn(msg: String, position: Position) {
messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
}
fun handle() {
override fun report() {
var numErrors = 0
var numWarnings = 0
messages.forEach {
@ -40,5 +53,5 @@ class ErrorReporter {
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
}
fun isEmpty() = messages.isEmpty()
override fun isEmpty() = messages.isEmpty()
}

View File

@ -1,247 +0,0 @@
package prog8.compiler
import prog8.ast.AstToSourceCode
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.statements.Directive
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.optimizer.*
import prog8.optimizer.UnusedCodeRemover
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.moduleName
import java.nio.file.Path
import kotlin.system.exitProcess
import kotlin.system.measureTimeMillis
class CompilationResult(val success: Boolean,
val programAst: Program,
val programName: String,
val importedFiles: List<Path>)
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
slowCodegenWarnings: Boolean,
compilationTarget: String,
outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
when(compilationTarget) {
C64Target.name -> CompilationTarget.instance = C64Target
Cx16Target.name -> CompilationTarget.instance = Cx16Target
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
compilationOptions.slowCodegenWarnings = slowCodegenWarnings
compilationOptions.optimize = optimize
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (compilationOptions.optimize)
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
// printAst(programAst)
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, compilationOptions)
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, importedFiles)
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
System.err.println(px.message)
System.err.print("\u001b[0m") // reset
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
} catch (x: Exception) {
print("\u001b[91m") // bright red
println("\n* internal error *")
print("\u001b[0m") // reset
System.out.flush()
throw x
} catch (x: NotImplementedError) {
print("\u001b[91m") // bright red
println("\n* internal error: missing feature/code *")
print("\u001b[0m") // reset
System.out.flush()
throw x
}
return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList())
}
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
println("Compiler target: ${CompilationTarget.instance.name}. Parsing...")
val importer = ModuleImporter()
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
importer.importModule(programAst, filepath)
errors.handle()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// depending on the machine and compiler options we may have to include some libraries
CompilationTarget.instance.machine.importLibs(compilerOptions, importer, programAst)
// always import prog8_lib and math
importer.importLibraryModule(programAst, "math")
importer.importLibraryModule(programAst, "prog8_lib")
errors.handle()
return Triple(programAst, compilerOptions, importedFiles)
}
private fun determineCompilationOptions(program: Program): CompilationOptions {
val mainModule = program.modules.first()
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase()
val allOptions = program.modules.flatMap { it.statements }.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
val floatsEnabled = allOptions.any { it.name == "enable_floats" }
val noSysInit = allOptions.any { it.name == "no_sysinit" }
var zpType: ZeropageType =
if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
else
try {
ZeropageType.valueOf(zpoption)
} catch (x: IllegalArgumentException) {
ZeropageType.KERNALSAFE
// error will be printed by the astchecker
}
if (zpType==ZeropageType.FLOATSAFE && CompilationTarget.instance.name == Cx16Target.name) {
System.err.println("Warning: Cx16 target must use zp option basicsafe instead of floatsafe")
zpType = ZeropageType.BASICSAFE
}
val zpReserved = mainModule.statements
.asSequence()
.filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args }
.map { it[0].int!!..it[1].int!! }
.toList()
if(outputType!=null && !OutputType.values().any {it.name==outputType}) {
System.err.println("invalid output type $outputType")
exitProcess(1)
}
if(launcherType!=null && !LauncherType.values().any {it.name==launcherType}) {
System.err.println("invalid launcher type $launcherType")
exitProcess(1)
}
return CompilationOptions(
if (outputType == null) OutputType.PRG else OutputType.valueOf(outputType),
if (launcherType == null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
zpType, zpReserved, floatsEnabled, noSysInit
)
}
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing for target ${CompilationTarget.instance.name}...")
programAst.checkIdentifiers(errors)
errors.handle()
programAst.constantFold(errors)
errors.handle()
programAst.reorderStatements(errors)
errors.handle()
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors)
errors.handle()
programAst.checkIdentifiers(errors)
errors.handle()
}
private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.splitBinaryExpressions()
val optsDone3 = programAst.optimizeStatements(errors)
programAst.constantFold(errors) // because simplified statements and expressions can result in more constants that can be folded away
errors.handle()
if (optsDone1 + optsDone2 + optsDone3 == 0)
break
}
val remover = UnusedCodeRemover(programAst, errors)
remover.visit(programAst)
remover.applyModifications()
errors.handle()
}
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
programAst.addTypecasts(errors)
errors.handle()
programAst.variousCleanups()
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
errors.handle()
val callGraph = CallGraph(programAst)
callGraph.checkRecursiveCalls(errors)
errors.handle()
programAst.verifyFunctionArgTypes()
programAst.moveMainAndStartToFirst()
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
programAst.processAstBeforeAsmGeneration(errors)
errors.handle()
// printAst(programAst)
CompilationTarget.instance.machine.initializeZeropage(compilerOptions)
val assembly = CompilationTarget.instance.asmGenerator(
programAst,
errors,
CompilationTarget.instance.machine.zeropage,
compilerOptions,
outputDir).compileToAssembly()
assembly.assemble(compilerOptions)
errors.handle()
return assembly.name
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}

View File

@ -21,7 +21,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: ErrorReporter): Int {
fun allocate(scopedname: String, datatype: DataType, position: Position?, errors: IErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE)

View File

@ -1,4 +1,4 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.INameScope
import prog8.ast.Module
@ -6,17 +6,21 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.functions.BuiltinFunctions
import prog8.functions.builtinFunctionReturnType
import prog8.compiler.target.ICompilationTarget
import java.io.File
internal class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions,
private val errors: ErrorReporter) : IAstVisitor {
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget
) : IAstVisitor {
override fun visit(program: Program) {
assert(program === this.program)
@ -37,19 +41,6 @@ internal class AstChecker(private val program: Program,
}
}
// there can be an optional single 'irq' block with a 'irq' subroutine in it,
// which will be used as the 60hz irq routine in the vm if it's present
val irqBlocks = program.modules.flatMap { it.statements }.filter { it is Block && it.name=="irq" }.map { it as Block }
if(irqBlocks.size>1)
errors.err("more than one 'irq' block", irqBlocks[0].position)
for(irqBlock in irqBlocks) {
val irqSub = irqBlock.subScope("irq") as? Subroutine
if (irqSub != null) {
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
errors.err("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position)
}
}
super.visit(program)
}
@ -99,7 +90,7 @@ internal class AstChecker(private val program: Program,
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
errors.err("can only loop over an iterable type", forLoop.position)
} else {
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace)
val loopvar = forLoop.loopVar.targetVarDecl(program)
if(loopvar==null || loopvar.type== VarDeclType.CONST) {
errors.err("for loop requires a variable to loop with", forLoop.position)
} else {
@ -150,21 +141,24 @@ internal class AstChecker(private val program: Program,
}
override fun visit(jump: Jump) {
if(jump.identifier!=null) {
val targetStatement = checkFunctionOrLabelExists(jump.identifier, jump)
val ident = jump.identifier
if(ident!=null) {
val targetStatement = checkFunctionOrLabelExists(ident, jump)
if(targetStatement!=null) {
if(targetStatement is BuiltinFunctionStatementPlaceholder)
errors.err("can't jump to a builtin function", jump.position)
}
}
if(jump.address!=null && (jump.address < 0 || jump.address > 65535))
val addr = jump.address
if(addr!=null && (addr < 0 || addr > 65535))
errors.err("jump address must be valid integer 0..\$ffff", jump.position)
super.visit(jump)
}
override fun visit(block: Block) {
if(block.address!=null && (block.address<0 || block.address>65535)) {
val addr = block.address
if(addr!=null && (addr<0 || addr>65535)) {
errors.err("block memory address must be valid integer 0..\$ffff", block.position)
}
@ -180,7 +174,7 @@ internal class AstChecker(private val program: Program,
else -> false
}
if (!ok) {
errors.err("statement occurs in a block, where it will never be executed. Use it in a subroutine instead.", statement.position)
errors.err("non-declarative statement occurs in block scope, where it will never be executed. Move it to a subroutine instead.", statement.position)
break
}
}
@ -224,13 +218,13 @@ internal class AstChecker(private val program: Program,
err("subroutines can only have one return value")
// subroutine must contain at least one 'return' or 'goto'
// (or if it has an asm block, that must contain a 'rts' or 'jmp')
// (or if it has an asm block, that must contain a 'rts' or 'jmp' or 'bra')
if(subroutine.statements.count { it is Return || it is Jump } == 0) {
if (subroutine.amountOfRtsInAsm() == 0) {
if (subroutine.returntypes.isNotEmpty()) {
// for asm subroutines with an address, no statement check is possible.
if (subroutine.asmAddress == null && !subroutine.inline)
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or 'rts' / 'jmp' in case of %asm)")
err("non-inline subroutine has result value(s) and thus must have at least one 'return' or 'goto' in it (or rts/jmp/bra in case of %asm)")
}
}
}
@ -315,9 +309,11 @@ internal class AstChecker(private val program: Program,
RegisterOrPair.R13,
RegisterOrPair.R14,
RegisterOrPair.R15 -> { /* no sensible way to count this */ }
null ->
if(p.statusflag!=null)
statusflagCounts[p.statusflag] = statusflagCounts.getValue(p.statusflag) + 1
null -> {
val statusf = p.statusflag
if (statusf != null)
statusflagCounts[statusf] = statusflagCounts.getValue(statusf) + 1
}
}
}
}
@ -342,10 +338,6 @@ internal class AstChecker(private val program: Program,
if(statusFlagsNoCarry.isNotEmpty())
err("can only use Carry as status flag parameter")
val carryParameter = subroutine.asmParameterRegisters.singleOrNull { it.statusflag==Statusflag.Pc }
if(carryParameter!=null && carryParameter !== subroutine.asmParameterRegisters.last())
err("carry parameter has to come last")
} else {
// Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// Instead, their reference (address) should be passed (as an UWORD).
@ -376,13 +368,13 @@ internal class AstChecker(private val program: Program,
override fun visit(assignment: Assignment) {
if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
val stmt = (assignment.value as FunctionCall).target.targetStatement(program)
if (stmt is Subroutine) {
val idt = assignment.target.inferType(program)
if(!idt.isKnown) {
errors.err("return type mismatch", assignment.value.position)
}
if(stmt.returntypes.size <= 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(DataType.BYTE)) {
if(stmt.returntypes.isEmpty() || (stmt.returntypes.size == 1 && stmt.returntypes.single() isNotAssignableTo idt.typeOrElse(DataType.BYTE))) {
errors.err("return type mismatch", assignment.value.position)
}
}
@ -390,7 +382,7 @@ internal class AstChecker(private val program: Program,
val targetIdent = assignment.target.identifier
if(targetIdent!=null) {
val targetVar = targetIdent.targetVarDecl(program.namespace)
val targetVar = targetIdent.targetVarDecl(program)
if(targetVar?.struct != null) {
val sourceStructLv = assignment.value as? ArrayLiteralValue
if (sourceStructLv != null) {
@ -399,7 +391,7 @@ internal class AstChecker(private val program: Program,
} else {
val sourceIdent = assignment.value as? IdentifierReference
if (sourceIdent != null) {
val sourceVar = sourceIdent.targetVarDecl(program.namespace)
val sourceVar = sourceIdent.targetVarDecl(program)
if (sourceVar?.struct != null) {
if (sourceVar.struct !== targetVar.struct)
errors.err("assignment of different struct types", assignment.position)
@ -487,7 +479,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(addressOf: AddressOf) {
val variable=addressOf.identifier.targetVarDecl(program.namespace)
val variable=addressOf.identifier.targetVarDecl(program)
if(variable!=null
&& variable.datatype !in ArrayDatatypes
&& variable.type!=VarDeclType.MEMORY
@ -501,7 +493,7 @@ internal class AstChecker(private val program: Program,
fun err(msg: String, position: Position?=null) = errors.err(msg, position ?: decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true)
err("recursive var declaration")
// CONST can only occur on simple types (byte, word, float)
@ -747,7 +739,7 @@ internal class AstChecker(private val program: Program,
err("this directive may only occur in a block or at module level")
if(directive.args.isEmpty())
err("missing option directive argument(s)")
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit")}.any { !it })
else if(directive.args.map{it.name in setOf("enable_floats", "force_output", "no_sysinit", "align_word", "align_page")}.any { !it })
err("invalid option directive argument(s)")
}
"%target" -> {
@ -782,7 +774,7 @@ internal class AstChecker(private val program: Program,
fun isPassByReferenceElement(e: Expression): Boolean {
if(e is IdentifierReference) {
val decl = e.targetVarDecl(program.namespace)!!
val decl = e.targetVarDecl(program)!!
return decl.datatype in PassByReferenceDatatypes
}
return e is StringLiteralValue
@ -790,7 +782,7 @@ internal class AstChecker(private val program: Program,
if(array.parent is VarDecl) {
if (!array.value.all { it is NumericLiteralValue || it is AddressOf || isPassByReferenceElement(it) })
errors.err("array literal for variable initialization contains invalid types", array.position)
errors.err("array literal for variable initialization contains non-constant elements", array.position)
} else if(array.parent is ForLoop) {
if (!array.value.all { it.constValue(program) != null })
errors.err("array literal for iteration must contain constants. Try using a separate array variable instead?", array.position)
@ -935,12 +927,12 @@ internal class AstChecker(private val program: Program,
errors.warn("sgn() of unsigned type is always 0 or 1, this is perhaps not what was intended", functionCall.args.first().position)
}
val error = VerifyFunctionArgTypes.checkTypes(functionCall, functionCall.definingScope(), program)
val error = VerifyFunctionArgTypes.checkTypes(functionCall, program)
if(error!=null)
errors.err(error, functionCall.position)
// check the functions that return multiple returnvalues.
val stmt = functionCall.target.targetStatement(program.namespace)
val stmt = functionCall.target.targetStatement(program)
if (stmt is Subroutine) {
if (stmt.returntypes.size > 1) {
// Currently, it's only possible to handle ONE (or zero) return values from a subroutine.
@ -961,6 +953,20 @@ internal class AstChecker(private val program: Program,
}
}
// functions that don't return a value, can't be used in an expression or assignment
if(targetStatement is Subroutine) {
if(targetStatement.returntypes.isEmpty()) {
if(functionCall.parent is Expression || functionCall.parent is Assignment)
errors.err("subroutine doesn't return a value", functionCall.position)
}
}
else if(targetStatement is BuiltinFunctionStatementPlaceholder) {
if(builtinFunctionReturnType(targetStatement.name, functionCall.args, program).isUnknown) {
if(functionCall.parent is Expression || functionCall.parent is Assignment)
errors.err("function doesn't return a value", functionCall.position)
}
}
super.visit(functionCall)
}
@ -998,7 +1004,8 @@ internal class AstChecker(private val program: Program,
}
}
val error = VerifyFunctionArgTypes.checkTypes(functionCallStatement, functionCallStatement.definingScope(), program)
val error =
VerifyFunctionArgTypes.checkTypes(functionCallStatement, program)
if(error!=null) {
errors.err(error, functionCallStatement.args.firstOrNull()?.position ?: functionCallStatement.position)
}
@ -1025,7 +1032,7 @@ internal class AstChecker(private val program: Program,
errors.err("swap requires args of numerical type", position)
}
else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program)?.datatype == DataType.STR) {
errors.err("any/all on a string is useless (is always true unless the string is empty)", position)
}
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
@ -1039,6 +1046,28 @@ internal class AstChecker(private val program: Program,
if (!argIDt.isKnown)
return
}
// check that cx16 virtual registers aren't used as arguments in a conflicting way
val params = target.asmParameterRegisters.withIndex().toList()
for(arg in args.withIndex()) {
var ident: IdentifierReference? = null
if(arg.value is IdentifierReference)
ident = arg.value as IdentifierReference
else if(arg.value is FunctionCall) {
val fcall = arg.value as FunctionCall
if(fcall.target.nameInSource == listOf("lsb") || fcall.target.nameInSource == listOf("msb"))
ident = fcall.args[0] as? IdentifierReference
}
if(ident!=null && ident.nameInSource[0] == "cx16" && ident.nameInSource[1].startsWith("r")) {
val reg = RegisterOrPair.valueOf(ident.nameInSource[1].toUpperCase())
val same = params.filter { it.value.registerOrPair==reg }
for(s in same) {
if(s.index!=arg.index) {
errors.err("conflicting register $reg used as argument but is also a target register for another parameter", ident.position)
}
}
}
}
}
}
}
@ -1058,7 +1087,7 @@ internal class AstChecker(private val program: Program,
}
}
} else if(postIncrDecr.target.arrayindexed != null) {
val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program.namespace)
val target = postIncrDecr.target.arrayindexed?.arrayvar?.targetStatement(program)
if(target==null) {
errors.err("undefined symbol", postIncrDecr.position)
}
@ -1073,7 +1102,7 @@ internal class AstChecker(private val program: Program,
}
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
val target = arrayIndexedExpression.arrayvar.targetStatement(program.namespace)
val target = arrayIndexedExpression.arrayvar.targetStatement(program)
if(target is VarDecl) {
if(target.datatype !in IterableDatatypes)
errors.err("indexing requires an iterable variable", arrayIndexedExpression.position)
@ -1096,15 +1125,9 @@ internal class AstChecker(private val program: Program,
errors.err("indexing requires a variable to act upon", arrayIndexedExpression.position)
// check index value 0..255
val dtxNum = arrayIndexedExpression.indexer.indexNum?.inferType(program)?.typeOrElse(DataType.STRUCT)
if(dtxNum!=null && dtxNum != DataType.UBYTE && dtxNum != DataType.BYTE)
val dtxNum = arrayIndexedExpression.indexer.indexExpr.inferType(program)
if(!dtxNum.istype(DataType.UBYTE) && !dtxNum.istype(DataType.BYTE))
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
val dtxVar = arrayIndexedExpression.indexer.indexVar?.inferType(program)?.typeOrElse(DataType.STRUCT)
if(dtxVar!=null && dtxVar != DataType.UBYTE && dtxVar != DataType.BYTE)
errors.err("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)
if(arrayIndexedExpression.indexer.origExpression!=null)
throw FatalAstException("array indexer should have been replaced with a temp var @ ${arrayIndexedExpression.indexer.position}")
super.visit(arrayIndexedExpression)
}
@ -1113,12 +1136,18 @@ internal class AstChecker(private val program: Program,
val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT)
if(conditionType !in IntegerDatatypes)
errors.err("when condition must be an integer value", whenStatement.position)
val choiceValues = whenStatement.choiceValues(program)
val occurringValues = choiceValues.map {it.first}
val tally = choiceValues.associate { it.second to occurringValues.count { ov->it.first==ov} }
tally.filter { it.value>1 }.forEach {
errors.err("choice value occurs multiple times", it.key.position)
val tally = mutableSetOf<Int>()
for((choices, choiceNode) in whenStatement.choiceValues(program)) {
if(choices!=null) {
for (c in choices) {
if(c in tally)
errors.err("choice value already occurs earlier", choiceNode.position)
else
tally.add(c)
}
}
}
if(whenStatement.choices.isEmpty())
errors.err("empty when statement", whenStatement.position)
@ -1165,7 +1194,7 @@ internal class AstChecker(private val program: Program,
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: Statement): Statement? {
val targetStatement = target.targetStatement(program.namespace)
val targetStatement = target.targetStatement(program)
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
return targetStatement
else if(targetStatement==null)
@ -1255,7 +1284,7 @@ internal class AstChecker(private val program: Program,
// check if the floating point values are all within range
val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
if(doubles.any { it < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || it > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE })
if(doubles.any { it < compTarget.machine.FLOAT_MAX_NEGATIVE || it > compTarget.machine.FLOAT_MAX_POSITIVE })
return err("floating point value overflow")
return true
}
@ -1329,7 +1358,7 @@ internal class AstChecker(private val program: Program,
val array = value.value.map {
when (it) {
is NumericLiteralValue -> it.number.toInt()
is AddressOf -> it.identifier.heapId(program.namespace)
is AddressOf -> it.identifier.hashCode() and 0xffff
is TypecastExpression -> {
val constVal = it.expression.constValue(program)
val cast = constVal?.cast(it.type)
@ -1383,12 +1412,15 @@ internal class AstChecker(private val program: Program,
if(sourceDatatype==DataType.STRUCT) {
val structLv = sourceValue as ArrayLiteralValue
val numValues = structLv.value.size
val targetstruct = target.identifier!!.targetVarDecl(program.namespace)!!.struct!!
val targetstruct = target.identifier!!.targetVarDecl(program)!!.struct!!
return targetstruct.numberOfElements == numValues
}
false
}
else -> errors.err("cannot assign new value to variable of type $targetDatatype", position)
else -> {
errors.err("cannot assign new value to variable of type $targetDatatype", position)
false
}
}
if(result)

View File

@ -1,31 +1,32 @@
package prog8.ast.base
package prog8.compiler.astprocessing
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.ast.base.FatalAstException
import prog8.ast.statements.Directive
import prog8.compiler.CompilationOptions
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
val checker = AstChecker(this, compilerOptions, errors)
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: IErrorReporter, compTarget: ICompilationTarget) {
val checker = AstChecker(this, compilerOptions, errors, compTarget)
checker.visit(this)
}
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
val fixer = BeforeAsmGenerationAstChanger(this, errors)
internal fun Program.processAstBeforeAsmGeneration(errors: IErrorReporter, compTarget: ICompilationTarget) {
val fixer = BeforeAsmGenerationAstChanger(this, errors, compTarget)
fixer.visit(this)
fixer.applyModifications()
}
internal fun Program.reorderStatements(errors: ErrorReporter) {
internal fun Program.reorderStatements(errors: IErrorReporter) {
val reorder = StatementReorderer(this, errors)
reorder.visit(this)
reorder.applyModifications()
}
internal fun Program.addTypecasts(errors: ErrorReporter) {
internal fun Program.addTypecasts(errors: IErrorReporter) {
val caster = TypecastsAdder(this, errors)
caster.visit(this)
caster.applyModifications()
@ -36,15 +37,9 @@ internal fun Program.verifyFunctionArgTypes() {
fixer.visit(this)
}
internal fun Module.checkImportedValid() {
val imr = ImportedModuleDirectiveRemover()
imr.visit(this, this.parent)
imr.applyModifications()
}
internal fun Program.checkIdentifiers(errors: IErrorReporter, compTarget: ICompilationTarget) {
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val checker2 = AstIdentifiersChecker(this, errors)
val checker2 = AstIdentifiersChecker(this, errors, compTarget)
checker2.visit(this)
if(errors.isEmpty()) {

View File

@ -1,14 +1,20 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.target.ICompilationTarget
internal class AstIdentifiersChecker(private val program: Program, private val errors: ErrorReporter) : IAstVisitor {
internal class AstIdentifiersChecker(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : IAstVisitor {
private var blocks = mutableMapOf<String, Block>()
private fun nameError(name: String, position: Position, existing: Statement) {
@ -22,7 +28,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
}
override fun visit(block: Block) {
if(block.name in CompilationTarget.instance.machine.opcodeNames)
if(block.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${block.name}'", block.position)
val existing = blocks[block.name]
@ -44,8 +50,8 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
override fun visit(directive: Directive) {
if(directive.directive=="%target") {
val compatibleTarget = directive.args.single().name
if (compatibleTarget != CompilationTarget.instance.name)
errors.err("module's compilation target ($compatibleTarget) differs from active target (${CompilationTarget.instance.name})", directive.position)
if (compatibleTarget != compTarget.name)
errors.err("module's compilation target ($compatibleTarget) differs from active target (${compTarget.name})", directive.position)
}
super.visit(directive)
@ -57,7 +63,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
if(decl.name in BuiltinFunctions)
errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in CompilationTarget.instance.machine.opcodeNames)
if(decl.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position)
if(decl.datatype==DataType.STRUCT) {
@ -96,7 +102,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
}
override fun visit(subroutine: Subroutine) {
if(subroutine.name in CompilationTarget.instance.machine.opcodeNames) {
if(subroutine.name in compTarget.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
@ -136,7 +142,7 @@ internal class AstIdentifiersChecker(private val program: Program, private val e
}
override fun visit(label: Label) {
if(label.name in CompilationTarget.instance.machine.opcodeNames)
if(label.name in compTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) {

View File

@ -1,10 +1,16 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.base.DataType
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.ParameterVarDecl
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
@ -26,7 +32,7 @@ internal class AstVariousTransforms(private val program: Program) : AstWalker()
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// For non-kernel subroutines and non-asm parameters:
// For non-kernal subroutines and non-asm parameters:
// inject subroutine params as local variables (if they're not there yet).
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()

View File

@ -1,10 +1,15 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.base.DataType
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.statements.VarDecl
import prog8.ast.statements.WhenChoice
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
@ -12,13 +17,10 @@ internal class LiteralsToAutoVars(private val program: Program) : AstWalker() {
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl && string.parent !is WhenChoice) {
// replace the literal string by a identifier reference to a new local vardecl
val vardecl = VarDecl.createAuto(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope())
)
// replace the literal string by a identifier reference to the interned string
val scopedName = program.internString(string)
val identifier = IdentifierReference(scopedName, string.position)
return listOf(IAstModification.ReplaceNode(string, identifier, parent))
}
return noModifications
}

View File

@ -1,4 +1,4 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
/*

View File

@ -1,13 +1,19 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.*
import prog8.ast.IFunctionCall
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
internal class StatementReorderer(val program: Program, val errors: ErrorReporter) : AstWalker() {
internal class StatementReorderer(val program: Program, val errors: IErrorReporter) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - library blocks are put last.
@ -82,12 +88,11 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
override fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> {
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program.namespace)
val arrayVar = arrayIndexedExpression.arrayvar.targetVarDecl(program)
if(arrayVar!=null && arrayVar.datatype == DataType.UWORD) {
// rewrite pointervar[index] into @(pointervar+index)
val indexer = arrayIndexedExpression.indexer
val index = (indexer.indexNum ?: indexer.indexVar)!!
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", index, arrayIndexedExpression.position)
val add = BinaryExpression(arrayIndexedExpression.arrayvar, "+", indexer.indexExpr, arrayIndexedExpression.position)
return if(parent is AssignTarget) {
// we're part of the target of an assignment, we have to actually change the assign target itself
val memwrite = DirectMemoryWrite(add, arrayIndexedExpression.position)
@ -99,23 +104,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
}
}
when (val expr2 = arrayIndexedExpression.indexer.origExpression) {
is NumericLiteralValue -> {
arrayIndexedExpression.indexer.indexNum = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is IdentifierReference -> {
arrayIndexedExpression.indexer.indexVar = expr2
arrayIndexedExpression.indexer.origExpression = null
return noModifications
}
is Expression -> {
// replace complex indexing with a temp variable
return getAutoIndexerVarFor(arrayIndexedExpression)
}
else -> return noModifications
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
@ -140,7 +129,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
}
is IFunctionCall -> {
val argnum = parent.args.indexOf(expr)
when (val callee = parent.target.targetStatement(program.namespace)) {
when (val callee = parent.target.targetStatement(program)) {
is Subroutine -> {
val paramType = callee.parameters[argnum].type
if(leftDt isAssignableTo paramType) {
@ -164,41 +153,41 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
else -> return noModifications
}
}
else if(expr.operator in logicalOperators) {
// make sure that logical expressions like "var and other-logical-expression
// is rewritten as "var!=0 and other-logical-expression", to avoid bitwise boolean and
// generating the wrong results later
fun wrapped(expr: Expression): Expression =
BinaryExpression(expr, "!=", NumericLiteralValue(DataType.UBYTE, 0, expr.position), expr.position)
fun isLogicalExpr(expr: Expression?): Boolean {
if(expr is BinaryExpression && expr.operator in (logicalOperators + comparisonOperators))
return true
if(expr is PrefixExpression && expr.operator in logicalOperators)
return true
return false
}
return if(isLogicalExpr(expr.left)) {
if(isLogicalExpr(expr.right))
noModifications
else
listOf(IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr))
} else {
if(isLogicalExpr(expr.right))
listOf(IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr))
else {
listOf(
IAstModification.ReplaceNode(expr.left, wrapped(expr.left), expr),
IAstModification.ReplaceNode(expr.right, wrapped(expr.right), expr)
)
}
}
}
return noModifications
}
private fun getAutoIndexerVarFor(expr: ArrayIndexedExpression): MutableList<IAstModification> {
val modifications = mutableListOf<IAstModification>()
val subroutine = expr.definingSubroutine()!!
val statement = expr.containingStatement()
val indexerVarPrefix = "prog8_autovar_index_"
val repo = subroutine.asmGenInfo.usedAutoArrayIndexerForStatements
// TODO make this a bit smarter so it can reuse indexer variables. BUT BEWARE of scoping+initialization problems then
// add another loop index var to be used for this expression
val indexerVarName = "$indexerVarPrefix${expr.indexer.hashCode()}"
val indexerVar = AsmGenInfo.ArrayIndexerInfo(indexerVarName, expr.indexer)
repo.add(indexerVar)
// create the indexer var at block level scope
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE,
null, indexerVarName, null, null, isArray = false, autogeneratedDontRemove = true, position = expr.position)
modifications.add(IAstModification.InsertFirst(vardecl, subroutine))
// replace the indexer with just the variable
// assign the indexing expression to the helper variable, but only if that hasn't been done already
val indexerExpression = expr.indexer.origExpression!!
val target = AssignTarget(IdentifierReference(listOf(indexerVar.name), indexerExpression.position), null, null, indexerExpression.position)
val assign = Assignment(target, indexerExpression, indexerExpression.position)
modifications.add(IAstModification.InsertBefore(statement, assign, statement.definingScope()))
modifications.add(IAstModification.SetExpression( {
expr.indexer.indexVar = it as IdentifierReference
expr.indexer.indexNum = null
expr.indexer.origExpression = null
}, target.identifier!!.copy(), expr.indexer))
return modifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
it.first?.first() ?: Int.MAX_VALUE
@ -233,32 +222,23 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program)
var assignments = emptyList<Assignment>()
if(targetType.istype(DataType.STRUCT) && (valueType.istype(DataType.STRUCT) || valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes )) {
assignments = if (assignment.value is ArrayLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment) // 'structvar = [ ..... ] '
if (assignment.value is ArrayLiteralValue) {
errors.err("cannot assign non-const array value, use separate assignment per field", assignment.position)
} else {
flattenStructAssignmentFromIdentifier(assignment) // 'structvar1 = structvar2'
return copyStructValue(assignment)
}
}
if(targetType.typeOrElse(DataType.STRUCT) in ArrayDatatypes && valueType.typeOrElse(DataType.STRUCT) in ArrayDatatypes ) {
assignments = if (assignment.value is ArrayLiteralValue) {
flattenArrayAssignmentFromArrayLiteral(assignment) // 'arrayvar = [ ..... ] '
if (assignment.value is ArrayLiteralValue) {
errors.err("cannot assign non-const array value, use separate assignment per element", assignment.position)
} else {
flattenArrayAssignmentFromIdentifier(assignment) // 'arrayvar1 = arrayvar2'
return copyArrayValue(assignment)
}
}
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
val scope = assignment.definingScope()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, scope) }
modifications.add(IAstModification.Remove(assignment, scope))
return modifications
}
return noModifications
}
@ -311,113 +291,98 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
return noModifications
}
private fun flattenArrayAssignmentFromArrayLiteral(assign: Assignment): List<Assignment> {
private fun copyArrayValue(assign: Assignment): List<IAstModification> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
val alv = assign.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
}
val targetVar = identifier.targetVarDecl(program)!!
private fun flattenArrayAssignmentFromIdentifier(assign: Assignment): List<Assignment> {
val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program.namespace)!!
if(targetVar.arraysize==null)
errors.err("array has no defined size", assign.position)
if(assign.value !is IdentifierReference) {
errors.err("invalid array value to assign to other array", assign.value.position)
return noModifications
}
val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program.namespace)!!
val sourceVar = sourceIdent.targetVarDecl(program)!!
if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position)
return emptyList()
errors.err("value must be an array", sourceIdent.position)
} else {
if (sourceVar.arraysize!!.constIndex() != targetVar.arraysize!!.constIndex())
errors.err("element count mismatch", assign.position)
if (sourceVar.datatype != targetVar.datatype)
errors.err("element type mismatch", assign.position)
}
val alv = sourceVar.value as? ArrayLiteralValue
return flattenArrayAssign(targetVar, alv, identifier, assign.position)
if(!errors.isEmpty())
return noModifications
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), assign.position),
mutableListOf(
AddressOf(sourceIdent, assign.position),
AddressOf(identifier, assign.position),
NumericLiteralValue.optimalInteger(targetVar.arraysize!!.constIndex()!!, assign.position)
),
true,
assign.position
)
return listOf(IAstModification.ReplaceNode(assign, memcopy, assign.parent))
}
private fun flattenArrayAssign(targetVar: VarDecl, alv: ArrayLiteralValue?, identifier: IdentifierReference, position: Position): List<Assignment> {
if(targetVar.arraysize==null) {
errors.err("array has no defined size", identifier.position)
return emptyList()
}
if(alv==null || alv.value.size != targetVar.arraysize!!.constIndex()) {
errors.err("element count mismatch", position)
return emptyList()
}
// TODO use a pointer loop instead of individual assignments
return alv.value.mapIndexed { index, value ->
val idx = ArrayIndexedExpression(identifier, ArrayIndex(NumericLiteralValue(DataType.UBYTE, index, position), position), position)
Assignment(AssignTarget(null, idx, null, position), value, value.position)
}
}
private fun flattenStructAssignmentFromStructLiteral(structAssignment: Assignment): List<Assignment> {
private fun copyStructValue(structAssignment: Assignment): List<IAstModification> {
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
val slv = structAssignment.value as? ArrayLiteralValue
if(slv==null || slv.value.size != struct.numberOfElements) {
errors.err("element count mismatch", structAssignment.position)
return emptyList()
}
return struct.statements.zip(slv.value).map { (targetDecl, sourceValue) ->
targetDecl as VarDecl
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position),
sourceValue, sourceValue.position)
assign.linkParents(structAssignment)
assign
}
}
private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment): List<Assignment> {
// TODO use memcopy beyond a certain number of elements
val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val targetVar = identifier.targetVarDecl(program)!!
val struct = targetVar.struct!!
when (structAssignment.value) {
is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program)!!
val memsize = struct.memsize(program.memsizer)
when {
sourceVar.struct!=null -> {
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
errors.err("struct type mismatch", structAssignment.position)
return listOf()
}
if(struct.statements.size!=sourceStruct.statements.size)
return listOf() // error will be printed elsewhere
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(idref, null, null, structAssignment.position), sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
if(struct.statements.size!=sourceStruct.statements.size) {
errors.err("struct element count mismatch", structAssignment.position)
return listOf()
}
if(memsize!=sourceStruct.memsize(program.memsizer)) {
errors.err("memory size mismatch", structAssignment.position)
return listOf()
}
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
mutableListOf(
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
AddressOf(identifier, structAssignment.position),
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
),
true,
structAssignment.position
)
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
}
sourceVar.isArray -> {
val array = (sourceVar.value as ArrayLiteralValue).value
if(struct.statements.size!=array.size)
return listOf() // error will be printed elsewhere
return struct.statements.zip(array).map {
val decl = it.first as VarDecl
val mangled = mangledStructMemberName(identifierName, decl.name)
val targetName = IdentifierReference(listOf(mangled), structAssignment.position)
val target = AssignTarget(targetName, null, null, structAssignment.position)
val assign = Assignment(target, it.second, structAssignment.position)
assign.linkParents(structAssignment)
assign
val array = sourceVar.value as ArrayLiteralValue
if(struct.statements.size!=array.value.size) {
errors.err("struct element count mismatch", structAssignment.position)
return listOf()
}
if(memsize!=array.memsize(program.memsizer)) {
errors.err("memory size mismatch", structAssignment.position)
return listOf()
}
val memcopy = FunctionCallStatement(IdentifierReference(listOf("sys", "memcopy"), structAssignment.position),
mutableListOf(
AddressOf(structAssignment.value as IdentifierReference, structAssignment.position),
AddressOf(identifier, structAssignment.position),
NumericLiteralValue.optimalInteger(memsize, structAssignment.position)
),
true,
structAssignment.position
)
return listOf(IAstModification.ReplaceNode(structAssignment, memcopy, structAssignment.parent))
}
else -> {
throw FatalAstException("can only assign arrays or structs to structs")
@ -425,7 +390,7 @@ internal class StatementReorderer(val program: Program, val errors: ErrorReporte
}
}
is ArrayLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
throw IllegalArgumentException("not going to do a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}

View File

@ -1,16 +1,18 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.functions.BuiltinFunctions
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() {
class TypecastsAdder(val program: Program, val errors: IErrorReporter) : AstWalker() {
/*
* Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
* (this includes function call arguments)
@ -106,18 +108,18 @@ class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalke
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope())
return afterFunctionCallArgs(functionCallStatement)
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCall, functionCall.definingScope())
return afterFunctionCallArgs(functionCall)
}
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
private fun afterFunctionCallArgs(call: IFunctionCall): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter's type
val modifications = mutableListOf<IAstModification>()
when(val sub = call.target.targetStatement(scope)) {
when(val sub = call.target.targetStatement(program)) {
is Subroutine -> {
sub.parameters.zip(call.args).forEachIndexed { index, pair ->
val argItype = pair.second.inferType(program)

View File

@ -0,0 +1,73 @@
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.base.Position
import prog8.ast.expressions.DirectMemoryRead
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
internal class VariousCleanups: AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> {
return listOf(IAstModification.Remove(nopStatement, parent as INameScope))
}
override fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
return if(parent is INameScope)
listOf(ScopeFlatten(scope, parent as INameScope))
else
noModifications
}
class ScopeFlatten(val scope: AnonymousScope, val into: INameScope) : IAstModification {
override fun perform() {
val idx = into.statements.indexOf(scope)
if(idx>=0) {
into.statements.addAll(idx+1, scope.statements)
into.statements.remove(scope)
}
}
}
override fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
if(typecast.expression is NumericLiteralValue) {
val value = (typecast.expression as NumericLiteralValue).cast(typecast.type)
if(value.isValid)
return listOf(IAstModification.ReplaceNode(typecast, value.valueOrZero(), parent))
}
return noModifications
}
override fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return before(functionCallStatement as IFunctionCall, parent, functionCallStatement.position)
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return before(functionCall as IFunctionCall, parent, functionCall.position)
}
private fun before(functionCall: IFunctionCall, parent: Node, position: Position): Iterable<IAstModification> {
if(functionCall.target.nameInSource==listOf("peek")) {
// peek(a) is synonymous with @(a)
val memread = DirectMemoryRead(functionCall.args.single(), position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, memread, parent))
}
if(functionCall.target.nameInSource==listOf("poke")) {
// poke(a, v) is synonymous with @(a) = v
val tgt = AssignTarget(null, null, DirectMemoryWrite(functionCall.args[0], position), position)
val assign = Assignment(tgt, functionCall.args[1], position)
return listOf(IAstModification.ReplaceNode(functionCall as Node, assign, parent))
}
return noModifications
}
}

View File

@ -1,26 +1,26 @@
package prog8.ast.processing
package prog8.compiler.astprocessing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.expressions.Expression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.IAstVisitor
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.compiler.functions.BuiltinFunctions
class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
override fun visit(functionCall: FunctionCall) {
val error = checkTypes(functionCall as IFunctionCall, functionCall.definingScope(), program)
val error = checkTypes(functionCall as IFunctionCall, program)
if(error!=null)
throw CompilerException(error)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val error = checkTypes(functionCallStatement as IFunctionCall, functionCallStatement.definingScope(), program)
val error = checkTypes(functionCallStatement as IFunctionCall, program)
if (error!=null)
throw CompilerException(error)
}
@ -39,13 +39,13 @@ class VerifyFunctionArgTypes(val program: Program) : IAstVisitor {
return false
}
fun checkTypes(call: IFunctionCall, scope: INameScope, program: Program): String? {
fun checkTypes(call: IFunctionCall, program: Program): String? {
val argITypes = call.args.map { it.inferType(program) }
val firstUnknownDt = argITypes.indexOfFirst { it.isUnknown }
if(firstUnknownDt>=0)
return "argument ${firstUnknownDt+1} invalid argument type"
val argtypes = argITypes.map { it.typeOrElse(DataType.STRUCT) }
val target = call.target.targetStatement(scope)
val target = call.target.targetStatement(program)
if (target is Subroutine) {
if(call.args.size != target.parameters.size)
return "invalid number of arguments"

View File

@ -1,5 +1,6 @@
package prog8.functions
package prog8.compiler.functions
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
@ -12,7 +13,7 @@ import kotlin.math.*
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer) -> NumericLiteralValue
class ReturnConvention(val dt: DataType, val reg: RegisterOrPair?, val floatFac1: Boolean)
@ -87,6 +88,7 @@ class FSignature(val name: String,
}
}
@Suppress("UNUSED_ANONYMOUS_PARAMETER")
private val functionSignatures: List<FSignature> = listOf(
// this set of function have no return value and operate in-place:
FSignature("rol" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
@ -95,42 +97,48 @@ private val functionSignatures: List<FSignature> = listOf(
FSignature("ror2" , false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
FSignature("sort" , false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("reverse" , false, listOf(FParam("array", ArrayDatatypes)), null),
FSignature("cmp" , false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
// these few have a return value depending on the argument(s):
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("max" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
FSignature("min" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
FSignature("sum" , true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
FSignature("abs" , true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
FSignature("len" , true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
FSignature("sizeof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinSizeof),
FSignature("offsetof" , true, listOf(FParam("object", DataType.values().toSet())), DataType.UBYTE, ::builtinOffsetof),
// normal functions follow:
FSignature("sgn" , true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sin) },
FSignature("sin8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
FSignature("sin8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
FSignature("sin16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
FSignature("sin16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::cos) },
FSignature("cos8" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
FSignature("cos8u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
FSignature("cos16" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
FSignature("cos16u" , true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
FSignature("tan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::tan) },
FSignature("atan" , true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::atan) },
FSignature("ln" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::log) },
FSignature("log2" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, ::log2) },
FSignature("sqrt16" , true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
FSignature("sqrt" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::sqrt) },
FSignature("rad" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toRadians) },
FSignature("deg" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArg(a, p, prg, Math::toDegrees) },
FSignature("round" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
FSignature("floor" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
FSignature("ceil" , true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg, ct -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
FSignature("any" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAny) },
FSignature("all" , true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg, ct -> collectionArg(a, p, prg, ::builtinAll) },
FSignature("lsb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 } },
FSignature("msb" , true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg, ct -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255} },
FSignature("mkword" , true, listOf(FParam("msb", setOf(DataType.UBYTE)), FParam("lsb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
FSignature("peek" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UBYTE),
FSignature("peekw" , true, listOf(FParam("address", setOf(DataType.UWORD))), DataType.UWORD),
FSignature("poke" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UBYTE))), null),
FSignature("pokew" , false, listOf(FParam("address", setOf(DataType.UWORD)), FParam("value", setOf(DataType.UWORD))), null),
FSignature("fastrnd8" , false, emptyList(), DataType.UBYTE),
FSignature("rnd" , false, emptyList(), DataType.UBYTE),
FSignature("rndw" , false, emptyList(), DataType.UWORD),
FSignature("rndf" , false, emptyList(), DataType.FLOAT),
@ -266,7 +274,8 @@ private fun collectionArg(args: List<Expression>, position: Position, program: P
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
}
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinAbs(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = float or int, result type= isSameAs as argument type
if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position)
@ -279,14 +288,14 @@ private fun builtinAbs(args: List<Expression>, position: Position, program: Prog
}
}
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
private fun builtinOffsetof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("offsetof requires one argument", position)
val idref = args[0] as? IdentifierReference
?: throw SyntaxError("offsetof argument should be an identifier", position)
val vardecl = idref.targetVarDecl(program.namespace)!!
val vardecl = idref.targetVarDecl(program)!!
val struct = vardecl.struct
if (struct == null || vardecl.datatype == DataType.STRUCT)
throw SyntaxError("offsetof can only be used on struct members", position)
@ -296,12 +305,12 @@ private fun builtinOffsetof(args: List<Expression>, position: Position, program:
for(member in struct.statements) {
if((member as VarDecl).name == membername)
return NumericLiteralValue(DataType.UBYTE, offset, position)
offset += member.datatype.memorySize()
offset += memsizer.memorySize(member.datatype)
}
throw SyntaxError("undefined struct member", position)
}
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
private fun builtinSizeof(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// 1 arg, type = anything, result type = ubyte
if(args.size!=1)
throw SyntaxError("sizeof requires one argument", position)
@ -310,17 +319,16 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
val dt = args[0].inferType(program)
if(dt.isKnown) {
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
val target = (args[0] as IdentifierReference).targetStatement(program)
?: throw CannotEvaluateException("sizeof", "no target")
fun structSize(target: StructDecl) =
NumericLiteralValue(DataType.UBYTE, target.statements.map { (it as VarDecl).datatype.memorySize() }.sum(), position)
fun structSize(target: StructDecl) = NumericLiteralValue(DataType.UBYTE, target.memsize(memsizer), position)
return when {
dt.typeOrElse(DataType.STRUCT) in ArrayDatatypes -> {
val length = (target as VarDecl).arraysize!!.constIndex() ?: throw CannotEvaluateException("sizeof", "unknown array size")
val elementDt = ArrayElementTypes.getValue(dt.typeOrElse(DataType.STRUCT))
numericLiteral(elementDt.memorySize() * length, position)
numericLiteral(memsizer.memorySize(elementDt) * length, position)
}
dt.istype(DataType.STRUCT) -> {
when (target) {
@ -330,19 +338,20 @@ private fun builtinSizeof(args: List<Expression>, position: Position, program: P
}
}
dt.istype(DataType.STR) -> throw SyntaxError("sizeof str is undefined, did you mean len?", position)
else -> NumericLiteralValue(DataType.UBYTE, dt.typeOrElse(DataType.STRUCT).memorySize(), position)
else -> NumericLiteralValue(DataType.UBYTE, memsizer.memorySize(dt.typeOrElse(DataType.STRUCT)), position)
}
} else {
throw SyntaxError("sizeof invalid argument type", position)
}
}
private fun builtinLen(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinLen(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program)
var arraySize = directMemVar?.arraysize?.constIndex()
if(arraySize != null)
return NumericLiteralValue.optimalInteger(arraySize, position)
@ -350,7 +359,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
val target = (args[0] as IdentifierReference).targetVarDecl(program)
?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) {
@ -361,7 +370,7 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.STR -> {
val refLv = target.value as StringLiteralValue
val refLv = target.value as? StringLiteralValue ?: throw CannotEvaluateException("len", "stringsize unknown")
NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
}
DataType.STRUCT -> throw SyntaxError("cannot use len on struct, did you mean sizeof?", args[0].position)
@ -371,7 +380,8 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
}
private fun builtinMkword(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinMkword(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 2)
throw SyntaxError("mkword requires msb and lsb arguments", position)
val constMsb = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -380,7 +390,8 @@ private fun builtinMkword(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, result, position)
}
private fun builtinSin8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -388,7 +399,8 @@ private fun builtinSin8(args: List<Expression>, position: Position, program: Pro
return NumericLiteralValue(DataType.BYTE, (127.0 * sin(rad)).toInt().toShort(), position)
}
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -396,7 +408,8 @@ private fun builtinSin8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toInt().toShort(), position)
}
private fun builtinCos8(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos8(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -404,7 +417,8 @@ private fun builtinCos8(args: List<Expression>, position: Position, program: Pro
return NumericLiteralValue(DataType.BYTE, (127.0 * cos(rad)).toInt().toShort(), position)
}
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos8u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -412,7 +426,8 @@ private fun builtinCos8u(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toInt().toShort(), position)
}
private fun builtinSin16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -420,7 +435,8 @@ private fun builtinSin16(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.WORD, (32767.0 * sin(rad)).toInt(), position)
}
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSin16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -428,7 +444,8 @@ private fun builtinSin16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * sin(rad)).toInt(), position)
}
private fun builtinCos16(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos16(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -436,7 +453,8 @@ private fun builtinCos16(args: List<Expression>, position: Position, program: Pr
return NumericLiteralValue(DataType.WORD, (32767.0 * cos(rad)).toInt(), position)
}
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinCos16u(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
@ -444,7 +462,8 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
}
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@Suppress("UNUSED_PARAMETER")
private fun builtinSgn(args: List<Expression>, position: Position, program: Program, memsizer: IMemSizer): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()

View File

@ -1,47 +0,0 @@
package prog8.compiler.target
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.nio.file.Path
internal interface CompilationTarget {
val name: String
val machine: IMachineDefinition
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path): IAssemblyGenerator
companion object {
lateinit var instance: CompilationTarget
}
}
internal object C64Target: CompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
AsmGen(program, errors, zp, options, path)
}
internal object Cx16Target: CompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun asmGenerator(program: Program, errors: ErrorReporter, zp: Zeropage, options: CompilationOptions, path: Path) =
AsmGen(program, errors, zp, options, path)
}

View File

@ -0,0 +1,119 @@
package prog8.compiler.target
import prog8.ast.IMemSizer
import prog8.ast.IStringEncoding
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.AssignTarget
import prog8.compiler.CompilationOptions
import prog8.compiler.IErrorReporter
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cx16.CX16MachineDefinition
import java.nio.file.Path
interface ICompilationTarget: IStringEncoding, IMemSizer {
val name: String
val machine: IMachineDefinition
override fun encodeString(str: String, altEncoding: Boolean): List<Short>
override fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
fun isInRegularRAM(target: AssignTarget, program: Program): Boolean {
val memAddr = target.memoryAddress
val arrayIdx = target.arrayindexed
val ident = target.identifier
when {
memAddr != null -> {
return when (memAddr.addressExpression) {
is NumericLiteralValue -> {
machine.isRegularRAMaddress((memAddr.addressExpression as NumericLiteralValue).number.toInt())
}
is IdentifierReference -> {
val decl = (memAddr.addressExpression as IdentifierReference).targetVarDecl(program)
if ((decl?.type == VarDeclType.VAR || decl?.type == VarDeclType.CONST) && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
false
}
else -> false
}
}
arrayIdx != null -> {
val targetStmt = arrayIdx.arrayvar.targetVarDecl(program)
return if (targetStmt?.type == VarDeclType.MEMORY) {
val addr = targetStmt.value as? NumericLiteralValue
if (addr != null)
machine.isRegularRAMaddress(addr.number.toInt())
else
false
} else true
}
ident != null -> {
val decl = ident.targetVarDecl(program)!!
return if (decl.type == VarDeclType.MEMORY && decl.value is NumericLiteralValue)
machine.isRegularRAMaddress((decl.value as NumericLiteralValue).number.toInt())
else
true
}
else -> return true
}
}
}
internal object C64Target: ICompilationTarget {
override val name = "c64"
override val machine = C64MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal object Cx16Target: ICompilationTarget {
override val name = "cx16"
override val machine = CX16MachineDefinition
override fun encodeString(str: String, altEncoding: Boolean) =
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
override fun decodeString(bytes: List<Short>, altEncoding: Boolean) =
if(altEncoding) Petscii.decodeScreencode(bytes, true) else Petscii.decodePetscii(bytes, true)
override fun memorySize(dt: DataType): Int {
return when(dt) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
DataType.FLOAT -> machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
internal fun asmGeneratorFor(
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)
return AsmGen(program, errors, zp, options, compTarget, outputDir)
}

View File

@ -1,22 +1,20 @@
package prog8.compiler.target
import prog8.ast.Program
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.parser.ModuleImporter
internal interface IMachineFloat {
interface IMachineFloat {
fun toDouble(): Double
fun makeFloatFillAsm(): String
}
internal enum class CpuType {
enum class CpuType {
CPU6502,
CPU65c02
}
internal interface IMachineDefinition {
interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
@ -32,7 +30,8 @@ internal interface IMachineDefinition {
fun initializeZeropage(compilerOptions: CompilationOptions)
fun getFloat(num: Number): IMachineFloat
fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program)
fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String>
fun launchEmulator(programName: String)
fun isRegularRAMaddress(address: Int): Boolean
}

View File

@ -2,13 +2,12 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path
import kotlin.system.exitProcess
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
class AssemblyProgram(override val name: String, outputDir: Path, private val compTarget: String) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
private val prgFile = outputDir.resolve("$name.prg")
private val binFile = outputDir.resolve("$name.bin")
@ -23,12 +22,12 @@ class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyPro
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating prg for target ${CompilationTarget.instance.name}.")
println("\nCreating prg for target $compTarget.")
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
println("\nCreating raw binary for target ${CompilationTarget.instance.name}.")
println("\nCreating raw binary for target $compTarget.")
binFile
}
}

View File

@ -1,11 +1,9 @@
package prog8.compiler.target.c64
import prog8.ast.Program
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.IMachineFloat
import prog8.parser.ModuleImporter
import java.io.IOException
import kotlin.math.absoluteValue
import kotlin.math.pow
@ -30,9 +28,11 @@ internal object C64MachineDefinition: IMachineDefinition {
override fun getFloat(num: Number) = Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib")
override fun importLibs(compilerOptions: CompilationOptions,compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(programName: String) {
@ -102,13 +102,14 @@ internal object C64MachineDefinition: IMachineDefinition {
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
// 0x90-0xfa is 'kernel work storage area'
// 0x90-0xfa is 'kernal work storage area'
))
}
if (options.zeropage == ZeropageType.FLOATSAFE) {
// remove the zero page locations used for floating point operations from the free list
free.removeAll(listOf(
0x22, 0x23, 0x24, 0x25,
0x10, 0x11, 0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,

View File

@ -172,7 +172,7 @@ object Petscii {
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
@ -236,7 +236,7 @@ object Petscii {
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
@ -431,7 +431,7 @@ object Petscii {
'\u258c', // ▌ 0xA1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xA2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xA3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0xA4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xA5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xA6 -> MEDIUM SHADE
'\u2595', // ▕ 0xA7 -> RIGHT ONE EIGHTH BLOCK
@ -495,7 +495,7 @@ object Petscii {
'\u258c', // ▌ 0xE1 -> LEFT HALF BLOCK
'\u2584', // ▄ 0xE2 -> LOWER HALF BLOCK
'\u2594', // ▔ 0xE3 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0xE4 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0xE5 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0xE6 -> MEDIUM SHADE
'\u2595', // ▕ 0xE7 -> RIGHT ONE EIGHTH BLOCK
@ -626,7 +626,7 @@ object Petscii {
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK
@ -885,7 +885,7 @@ object Petscii {
'\u258c', // ▌ 0x61 -> LEFT HALF BLOCK
'\u2584', // ▄ 0x62 -> LOWER HALF BLOCK
'\u2594', // ▔ 0x63 -> UPPER ONE EIGHTH BLOCK
'\u2581', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'_', // ▁ 0x64 -> LOWER ONE EIGHTH BLOCK
'\u258f', // ▏ 0x65 -> LEFT ONE EIGHTH BLOCK
'\u2592', // ▒ 0x66 -> MEDIUM SHADE
'\u2595', // ▕ 0x67 -> RIGHT ONE EIGHTH BLOCK

View File

@ -1,28 +1,21 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.FSignature
import prog8.compiler.target.*
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
import prog8.compiler.target.IAssemblyGenerator
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.codegen.assignment.AssignmentAsmGen
import prog8.compiler.target.generatedLabelPrefix
import prog8.functions.BuiltinFunctions
import prog8.functions.FSignature
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.AssignmentAsmGen
import java.io.CharConversionException
import java.nio.file.Path
import java.nio.file.Paths
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
@ -30,9 +23,10 @@ import kotlin.math.absoluteValue
internal class AsmGen(private val program: Program,
val errors: ErrorReporter,
val errors: IErrorReporter,
val zeropage: Zeropage,
val options: CompilationOptions,
private val compTarget: ICompilationTarget,
private val outputDir: Path): IAssemblyGenerator {
// for expressions and augmented assignments:
@ -43,12 +37,12 @@ internal class AsmGen(private val program: Program,
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
private val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
private val breakpointLabels = mutableListOf<String>()
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this)
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this, expressionsAsmGen)
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this, assignmentAsmGen)
internal val loopEndLabels = ArrayDeque<String>()
private val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
internal val slabs = mutableMapOf<String, Int>()
@ -92,15 +86,17 @@ internal class AsmGen(private val program: Program,
}
}
return AssemblyProgram(program.name, outputDir)
return AssemblyProgram(program.name, outputDir, compTarget.name)
}
internal fun isTargetCpu(cpu: CpuType) = compTarget.machine.cpu == cpu
internal fun haveFPWR() = compTarget is Cx16Target
private fun header() {
val ourName = this.javaClass.name
val cpu = when(CompilationTarget.instance.machine.cpu) {
val cpu = when(compTarget.machine.cpu) {
CpuType.CPU6502 -> "6502"
CpuType.CPU65c02 -> "65c02"
CpuType.CPU65c02 -> "w65c02"
else -> "unsupported"
}
@ -113,16 +109,16 @@ internal class AsmGen(private val program: Program,
program.actualLoadAddress = program.definedLoadAddress
if (program.actualLoadAddress == 0) // fix load address
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
CompilationTarget.instance.machine.BASIC_LOAD_ADDRESS else CompilationTarget.instance.machine.RAW_LOAD_ADDRESS
compTarget.machine.BASIC_LOAD_ADDRESS else compTarget.machine.RAW_LOAD_ADDRESS
// the global prog8 variables needed
val zp = CompilationTarget.instance.machine.zeropage
val zp = compTarget.machine.zeropage
out("P8ZP_SCRATCH_B1 = ${zp.SCRATCH_B1}")
out("P8ZP_SCRATCH_REG = ${zp.SCRATCH_REG}")
out("P8ZP_SCRATCH_W1 = ${zp.SCRATCH_W1} ; word")
out("P8ZP_SCRATCH_W2 = ${zp.SCRATCH_W2} ; word")
out("P8ESTACK_LO = ${CompilationTarget.instance.machine.ESTACK_LO.toHex()}")
out("P8ESTACK_HI = ${CompilationTarget.instance.machine.ESTACK_HI.toHex()}")
out("P8ESTACK_LO = ${compTarget.machine.ESTACK_LO.toHex()}")
out("P8ESTACK_HI = ${compTarget.machine.ESTACK_HI.toHex()}")
when {
options.launcher == LauncherType.BASIC -> {
@ -136,13 +132,15 @@ internal class AsmGen(private val program: Program,
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
if(!options.noSysInit)
out(" jsr ${CompilationTarget.instance.name}.init_system")
out(" jsr ${compTarget.name}.init_system")
out(" jsr ${compTarget.name}.init_system_phase2")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
if(!options.noSysInit)
out(" jsr ${CompilationTarget.instance.name}.init_system")
out(" jsr ${compTarget.name}.init_system")
out(" jsr ${compTarget.name}.init_system_phase2")
}
options.output == OutputType.RAW -> {
out("; ---- raw assembler program ----")
@ -159,7 +157,7 @@ internal class AsmGen(private val program: Program,
pha""")
}
out(" jmp main.start ; start program / force start proc to be included")
jmp("main.start")
}
private fun slaballocations() {
@ -174,7 +172,7 @@ internal class AsmGen(private val program: Program,
// the global list of all floating point constants for the whole program
out("; global float constants")
for (flt in globalFloatConsts) {
val floatFill = CompilationTarget.instance.machine.getFloat(flt.key).makeFloatFillAsm()
val floatFill = compTarget.machine.getFloat(flt.key).makeFloatFillAsm()
val floatvalue = flt.key
out("${flt.value}\t.byte $floatFill ; float $floatvalue")
}
@ -183,13 +181,17 @@ internal class AsmGen(private val program: Program,
private fun block2asm(block: Block) {
out("\n\n; ---- block: '${block.name}' ----")
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
if(block.address!=null) {
out(".cerror * > ${block.address.toHex()}, 'block address overlaps by ', *-${block.address.toHex()},' bytes'")
out("* = ${block.address.toHex()}")
if(block.address!=null)
out("* = ${block.address!!.toHex()}")
else {
if("align_word" in block.options())
out("\t.align 2")
else if("align_page" in block.options())
out("\t.align $100")
}
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
outputSourceLine(block)
zeropagevars2asm(block.statements)
memdefs2asm(block.statements)
@ -268,7 +270,7 @@ internal class AsmGen(private val program: Program,
try {
val errors = ErrorReporter()
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
errors.handle()
errors.report()
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
// make sure we add the var to the set of zpvars for this block
allocatedZeropageVariables[fullName] = address to variable.datatype
@ -344,7 +346,7 @@ internal class AsmGen(private val program: Program,
}
val floatFills = array.map {
val number = (it as NumericLiteralValue).number
CompilationTarget.instance.machine.getFloat(number).makeFloatFillAsm()
compTarget.machine.getFloat(number).makeFloatFillAsm()
}
out(name)
for (f in array.zip(floatFills))
@ -354,7 +356,7 @@ internal class AsmGen(private val program: Program,
}
private fun memdefs2asm(statements: List<Statement>) {
out("\n; memdefs and kernel subroutines")
out("\n; memdefs and kernal subroutines")
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
for(m in memvars) {
if(m.value is NumericLiteralValue)
@ -364,10 +366,11 @@ internal class AsmGen(private val program: Program,
}
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
for(sub in asmSubs) {
if(sub.asmAddress!=null) {
val addr = sub.asmAddress
if(addr!=null) {
if(sub.statements.isNotEmpty())
throw AssemblyError("kernel subroutine cannot have statements")
out(" ${sub.name} = ${sub.asmAddress.toHex()}")
throw AssemblyError("kernal subroutine cannot have statements")
out(" ${sub.name} = ${addr.toHex()}")
}
}
}
@ -404,8 +407,9 @@ internal class AsmGen(private val program: Program,
}
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
val string = (lastvar.value as StringLiteralValue).value
out("${lastvar.name}\t; ${lastvar.datatype} \"${escape(string).replace("\u0000", "<NULL>")}\"")
val sv = lastvar.value as StringLiteralValue
val altEncoding = if(sv.altEncoding) "@" else ""
out("${lastvar.name}\t; ${lastvar.datatype} $altEncoding\"${escape(sv.value).replace("\u0000", "<NULL>")}\"")
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
for (chunk in outputBytes.chunked(16))
out(" .byte " + chunk.joinToString())
@ -433,10 +437,10 @@ internal class AsmGen(private val program: Program,
"$" + it.number.toInt().toString(16).padStart(4, '0')
}
is AddressOf -> {
it.identifier.firstStructVarName(program.namespace) ?: asmSymbolName(it.identifier)
it.identifier.firstStructVarName(program) ?: asmSymbolName(it.identifier)
}
is IdentifierReference -> {
it.firstStructVarName(program.namespace) ?: asmSymbolName(it)
it.firstStructVarName(program) ?: asmSymbolName(it)
}
else -> throw AssemblyError("weird array elt dt")
}
@ -498,17 +502,23 @@ internal class AsmGen(private val program: Program,
}
internal fun asmSymbolName(identifier: IdentifierReference): String {
return if(identifier.memberOfStruct(program.namespace)!=null) {
val name = identifier.targetVarDecl(program.namespace)!!.name
return if(identifier.memberOfStruct(program)!=null) {
val name = identifier.targetVarDecl(program)!!.name
fixNameSymbols(name)
} else {
fixNameSymbols(identifier.nameInSource.joinToString("."))
}
}
internal fun asmSymbolName(regs: RegisterOrPair): String =
if(regs in Cx16VirtualRegisters)
"cx16." + regs.toString().toLowerCase()
else
throw AssemblyError("no symbol name for register $regs")
internal fun asmVariableName(identifier: IdentifierReference): String {
return if(identifier.memberOfStruct(program.namespace)!=null) {
val name = identifier.targetVarDecl(program.namespace)!!.name
return if(identifier.memberOfStruct(program)!=null) {
val name = identifier.targetVarDecl(program)!!.name
fixNameSymbols(name)
} else {
fixNameSymbols(identifier.nameInSource.joinToString("."))
@ -524,9 +534,9 @@ internal class AsmGen(private val program: Program,
internal fun loadByteFromPointerIntoA(pointervar: IdentifierReference): Pair<Boolean, String> {
// returns if the pointer is already on the ZP itself or not (in the latter case SCRATCH_W1 is used as intermediary)
val sourceName = asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program.namespace)!!
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
if (isTargetCpu(CpuType.CPU65c02)) {
return if (isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
out(" lda ($sourceName)")
@ -561,7 +571,7 @@ internal class AsmGen(private val program: Program,
private fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
internal fun saveRegisterLocal(register: CpuRegister, scope: Subroutine) {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
if (isTargetCpu(CpuType.CPU65c02)) {
// just use the cpu's stack for all registers, shorter code
when (register) {
CpuRegister.A -> out(" pha")
@ -590,7 +600,7 @@ internal class AsmGen(private val program: Program,
when (register) {
CpuRegister.A -> out(" pha")
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phx")
if (isTargetCpu(CpuType.CPU65c02)) out(" phx")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | txa | pha | lda P8ZP_SCRATCH_REG")
@ -599,7 +609,7 @@ internal class AsmGen(private val program: Program,
}
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" phy")
if (isTargetCpu(CpuType.CPU65c02)) out(" phy")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | tya | pha | lda P8ZP_SCRATCH_REG")
@ -611,7 +621,7 @@ internal class AsmGen(private val program: Program,
}
internal fun restoreRegisterLocal(register: CpuRegister) {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
if (isTargetCpu(CpuType.CPU65c02)) {
when (register) {
// this just used the stack, for all registers. Shorter code.
CpuRegister.A -> out(" pla")
@ -636,7 +646,7 @@ internal class AsmGen(private val program: Program,
out(" pla")
}
CpuRegister.X -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" plx")
if (isTargetCpu(CpuType.CPU65c02)) out(" plx")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | pla | tax | lda P8ZP_SCRATCH_REG")
@ -645,7 +655,7 @@ internal class AsmGen(private val program: Program,
}
}
CpuRegister.Y -> {
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) out(" ply")
if (isTargetCpu(CpuType.CPU65c02)) out(" ply")
else {
if(keepA)
out(" sta P8ZP_SCRATCH_REG | pla | tay | lda P8ZP_SCRATCH_REG")
@ -685,7 +695,7 @@ internal class AsmGen(private val program: Program,
is Break -> {
if(loopEndLabels.isEmpty())
throw AssemblyError("break statement out of context ${stmt.position}")
out(" jmp ${loopEndLabels.peek()}")
jmp(loopEndLabels.peek())
}
is WhileLoop -> translate(stmt)
is RepeatLoop -> translate(stmt)
@ -705,12 +715,15 @@ internal class AsmGen(private val program: Program,
val reg = register.toString().toLowerCase()
val indexnum = expr.indexer.constIndex()
if(indexnum!=null) {
val indexValue = indexnum * elementDt.memorySize() + if(addOneExtra) 1 else 0
val indexValue = indexnum * compTarget.memorySize(elementDt) + if(addOneExtra) 1 else 0
out(" ld$reg #$indexValue")
return
}
val indexName = asmVariableName(expr.indexer.indexVar!!)
val indexVar = expr.indexer.indexExpr as? IdentifierReference
?: throw AssemblyError("array indexer should have been replaced with a temp var @ ${expr.indexer.position}")
val indexName = asmVariableName(indexVar)
if(addOneExtra) {
// add 1 to the result
when(elementDt) {
@ -731,7 +744,7 @@ internal class AsmGen(private val program: Program,
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
require(compTarget.memorySize(DataType.FLOAT)==5)
out("""
lda $indexName
asl a
@ -758,7 +771,7 @@ internal class AsmGen(private val program: Program,
}
}
DataType.FLOAT -> {
require(DataType.FLOAT.memorySize()==5)
require(compTarget.memorySize(DataType.FLOAT)==5)
out("""
lda $indexName
asl a
@ -782,8 +795,8 @@ internal class AsmGen(private val program: Program,
internal fun translateExpression(indexer: ArrayIndex) =
expressionsAsmGen.translateExpression(indexer)
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack)
internal fun translateBuiltinFunctionCallExpression(functionCall: FunctionCall, signature: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature, resultToStack, resultRegister)
internal fun translateFunctionCall(functionCall: FunctionCall) =
functioncallAsmGen.translateFunctionCall(functionCall)
@ -920,7 +933,7 @@ internal class AsmGen(private val program: Program,
val endLabel = makeLabel("if_end")
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, elseLabel)
translate(stmt.truepart)
out(" jmp $endLabel")
jmp(endLabel)
out(elseLabel)
translate(stmt.elsepart)
out(endLabel)
@ -942,7 +955,7 @@ internal class AsmGen(private val program: Program,
// endless loop
out(repeatLabel)
translate(stmt.body)
out(" jmp $repeatLabel")
jmp(repeatLabel)
out(endLabel)
}
is NumericLiteralValue -> {
@ -962,15 +975,15 @@ internal class AsmGen(private val program: Program,
}
}
is IdentifierReference -> {
val vardecl = (stmt.iterations as IdentifierReference).targetStatement(program.namespace) as VarDecl
val vardecl = (stmt.iterations as IdentifierReference).targetStatement(program) as VarDecl
val name = asmVariableName(stmt.iterations as IdentifierReference)
when(vardecl.datatype) {
DataType.UBYTE, DataType.BYTE -> {
out(" lda $name")
assignVariableToRegister(name, RegisterOrPair.A)
repeatByteCountInA(null, repeatLabel, endLabel, stmt.body)
}
DataType.UWORD, DataType.WORD -> {
out(" lda $name | ldy $name+1")
assignVariableToRegister(name, RegisterOrPair.AY)
repeatWordCountInAY(null, repeatLabel, endLabel, stmt.body)
}
else -> throw AssemblyError("invalid loop variable datatype $vardecl")
@ -998,9 +1011,11 @@ internal class AsmGen(private val program: Program,
}
private fun repeatWordCountInAY(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
// note: A/Y must have been loaded with the number of iterations!
if(constIterations==0)
return
// note: A/Y must have been loaded with the number of iterations already!
// no need to explicitly test for 0 iterations as this is done in the count down logic below
val counterVar = makeLabel("repeatcounter")
out("""
sta $counterVar
@ -1009,48 +1024,48 @@ $repeatLabel lda $counterVar
bne +
lda $counterVar+1
beq $endLabel
+ lda $counterVar
lda $counterVar
bne +
dec $counterVar+1
+ dec $counterVar
""")
translate(body)
out(" jmp $repeatLabel")
if(constIterations!=null && constIterations>=16 && zeropage.available() > 1) {
// allocate count var on ZP
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, body.position, errors)
out("""$counterVar = $zpAddr ; auto zp UWORD""")
} else {
out("""
$counterVar .word 0""")
}
out(endLabel)
jmp(repeatLabel)
if(constIterations!=null && constIterations>=16 && zeropage.available() > 1) {
// allocate count var on ZP TODO can be shared with countervars from other subroutines
val zpAddr = zeropage.allocate(counterVar, DataType.UWORD, body.position, errors)
out("$counterVar = $zpAddr ; auto zp UWORD")
} else {
out("$counterVar .word 0")
}
out(endLabel)
}
private fun repeatByteCountInA(constIterations: Int?, repeatLabel: String, endLabel: String, body: AnonymousScope) {
// note: A must have been loaded with the number of iterations!
if(constIterations==0)
return
// note: A must have been loaded with the number of iterations already!
val counterVar = makeLabel("repeatcounter")
if(constIterations==null)
out(" beq $endLabel")
out("""
sta $counterVar
$repeatLabel""")
out(" beq $endLabel ; skip loop if zero iters")
val counterVar = makeLabel("repeatcounter")
out(" sta $counterVar")
out(repeatLabel)
translate(body)
out("""
dec $counterVar
bne $repeatLabel
beq $endLabel""")
if(constIterations!=null && constIterations>=16 && zeropage.available() > 0) {
// allocate count var on ZP
// allocate count var on ZP TODO can be shared with countervars from other subroutines
val zpAddr = zeropage.allocate(counterVar, DataType.UBYTE, body.position, errors)
out("""$counterVar = $zpAddr ; auto zp UBYTE""")
out("$counterVar = $zpAddr ; auto zp UBYTE")
} else {
out("""
$counterVar .byte 0""")
out("$counterVar .byte 0")
}
out(endLabel)
}
@ -1063,7 +1078,7 @@ $counterVar .byte 0""")
out(whileLabel)
expressionsAsmGen.translateComparisonExpressionWithJumpIfFalse(booleanCondition, endLabel)
translate(stmt.body)
out(" jmp $whileLabel")
jmp(whileLabel)
out(endLabel)
loopEndLabels.pop()
}
@ -1097,7 +1112,7 @@ $counterVar .byte 0""")
if(choice.values==null) {
// the else choice
translate(choice.statements)
out(" jmp $endLabel")
jmp(endLabel)
} else {
choiceBlocks.add(choiceLabel to choice.statements)
for (cv in choice.values!!) {
@ -1116,11 +1131,11 @@ $counterVar .byte 0""")
}
}
}
out(" jmp $endLabel")
jmp(endLabel)
for(choiceBlock in choiceBlocks) {
out(choiceBlock.first)
translate(choiceBlock.second)
out(" jmp $endLabel")
jmp(endLabel)
}
out(endLabel)
}
@ -1179,7 +1194,7 @@ $counterVar .byte 0""")
val endLabel = makeLabel("branch_end")
out(" $instruction $elseLabel")
translate(stmt.truepart)
out(" jmp $endLabel")
jmp(endLabel)
out(elseLabel)
translate(stmt.elsepart)
out(endLabel)
@ -1222,7 +1237,9 @@ $counterVar .byte 0""")
"%asmbinary" -> {
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
out(" .binary \"${stmt.args[0].str}\" $offset $length")
val includedSourcePath = stmt.definingModule().source.resolveSibling(stmt.args[0].str)
val relPath = Paths.get("").relativize(includedSourcePath)
out(" .binary \"./$relPath\" $offset $length")
}
"%breakpoint" -> {
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
@ -1234,22 +1251,23 @@ $label nop""")
}
}
private fun translate(jmp: Jump) {
out(" jmp ${getJumpTarget(jmp)}")
}
private fun translate(jump: Jump) = jmp(getJumpTarget(jump))
private fun getJumpTarget(jmp: Jump): String {
private fun getJumpTarget(jump: Jump): String {
val ident = jump.identifier
val label = jump.generatedLabel
val addr = jump.address
return when {
jmp.identifier!=null -> {
val target = jmp.identifier.targetStatement(program.namespace)
val asmName = asmSymbolName(jmp.identifier)
ident!=null -> {
val target = ident.targetStatement(program)
val asmName = asmSymbolName(ident)
if(target is Label)
"_$asmName" // prefix with underscore to jump to local label
else
asmName
}
jmp.generatedLabel!=null -> jmp.generatedLabel
jmp.address!=null -> jmp.address.toHex()
label!=null -> label
addr!=null -> addr.toHex()
else -> "????"
}
}
@ -1264,12 +1282,12 @@ $label nop""")
when (returnType) {
in NumericDatatypes -> {
assignExpressionToRegister(returnvalue, returnReg.registerOrPair)
assignExpressionToRegister(returnvalue, returnReg.registerOrPair!!)
}
else -> {
// all else take its address and assign that also to AY register pair
val addrofValue = AddressOf(returnvalue as IdentifierReference, returnvalue.position)
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair)
assignmentAsmGen.assignExpressionToRegister(addrofValue, returnReg.registerOrPair!!)
}
}
}
@ -1294,7 +1312,7 @@ $label nop""")
// sign extend signed byte on stack to signed word on stack
when(valueDt) {
DataType.UBYTE -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(isTargetCpu(CpuType.CPU65c02))
out(" stz P8ESTACK_HI+1,x")
else
out(" lda #0 | sta P8ESTACK_HI+1,x")
@ -1308,7 +1326,7 @@ $label nop""")
// sign extend signed byte in a var to a full word in that variable
when(valueDt) {
DataType.UBYTE -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(isTargetCpu(CpuType.CPU65c02))
out(" stz $asmvar+1")
else
out(" lda #0 | sta $asmvar+1")
@ -1328,10 +1346,17 @@ $label nop""")
internal fun isZpVar(scopedName: String): Boolean = scopedName in allocatedZeropageVariables
internal fun isZpVar(variable: IdentifierReference): Boolean {
val vardecl = variable.targetVarDecl(program.namespace)!!
val vardecl = variable.targetVarDecl(program)!!
return vardecl.makeScopedName(vardecl.name) in allocatedZeropageVariables
}
internal fun jmp(asmLabel: String) {
if(isTargetCpu(CpuType.CPU65c02))
out(" bra $asmLabel") // note: 64tass will convert this automatically to a jmp if the relative distance is too large
else
out(" jmp $asmLabel")
}
internal fun pointerViaIndexRegisterPossible(pointerOffsetExpr: Expression): Pair<Expression, Expression>? {
if(pointerOffsetExpr is BinaryExpression && pointerOffsetExpr.operator=="+") {
val leftDt = pointerOffsetExpr.left.inferType(program)
@ -1361,39 +1386,44 @@ $label nop""")
internal fun tryOptimizedPointerAccessWithA(expr: BinaryExpression, write: Boolean): Boolean {
// optimize pointer,indexregister if possible
fun evalBytevalueWillClobberA(expr: Expression): Boolean {
val dt = expr.inferType(program)
if(!dt.istype(DataType.UBYTE) && !dt.istype(DataType.BYTE))
return true
return when(expr) {
is IdentifierReference -> false
is NumericLiteralValue -> false
is DirectMemoryRead -> expr.addressExpression !is IdentifierReference && expr.addressExpression !is NumericLiteralValue
is TypecastExpression -> evalBytevalueWillClobberA(expr.expression)
else -> true
}
}
if(expr.operator=="+") {
val ptrAndIndex = pointerViaIndexRegisterPossible(expr)
if(ptrAndIndex!=null) {
val pointervar = ptrAndIndex.first as? IdentifierReference
if(write) {
when(ptrAndIndex.second) {
is NumericLiteralValue, is IdentifierReference -> {
if(pointervar!=null && isZpVar(pointervar)) {
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" sta (P8ZP_SCRATCH_W2),y")
}
}
else -> {
// same as above but we need to save the A register
if(pointervar!=null && isZpVar(pointervar)) {
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" pla")
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
}
}
if(pointervar!=null && isZpVar(pointervar)) {
val saveA = evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (${asmSymbolName(pointervar)}),y")
} else {
// copy the pointer var to zp first
val saveA = evalBytevalueWillClobberA(ptrAndIndex.first) || evalBytevalueWillClobberA(ptrAndIndex.second)
if(saveA)
out(" pha")
assignExpressionToVariable(ptrAndIndex.first, asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
assignExpressionToRegister(ptrAndIndex.second, RegisterOrPair.Y)
if(saveA)
out(" pla")
out(" sta (P8ZP_SCRATCH_W2),y")
}
} else {
if(pointervar!=null && isZpVar(pointervar)) {
@ -1411,5 +1441,4 @@ $label nop""")
}
return false
}
}

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations

View File

@ -1,32 +1,32 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.assignment.AsmAssignSource
import prog8.compiler.target.c64.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.c64.codegen.assignment.AsmAssignment
import prog8.compiler.target.c64.codegen.assignment.SourceStorageKind
import prog8.compiler.target.c64.codegen.assignment.TargetStorageKind
import prog8.compiler.functions.FSignature
import prog8.compiler.target.CpuType
import prog8.compiler.target.cpu6502.codegen.assignment.*
import prog8.compiler.target.subroutineFloatEvalResultVar2
import prog8.compiler.toHex
import prog8.functions.FSignature
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen, private val assignAsmGen: AssignmentAsmGen) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature, resultToStack: Boolean) {
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack)
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
translateFunctioncall(fcall, func, discardResult = false, resultToStack = resultToStack, resultRegister = resultRegister)
}
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false)
translateFunctioncall(fcall, func, discardResult = true, resultToStack = false, resultRegister = null)
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean) {
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if (discardResult && func.pure)
return // can just ignore the whole function call altogether
@ -34,41 +34,115 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("cannot both discard the result AND put it onto stack")
val sscope = (fcall as Node).definingSubroutine()
when (func.name) {
"msb" -> funcMsb(fcall, resultToStack)
"lsb" -> funcLsb(fcall, resultToStack)
"mkword" -> funcMkword(fcall, resultToStack)
"abs" -> funcAbs(fcall, func, resultToStack, sscope)
"msb" -> funcMsb(fcall, resultToStack, resultRegister)
"lsb" -> funcLsb(fcall, resultToStack, resultRegister)
"mkword" -> funcMkword(fcall, resultToStack, resultRegister)
"abs" -> funcAbs(fcall, func, resultToStack, resultRegister, sscope)
"swap" -> funcSwap(fcall)
"min", "max" -> funcMinMax(fcall, func, resultToStack)
"sum" -> funcSum(fcall, resultToStack)
"any", "all" -> funcAnyAll(fcall, func, resultToStack)
"min", "max" -> funcMinMax(fcall, func, resultToStack, resultRegister, sscope)
"sum" -> funcSum(fcall, resultToStack, resultRegister, sscope)
"any", "all" -> funcAnyAll(fcall, func, resultToStack, resultRegister, sscope)
"sin8", "sin8u", "sin16", "sin16u",
"cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, sscope)
"cos8", "cos8u", "cos16", "cos16u" -> funcSinCosInt(fcall, func, resultToStack, resultRegister, sscope)
"sgn" -> funcSgn(fcall, func, resultToStack, resultRegister, sscope)
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, sscope)
"rnd", "rndw" -> funcRnd(func, resultToStack)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, sscope)
"rndf" -> funcVariousFloatFuncs(fcall, func, resultToStack, resultRegister, sscope)
"fastrnd8", "rnd", "rndw" -> funcRnd(func, resultToStack, resultRegister, sscope)
"sqrt16" -> funcSqrt16(fcall, func, resultToStack, resultRegister, sscope)
"rol" -> funcRol(fcall)
"rol2" -> funcRol2(fcall)
"ror" -> funcRor(fcall)
"ror2" -> funcRor2(fcall)
"sort" -> funcSort(fcall)
"reverse" -> funcReverse(fcall)
"memory" -> funcMemory(fcall, discardResult, resultToStack)
else -> TODO("missing asmgen for builtin func ${func.name}")
"memory" -> funcMemory(fcall, discardResult, resultToStack, resultRegister)
"peekw" -> funcPeekW(fcall, resultToStack, resultRegister)
"peek" -> throw AssemblyError("peek() should have been replaced by @()")
"pokew" -> funcPokeW(fcall)
"poke" -> throw AssemblyError("poke() should have been replaced by @()")
"cmp" -> funcCmp(fcall)
else -> throw AssemblyError("missing asmgen for builtin func ${func.name}")
}
}
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean) {
private fun funcCmp(fcall: IFunctionCall) {
val arg1 = fcall.args[0]
val arg2 = fcall.args[1]
val dt1 = arg1.inferType(program).typeOrElse(DataType.STRUCT)
val dt2 = arg2.inferType(program).typeOrElse(DataType.STRUCT)
if(dt1 in ByteDatatypes) {
if(dt2 in ByteDatatypes) {
when (arg2) {
is IdentifierReference -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${asmgen.asmVariableName(arg2)}")
}
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp #${arg2.number}")
}
is DirectMemoryRead -> {
if(arg2.addressExpression is NumericLiteralValue) {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp ${arg2.addressExpression.constValue(program)!!.number.toHex()}")
} else {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine())
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
}
}
else -> {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_B1", DataType.UBYTE, (fcall as Node).definingSubroutine())
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.A)
asmgen.out(" cmp P8ZP_SCRATCH_B1")
}
}
} else
throw AssemblyError("args for cmp() should have same dt")
} else {
// dt1 is a word
if(dt2 in WordDatatypes) {
when (arg2) {
is IdentifierReference -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy ${asmgen.asmVariableName(arg2)}+1
bne +
cmp ${asmgen.asmVariableName(arg2)}
+""")
}
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy #>${arg2.number}
bne +
cmp #<${arg2.number}
+""")
}
else -> {
asmgen.assignExpressionToVariable(arg2, "P8ZP_SCRATCH_W1", DataType.UWORD, (fcall as Node).definingSubroutine())
asmgen.assignExpressionToRegister(arg1, RegisterOrPair.AY)
asmgen.out("""
cpy P8ZP_SCRATCH_W1+1
bne +
cmp P8ZP_SCRATCH_W1
+""")
}
}
} else
throw AssemblyError("args for cmp() should have same dt")
}
}
private fun funcMemory(fcall: IFunctionCall, discardResult: Boolean, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(discardResult || fcall !is FunctionCall)
throw AssemblyError("should not discard result of memory allocation at $fcall")
val scope = fcall.definingScope()
val nameRef = fcall.args[0] as IdentifierReference
val name = (nameRef.targetVarDecl(program.namespace)!!.value as StringLiteralValue).value
val name = (nameRef.targetVarDecl(program)!!.value as StringLiteralValue).value
val size = (fcall.args[1] as NumericLiteralValue).number.toInt()
val existingSize = asmgen.slabs[name]
@ -82,39 +156,43 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if(resultToStack)
AsmAssignTarget(TargetStorageKind.STACK, program, asmgen, DataType.UWORD, null)
else
AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen)
val assign = AsmAssignment(src, target, false, fcall.position)
AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, null, program, asmgen)
val assign = AsmAssignment(src, target, false, program.memsizer, fcall.position)
asmgen.translateNormalAssignment(assign)
// remove the variable for the name, it's not used as a variable only as a tag for the assembler.
val nameDecl = scope.statements.single { it is VarDecl && it.name==nameRef.nameInSource.single() }
asmgen.removals.add(Pair(nameDecl, scope))
asmgen.slabs[name] = size
}
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
private fun funcSqrt16(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_sqrt16_stack")
else
else {
asmgen.out(" jsr prog8_lib.func_sqrt16_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
}
private fun funcSinCosInt(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
private fun funcSinCosInt(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_${func.name}_stack")
else
when(func.name) {
"sin8", "sin8u", "cos8", "cos8u" -> asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
"sin16", "sin16u", "cos16", "cos16u" -> asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
"sin8", "sin8u", "cos8", "cos8u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
"sin16", "sin16u", "cos16", "cos16u" -> {
asmgen.out(" jsr prog8_lib.func_${func.name}_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
}
}
private fun funcReverse(fcall: IFunctionCall) {
val variable = fcall.args.single()
if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!!
val decl = variable.targetVarDecl(program)!!
val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.constIndex()
when (decl.datatype) {
@ -153,7 +231,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun funcSort(fcall: IFunctionCall) {
val variable = fcall.args.single()
if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!!
val decl = variable.targetVarDecl(program)!!
val varName = asmgen.asmVariableName(variable)
val numElements = decl.arraysize!!.constIndex()
when (decl.datatype) {
@ -386,19 +464,20 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun translateRolRorArrayArgs(arrayvar: IdentifierReference, indexer: ArrayIndex, operation: String, dt: Char) {
asmgen.assignExpressionToVariable(AddressOf(arrayvar, arrayvar.position), "prog8_lib.${operation}_array_u${dt}._arg_target", DataType.UWORD, null)
val indexerExpr = if(indexer.indexVar!=null) indexer.indexVar!! else indexer.indexNum!!
asmgen.assignExpressionToVariable(indexerExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
asmgen.assignExpressionToVariable(indexer.indexExpr, "prog8_lib.${operation}_array_u${dt}._arg_index", DataType.UBYTE, null)
}
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
private fun funcVariousFloatFuncs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
if(resultToStack)
asmgen.out(" jsr floats.func_${func.name}_stack")
else
else {
asmgen.out(" jsr floats.func_${func.name}_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
}
}
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
private fun funcSgn(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
@ -419,10 +498,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.FLOAT -> asmgen.out(" jsr floats.func_sign_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
}
private fun funcAnyAll(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean) {
private fun funcAnyAll(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
@ -439,10 +519,11 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_into_A")
else -> throw AssemblyError("weird type $dt")
}
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
}
private fun funcMinMax(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean) {
private fun funcMinMax(fcall: IFunctionCall, function: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
@ -456,17 +537,32 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
} else {
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_${function.name}_f_fac1")
DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_ub_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_${function.name}_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_${function.name}_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt")
}
}
}
private fun funcSum(fcall: IFunctionCall, resultToStack: Boolean) {
private fun funcSum(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
outputAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
if(resultToStack) {
@ -480,11 +576,26 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
} else {
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
DataType.ARRAY_F -> asmgen.out(" jsr floats.func_sum_f_fac1")
DataType.ARRAY_UB, DataType.STR -> {
asmgen.out(" jsr prog8_lib.func_sum_ub_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_B -> {
asmgen.out(" jsr prog8_lib.func_sum_b_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_UW -> {
asmgen.out(" jsr prog8_lib.func_sum_uw_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_W -> {
asmgen.out(" jsr prog8_lib.func_sum_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.ARRAY_F -> {
asmgen.out(" jsr floats.func_sum_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
}
else -> throw AssemblyError("weird type $dt")
}
}
@ -568,10 +679,10 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("unknown dt")
val elementDt = elementIDt.typeOrElse(DataType.STRUCT)
val firstNum = first.indexer.indexNum
val firstVar = first.indexer.indexVar
val secondNum = second.indexer.indexNum
val secondVar = second.indexer.indexVar
val firstNum = first.indexer.indexExpr as? NumericLiteralValue
val firstVar = first.indexer.indexExpr as? IdentifierReference
val secondNum = second.indexer.indexExpr as? NumericLiteralValue
val secondVar = second.indexer.indexExpr as? IdentifierReference
if(firstNum!=null && secondNum!=null) {
swapArrayValues(elementDt, arrayVarName1, firstNum, arrayVarName2, secondNum)
@ -607,12 +718,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W2"),
targetFromExpr(first, datatype),
false, first.position
false, program.memsizer, first.position
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, datatype, variableAsmName = "P8ZP_SCRATCH_W1"),
targetFromExpr(second, datatype),
false, second.position
false, program.memsizer, second.position
)
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
@ -624,12 +735,12 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
val assignFirst = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
targetFromExpr(first, datatype),
false, first.position
false, program.memsizer, first.position
)
val assignSecond = AsmAssignment(
AsmAssignSource(SourceStorageKind.STACK, program, asmgen, DataType.FLOAT),
targetFromExpr(second, datatype),
false, second.position
false, program.memsizer, second.position
)
asmgen.translateNormalAssignment(assignFirst)
asmgen.translateNormalAssignment(assignSecond)
@ -639,8 +750,8 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val index1 = indexValue1.number.toInt() * elementDt.memorySize()
val index2 = indexValue2.number.toInt() * elementDt.memorySize()
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
@ -753,7 +864,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexValue1: NumericLiteralValue, arrayVarName2: String, indexName2: IdentifierReference) {
val index1 = indexValue1.number.toInt() * elementDt.memorySize()
val index1 = indexValue1.number.toInt() * program.memsizer.memorySize(elementDt)
val idxAsmName2 = asmgen.asmVariableName(indexName2)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
@ -812,7 +923,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
private fun swapArrayValues(elementDt: DataType, arrayVarName1: String, indexName1: IdentifierReference, arrayVarName2: String, indexValue2: NumericLiteralValue) {
val idxAsmName1 = asmgen.asmVariableName(indexName1)
val index2 = indexValue2.number.toInt() * elementDt.memorySize()
val index2 = indexValue2.number.toInt() * program.memsizer.memorySize(elementDt)
when(elementDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out("""
@ -868,7 +979,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, scope: Subroutine?) {
private fun funcAbs(fcall: IFunctionCall, func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
translateArguments(fcall.args, func, scope)
val dt = fcall.args.single().inferType(program).typeOrElse(DataType.STRUCT)
if(resultToStack) {
@ -880,40 +991,229 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
} else {
when (dt) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b_into_A")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w_into_AY")
DataType.FLOAT -> asmgen.out(" jsr floats.abs_f_fac1")
in ByteDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_b_into_A")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
in WordDatatypes -> {
asmgen.out(" jsr prog8_lib.abs_w_into_AY")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
DataType.FLOAT -> {
asmgen.out(" jsr floats.abs_f_fac1")
assignAsmGen.assignFAC1float(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.FAC1, scope, program, asmgen))
}
else -> throw AssemblyError("weird type")
}
}
}
private fun funcRnd(func: FSignature, resultToStack: Boolean) {
private fun funcRnd(func: FSignature, resultToStack: Boolean, resultRegister: RegisterOrPair?, scope: Subroutine?) {
when(func.name) {
"fastrnd8" -> {
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_fastrnd8_stack")
else {
asmgen.out(" jsr math.fast_randbyte")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
}
"rnd" -> {
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_rnd_stack")
else
else {
asmgen.out(" jsr math.randbyte")
assignAsmGen.assignRegisterByte(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.A, scope, program, asmgen), CpuRegister.A)
}
}
"rndw" -> {
if(resultToStack)
asmgen.out(" jsr prog8_lib.func_rndw_stack")
else
else {
asmgen.out(" jsr math.randword")
assignAsmGen.assignRegisterpairWord(AsmAssignTarget.fromRegisters(resultRegister ?: RegisterOrPair.AY, scope, program, asmgen), RegisterOrPair.AY)
}
}
else -> throw AssemblyError("wrong func")
}
}
private fun funcMkword(fcall: IFunctionCall, resultToStack: Boolean) {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if(resultToStack)
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
private fun funcPokeW(fcall: IFunctionCall) {
when(val addrExpr = fcall.args[0]) {
is NumericLiteralValue -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
val addr = addrExpr.number.toHex()
asmgen.out(" sta $addr | sty ${addr}+1")
return
}
is IdentifierReference -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out("""
sta ($varname)
txa
ldy #1
sta ($varname),y""")
} else {
asmgen.out("""
ldy #0
sta ($varname),y
txa
iny
sta ($varname),y""")
}
asmgen.restoreRegisterLocal(CpuRegister.X)
return
}
}
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
asmgen.saveRegisterLocal(CpuRegister.X, (fcall as Node).definingSubroutine()!!)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AX)
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
sta ($varname),y
txa
iny
sta ($varname),y""")
asmgen.restoreRegisterLocal(CpuRegister.X)
return
}
}
}
asmgen.assignExpressionToVariable(fcall.args[0], "P8ZP_SCRATCH_W1", DataType.UWORD, null)
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_pokew")
}
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean) {
private fun funcPeekW(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
when(val addrExpr = fcall.args[0]) {
is NumericLiteralValue -> {
val addr = addrExpr.number.toHex()
asmgen.out(" lda $addr | ldy ${addr}+1")
}
is IdentifierReference -> {
val varname = asmgen.asmVariableName(addrExpr)
if(asmgen.isZpVar(addrExpr)) {
// pointervar is already in the zero page, no need to copy
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
asmgen.out("""
ldy #1
lda ($varname),y
tay
lda ($varname)""")
} else {
asmgen.out("""
ldy #0
lda ($varname),y
pha
iny
lda ($varname),y
tay
pla""")
}
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peekw")
}
}
is BinaryExpression -> {
if(addrExpr.operator=="+" && addrExpr.left is IdentifierReference && addrExpr.right is NumericLiteralValue) {
val varname = asmgen.asmVariableName(addrExpr.left as IdentifierReference)
val index = (addrExpr.right as NumericLiteralValue).number.toHex()
asmgen.out("""
ldy #$index
lda ($varname),y
pha
iny
lda ($varname),y
tay
pla""")
} else {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peekw")
}
}
else -> {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.AY)
asmgen.out(" jsr prog8_lib.func_peekw")
}
}
if(resultToStack){
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
} else {
when(resultRegister ?: RegisterOrPair.AY) {
RegisterOrPair.AY -> {}
RegisterOrPair.AX -> asmgen.out(" sty P8ZP_SCRATCH_REG | ldx P8ZP_SCRATCH_REG")
RegisterOrPair.XY -> asmgen.out(" tax")
in Cx16VirtualRegisters -> asmgen.out(" sta cx16.${resultRegister.toString().toLowerCase()} | sty cx16.${resultRegister.toString().toLowerCase()}+1")
else -> throw AssemblyError("invalid reg")
}
}
}
private fun funcMkword(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
if(resultToStack) {
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
asmgen.out(" sta P8ESTACK_LO,x | tya | sta P8ESTACK_HI,x | dex")
} else {
val reg = resultRegister ?: RegisterOrPair.AY
var needAsave = !(fcall.args[0] is DirectMemoryRead || fcall.args[0] is NumericLiteralValue || fcall.args[0] is IdentifierReference)
if(!needAsave) {
val mr0 = fcall.args[0] as? DirectMemoryRead
val mr1 = fcall.args[1] as? DirectMemoryRead
if (mr0 != null)
needAsave = mr0.addressExpression !is NumericLiteralValue && mr0.addressExpression !is IdentifierReference
if (mr1 != null)
needAsave = needAsave or (mr1.addressExpression !is NumericLiteralValue && mr1.addressExpression !is IdentifierReference)
}
when(reg) {
RegisterOrPair.AX -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if(needAsave)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.X) // msb
if(needAsave)
asmgen.out(" pla")
}
RegisterOrPair.AY -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if(needAsave)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
if(needAsave)
asmgen.out(" pla")
}
RegisterOrPair.XY -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
if(needAsave)
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.Y) // msb
if(needAsave)
asmgen.out(" pla")
asmgen.out(" tax")
}
in Cx16VirtualRegisters -> {
asmgen.assignExpressionToRegister(fcall.args[1], RegisterOrPair.A) // lsb
asmgen.out(" sta cx16.${reg.toString().toLowerCase()}")
asmgen.assignExpressionToRegister(fcall.args[0], RegisterOrPair.A) // msb
asmgen.out(" sta cx16.${reg.toString().toLowerCase()}+1")
}
else -> throw AssemblyError("invalid mkword target reg")
}
}
}
private fun funcMsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("msb required word argument")
@ -921,19 +1221,43 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
throw AssemblyError("msb(const) should have been const-folded away")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg)
asmgen.out(" lda $sourceName+1")
if (resultToStack)
asmgen.out(" sta P8ESTACK_LO,x | dex")
if(resultToStack) {
asmgen.out(" lda $sourceName+1 | sta P8ESTACK_LO,x | dex")
} else {
when(resultRegister) {
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName+1")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName+1")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName+1")
else -> throw AssemblyError("invalid reg")
}
}
} else {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
if (resultToStack)
if(resultToStack) {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.out(" tya | sta P8ESTACK_LO,x | dex")
else
asmgen.out(" tya")
} else {
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.out(" tya")
}
RegisterOrPair.X -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AX)
asmgen.out(" pla")
}
RegisterOrPair.Y -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.out(" pla")
}
else -> throw AssemblyError("invalid reg")
}
}
}
}
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean) {
private fun funcLsb(fcall: IFunctionCall, resultToStack: Boolean, resultRegister: RegisterOrPair?) {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("lsb required word argument")
@ -942,16 +1266,45 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
if (arg is IdentifierReference) {
val sourceName = asmgen.asmVariableName(arg)
asmgen.out(" lda $sourceName")
if (resultToStack)
asmgen.out(" sta P8ESTACK_LO,x | dex")
if(resultToStack) {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x | dex")
} else {
when(resultRegister) {
null, RegisterOrPair.A -> asmgen.out(" lda $sourceName")
RegisterOrPair.X -> asmgen.out(" ldx $sourceName")
RegisterOrPair.Y -> asmgen.out(" ldy $sourceName")
else -> throw AssemblyError("invalid reg")
}
}
} else {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
if (resultToStack)
if(resultToStack) {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
asmgen.out(" sta P8ESTACK_LO,x | dex")
} else {
when(resultRegister) {
null, RegisterOrPair.A -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
// NOTE: we rely on the fact that the above assignment to AY, assigns the Lsb to A as the last instruction.
// this is required because the compiler assumes the status bits are set according to what A is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.X -> {
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.XY)
// NOTE: we rely on the fact that the above assignment to XY, assigns the Lsb to X as the last instruction.
// this is required because the compiler assumes the status bits are set according to what X is (lsb)
// and will not generate another cmp when lsb() is directly used inside a comparison expression.
}
RegisterOrPair.Y -> {
asmgen.out(" pha")
asmgen.assignExpressionToRegister(fcall.args.single(), RegisterOrPair.AY)
asmgen.out(" tay | pla | cpy #0")
}
else -> throw AssemblyError("invalid reg")
}
}
}
}
@ -959,7 +1312,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
// address in P8ZP_SCRATCH_W1, number of elements in A
arg as IdentifierReference
val identifierName = asmgen.asmVariableName(arg)
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.constIndex()!!
val size = arg.targetVarDecl(program)!!.arraysize!!.constIndex()!!
asmgen.out("""
lda #<$identifierName
ldy #>$identifierName
@ -1014,7 +1367,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, conv.dt, null, variableAsmName = varname)
val assign = AsmAssignment(src, tgt, false, value.position)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
}
conv.reg != null -> {
@ -1030,7 +1383,7 @@ internal class BuiltinFunctionsAsmGen(private val program: Program, private val
}
}
val tgt = AsmAssignTarget.fromRegisters(conv.reg, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, value.position)
val assign = AsmAssignment(src, tgt, false, program.memsizer, value.position)
asmgen.translateNormalAssignment(assign)
}
else -> throw AssemblyError("callconv")

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.ArrayElementTypes
@ -7,8 +7,8 @@ import prog8.ast.base.RegisterOrPair
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.ForLoop
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.toHex
import kotlin.math.absoluteValue
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -57,9 +57,9 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
lda $varname
$modifiedLabel cmp #0 ; modified
beq $endLabel
$incdec $varname
jmp $loopLabel
$endLabel""")
$incdec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
} else {
@ -117,16 +117,15 @@ $modifiedLabel2 cmp #0 ; modified
asmgen.out("""
+ inc $varname
bne $loopLabel
inc $varname+1
jmp $loopLabel
""")
inc $varname+1""")
asmgen.jmp(loopLabel)
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname
jmp $loopLabel""")
+ dec $varname""")
asmgen.jmp(loopLabel)
}
asmgen.out(endLabel)
}
@ -239,7 +238,7 @@ $endLabel""")
val endLabel = asmgen.makeLabel("for_end")
asmgen.loopEndLabels.push(endLabel)
val iterableName = asmgen.asmVariableName(ident)
val decl = ident.targetVarDecl(program.namespace)!!
val decl = ident.targetVarDecl(program)!!
when(iterableDt) {
DataType.STR -> {
asmgen.out("""
@ -369,7 +368,7 @@ $loopLabel""")
throw AssemblyError("step 0, 1 and -1 should have been handled specifically $stmt")
}
2 -> {
if(range.last==255) {
if(range.last==255 || range.last==254) {
asmgen.out("""
inc $varname
beq $endLabel
@ -377,34 +376,34 @@ $loopLabel""")
bne $loopLabel""")
} else {
asmgen.out("""
inc $varname
inc $varname
lda $varname
cmp #${range.last}
beq $endLabel
inc $varname
inc $varname
jmp $loopLabel""")
cmp #${range.last+2}
bne $loopLabel""")
}
}
-2 -> {
when (range.last) {
0 -> asmgen.out("""
lda $varname
beq $endLabel
dec $varname
dec $varname
jmp $loopLabel""")
0 -> {
asmgen.out("""
lda $varname
beq $endLabel
dec $varname
dec $varname""")
asmgen.jmp(loopLabel)
}
1 -> asmgen.out("""
dec $varname
beq $endLabel
dec $varname
bne $loopLabel""")
dec $varname
beq $endLabel
dec $varname
bne $loopLabel""")
else -> asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
dec $varname
dec $varname
jmp $loopLabel""")
dec $varname
dec $varname
lda $varname
cmp #${range.last-2}
bne $loopLabel""")
}
}
else -> {
@ -415,8 +414,8 @@ $loopLabel""")
beq $endLabel
clc
adc #${range.step}
sta $varname
jmp $loopLabel""")
sta $varname""")
asmgen.jmp(loopLabel)
}
}
asmgen.out(endLabel)
@ -452,9 +451,9 @@ $loopLabel""")
sta $varname
lda $varname+1
adc #>${range.step}
sta $varname+1
jmp $loopLabel
$endLabel""")
sta $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
}
}
@ -480,11 +479,10 @@ $loopLabel""")
$endLabel""")
} else {
asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
inc $varname
jmp $loopLabel
lda $varname
cmp #${range.last+1}
bne $loopLabel
$endLabel""")
}
asmgen.loopEndLabels.pop()
@ -505,23 +503,22 @@ $loopLabel""")
asmgen.out("""
lda $varname
beq $endLabel
dec $varname
jmp $loopLabel
$endLabel""")
dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
}
1 -> {
asmgen.out("""
dec $varname
jmp $loopLabel
bne $loopLabel
$endLabel""")
}
else -> {
asmgen.out("""
lda $varname
cmp #${range.last}
beq $endLabel
dec $varname
jmp $loopLabel
lda $varname
cmp #${range.last-1}
bne $loopLabel
$endLabel""")
}
}
@ -546,13 +543,12 @@ $loopLabel""")
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ inc $varname
bne $loopLabel
inc $varname+1
jmp $loopLabel
$endLabel""")
inc $varname+1""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
}
@ -574,14 +570,13 @@ $loopLabel""")
bne +
lda $varname+1
cmp #>${range.last}
bne +
beq $endLabel
+ lda $varname
bne +
dec $varname+1
+ dec $varname
jmp $loopLabel
$endLabel""")
+ dec $varname""")
asmgen.jmp(loopLabel)
asmgen.out(endLabel)
asmgen.loopEndLabels.pop()
}

View File

@ -1,4 +1,4 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Node
@ -7,9 +7,11 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.assignment.*
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignSource
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignTarget
import prog8.compiler.target.cpu6502.codegen.assignment.AsmAssignment
import prog8.compiler.target.cpu6502.codegen.assignment.TargetStorageKind
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -22,7 +24,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
internal fun saveXbeforeCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
@ -34,7 +36,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
internal fun restoreXafterCall(stmt: IFunctionCall) {
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(sub.shouldSaveX()) {
val regSaveOnStack = sub.asmAddress==null // rom-routines don't require registers to be saved on stack, normal subroutines do because they can contain nested calls
val (keepAonEntry: Boolean, keepAonReturn: Boolean) = sub.shouldKeepA()
@ -52,9 +54,10 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// NOTE: does NOT output code to save/restore the X register for this call! Every caller should deal with this in their own way!!
// (you can use subroutine.shouldSaveX() and saveX()/restoreX() routines as a help for this)
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val sub = stmt.target.targetSubroutine(program) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
val subName = asmgen.asmSymbolName(stmt.target)
if(stmt.args.isNotEmpty()) {
if(sub.asmParameterRegisters.isEmpty()) {
// via variables
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
@ -66,18 +69,37 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
// just a single parameter, no risk of clobbering registers
argumentViaRegister(sub, IndexedValue(0, sub.parameters.single()), stmt.args[0])
} else {
fun isNoClobberRisk(expr: Expression): Boolean {
if(expr is AddressOf ||
expr is NumericLiteralValue ||
expr is StringLiteralValue ||
expr is ArrayLiteralValue ||
expr is IdentifierReference)
return true
if(expr is FunctionCall) {
if(expr.target.nameInSource==listOf("lsb") || expr.target.nameInSource==listOf("msb"))
return isNoClobberRisk(expr.args[0])
if(expr.target.nameInSource==listOf("mkword"))
return isNoClobberRisk(expr.args[0]) && isNoClobberRisk(expr.args[1])
}
return false
}
when {
stmt.args.all {it is AddressOf ||
it is NumericLiteralValue ||
it is StringLiteralValue ||
it is ArrayLiteralValue ||
it is IdentifierReference} -> {
stmt.args.all {isNoClobberRisk(it)} -> {
// There's no risk of clobbering for these simple argument types. Optimize the register loading directly from these values.
// register assignment order: 1) cx16 virtual word registers, 2) actual CPU registers, 3) CPU Carry status flag.
val argsInfo = sub.parameters.withIndex().zip(stmt.args).zip(sub.asmParameterRegisters)
val (vregsArgsInfo, otherRegsArgsInfo) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
for(arg in vregsArgsInfo)
val (cx16virtualRegs, args2) = argsInfo.partition { it.second.registerOrPair in Cx16VirtualRegisters }
val (cpuRegs, statusRegs) = args2.partition { it.second.registerOrPair!=null }
for(arg in cx16virtualRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in otherRegsArgsInfo)
for(arg in cpuRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
for(arg in statusRegs)
argumentViaRegister(sub, arg.first.first, arg.first.second)
}
else -> {
@ -150,12 +172,12 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
""")
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
else
asmgen.out(" lda #0 | sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}+1")
}
in WordDatatypes ->
in WordDatatypes, in IterableDatatypes ->
asmgen.out("""
lda P8ESTACK_LO$plusIdxStr,x
sta cx16.${argi.value.second.registerOrPair.toString().toLowerCase()}
@ -240,71 +262,67 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(valueDt largerThan requiredDt)
throw AssemblyError("can only convert byte values to word param types")
}
when {
statusflag!=null -> {
if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
lda $sourceName
beq +
sec
bcs ++
+ clc
+ pla
""")
}
else -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
beq +
sec
bcs ++
+ clc
+""")
}
if (statusflag!=null) {
if(requiredDt!=valueDt)
throw AssemblyError("for statusflag, byte value is required")
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmVariableName(value)
asmgen.out("""
pha
lda $sourceName
beq +
sec
bcs ++
+ clc
+ pla
""")
}
else -> {
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out("""
beq +
sec
bcs ++
+ clc
+""")
}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
else -> {
// via register or register pair
register!!
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
asmgen.signExtendVariableLsb(scratchVar, valueDt)
asmgen.assignVariableToRegister(scratchVar, register)
}
else {
val target: AsmAssignTarget =
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
else
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
} else throw AssemblyError("can only use Carry as status flag parameter")
}
else {
// via register or register pair
register!!
if(requiredDt largerThan valueDt) {
// we need to sign extend the source, do this via temporary word variable
val scratchVar = asmgen.asmVariableName("P8ZP_SCRATCH_W1")
asmgen.assignExpressionToVariable(value, scratchVar, DataType.UBYTE, sub)
asmgen.signExtendVariableLsb(scratchVar, valueDt)
asmgen.assignVariableToRegister(scratchVar, register)
} else {
val target: AsmAssignTarget =
if(parameter.value.type in ByteDatatypes && (register==RegisterOrPair.AX || register == RegisterOrPair.AY || register==RegisterOrPair.XY || register in Cx16VirtualRegisters))
AsmAssignTarget(TargetStorageKind.REGISTER, program, asmgen, parameter.value.type, sub, register = register)
else
AsmAssignTarget.fromRegisters(register, sub, program, asmgen)
val src = if(valueDt in PassByReferenceDatatypes) {
if(value is IdentifierReference) {
val addr = AddressOf(value, Position.DUMMY)
AsmAssignSource.fromAstSource(addr, program, asmgen).adjustSignedUnsigned(target)
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, Position.DUMMY))
} else {
AsmAssignSource.fromAstSource(value, program, asmgen).adjustSignedUnsigned(target)
}
asmgen.translateNormalAssignment(AsmAssignment(src, target, false, program.memsizer, Position.DUMMY))
}
}
}

View File

@ -1,12 +1,12 @@
package prog8.compiler.target.c64.codegen
package prog8.compiler.target.cpu6502.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.PostIncrDecr
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.toHex
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
@ -66,8 +66,9 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
targetArrayIdx!=null -> {
val asmArrayvarname = asmgen.asmVariableName(targetArrayIdx.arrayvar)
val elementDt = targetArrayIdx.inferType(program).typeOrElse(DataType.STRUCT)
if(targetArrayIdx.indexer.indexNum!=null) {
val indexValue = targetArrayIdx.indexer.constIndex()!! * elementDt.memorySize()
val constIndex = targetArrayIdx.indexer.constIndex()
if(constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $asmArrayvarname+$indexValue" else " dec $asmArrayvarname+$indexValue")
in WordDatatypes -> {

View File

@ -1,11 +1,12 @@
package prog8.compiler.target.c64.codegen.assignment
package prog8.compiler.target.cpu6502.codegen.assignment
import prog8.ast.IMemSizer
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.cpu6502.codegen.AsmGen
internal enum class TargetStorageKind {
@ -104,7 +105,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
private val variableAsmName: String? = null,
val array: ArrayIndexedExpression? = null,
val memory: DirectMemoryRead? = null,
val register: CpuRegister? = null,
val register: RegisterOrPair? = null,
val number: NumericLiteralValue? = null,
val expression: Expression? = null
)
@ -119,13 +120,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
asmgen.asmVariableName(array.arrayvar)
companion object {
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource {
return when {
indexer.indexNum!=null -> fromAstSource(indexer.indexNum!!, program, asmgen)
indexer.indexVar!=null -> fromAstSource(indexer.indexVar!!, program, asmgen)
else -> throw AssemblyError("weird indexer")
}
}
fun fromAstSource(indexer: ArrayIndex, program: Program, asmgen: AsmGen): AsmAssignSource = fromAstSource(indexer.indexExpr, program, asmgen)
fun fromAstSource(value: Expression, program: Program, asmgen: AsmGen): AsmAssignSource {
val cv = value.constValue(program)
@ -138,7 +133,15 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
is ArrayLiteralValue -> throw AssemblyError("array literal value should not occur anymore for asm generation")
is IdentifierReference -> {
val dt = value.inferType(program).typeOrElse(DataType.STRUCT)
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = asmgen.asmVariableName(value))
val varName=asmgen.asmVariableName(value)
// special case: "cx16.r[0-15]" are 16-bits virtual registers of the commander X16 system
if(dt==DataType.UWORD && varName.toLowerCase().startsWith("cx16.r")) {
val regStr = varName.toLowerCase().substring(5)
val reg = RegisterOrPair.valueOf(regStr.toUpperCase())
AsmAssignSource(SourceStorageKind.REGISTER, program, asmgen, dt, register = reg)
} else {
AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, dt, variableAsmName = varName)
}
}
is DirectMemoryRead -> {
AsmAssignSource(SourceStorageKind.MEMORY, program, asmgen, DataType.UBYTE, memory = value)
@ -148,7 +151,7 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
AsmAssignSource(SourceStorageKind.ARRAY, program, asmgen, dt, array = value)
}
is FunctionCall -> {
when (val sub = value.target.targetStatement(program.namespace)) {
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
val returnType = sub.returntypes.zip(sub.asmReturnvaluesRegisters).firstOrNull { rr -> rr.second.registerOrPair != null || rr.second.statusflag!=null }?.first
?: throw AssemblyError("can't translate zero return values in assignment")
@ -198,12 +201,13 @@ internal class AsmAssignSource(val kind: SourceStorageKind,
internal class AsmAssignment(val source: AsmAssignSource,
val target: AsmAssignTarget,
val isAugmentable: Boolean,
memsizer: IMemSizer,
val position: Position) {
init {
if(target.register !in setOf(RegisterOrPair.XY, RegisterOrPair.AX, RegisterOrPair.AY))
require(source.datatype != DataType.STRUCT) { "must not be placeholder datatype" }
require(source.datatype.memorySize() <= target.datatype.memorySize()) {
require(memsizer.memorySize(source.datatype) <= memsizer.memorySize(target.datatype)) {
"source storage size must be less or equal to target datatype storage size"
}
}

View File

@ -1,20 +1,21 @@
package prog8.compiler.target.c64.codegen.assignment
package prog8.compiler.target.cpu6502.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.functions.BuiltinFunctions
import prog8.compiler.functions.builtinFunctionReturnType
import prog8.compiler.target.CpuType
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
import prog8.compiler.toHex
import prog8.functions.BuiltinFunctions
import prog8.functions.builtinFunctionReturnType
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen, private val exprAsmgen: ExpressionsAsmGen) {
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen,
private val exprAsmgen: ExpressionsAsmGen
) {
private val augmentableAsmGen = AugmentableAssignmentAsmGen(program, this, exprAsmgen, asmgen)
@ -22,7 +23,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val target = AsmAssignTarget.fromAstAssignment(assignment, program, asmgen)
val source = AsmAssignSource.fromAstSource(assignment.value, program, asmgen).adjustSignedUnsigned(target)
val assign = AsmAssignment(source, target, assignment.isAugmentable, assignment.position)
val assign = AsmAssignment(source, target, assignment.isAugmentable, program.memsizer, assignment.position)
target.origAssign = assign
if(assign.isAugmentable)
@ -64,9 +65,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val value = assign.source.array!!
val elementDt = assign.source.datatype
val arrayVarName = asmgen.asmVariableName(value.arrayvar)
if (value.indexer.indexNum!=null) {
val constIndex = value.indexer.constIndex()
if (constIndex!=null) {
// constant array index value
val indexValue = value.indexer.constIndex()!! * elementDt.memorySize()
val indexValue = constIndex * program.memsizer.memorySize(elementDt)
when (elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue")
@ -114,7 +116,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
SourceStorageKind.MEMORY -> {
fun assignViaExprEval(expression: Expression) {
assignExpressionToVariable(expression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, assign.target.scope)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
@ -143,7 +145,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
SourceStorageKind.EXPRESSION -> {
when(val value = assign.source.expression!!) {
is AddressOf -> {
val sourceName = value.identifier.firstStructVarName(program.namespace) ?: asmgen.asmVariableName(value.identifier)
val sourceName = value.identifier.firstStructVarName(program) ?: asmgen.asmVariableName(value.identifier)
assignAddressOf(assign.target, sourceName)
}
is NumericLiteralValue -> throw AssemblyError("source kind should have been literalnumber")
@ -152,7 +154,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
is DirectMemoryRead -> throw AssemblyError("source kind should have been memory")
is TypecastExpression -> assignTypeCastedValue(assign.target, value.type, value.expression, value)
is FunctionCall -> {
when (val sub = value.target.targetStatement(program.namespace)) {
when (val sub = value.target.targetStatement(program)) {
is Subroutine -> {
asmgen.saveXbeforeCall(value)
asmgen.translateFunctionCall(value)
@ -209,34 +211,37 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
is BuiltinFunctionStatementPlaceholder -> {
val signature = BuiltinFunctions.getValue(sub.name)
asmgen.translateBuiltinFunctionCallExpression(value, signature, false)
val returntype = builtinFunctionReturnType(sub.name, value.args, program)
if(!returntype.isKnown)
throw AssemblyError("unknown dt")
when(returntype.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
DataType.STR -> {
when (assign.target.datatype) {
DataType.STR -> {
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
asmgen.translateBuiltinFunctionCallExpression(value, signature, false, assign.target.register)
if(assign.target.register==null) {
// still need to assign the result to the target variable/etc.
val returntype = builtinFunctionReturnType(sub.name, value.args, program)
if(!returntype.isKnown)
throw AssemblyError("unknown dt")
when(returntype.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> assignRegisterByte(assign.target, CpuRegister.A) // function's byte result is in A
in WordDatatypes -> assignRegisterpairWord(assign.target, RegisterOrPair.AY) // function's word result is in AY
DataType.STR -> {
when (assign.target.datatype) {
DataType.STR -> {
asmgen.out("""
pha
lda #<${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1
lda #>${assign.target.asmVarname}
sta P8ZP_SCRATCH_W1+1
pla
jsr prog8_lib.strcpy""")
}
DataType.UWORD -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
else -> throw AssemblyError("str return value type mismatch with target")
}
DataType.UWORD -> assignRegisterpairWord(assign.target, RegisterOrPair.AY)
else -> throw AssemblyError("str return value type mismatch with target")
}
DataType.FLOAT -> {
// float result from function sits in FAC1
assignFAC1float(assign.target)
}
else -> throw AssemblyError("weird result type")
}
DataType.FLOAT -> {
// float result from function sits in FAC1
assignFAC1float(assign.target)
}
else -> throw AssemblyError("weird result type")
}
}
else -> {
@ -260,7 +265,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
}
SourceStorageKind.REGISTER -> {
assignRegisterByte(assign.target, assign.source.register!!)
when(assign.source.datatype) {
DataType.UBYTE -> assignRegisterByte(assign.target, assign.source.register!!.asCpuRegister())
DataType.UWORD -> assignRegisterpairWord(assign.target, assign.source.register!!)
else -> throw AssemblyError("invalid register dt")
}
}
SourceStorageKind.STACK -> {
assignStackValue(assign.target)
@ -312,7 +321,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
fun assignViaExprEval(addressExpression: Expression) {
asmgen.assignExpressionToVariable(addressExpression, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | lda (P8ZP_SCRATCH_W2),y")
@ -349,7 +358,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// special case optimizations
if(target.kind==TargetStorageKind.VARIABLE) {
if(target.kind== TargetStorageKind.VARIABLE) {
if(value is IdentifierReference && valueDt != DataType.STRUCT)
return assignTypeCastedIdentifier(target.asmVarname, targetDt, asmgen.asmVariableName(value), valueDt)
@ -434,7 +443,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
val lsb = FunctionCall(IdentifierReference(listOf("lsb"), value.position), mutableListOf(value), value.position)
lsb.linkParents(value.parent)
val src = AsmAssignSource(SourceStorageKind.EXPRESSION, program, asmgen, DataType.UBYTE, expression = lsb)
val assign = AsmAssignment(src, target, false, value.position)
val assign = AsmAssignment(src, target, false, program.memsizer, value.position)
translateNormalAssignment(assign)
}
@ -466,7 +475,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
}
DataType.UWORD, DataType.WORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@ -489,7 +498,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName")
}
DataType.UWORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" lda $sourceAsmVarName | sta $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@ -562,11 +571,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
else -> throw AssemblyError("weird type")
}
}
DataType.STR -> {
if (targetDt != DataType.UWORD && targetDt == DataType.STR)
throw AssemblyError("cannot typecast a string into another incompatitble type")
TODO("assign typecasted string into target var")
}
DataType.STR -> throw AssemblyError("cannot typecast a string value")
else -> throw AssemblyError("weird type")
}
}
@ -585,7 +590,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
}
DataType.UWORD, DataType.WORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@ -613,7 +618,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName")
}
DataType.UWORD -> {
if(CompilationTarget.instance.machine.cpu==CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | stz $targetAsmVarName+1")
else
asmgen.out(" st${regs.toString().toLowerCase()} $targetAsmVarName | lda #0 | sta $targetAsmVarName+1")
@ -701,11 +706,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
else -> throw AssemblyError("weird type")
}
}
DataType.STR -> {
if (targetDt != DataType.UWORD && targetDt == DataType.STR)
throw AssemblyError("cannot typecast a string into another incompatitble type")
TODO("assign typecasted string into target var")
}
DataType.STR -> throw AssemblyError("cannot typecast a string value")
else -> throw AssemblyError("weird type")
}
}
@ -755,7 +756,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" inx | lda P8ESTACK_LO,x | sta ${target.asmVarname}+$scaledIdx")
@ -961,7 +962,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.ARRAY -> {
target.array!!
if(target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
when(target.datatype) {
in ByteDatatypes -> {
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
@ -1066,11 +1067,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1""")
if(target.array!!.indexer.indexNum!=null) {
val index = target.array.indexer.constIndex()!!
asmgen.out(" lda #$index")
val constIndex = target.array!!.indexer.constIndex()
if(constIndex!=null) {
asmgen.out(" lda #$constIndex")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float_from_fac1")
@ -1102,11 +1103,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1""")
if(target.array!!.indexer.indexNum!=null) {
val index = target.array.indexer.constIndex()!!
asmgen.out(" lda #$index")
val constIndex = target.array!!.indexer.constIndex()
if(constIndex!=null) {
asmgen.out(" lda #$constIndex")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float")
@ -1149,11 +1150,11 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
ldy #>${target.asmVarname}
sta P8ZP_SCRATCH_W2
sty P8ZP_SCRATCH_W2+1""")
if(target.array!!.indexer.indexNum!=null) {
val index = target.array.indexer.constIndex()!!
asmgen.out(" lda #$index")
val constIndex = target.array!!.indexer.constIndex()
if(constIndex!=null) {
asmgen.out(" lda #$constIndex")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out(" lda $asmvarname")
}
asmgen.out(" jsr floats.set_array_float")
@ -1184,7 +1185,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
if (target.constArrayIndexValue!=null) {
val scaledIdx = target.constArrayIndexValue!! * target.datatype.memorySize()
val scaledIdx = target.constArrayIndexValue!! * program.memsizer.memorySize(target.datatype)
asmgen.out(" lda $sourceName | sta ${target.asmVarname}+$scaledIdx")
}
else {
@ -1288,7 +1289,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
@ -1297,7 +1298,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
if (wordtarget.constArrayIndexValue!=null) {
val scaledIdx = wordtarget.constArrayIndexValue!! * 2
asmgen.out(" lda $sourceName | sta ${wordtarget.asmVarname}+$scaledIdx")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+$scaledIdx+1")
else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+$scaledIdx+1")
@ -1322,7 +1323,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.STACK -> {
asmgen.out(" lda $sourceName | sta P8ESTACK_LO,x")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI,x | dex")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
@ -1332,8 +1333,16 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
internal fun assignRegisterByte(target: AsmAssignTarget, register: CpuRegister) {
if(target.register !in Cx16VirtualRegisters)
require(target.datatype in ByteDatatypes)
// we make an exception in the type check for assigning something to a cx16 virtual register
if(target.register !in Cx16VirtualRegisters) {
if(target.kind==TargetStorageKind.VARIABLE) {
val parts = target.asmVarname.split('.')
if (parts.size != 2 || parts[0] != "cx16")
require(target.datatype in ByteDatatypes)
} else {
require(target.datatype in ByteDatatypes)
}
}
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1361,7 +1370,8 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
CpuRegister.X -> asmgen.out(" txa")
CpuRegister.Y -> asmgen.out(" tya")
}
asmgen.out(" ldy ${asmgen.asmVariableName(target.array!!.indexer.indexVar!!)} | sta ${target.asmVarname},y")
val indexVar = target.array!!.indexer.indexExpr as IdentifierReference
asmgen.out(" ldy ${asmgen.asmVariableName(indexVar)} | sta ${target.asmVarname},y")
}
}
TargetStorageKind.REGISTER -> {
@ -1431,7 +1441,15 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname} | stx ${target.asmVarname}+1")
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname} | sty ${target.asmVarname}+1")
RegisterOrPair.XY -> asmgen.out(" stx ${target.asmVarname} | sty ${target.asmVarname}+1")
else -> throw AssemblyError("expected reg pair")
in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
asmgen.out("""
lda $srcReg
sta ${target.asmVarname}
lda $srcReg+1
sta ${target.asmVarname}+1""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
}
}
TargetStorageKind.ARRAY -> {
@ -1441,24 +1459,43 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.AX -> asmgen.out(" sta ${target.asmVarname}+$idx | stx ${target.asmVarname}+$idx+1")
RegisterOrPair.AY -> asmgen.out(" sta ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1")
RegisterOrPair.XY -> asmgen.out(" stx ${target.asmVarname}+$idx | sty ${target.asmVarname}+$idx+1")
else -> throw AssemblyError("expected reg pair")
in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
asmgen.out("""
lda $srcReg
sta ${target.asmVarname}+$idx
lda $srcReg+1
sta ${target.asmVarname}+$idx+1""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
}
}
else {
when(regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
else -> throw AssemblyError("expected reg pair")
}
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y, true)
asmgen.out("""
pla
sta ${target.asmVarname},y
dey
pla
sta ${target.asmVarname},y""")
if (regs !in Cx16VirtualRegisters) {
when (regs) {
RegisterOrPair.AX -> asmgen.out(" pha | txa | pha")
RegisterOrPair.AY -> asmgen.out(" pha | tya | pha")
RegisterOrPair.XY -> asmgen.out(" txa | pha | tya | pha")
else -> throw AssemblyError("expected reg pair")
}
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y, true)
asmgen.out("""
pla
sta ${target.asmVarname},y
dey
pla
sta ${target.asmVarname},y""")
} else {
val srcReg = asmgen.asmSymbolName(regs)
asmgen.loadScaledArrayIndexIntoRegister(target.array!!, DataType.UWORD, CpuRegister.Y, true)
asmgen.out("""
lda $srcReg+1
sta ${target.asmVarname},y
dey
lda $srcReg
sta ${target.asmVarname},y""")
}
}
}
TargetStorageKind.REGISTER -> {
when(regs) {
@ -1472,7 +1509,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
stx cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("expected reg pair")
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
}
RegisterOrPair.AY -> when(target.register!!) {
RegisterOrPair.AY -> { }
@ -1484,7 +1521,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sty cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("expected reg pair")
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
}
RegisterOrPair.XY -> when(target.register!!) {
RegisterOrPair.AY -> { asmgen.out(" txa") }
@ -1496,16 +1533,40 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sty cx16.${target.register.toString().toLowerCase()}+1
""")
}
else -> throw AssemblyError("expected reg pair")
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
}
else -> throw AssemblyError("expected reg pair")
in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
if(regs!=target.register) {
when(target.register) {
RegisterOrPair.AX -> asmgen.out(" lda $srcReg | ldx $srcReg+1")
RegisterOrPair.AY -> asmgen.out(" lda $srcReg | ldy $srcReg+1")
RegisterOrPair.XY -> asmgen.out(" ldx $srcReg | ldy $srcReg+1")
in Cx16VirtualRegisters -> {
val targetReg = asmgen.asmSymbolName(target.register!!)
asmgen.out(" lda $srcReg | sta $targetReg | lda $srcReg+1 | sta $targetReg+1")
}
else -> throw AssemblyError("invalid reg")
}
}
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
}
}
TargetStorageKind.STACK -> {
when(regs) {
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta P8ESTACK_LO,x | sty P8ESTACK_HI,x | dex")
RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't use X here")
else -> throw AssemblyError("expected reg pair")
in Cx16VirtualRegisters -> {
val srcReg = asmgen.asmSymbolName(regs)
asmgen.out("""
lda $srcReg
sta P8ESTACK_LO,x
lda $srcReg+1
sta P8ESTACK_HI,x
dex""")
}
else -> throw AssemblyError("expected reg pair or cx16 virtual 16-bit register")
}
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't store word into memory byte")
@ -1513,7 +1574,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignConstantWord(target: AsmAssignTarget, word: Int) {
if(word==0 && CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
if(word==0 && asmgen.isTargetCpu(CpuType.CPU65c02)) {
// optimize setting zero value for this processor
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1608,7 +1669,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
private fun assignConstantByte(target: AsmAssignTarget, byte: Short) {
if(byte==0.toShort() && CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
if(byte==0.toShort() && asmgen.isTargetCpu(CpuType.CPU65c02)) {
// optimize setting zero value for this cpu
when(target.kind) {
TargetStorageKind.VARIABLE -> {
@ -1678,7 +1739,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
RegisterOrPair.FAC1, RegisterOrPair.FAC2 -> throw AssemblyError("expected typecasted byte to float")
in Cx16VirtualRegisters -> {
asmgen.out(" lda #${byte.toHex()} | sta cx16.${target.register.toString().toLowerCase()}")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz cx16.${target.register.toString().toLowerCase()}+1\n")
else
asmgen.out(" lda #0 | sta cx16.${target.register.toString().toLowerCase()}+1\n")
@ -1699,7 +1760,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
// optimized case for float zero
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
stz ${target.asmVarname}
stz ${target.asmVarname}+1
@ -1718,9 +1779,10 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
""")
}
TargetStorageKind.ARRAY -> {
if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * DataType.FLOAT.memorySize()
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
val constIndex = target.array!!.indexer.constIndex()
if (constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out("""
stz ${target.asmVarname}+$indexValue
stz ${target.asmVarname}+$indexValue+1
@ -1738,7 +1800,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta ${target.asmVarname}+$indexValue+4
""")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out("""
lda #<${target.asmVarname}
sta P8ZP_SCRATCH_W1
@ -1783,8 +1845,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.ARRAY -> {
val arrayVarName = target.asmVarname
if (target.array!!.indexer.indexNum!=null) {
val indexValue = target.array.indexer.constIndex()!! * DataType.FLOAT.memorySize()
val constIndex = target.array!!.indexer.constIndex()
if (constIndex!=null) {
val indexValue = constIndex * program.memsizer.memorySize(DataType.FLOAT)
asmgen.out("""
lda $constFloat
sta $arrayVarName+$indexValue
@ -1798,7 +1861,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
sta $arrayVarName+$indexValue+4
""")
} else {
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexVar!!)
val asmvarname = asmgen.asmVariableName(target.array.indexer.indexExpr as IdentifierReference)
asmgen.out("""
lda #<${constFloat}
sta P8ZP_SCRATCH_W1
@ -1917,7 +1980,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(wordtarget.kind) {
TargetStorageKind.VARIABLE -> {
asmgen.out(" lda ${address.toHex()} | sta ${wordtarget.asmVarname}")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
@ -1933,7 +1996,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
}
TargetStorageKind.STACK -> {
asmgen.out(" lda ${address.toHex()} | sta P8ESTACK_LO,x")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI,x | dex")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
@ -1945,7 +2008,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.VARIABLE -> {
asmgen.loadByteFromPointerIntoA(identifier)
asmgen.out(" sta ${wordtarget.asmVarname}")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${wordtarget.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${wordtarget.asmVarname}+1")
@ -1965,7 +2028,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
TargetStorageKind.STACK -> {
asmgen.loadByteFromPointerIntoA(identifier)
asmgen.out(" sta P8ESTACK_LO,x")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI,x | dex")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI,x | dex")
@ -1983,7 +2046,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
when(addressExpr) {
is NumericLiteralValue, is IdentifierReference -> {
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
@ -1993,7 +2056,7 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
asmgen.out(" pha")
assignExpressionToVariable(addressExpr, asmgen.asmVariableName("P8ZP_SCRATCH_W2"), DataType.UWORD, null)
asmgen.out(" pla")
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if (asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" sta (P8ZP_SCRATCH_W2)")
else
asmgen.out(" ldy #0 | sta (P8ZP_SCRATCH_W2),y")
@ -2003,9 +2066,9 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
fun storeAIntoPointerVar(pointervar: IdentifierReference) {
val sourceName = asmgen.asmVariableName(pointervar)
val vardecl = pointervar.targetVarDecl(program.namespace)!!
val vardecl = pointervar.targetVarDecl(program)!!
val scopedName = vardecl.makeScopedName(vardecl.name)
if (CompilationTarget.instance.machine.cpu == CpuType.CPU65c02) {
if (asmgen.isTargetCpu(CpuType.CPU65c02)) {
if (asmgen.isZpVar(scopedName)) {
// pointervar is already in the zero page, no need to copy
asmgen.out(" sta ($sourceName)")
@ -2051,21 +2114,21 @@ internal class AssignmentAsmGen(private val program: Program, private val asmgen
internal fun assignExpressionToRegister(expr: Expression, register: RegisterOrPair) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
val assign = AsmAssignment(src, tgt, false, expr.position)
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign)
}
internal fun assignExpressionToVariable(expr: Expression, asmVarName: String, dt: DataType, scope: Subroutine?) {
val src = AsmAssignSource.fromAstSource(expr, program, asmgen)
val tgt = AsmAssignTarget(TargetStorageKind.VARIABLE, program, asmgen, dt, scope, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, expr.position)
val assign = AsmAssignment(src, tgt, false, program.memsizer, expr.position)
translateNormalAssignment(assign)
}
internal fun assignVariableToRegister(asmVarName: String, register: RegisterOrPair) {
val tgt = AsmAssignTarget.fromRegisters(register, null, program, asmgen)
val src = AsmAssignSource(SourceStorageKind.VARIABLE, program, asmgen, tgt.datatype, variableAsmName = asmVarName)
val assign = AsmAssignment(src, tgt, false, Position.DUMMY)
val assign = AsmAssignment(src, tgt, false, program.memsizer, Position.DUMMY)
translateNormalAssignment(assign)
}
}

View File

@ -1,21 +1,20 @@
package prog8.compiler.target.c64.codegen.assignment
package prog8.compiler.target.cpu6502.codegen.assignment
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.Subroutine
import prog8.ast.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.CpuType
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.compiler.target.c64.codegen.ExpressionsAsmGen
import prog8.compiler.toHex
import prog8.compiler.target.cpu6502.codegen.AsmGen
import prog8.compiler.target.cpu6502.codegen.ExpressionsAsmGen
internal class AugmentableAssignmentAsmGen(private val program: Program,
private val assignmentAsmGen: AssignmentAsmGen,
private val exprAsmGen: ExpressionsAsmGen,
private val asmgen: AsmGen) {
private val asmgen: AsmGen
) {
fun translate(assign: AsmAssignment) {
require(assign.isAugmentable)
require(assign.source.kind== SourceStorageKind.EXPRESSION)
@ -200,9 +199,11 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
TargetStorageKind.ARRAY -> {
with(target.array!!.indexer) {
val indexNum = indexExpr as? NumericLiteralValue
val indexVar = indexExpr as? IdentifierReference
when {
indexNum!=null -> {
val targetVarName = "${target.asmVarname} + ${indexNum!!.number.toInt()*target.datatype.memorySize()}"
val targetVarName = "${target.asmVarname} + ${indexNum.number.toInt()*program.memsizer.memorySize(target.datatype)}"
when(target.datatype) {
in ByteDatatypes -> {
when {
@ -246,19 +247,19 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
when(target.datatype) {
in ByteDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.A, null, program, asmgen)
val assign = AsmAssignment(target.origAssign.source, tgt, false, value.position)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterByte(target, CpuRegister.A)
}
in WordDatatypes -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.AY, null, program, asmgen)
val assign = AsmAssignment(target.origAssign.source, tgt, false, value.position)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignRegisterpairWord(target, RegisterOrPair.AY)
}
DataType.FLOAT -> {
val tgt = AsmAssignTarget.fromRegisters(RegisterOrPair.FAC1, null, program, asmgen)
val assign = AsmAssignment(target.origAssign.source, tgt, false, value.position)
val assign = AsmAssignment(target.origAssign.source, tgt, false, program.memsizer, value.position)
assignmentAsmGen.translateNormalAssignment(assign)
assignmentAsmGen.assignFAC1float(target)
}
@ -269,8 +270,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
}
TargetStorageKind.REGISTER -> TODO("reg in-place modification")
TargetStorageKind.STACK -> TODO("stack in-place modification")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg in-place modification")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack in-place modification")
}
}
@ -321,7 +322,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> asmgen.out(" and P8ZP_SCRATCH_B1")
"|", "or" -> asmgen.out(" ora P8ZP_SCRATCH_B1")
"^", "xor" -> asmgen.out(" eor P8ZP_SCRATCH_B1")
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
if(ptrOnZp)
@ -362,7 +362,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> asmgen.out(" and $otherName")
"|", "or" -> asmgen.out(" ora $otherName")
"^", "xor" -> asmgen.out(" eor $otherName")
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
if(ptrOnZp)
@ -465,7 +464,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else
asmgen.out(" sta (P8ZP_SCRATCH_W1),y")
}
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -541,7 +539,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out(" eor $name | sta $name")
}
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -599,7 +596,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name")
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name")
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name")
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -632,7 +628,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"<<" -> {
if(value>=8) {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
@ -643,7 +639,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
if(value>0) {
if (dt == DataType.UBYTE) {
if(value>=8) {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
@ -671,7 +667,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> asmgen.out(" lda $name | and #$value | sta $name")
"|", "or" -> asmgen.out(" lda $name | ora #$value | sta $name")
"^", "xor" -> asmgen.out(" lda $name | eor #$value | sta $name")
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -858,14 +853,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"<<" -> {
when {
value>=16 -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name | stz $name+1")
else
asmgen.out(" lda #0 | sta $name | sta $name+1")
}
value==8 -> {
asmgen.out(" lda $name | sta $name+1")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
@ -885,14 +880,14 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
if(dt==DataType.UWORD) {
when {
value>=16 -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name | stz $name+1")
else
asmgen.out(" lda #0 | sta $name | sta $name+1")
}
value==8 -> {
asmgen.out(" lda $name+1 | sta $name")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
@ -941,13 +936,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> {
when {
value == 0 -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name | stz $name+1")
else
asmgen.out(" lda #0 | sta $name | sta $name+1")
}
value and 255 == 0 -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name")
else
asmgen.out(" lda #0 | sta $name")
@ -955,7 +950,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
value < 0x0100 -> {
asmgen.out(" lda $name | and #$value | sta $name")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
@ -979,14 +974,13 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
else -> asmgen.out(" lda $name | eor #<$value | sta $name | lda $name+1 | eor #>$value | sta $name+1")
}
}
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
private fun inplaceModification_word_variable_to_variable(name: String, dt: DataType, operator: String, ident: IdentifierReference) {
val otherName = asmgen.asmVariableName(ident)
val valueDt = ident.targetVarDecl(program.namespace)!!.datatype
val valueDt = ident.targetVarDecl(program)!!.datatype
when (valueDt) {
in ByteDatatypes -> {
// the other variable is a BYTE type so optimize for that
@ -1042,7 +1036,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"*" -> {
asmgen.out(" lda $otherName | sta P8ZP_SCRATCH_W1")
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ZP_SCRATCH_W1+1")
else
asmgen.out(" lda #0 | sta P8ZP_SCRATCH_W1+1")
@ -1055,8 +1049,49 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
lda math.multiply_words.result+1
sta $name+1""")
}
"/" -> TODO("div (u)wordvar/bytevar")
"%" -> TODO("(u)word remainder bytevar")
"/" -> {
if(dt==DataType.UWORD) {
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $otherName
ldy #0
jsr math.divmod_uw_asm
sta $name
sty $name+1
""")
} else {
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $otherName
ldy #0
jsr math.divmod_w_asm
sta $name
sty $name+1
""")
}
}
"%" -> {
if(valueDt!=DataType.UBYTE || dt!=DataType.UWORD)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out("""
lda $name
ldy $name+1
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda $otherName
ldy #0
jsr math.divmod_uw_asm
lda P8ZP_SCRATCH_W2
sta $name
lda P8ZP_SCRATCH_W2+1
sta $name+1
""") }
"<<" -> {
asmgen.out("""
ldy $otherName
@ -1093,7 +1128,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> {
asmgen.out(" lda $otherName | and $name | sta $name")
if(dt in WordDatatypes) {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
@ -1101,7 +1136,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
"|", "or" -> asmgen.out(" lda $otherName | ora $name | sta $name")
"^", "xor" -> asmgen.out(" lda $otherName | eor $name | sta $name")
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -1175,7 +1209,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
"&", "and" -> asmgen.out(" lda $name | and $otherName | sta $name | lda $name+1 | and $otherName+1 | sta $name+1")
"|", "or" -> asmgen.out(" lda $name | ora $otherName | sta $name | lda $name+1 | ora $otherName+1 | sta $name+1")
"^", "xor" -> asmgen.out(" lda $name | eor $otherName | sta $name | lda $name+1 | eor $otherName+1 | sta $name+1")
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -1352,7 +1385,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out(" and $name | sta $name")
if(dt in WordDatatypes) {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz $name+1")
else
asmgen.out(" lda #0 | sta $name+1")
@ -1366,7 +1399,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(value, RegisterOrPair.A)
asmgen.out(" eor $name | sta $name")
}
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -1407,7 +1439,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.assignExpressionToRegister(value, RegisterOrPair.AY)
asmgen.out(" eor $name | sta $name | tya | eor $name+1 | sta $name+1")
}
in comparisonOperators -> TODO("in-place modification for $operator")
else -> throw AssemblyError("invalid operator for in-place modification $operator")
}
}
@ -1455,7 +1486,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
jsr floats.FDIV
""")
}
in comparisonOperators -> TODO("in-place float modification for $operator")
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
}
asmgen.out("""
@ -1467,7 +1497,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
private fun inplaceModification_float_variable_to_variable(name: String, operator: String, ident: IdentifierReference, scope: Subroutine) {
val valueDt = ident.targetVarDecl(program.namespace)!!.datatype
val valueDt = ident.targetVarDecl(program)!!.datatype
if(valueDt != DataType.FLOAT)
throw AssemblyError("float variable expected")
@ -1475,7 +1505,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
"**" -> {
if(CompilationTarget.instance is Cx16Target) {
if(asmgen.haveFPWR()) {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$otherName
ldy #>$otherName
jsr floats.FPWR
""")
} else
// cx16 doesn't have FPWR() only FPWRT()
asmgen.out("""
lda #<$name
@ -1486,15 +1525,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
jsr floats.MOVFM
jsr floats.FPWRT
""")
} else
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$otherName
ldy #>$otherName
jsr floats.FPWR
""")
}
"+" -> {
asmgen.out("""
@ -1536,7 +1566,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
jsr floats.FDIV
""")
}
in comparisonOperators -> TODO("in-place float modification for $operator")
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
}
// store Fac1 back into memory
@ -1553,7 +1582,16 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.saveRegisterLocal(CpuRegister.X, scope)
when (operator) {
"**" -> {
if(CompilationTarget.instance is Cx16Target) {
if(asmgen.haveFPWR()) {
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$constValueName
ldy #>$constValueName
jsr floats.FPWR
""")
} else
// cx16 doesn't have FPWR() only FPWRT()
asmgen.out("""
lda #<$name
@ -1564,15 +1602,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
jsr floats.MOVFM
jsr floats.FPWRT
""")
} else
asmgen.out("""
lda #<$name
ldy #>$name
jsr floats.CONUPK
lda #<$constValueName
ldy #>$constValueName
jsr floats.FPWR
""")
}
"+" -> {
if (value == 0.0)
@ -1621,7 +1650,6 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
jsr floats.FDIV
""")
}
in comparisonOperators -> TODO("in-place float modification for $operator")
else -> throw AssemblyError("invalid operator for in-place float modification $operator")
}
// store Fac1 back into memory
@ -1645,7 +1673,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
DataType.UBYTE, DataType.BYTE -> {
when(target.kind) {
TargetStorageKind.VARIABLE -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz ${target.asmVarname}+1")
else
asmgen.out(" lda #0 | sta ${target.asmVarname}+1")
@ -1655,7 +1683,7 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
asmgen.out(" lda #0 | sta ${target.asmVarname},y")
}
TargetStorageKind.STACK -> {
if(CompilationTarget.instance.machine.cpu == CpuType.CPU65c02)
if(asmgen.isTargetCpu(CpuType.CPU65c02))
asmgen.out(" stz P8ESTACK_HI+1,x")
else
asmgen.out(" lda #0 | sta P8ESTACK_HI+1,x")
@ -1731,9 +1759,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
}
TargetStorageKind.ARRAY -> TODO("in-place not of ubyte array")
TargetStorageKind.REGISTER -> TODO("reg not")
TargetStorageKind.STACK -> TODO("stack not")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of ubyte array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
}
}
DataType.UWORD -> {
@ -1750,9 +1778,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta ${target.asmVarname}+1""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory not")
TargetStorageKind.ARRAY -> TODO("in-place not of uword array")
TargetStorageKind.REGISTER -> TODO("reg not")
TargetStorageKind.STACK -> TODO("stack not")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place not of uword array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg not")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack not")
}
}
else -> throw AssemblyError("boolean-not of invalid type")
@ -1797,9 +1825,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
}
}
}
TargetStorageKind.ARRAY -> TODO("in-place invert ubyte array")
TargetStorageKind.REGISTER -> TODO("reg invert")
TargetStorageKind.STACK -> TODO("stack invert")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert ubyte array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
}
}
DataType.UWORD -> {
@ -1814,9 +1842,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta ${target.asmVarname}+1""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for uword-memory invert")
TargetStorageKind.ARRAY -> TODO("in-place invert uword array")
TargetStorageKind.REGISTER -> TODO("reg invert")
TargetStorageKind.STACK -> TODO("stack invert")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place invert uword array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg invert")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack invert")
}
}
else -> throw AssemblyError("invert of invalid type")
@ -1835,9 +1863,9 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta ${target.asmVarname}""")
}
TargetStorageKind.MEMORY -> throw AssemblyError("can't in-place negate memory ubyte")
TargetStorageKind.ARRAY -> TODO("in-place negate byte array")
TargetStorageKind.REGISTER -> TODO("reg negate")
TargetStorageKind.STACK -> TODO("stack negate")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate byte array")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
}
}
DataType.WORD -> {
@ -1852,10 +1880,10 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sbc ${target.asmVarname}+1
sta ${target.asmVarname}+1""")
}
TargetStorageKind.ARRAY -> TODO("in-place negate word array")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate word array")
TargetStorageKind.MEMORY -> throw AssemblyError("no asm gen for word memory negate")
TargetStorageKind.REGISTER -> TODO("reg negate")
TargetStorageKind.STACK -> TODO("stack negate")
TargetStorageKind.REGISTER -> throw AssemblyError("missing codegen for reg negate")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack negate")
}
}
DataType.FLOAT -> {
@ -1868,8 +1896,8 @@ internal class AugmentableAssignmentAsmGen(private val program: Program,
sta ${target.asmVarname}+1
""")
}
TargetStorageKind.ARRAY -> TODO("in-place negate float array")
TargetStorageKind.STACK -> TODO("stack float negate")
TargetStorageKind.ARRAY -> throw AssemblyError("missing codegen for in-place negate float array")
TargetStorageKind.STACK -> throw AssemblyError("missing codegen for stack float negate")
else -> throw AssemblyError("weird target kind for float")
}
}

View File

@ -1,11 +1,9 @@
package prog8.compiler.target.cx16
import prog8.ast.Program
import prog8.compiler.*
import prog8.compiler.target.CpuType
import prog8.compiler.target.IMachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.parser.ModuleImporter
import java.io.IOException
internal object CX16MachineDefinition: IMachineDefinition {
@ -27,10 +25,11 @@ internal object CX16MachineDefinition: IMachineDefinition {
override lateinit var zeropage: Zeropage
override fun getFloat(num: Number) = C64MachineDefinition.Mflpt5.fromNumber(num)
override fun importLibs(compilerOptions: CompilationOptions, importer: ModuleImporter, program: Program) {
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
importer.importLibraryModule(program, "syslib")
override fun importLibs(compilerOptions: CompilationOptions, compilationTargetName: String): List<String> {
return if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG)
listOf("syslib")
else
emptyList()
}
override fun launchEmulator(programName: String) {

View File

@ -1,16 +1,16 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
internal class BinExprSplitter(private val program: Program) : AstWalker() {
internal class BinExprSplitter(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
// override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@ -53,7 +53,7 @@ X = BinExpr X = LeftExpr
*/
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program.namespace)) {
if(binExpr.operator in augmentAssignmentOperators && isSimpleTarget(assignment.target, program)) {
if(assignment.target isSameAs binExpr.left || assignment.target isSameAs binExpr.right)
return noModifications
@ -78,9 +78,9 @@ X = BinExpr X = LeftExpr
private fun isSimpleExpression(expr: Expression) =
expr is IdentifierReference || expr is NumericLiteralValue || expr is AddressOf || expr is DirectMemoryRead || expr is StringLiteralValue || expr is ArrayLiteralValue || expr is RangeExpr
private fun isSimpleTarget(target: AssignTarget, namespace: INameScope) =
private fun isSimpleTarget(target: AssignTarget, program: Program) =
if (target.identifier!=null || target.memoryAddress!=null)
target.isInRegularRAM(namespace)
compTarget.isInRegularRAM(target, program)
else
false

View File

@ -1,26 +1,29 @@
package prog8.optimizer
import prog8.ast.*
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.ErrorReporter
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.loadAsmIncludeFile
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import java.nio.file.Path
private val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq")
Pair("main", "start")
)
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr|bra)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
class CallGraph(private val program: Program) : IAstVisitor {
class CallGraph(private val program: Program, private val asmFileLoader: (filename: String, source: Path)->String) : IAstVisitor {
val imports = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val importedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
@ -77,7 +80,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
imports[thisModule] = imports.getValue(thisModule).plus(importedModule)
importedBy[importedModule] = importedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val asm = asmFileLoader(directive.args[0].str!!, thisModule.source)
val scope = directive.definingSubroutine()
if(scope!=null) {
scanAssemblyCode(asm, directive, scope)
@ -89,7 +92,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
override fun visit(identifier: IdentifierReference) {
// track symbol usage
val target = identifier.targetStatement(this.program.namespace)
val target = identifier.targetStatement(program)
if (target != null) {
addNodeAndParentScopes(target)
}
@ -126,7 +129,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
override fun visit(functionCall: FunctionCall) {
val otherSub = functionCall.target.targetSubroutine(program.namespace)
val otherSub = functionCall.target.targetSubroutine(program)
if (otherSub != null) {
functionCall.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
@ -137,7 +140,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
override fun visit(functionCallStatement: FunctionCallStatement) {
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
val otherSub = functionCallStatement.target.targetSubroutine(program)
if (otherSub != null) {
functionCallStatement.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
@ -147,8 +150,19 @@ class CallGraph(private val program: Program) : IAstVisitor {
super.visit(functionCallStatement)
}
override fun visit(addressOf: AddressOf) {
val otherSub = addressOf.identifier.targetSubroutine(program)
if(otherSub!=null) {
addressOf.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
calledBy[otherSub] = calledBy.getValue(otherSub).plus(thisSub)
}
}
super.visit(addressOf)
}
override fun visit(jump: Jump) {
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
val otherSub = jump.identifier?.targetSubroutine(program)
if (otherSub != null) {
jump.definingSubroutine()?.let { thisSub ->
calls[thisSub] = calls.getValue(thisSub).plus(otherSub)
@ -164,7 +178,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
override fun visit(inlineAssembly: InlineAssembly) {
// parse inline asm for subroutine calls (jmp, jsr)
// parse inline asm for subroutine calls (jmp, jsr, bra)
val scope = inlineAssembly.definingSubroutine()
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
super.visit(inlineAssembly)
@ -210,7 +224,7 @@ class CallGraph(private val program: Program) : IAstVisitor {
}
}
fun checkRecursiveCalls(errors: ErrorReporter) {
fun checkRecursiveCalls(errors: IErrorReporter) {
val cycles = recursionCycles()
if(cycles.any()) {
errors.warn("Program contains recursive subroutine calls. These only works in very specific limited scenarios!", Position.DUMMY)

View File

@ -9,28 +9,32 @@ import kotlin.math.pow
class ConstExprEvaluator {
fun evaluate(left: NumericLiteralValue, operator: String, right: NumericLiteralValue): Expression {
return when(operator) {
"+" -> plus(left, right)
"-" -> minus(left, right)
"*" -> multiply(left, right)
"/" -> divide(left, right)
"%" -> remainder(left, right)
"**" -> power(left, right)
"&" -> bitwiseand(left, right)
"|" -> bitwiseor(left, right)
"^" -> bitwisexor(left, right)
"and" -> logicaland(left, right)
"or" -> logicalor(left, right)
"xor" -> logicalxor(left, right)
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
"<<" -> shiftedleft(left, right)
">>" -> shiftedright(left, right)
else -> throw FatalAstException("const evaluation for invalid operator $operator")
try {
return when(operator) {
"+" -> plus(left, right)
"-" -> minus(left, right)
"*" -> multiply(left, right)
"/" -> divide(left, right)
"%" -> remainder(left, right)
"**" -> power(left, right)
"&" -> bitwiseand(left, right)
"|" -> bitwiseor(left, right)
"^" -> bitwisexor(left, right)
"and" -> logicaland(left, right)
"or" -> logicalor(left, right)
"xor" -> logicalxor(left, right)
"<" -> NumericLiteralValue.fromBoolean(left < right, left.position)
">" -> NumericLiteralValue.fromBoolean(left > right, left.position)
"<=" -> NumericLiteralValue.fromBoolean(left <= right, left.position)
">=" -> NumericLiteralValue.fromBoolean(left >= right, left.position)
"==" -> NumericLiteralValue.fromBoolean(left == right, left.position)
"!=" -> NumericLiteralValue.fromBoolean(left != right, left.position)
"<<" -> shiftedleft(left, right)
">>" -> shiftedright(left, right)
else -> throw FatalAstException("const evaluation for invalid operator $operator")
}
} catch (ax: FatalAstException) {
throw ExpressionError(ax.message, left.position)
}
}

View File

@ -4,13 +4,16 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.target.ICompilationTarget
import kotlin.math.pow
internal class ConstantFoldingOptimizer(private val program: Program) : AstWalker() {
internal class ConstantFoldingOptimizer(private val program: Program, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
@ -221,7 +224,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
range.step
}
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, range.position)
return RangeExpr(fromCast.valueOrZero(), toCast.valueOrZero(), newStep, compTarget, range.position)
}
// adjust the datatype of a range expression in for loops to the loop variable.
@ -230,7 +233,7 @@ internal class ConstantFoldingOptimizer(private val program: Program) : AstWalke
val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.loopVar.targetVarDecl(program.namespace) ?: throw UndefinedSymbolError(forLoop.loopVar)
val loopvar = forLoop.loopVar.targetVarDecl(program) ?: throw UndefinedSymbolError(forLoop.loopVar)
val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) {

View File

@ -4,16 +4,17 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.ForLoop
import prog8.ast.statements.VarDecl
import prog8.compiler.target.CompilationTarget
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
// Fix up the literal value's type to match that of the vardecl
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
internal class VarConstantValueTypeAdjuster(private val program: Program, private val errors: IErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
@ -38,7 +39,7 @@ internal class VarConstantValueTypeAdjuster(private val program: Program, privat
// Replace all constant identifiers with their actual value,
// and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: IErrorReporter, private val compTarget: ICompilationTarget) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
@ -74,7 +75,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call graph for this?
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexVar?.referencesIdentifier(decl.name) == true) {
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.indexExpr?.referencesIdentifier(decl.name) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
@ -92,19 +93,6 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
decl
))
}
} else if(arraysize.constIndex()==null) {
// see if we can calculate the size from other fields
try {
val cval = arraysize.indexVar?.constValue(program) ?: arraysize.origExpression?.constValue(program)
if (cval != null) {
arraysize.indexVar = null
arraysize.origExpression = null
arraysize.indexNum = cval
}
} catch (x: UndefinedSymbolError) {
errors.err(x.message, x.position)
return noModifications
}
}
}
@ -191,7 +179,7 @@ internal class ConstantIdentifierReplacer(private val program: Program, private
if(rangeExpr==null && litval!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < CompilationTarget.instance.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.instance.machine.FLOAT_MAX_POSITIVE)
if (fillvalue < compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", litval.position)
else {
// create the array itself, filled with the fillvalue.

View File

@ -2,11 +2,14 @@ package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.IntegerDatatypes
import prog8.ast.base.NumericDatatypes
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Assignment
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow

View File

@ -1,16 +1,19 @@
package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal fun Program.constantFold(errors: ErrorReporter) {
internal fun Program.constantFold(errors: IErrorReporter, compTarget: ICompilationTarget) {
val valuetypefixer = VarConstantValueTypeAdjuster(this, errors)
valuetypefixer.visit(this)
if(errors.isEmpty()) {
valuetypefixer.applyModifications()
val replacer = ConstantIdentifierReplacer(this, errors)
val replacer = ConstantIdentifierReplacer(this, errors, compTarget)
replacer.visit(this)
if (errors.isEmpty()) {
replacer.applyModifications()
@ -19,7 +22,7 @@ internal fun Program.constantFold(errors: ErrorReporter) {
if(errors.isEmpty()) {
valuetypefixer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this)
val optimizer = ConstantFoldingOptimizer(this, compTarget)
optimizer.visit(this)
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
@ -38,8 +41,11 @@ internal fun Program.constantFold(errors: ErrorReporter) {
}
internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
val optimizer = StatementOptimizer(this, errors)
internal fun Program.optimizeStatements(errors: IErrorReporter,
functions: IBuiltinFunctions,
compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String): Int {
val optimizer = StatementOptimizer(this, errors, functions, compTarget, asmFileLoader)
optimizer.visit(this)
val optimizationCount = optimizer.applyModifications()
@ -54,8 +60,8 @@ internal fun Program.simplifyExpressions() : Int {
return opti.applyModifications()
}
internal fun Program.splitBinaryExpressions() : Int {
val opti = BinExprSplitter(this)
internal fun Program.splitBinaryExpressions(compTarget: ICompilationTarget) : Int {
val opti = BinExprSplitter(this, compTarget)
opti.visit(this)
return opti.applyModifications()
}

View File

@ -1,30 +1,36 @@
package prog8.optimizer
import prog8.ast.IBuiltinFunctions
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.ast.walk.IAstVisitor
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
import kotlin.math.floor
internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter) : AstWalker() {
private val errors: IErrorReporter,
private val functions: IBuiltinFunctions,
private val compTarget: ICompilationTarget,
asmFileLoader: (filename: String, source: Path)->String
) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
private val callgraph = CallGraph(program)
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
private val callgraph = CallGraph(program, asmFileLoader)
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
errors.warn("removing empty block '${block.name}'", block.position)
if(block.name != program.internedStringsModuleName)
errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent as INameScope))
}
@ -50,8 +56,10 @@ internal class StatementOptimizer(private val program: Program,
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
if(!subroutine.isAsmSubroutine) {
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, subroutine.definingScope()))
}
}
return noModifications
@ -70,60 +78,58 @@ internal class StatementOptimizer(private val program: Program,
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in functions.names) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
if (functionName in functions.purefunctionNames) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, functionCallStatement.definingScope()))
}
}
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
// this is a C-64 specific optimization
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print")) {
if(functionCallStatement.target.nameInSource==listOf("txt", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference?
stringVar = if(arg is AddressOf) {
val stringVar: IdentifierReference? = if(arg is AddressOf) {
arg.identifier
} else {
arg as? IdentifierReference
}
if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program.namespace)!!
val vardecl = stringVar.targetVarDecl(program)!!
val string = vardecl.value as? StringLiteralValue
if(string!=null) {
val pos = functionCallStatement.position
if (string.value.length == 1) {
val firstCharEncoded = CompilationTarget.instance.encodeString(string.value, string.altEncoding)[0]
val firstCharEncoded = compTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if (string.value.length == 2) {
val firstTwoCharsEncoded = CompilationTarget.instance.encodeString(string.value.take(2), string.altEncoding)
val firstTwoCharsEncoded = compTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
IdentifierReference(listOf("txt", "chrout"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos
)
val anonscope = AnonymousScope(mutableListOf(), pos)
anonscope.statements.add(chrout1)
anonscope.statements.add(chrout2)
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
return listOf(
IAstModification.InsertBefore(functionCallStatement, chrout1, parent as INameScope),
IAstModification.ReplaceNode(functionCallStatement, chrout2, parent)
)
}
}
}
}
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
val subroutine = functionCallStatement.target.targetSubroutine(program)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return)
@ -135,7 +141,7 @@ internal class StatementOptimizer(private val program: Program,
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program.namespace)
val subroutine = functionCall.target.targetSubroutine(program)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value!=null) {
@ -203,14 +209,14 @@ internal class StatementOptimizer(private val program: Program,
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program.namespace)
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program)
if(iterable!=null) {
if(iterable.datatype==DataType.STR) {
val sv = iterable.value as StringLiteralValue
val size = sv.value.length
if(size==1) {
// loop over string of length 1 -> just assign the single character
val character = CompilationTarget.instance.encodeString(sv.value, sv.altEncoding)[0]
val character = compTarget.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopVar, null, null, forLoop.position), byte, forLoop.position))
@ -291,22 +297,10 @@ internal class StatementOptimizer(private val program: Program,
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
// remove empty choices
class ChoiceRemover(val choice: WhenChoice) : IAstModification {
override fun perform() {
whenStatement.choices.remove(choice)
}
}
return whenStatement.choices
.filter { !it.statements.containsCodeOrVars() }
.map { ChoiceRemover(it) }
}
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
val label = jump.identifier?.targetStatement(program)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, jump.definingScope()))
@ -390,7 +384,7 @@ internal class StatementOptimizer(private val program: Program,
// assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program))?.type
when (bexpr.operator) {
"+" -> {
if (rightCv == 0.0) {

View File

@ -3,24 +3,34 @@ package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.expressions.BinaryExpression
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.PrefixExpression
import prog8.ast.expressions.TypecastExpression
import prog8.ast.statements.*
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstModification
import prog8.compiler.IErrorReporter
import prog8.compiler.target.ICompilationTarget
import java.nio.file.Path
internal class UnusedCodeRemover(private val program: Program, private val errors: ErrorReporter): AstWalker() {
internal class UnusedCodeRemover(private val program: Program,
private val errors: IErrorReporter,
private val compTarget: ICompilationTarget,
private val asmFileLoader: (filename: String, source: Path)->String): AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program)
val callgraph = CallGraph(program, asmFileLoader)
val removals = mutableListOf<IAstModification>()
// remove all subroutines that aren't called, or are empty
// NOTE: part of this is also done already in the StatementOptimizer
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
val forceOutput = "force_output" in sub.definingBlock().options()
if (sub !== entrypoint && !forceOutput && !sub.isAsmSubroutine && (callgraph.calledBy[sub].isNullOrEmpty() || sub.containsNoCodeNorVars())) {
removals.add(IAstModification.Remove(sub, sub.definingScope()))
}
}
@ -92,7 +102,7 @@ internal class UnusedCodeRemover(private val program: Program, private val error
val assign1 = stmtPairs[0] as? Assignment
val assign2 = stmtPairs[1] as? Assignment
if (assign1 != null && assign2 != null && !assign2.isAugmentable) {
if (assign1.target.isSameAs(assign2.target, program) && assign1.target.isInRegularRAM(program.namespace)) {
if (assign1.target.isSameAs(assign2.target, program) && compTarget.isInRegularRAM(assign1.target, program)) {
if(assign2.target.identifier==null || !assign2.value.referencesIdentifier(*(assign2.target.identifier!!.nameInSource.toTypedArray())))
// only remove the second assignment if its value is a simple expression!
when(assign2.value) {

View File

@ -5,19 +5,24 @@ import org.hamcrest.Matchers.closeTo
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.*
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.Position
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.C64Target
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.Cx16Target
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.cx16.CX16MachineDefinition
import prog8.compiler.target.cx16.CX16MachineDefinition.CX16Zeropage
import java.io.CharConversionException
import java.nio.file.Path
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -50,8 +55,8 @@ class TestCompiler {
assertEquals("-\$c382", (-50050).toHex())
assertEquals("-\$ffff", (-65535).toHex())
assertEquals("-\$ffff", (-65535L).toHex())
assertFailsWith<CompilerException> { 65536.toHex() }
assertFailsWith<CompilerException> { 65536L.toHex() }
assertFailsWith<IllegalArgumentException> { 65536.toHex() }
assertFailsWith<IllegalArgumentException> { 65536L.toHex() }
}
@Test
@ -130,7 +135,7 @@ class TestC64Zeropage {
@Test
fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, errors)
@ -143,37 +148,37 @@ class TestC64Zeropage {
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
zp3.allocate("", DataType.FLOAT, null, errors)
}
@Test
fun testZpModesWithFloats() {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true, false, C64Target))
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true, false, C64Target))
}
assertFailsWith<CompilerException> {
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false))
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true, false, C64Target))
}
}
@Test
fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false, false, C64Target))
println(zp.free)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
@ -183,19 +188,19 @@ class TestC64Zeropage {
@Test
fun testFreeSpaces() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp1.available())
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false))
assertEquals(89, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false, false, C64Target))
assertEquals(85, zp2.available())
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, C64Target))
assertEquals(125, zp3.available())
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp1.available())
assertTrue(50 in zp1.free)
assertTrue(100 in zp1.free)
@ -204,7 +209,7 @@ class TestC64Zeropage {
assertTrue(200 in zp1.free)
assertTrue(255 in zp1.free)
assertTrue(199 in zp1.free)
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false))
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false, false, C64Target))
assertEquals(139, zp2.available())
assertFalse(50 in zp2.free)
assertFalse(100 in zp2.free)
@ -217,7 +222,7 @@ class TestC64Zeropage {
@Test
fun testBasicsafeAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available())
assertFailsWith<ZeropageDepletedError> {
@ -240,7 +245,7 @@ class TestC64Zeropage {
@Test
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, C64Target))
assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3)
@ -270,7 +275,7 @@ class TestC64Zeropage {
@Test
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, C64Target))
assertEquals(18, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
@ -289,7 +294,7 @@ class TestC64Zeropage {
@Test
fun testReservedLocations() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, C64Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
}
@ -299,9 +304,30 @@ class TestC64Zeropage {
class TestCx16Zeropage {
@Test
fun testReservedLocations() {
val zp = CX16MachineDefinition.CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false))
val zp = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false, false, Cx16Target))
assertEquals(zp.SCRATCH_REG, zp.SCRATCH_B1+1, "zp _B1 and _REG must be next to each other to create a word")
}
@Test
fun testFreeSpaces() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true, false, Cx16Target))
assertEquals(88, zp1.available())
val zp3 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false, false, Cx16Target))
assertEquals(175, zp3.available())
val zp4 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp4.available())
}
@Test
fun testReservedSpace() {
val zp1 = CX16Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false, false, Cx16Target))
assertEquals(216, zp1.available())
assertTrue(0x22 in zp1.free)
assertTrue(0x80 in zp1.free)
assertTrue(0xff in zp1.free)
assertFalse(0x02 in zp1.free)
assertFalse(0x21 in zp1.free)
}
}
@ -401,74 +427,77 @@ class TestPetscii {
class TestMemory {
private class DummyFunctions: IBuiltinFunctions {
override val names: Set<String> = emptySet()
override val purefunctionNames: Set<String> = emptySet()
override fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue? = null
override fun returnType(name: String, args: MutableList<Expression>) = InferredTypes.InferredType.unknown()
}
private class DummyMemsizer: IMemSizer {
override fun memorySize(dt: DataType): Int = 0
}
@Test
fun testInValidRamC64_memory_addresses() {
CompilationTarget.instance = C64Target
var memexpr = NumericLiteralValue.optimalInteger(0x0000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0x9fff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xc000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
assertTrue(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xcfff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertTrue(target.isInRegularRAM(scope))
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotInValidRamC64_memory_addresses() {
CompilationTarget.instance = C64Target
var memexpr = NumericLiteralValue.optimalInteger(0xa000, Position.DUMMY)
var target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
var scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xafff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xd000, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
assertFalse(C64Target.isInRegularRAM(target, program))
memexpr = NumericLiteralValue.optimalInteger(0xffff, Position.DUMMY)
target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_memory_identifiers() {
CompilationTarget.instance = C64Target
var target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.VAR)
assertTrue(target.isInRegularRAM(target.definingScope()))
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.VAR)
assertFalse(target.isInRegularRAM(target.definingScope()))
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.CONST)
assertTrue(target.isInRegularRAM(target.definingScope()))
assertTrue(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0xd020, VarDeclType.CONST)
assertFalse(target.isInRegularRAM(target.definingScope()))
assertFalse(C64Target.isInRegularRAM(target, program))
target = createTestProgramForMemoryRefViaVar(0x1000, VarDeclType.MEMORY)
assertFalse(target.isInRegularRAM(target.definingScope()))
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
@ -478,89 +507,95 @@ class TestMemory {
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
module.linkParents(ParentSentinel)
return target
}
@Test
fun testInValidRamC64_memory_expression() {
CompilationTarget.instance = C64Target
val memexpr = PrefixExpression("+", NumericLiteralValue.optimalInteger(0x1000, Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, null, DirectMemoryWrite(memexpr, Position.DUMMY), Position.DUMMY)
val scope = AnonymousScope(mutableListOf(), Position.DUMMY)
assertFalse(target.isInRegularRAM(scope))
val program = Program("test", mutableListOf(), DummyFunctions(), DummyMemsizer())
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_variable() {
CompilationTarget.instance = C64Target
val decl = VarDecl(VarDeclType.VAR, DataType.BYTE, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_memmap_variable() {
CompilationTarget.instance = C64Target
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotInValidRamC64_memmap_variable() {
CompilationTarget.instance = C64Target
val address = 0xd020
val decl = VarDecl(VarDeclType.MEMORY, DataType.UBYTE, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val target = AssignTarget(IdentifierReference(listOf("address"), Position.DUMMY), null, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertFalse(target.isInRegularRAM(target.definingScope()))
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_array() {
CompilationTarget.instance = C64Target
val decl = VarDecl(VarDeclType.VAR, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, null, false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testInValidRamC64_array_memmapped() {
CompilationTarget.instance = C64Target
val address = 0x1000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertTrue(target.isInRegularRAM(target.definingScope()))
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertTrue(C64Target.isInRegularRAM(target, program))
}
@Test
fun testNotValidRamC64_array_memmapped() {
CompilationTarget.instance = C64Target
val address = 0xe000
val decl = VarDecl(VarDeclType.MEMORY, DataType.ARRAY_UB, ZeropageWish.DONTCARE, null, "address", null, NumericLiteralValue.optimalInteger(address, Position.DUMMY), false, false, Position.DUMMY)
val arrayindexed = ArrayIndexedExpression(IdentifierReference(listOf("address"), Position.DUMMY), ArrayIndex(NumericLiteralValue.optimalInteger(1, Position.DUMMY), Position.DUMMY), Position.DUMMY)
val target = AssignTarget(null, arrayindexed, null, Position.DUMMY)
val assignment = Assignment(target, NumericLiteralValue.optimalInteger(0, Position.DUMMY), Position.DUMMY)
val subroutine = Subroutine("test", emptyList(), emptyList(), emptyList(), emptyList(), emptySet(), null, false, false, mutableListOf(decl, assignment), Position.DUMMY)
subroutine.linkParents(ParentSentinel)
assertFalse(target.isInRegularRAM(target.definingScope()))
val module = Module("test", mutableListOf(subroutine), Position.DUMMY, false, Path.of(""))
val program = Program("test", mutableListOf(module), DummyFunctions(), DummyMemsizer())
module.linkParents(ParentSentinel)
assertFalse(C64Target.isInRegularRAM(target, program))
}
}

View File

@ -0,0 +1,13 @@
.PHONY: all clean test
all: test
clean:
rm -f *.prg *.asm *.vice-*
test: clean
p8compile -target cx16 *.p8 >/dev/null
for program in *.prg; do \
echo "RUNNING:" $$program ; \
x16emu -run -prg $$program >/dev/null ; \
done

View File

@ -0,0 +1,18 @@
.PHONY: all clean test
all: test
clean:
rm -f *.prg *.asm *.vice-* test_*.p8
test: clean generate test_prgs
generate:
python make_tests.py
p8compile -noopt -target cx16 *.p8 >/dev/null
test_prgs:
for program in *.prg; do \
echo "RUNNING:" $$program ; \
x16emu -run -prg $$program >/dev/null ; \
done

View File

@ -0,0 +1,508 @@
# generates various Prog8 files with a huge amount of number comparion tests,
# for all supported datatypes and all comparison operators.
import sys
index = 0
def minmaxvalues(dt):
if dt == "ubyte":
return 0, 255
elif dt == "uword":
return 0, 65535
elif dt == "byte":
return -128, 127
elif dt == "word":
return -32768, 32767
elif dt == "float":
return -99999999, 99999999
else:
raise ValueError(dt)
def gen_test(dt, comparison, left, right, expected):
global index
etxt = f"{left} {comparison} {right}"
if eval(etxt) != expected:
raise ValueError("invalid comparison: "+etxt+" for "+dt)
if expected:
stmt_ok = lambda ix: "num_successes++"
stmt_else = lambda ix: f"error({ix})"
else:
stmt_ok = lambda ix: f"error({ix})"
stmt_else = lambda ix: "num_successes++"
def c(number):
if dt not in ("byte", "ubyte"):
return f"({number} as {dt})"
return str(number)
print(
f""" left = {c(left)}
right = {c(right)}
"""
)
# const <op> const
index += 1
print(
f""" ; test #{index}
if {c(left)} {comparison} {c(right)} {{
{stmt_ok(index)}
}} else {{
{stmt_else(index)}
}}
""")
# const <op> var
index += 1
print(
f""" ; test #{index}
if {c(left)} {comparison} right {{
{stmt_ok(index)}
}} else {{
{stmt_else(index)}
}}
""")
# const <op> expr
index += 1
print(
f""" ; test #{index}
if {c(left)} {comparison} right+zero {{
{stmt_ok(index)}
}} else {{
{stmt_else(index)}
}}
""")
# var <op> const
index += 1
print(
f""" ; test #{index}
if left {comparison} {c(right)} {{
{stmt_ok(index)}
}} else {{
{stmt_else(index)}
}}
""")
# var <op> var
index += 1
print(
f""" ; test #{index}
if left {comparison} right {{
{stmt_ok(index)}
}} else {{
{stmt_else(index)}
}}
""")
# var <op> expr
index += 1
print(
f""" ; test #{index}
if left {comparison} right+zero {{
{stmt_ok(index)}
}} else {{
{stmt_else(index)}
}}
""")
# expr <op> const
index += 1
print(
f""" ; test #{index}
if left+zero {comparison} {c(right)} {{
{stmt_ok(index)}
}} else {{
{stmt_else(index)}
}}
""")
# expr <op> var
index += 1
print(
f""" ; test #{index}
if left+zero {comparison} right {{
{stmt_ok(index)}
}} else {{
{stmt_else(index)}
}}
""")
# expr <op> expr
index += 1
print(
f""" ; test #{index}
if left+zero {comparison} right+zero {{
{stmt_ok(index)}
}} else {{
{stmt_else(index)}
}}
""")
def gen_comp_header(dt, operator):
print(" ; tests: ", dt, operator)
print(" comparison = \""+operator+"\"")
print(" txt.print(datatype)")
print(" txt.spc()")
print(" txt.print(comparison)")
print(" txt.nl()")
def gen_comp_equal(dt):
minval, maxval = minmaxvalues(dt)
gen_comp_header(dt, "==")
gen_test(dt, "==", 0, 0, True)
gen_test(dt, "==", 0, 1, False)
gen_test(dt, "==", 100, 100, True)
gen_test(dt, "==", 100, 101, False)
if maxval >= 200:
gen_test(dt, "==", 200, 200, True)
gen_test(dt, "==", 200, 201, False)
if maxval >= 9999:
gen_test(dt, "==", 9999, 9999, True)
gen_test(dt, "==", 9999, 10000, False)
gen_test(dt, "==", 0x5000, 0x5000, True)
gen_test(dt, "==", 0x5000, 0x5001, False)
gen_test(dt, "==", 0x5000, 0x4fff, False)
if maxval >= 30000:
gen_test(dt, "==", 30000, 30000, True)
gen_test(dt, "==", 30000, 30001, False)
if maxval >= 40000:
gen_test(dt, "==", 0xf000, 0xf000, True)
gen_test(dt, "==", 0xf000, 0xf001, False)
gen_test(dt, "==", 0xf000, 0xffff, False)
if minval < 0:
gen_test(dt, "==", 0, -1, False)
gen_test(dt, "==", -100, -100, True)
if minval < -200:
gen_test(dt, "==", -200, -200, True)
gen_test(dt, "==", -200, -201, False)
if minval < -9999:
gen_test(dt, "==", -0x5000, -0x5000, True)
gen_test(dt, "==", -0x5000, -0x5001, False)
gen_test(dt, "==", -0x5000, -0x4fff, False)
gen_test(dt, "==", -9999, -9999, True)
gen_test(dt, "==", -9999, -10000, False)
gen_test(dt, "==", minval, minval, True)
gen_test(dt, "==", minval, minval+1, False)
gen_test(dt, "==", maxval, maxval, True)
gen_test(dt, "==", maxval, maxval-1, False)
def gen_comp_notequal(dt):
minval, maxval = minmaxvalues(dt)
gen_comp_header(dt, "!=")
gen_test(dt, "!=", 0, 0, False)
gen_test(dt, "!=", 0, 1, True)
gen_test(dt, "!=", 100, 100, False)
gen_test(dt, "!=", 100, 101, True)
if maxval >= 200:
gen_test(dt, "!=", 200, 200, False)
gen_test(dt, "!=", 200, 201, True)
if maxval >= 9999:
gen_test(dt, "!=", 9999, 9999, False)
gen_test(dt, "!=", 9999, 10000, True)
gen_test(dt, "!=", 0x5000, 0x5000, False)
gen_test(dt, "!=", 0x5000, 0x5001, True)
gen_test(dt, "!=", 0x5000, 0x4fff, True)
if maxval >= 30000:
gen_test(dt, "!=", 30000, 30000, False)
gen_test(dt, "!=", 30000, 30001, True)
if maxval >= 40000:
gen_test(dt, "!=", 0xf000, 0xf000, False)
gen_test(dt, "!=", 0xf000, 0xf001, True)
gen_test(dt, "!=", 0xf000, 0xffff, True)
if minval < 0:
gen_test(dt, "!=", 0, -1, True)
gen_test(dt, "!=", -100, -100, False)
if minval < -200:
gen_test(dt, "!=", -200, -200, False)
gen_test(dt, "!=", -200, -201, True)
if minval < -9999:
gen_test(dt, "!=", -0x5000, -0x5000, False)
gen_test(dt, "!=", -0x5000, -0x5001, True)
gen_test(dt, "!=", -0x5000, -0x4fff, True)
gen_test(dt, "!=", -9999, -9999, False)
gen_test(dt, "!=", -9999, -10000, True)
gen_test(dt, "!=", minval, minval, False)
gen_test(dt, "!=", minval, minval+1, True)
gen_test(dt, "!=", maxval, maxval, False)
gen_test(dt, "!=", maxval, maxval-1, True)
def gen_comp_less(dt):
minval, maxval = minmaxvalues(dt)
gen_comp_header(dt, "<")
gen_test(dt, "<", 0, 0, False)
gen_test(dt, "<", 0, 1, True)
gen_test(dt, "<", 100, 100, False)
gen_test(dt, "<", 100, 101, True)
gen_test(dt, "<", 100, 99, False)
if maxval >= 200:
gen_test(dt, "<", 200, 200, False)
gen_test(dt, "<", 200, 201, True)
gen_test(dt, "<", 200, 199, False)
if maxval >= 9999:
gen_test(dt, "<", 9999, 9999, False)
gen_test(dt, "<", 9999, 10000, True)
gen_test(dt, "<", 9999, 9998, False)
gen_test(dt, "<", 0x5000, 0x5000, False)
gen_test(dt, "<", 0x5000, 0x5001, True)
gen_test(dt, "<", 0x5000, 0x4fff, False)
if maxval >= 30000:
gen_test(dt, "<", 30000, 30000, False)
gen_test(dt, "<", 30000, 30001, True)
gen_test(dt, "<", 30000, 29999, False)
if maxval >= 40000:
gen_test(dt, "<", 0xf000, 0xf000, False)
gen_test(dt, "<", 0xf000, 0xf001, True)
gen_test(dt, "<", 0xf000, 0xefff, False)
if minval < 0:
gen_test(dt, "<", 0, -1, False)
gen_test(dt, "<", -100, -100, False)
gen_test(dt, "<", -100, -101, False)
gen_test(dt, "<", -100, -99, True)
if minval < -200:
gen_test(dt, "<", -200, -200, False)
gen_test(dt, "<", -200, -201, False)
gen_test(dt, "<", -200, -199, True)
if minval < -9999:
gen_test(dt, "<", -0x5000, -0x5000, False)
gen_test(dt, "<", -0x5000, -0x5001, False)
gen_test(dt, "<", -0x5000, -0x4fff, True)
gen_test(dt, "<", -9999, -9999, False)
gen_test(dt, "<", -9999, -10000, False)
gen_test(dt, "<", -9999, -9998, True)
def gen_comp_greater(dt):
minval, maxval = minmaxvalues(dt)
gen_comp_header(dt, ">")
gen_test(dt, ">", 0, 0, False)
gen_test(dt, ">", 0, 1, False)
gen_test(dt, ">", 100, 100, False)
gen_test(dt, ">", 100, 101, False)
gen_test(dt, ">", 100, 99, True)
if maxval >= 200:
gen_test(dt, ">", 200, 200, False)
gen_test(dt, ">", 200, 201, False)
gen_test(dt, ">", 200, 199, True)
if maxval >= 9999:
gen_test(dt, ">", 9999, 9999, False)
gen_test(dt, ">", 9999, 10000, False)
gen_test(dt, ">", 9999, 9998, True)
gen_test(dt, ">", 0x5000, 0x5000, False)
gen_test(dt, ">", 0x5000, 0x5001, False)
gen_test(dt, ">", 0x5000, 0x4fff, True)
if maxval >= 30000:
gen_test(dt, ">", 30000, 30000, False)
gen_test(dt, ">", 30000, 30001, False)
gen_test(dt, ">", 30000, 29999, True)
if maxval >= 40000:
gen_test(dt, ">", 0xf000, 0xf000, False)
gen_test(dt, ">", 0xf000, 0xf001, False)
gen_test(dt, ">", 0xf000, 0xefff, True)
if minval < 0:
gen_test(dt, ">", 0, -1, True)
gen_test(dt, ">", -100, -100, False)
gen_test(dt, ">", -100, -101, True)
gen_test(dt, ">", -100, -99, False)
if minval < -200:
gen_test(dt, ">", -200, -200, False)
gen_test(dt, ">", -200, -201, True)
gen_test(dt, ">", -200, -199, False)
if minval < -9999:
gen_test(dt, ">", -0x5000, -0x5000, False)
gen_test(dt, ">", -0x5000, -0x5001, True)
gen_test(dt, ">", -0x5000, -0x4fff, False)
gen_test(dt, ">", -9999, -9999, False)
gen_test(dt, ">", -9999, -10000, True)
gen_test(dt, ">", -9999, -9998, False)
def gen_comp_lessequal(dt):
minval, maxval = minmaxvalues(dt)
gen_comp_header(dt, "<=")
gen_test(dt, "<=", 0, 0, True)
gen_test(dt, "<=", 0, 1, True)
gen_test(dt, "<=", 100, 100, True)
gen_test(dt, "<=", 100, 101, True)
gen_test(dt, "<=", 100, 99, False)
if maxval >= 200:
gen_test(dt, "<=", 200, 200, True)
gen_test(dt, "<=", 200, 201, True)
gen_test(dt, "<=", 200, 199, False)
if maxval >= 9999:
gen_test(dt, "<=", 9999, 9999, True)
gen_test(dt, "<=", 9999, 10000, True)
gen_test(dt, "<=", 9999, 9998, False)
gen_test(dt, "<=", 0x5000, 0x5000, True)
gen_test(dt, "<=", 0x5000, 0x5001, True)
gen_test(dt, "<=", 0x5000, 0x4fff, False)
if maxval >= 30000:
gen_test(dt, "<=", 30000, 30000, True)
gen_test(dt, "<=", 30000, 30001, True)
gen_test(dt, "<=", 30000, 29999, False)
if maxval >= 40000:
gen_test(dt, "<=", 0xf000, 0xf000, True)
gen_test(dt, "<=", 0xf000, 0xf001, True)
gen_test(dt, "<=", 0xf000, 0xefff, False)
if minval < 0:
gen_test(dt, "<=", 0, -1, False)
gen_test(dt, "<=", -100, -100, True)
gen_test(dt, "<=", -100, -101, False)
gen_test(dt, "<=", -100, -99, True)
if minval < -200:
gen_test(dt, "<=", -200, -200, True)
gen_test(dt, "<=", -200, -201, False)
gen_test(dt, "<=", -200, -199, True)
if minval < -9999:
gen_test(dt, "<=", -0x5000, -0x5000, True)
gen_test(dt, "<=", -0x5000, -0x5001, False)
gen_test(dt, "<=", -0x5000, -0x4fff, True)
gen_test(dt, "<=", -9999, -9999, True)
gen_test(dt, "<=", -9999, -10000, False)
gen_test(dt, "<=", -9999, -9998, True)
def gen_comp_greaterequal(dt):
minval, maxval = minmaxvalues(dt)
gen_comp_header(dt, ">=")
gen_test(dt, ">=", 0, 0, True)
gen_test(dt, ">=", 0, 1, False)
gen_test(dt, ">=", 100, 100, True)
gen_test(dt, ">=", 100, 101, False)
gen_test(dt, ">=", 100, 99, True)
if maxval >= 200:
gen_test(dt, ">=", 200, 200, True)
gen_test(dt, ">=", 200, 201, False)
gen_test(dt, ">=", 200, 199, True)
if maxval >= 9999:
gen_test(dt, ">=", 9999, 9999, True)
gen_test(dt, ">=", 9999, 10000, False)
gen_test(dt, ">=", 9999, 9998, True)
gen_test(dt, ">=", 0x5000, 0x5000, True)
gen_test(dt, ">=", 0x5000, 0x5001, False)
gen_test(dt, ">=", 0x5000, 0x4fff, True)
if maxval >= 30000:
gen_test(dt, ">=", 30000, 30000, True)
gen_test(dt, ">=", 30000, 30001, False)
gen_test(dt, ">=", 30000, 29999, True)
if maxval >= 40000:
gen_test(dt, ">=", 0xf000, 0xf000, True)
gen_test(dt, ">=", 0xf000, 0xf001, False)
gen_test(dt, ">=", 0xf000, 0xefff, True)
if minval < 0:
gen_test(dt, ">=", 0, -1, True)
gen_test(dt, ">=", -100, -100, True)
gen_test(dt, ">=", -100, -101, True)
gen_test(dt, ">=", -100, -99, False)
if minval < -200:
gen_test(dt, ">=", -200, -200, True)
gen_test(dt, ">=", -200, -201, True)
gen_test(dt, ">=", -200, -199, False)
if minval < -9999:
gen_test(dt, ">=", -0x5000, -0x5000, True)
gen_test(dt, ">=", -0x5000, -0x5001, True)
gen_test(dt, ">=", -0x5000, -0x4fff, False)
gen_test(dt, ">=", -9999, -9999, True)
gen_test(dt, ">=", -9999, -10000, True)
gen_test(dt, ">=", -9999, -9998, False)
def generate_test_routine_equalsnotequals(dt):
print(f"""
sub test_comparisons() {{
{dt} left
{dt} right
{dt} zero = 0
""")
gen_comp_equal(dt)
gen_comp_notequal(dt)
print(" }")
def generate_test_routine_lessgreater(dt):
print(f"""
sub test_comparisons() {{
{dt} left
{dt} right
{dt} zero = 0
""")
gen_comp_less(dt)
gen_comp_greater(dt)
print(" }")
def generate_test_routine_lessequalsgreaterequals(dt):
print(f"""
sub test_comparisons() {{
{dt} left
{dt} right
{dt} zero = 0
""")
gen_comp_lessequal(dt)
gen_comp_greaterequal(dt)
print(" }")
def generate(dt, operators):
global index
index = 0
print(f"""
%import textio
%import floats
%import test_stack
%zeropage basicsafe
main {{
uword num_errors = 0
uword num_successes = 0
str datatype = "{dt}"
uword comparison
sub start() {{
test_comparisons()
print_results()
test_stack.test()
}}
sub error(uword index) {{
txt.print(" ! error in test ")
txt.print_uw(index)
txt.chrout(' ')
txt.print(datatype)
txt.chrout(' ')
txt.print(comparison)
txt.nl()
num_errors++
}}
""")
if operators=="eq":
generate_test_routine_equalsnotequals(dt)
elif operators=="lt":
generate_test_routine_lessgreater(dt)
elif operators=="lteq":
generate_test_routine_lessequalsgreaterequals(dt)
else:
raise ValueError(operators)
print(f"""
sub print_results() {{
txt.nl()
txt.print("total {index}: ")
txt.print_uw(num_successes)
txt.print(" good, ")
txt.print_uw(num_errors)
txt.print(" errors!\\n")
}}
}}
""")
if __name__ == '__main__':
for dt in ["ubyte", "uword", "byte", "word", "float"]:
sys.stdout = open(f"test_{dt}_eq.p8", "wt")
generate(dt, "eq")
sys.stdout = open(f"test_{dt}_lt.p8", "wt")
generate(dt, "lt")
sys.stdout = open(f"test_{dt}_lteq.p8", "wt")
generate(dt, "lteq")

View File

@ -396,14 +396,18 @@ galaxy10 {
sub init(ubyte galaxy10num) {
number = 1
planet10.number = 255
seed = [base0, base1, base2]
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy10num-1 {
nextgalaxy10()
}
}
sub nextgalaxy10() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
@ -576,8 +580,10 @@ galaxy10 {
planet10.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet10.species_kind = (planet10.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet10.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
planet10.goatsoup_seed[0] = lsb(seed[1])
planet10.goatsoup_seed[1] = msb(seed[1])
planet10.goatsoup_seed[2] = lsb(seed[2])
planet10.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {

View File

@ -396,14 +396,18 @@ galaxy2 {
sub init(ubyte galaxy2num) {
number = 1
planet2.number = 255
seed = [base0, base1, base2]
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy2num-1 {
nextgalaxy2()
}
}
sub nextgalaxy2() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
@ -577,7 +581,10 @@ galaxy2 {
planet2.species_kind = (planet2.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet2.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
planet2.goatsoup_seed[0] = lsb(seed[1])
planet2.goatsoup_seed[1] = msb(seed[1])
planet2.goatsoup_seed[2] = lsb(seed[2])
planet2.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {

View File

@ -396,14 +396,18 @@ galaxy3 {
sub init(ubyte galaxy3num) {
number = 1
planet3.number = 255
seed = [base0, base1, base2]
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy3num-1 {
nextgalaxy3()
}
}
sub nextgalaxy3() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
@ -577,7 +581,10 @@ galaxy3 {
planet3.species_kind = (planet3.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet3.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
planet3.goatsoup_seed[0] = lsb(seed[1])
planet3.goatsoup_seed[1] = msb(seed[1])
planet3.goatsoup_seed[2] = lsb(seed[2])
planet3.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {

View File

@ -396,14 +396,18 @@ galaxy4 {
sub init(ubyte galaxy4num) {
number = 1
planet4.number = 255
seed = [base0, base1, base2]
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy4num-1 {
nextgalaxy4()
}
}
sub nextgalaxy4() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
@ -577,7 +581,10 @@ galaxy4 {
planet4.species_kind = (planet4.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet4.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
planet4.goatsoup_seed[0] = lsb(seed[1])
planet4.goatsoup_seed[1] = msb(seed[1])
planet4.goatsoup_seed[2] = lsb(seed[2])
planet4.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {

View File

@ -396,14 +396,18 @@ galaxy5 {
sub init(ubyte galaxy5num) {
number = 1
planet5.number = 255
seed = [base0, base1, base2]
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy5num-1 {
nextgalaxy5()
}
}
sub nextgalaxy5() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
@ -577,7 +581,10 @@ galaxy5 {
planet5.species_kind = (planet5.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet5.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
planet5.goatsoup_seed[0] = lsb(seed[1])
planet5.goatsoup_seed[1] = msb(seed[1])
planet5.goatsoup_seed[2] = lsb(seed[2])
planet5.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {

View File

@ -396,14 +396,18 @@ galaxy6 {
sub init(ubyte galaxy6num) {
number = 1
planet6.number = 255
seed = [base0, base1, base2]
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy6num-1 {
nextgalaxy6()
}
}
sub nextgalaxy6() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
@ -577,7 +581,10 @@ galaxy6 {
planet6.species_kind = (planet6.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet6.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
planet6.goatsoup_seed[0] = lsb(seed[1])
planet6.goatsoup_seed[1] = msb(seed[1])
planet6.goatsoup_seed[2] = lsb(seed[2])
planet6.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {

View File

@ -396,14 +396,18 @@ galaxy7 {
sub init(ubyte galaxy7num) {
number = 1
planet7.number = 255
seed = [base0, base1, base2]
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy7num-1 {
nextgalaxy7()
}
}
sub nextgalaxy7() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
@ -577,7 +581,10 @@ galaxy7 {
planet7.species_kind = (planet7.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet7.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
planet7.goatsoup_seed[0] = lsb(seed[1])
planet7.goatsoup_seed[1] = msb(seed[1])
planet7.goatsoup_seed[2] = lsb(seed[2])
planet7.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {

View File

@ -396,14 +396,18 @@ galaxy8 {
sub init(ubyte galaxy8num) {
number = 1
planet8.number = 255
seed = [base0, base1, base2]
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy8num-1 {
nextgalaxy8()
}
}
sub nextgalaxy8() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
@ -577,7 +581,10 @@ galaxy8 {
planet8.species_kind = (planet8.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet8.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
planet8.goatsoup_seed[0] = lsb(seed[1])
planet8.goatsoup_seed[1] = msb(seed[1])
planet8.goatsoup_seed[2] = lsb(seed[2])
planet8.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {

View File

@ -396,14 +396,18 @@ galaxy9 {
sub init(ubyte galaxy9num) {
number = 1
planet9.number = 255
seed = [base0, base1, base2]
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxy9num-1 {
nextgalaxy9()
}
}
sub nextgalaxy9() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
@ -576,8 +580,10 @@ galaxy9 {
planet9.species_look = (seed2_msb ^ msb(seed[1])) & 7 ;bits 0-2 of (w0_hi EOR w1_hi)
planet9.species_kind = (planet9.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet9.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
planet9.goatsoup_seed[0] = lsb(seed[1])
planet9.goatsoup_seed[1] = msb(seed[1])
planet9.goatsoup_seed[2] = lsb(seed[2])
planet9.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {

View File

@ -406,14 +406,18 @@ galaxy {
sub init(ubyte galaxynum) {
number = 1
planet.number = 255
seed = [base0, base1, base2]
seed[0] = base0
seed[1] = base1
seed[2] = base2
repeat galaxynum-1 {
nextgalaxy()
}
}
sub nextgalaxy() {
seed = [twist(seed[0]), twist(seed[1]), twist(seed[2])]
seed[0] = twist(seed[0])
seed[1] = twist(seed[1])
seed[2] = twist(seed[2])
number++
if number==9
number = 1
@ -587,7 +591,10 @@ galaxy {
planet.species_kind = (planet.species_look + (seed2_msb & 3)) & 7 ;Add bits 0-1 of w2_hi to A from previous step, and take bits 0-2 of the result
}
planet.goatsoup_seed = [lsb(seed[1]), msb(seed[1]), lsb(seed[2]), seed2_msb]
planet.goatsoup_seed[0] = lsb(seed[1])
planet.goatsoup_seed[1] = msb(seed[1])
planet.goatsoup_seed[2] = lsb(seed[2])
planet.goatsoup_seed[3] = seed2_msb
}
sub tweakseed() {

67
compilerAst/build.gradle Normal file
View File

@ -0,0 +1,67 @@
plugins {
id 'antlr'
id 'java'
id "org.jetbrains.kotlin.jvm" version "1.4.30"
}
targetCompatibility = 11
sourceCompatibility = 11
repositories {
mavenCentral()
}
configurations {
// strange antlr plugin issue, see https://github.com/gradle/gradle/issues/820
// this avoids linking in the complete antlr binary jar
compile {
extendsFrom = extendsFrom.findAll { it != configurations.antlr }
}
}
dependencies {
antlr 'org.antlr:antlr4:4.9'
implementation 'org.antlr:antlr4-runtime:4.9'
implementation project(':parser')
// antlr('org.antlr:antlr4:4.9') {
// exclude group: 'com.ibm.icu', module: 'icu4j'
// }
}
compileKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "11"
useIR = true
}
}
sourceSets {
main {
java {
srcDirs = ["${project.projectDir}/src"]
}
resources {
srcDirs = ["${project.projectDir}/res"]
}
}
test {
java {
srcDirs = ["${project.projectDir}/test"]
}
}
}
task wrapper(type: Wrapper) {
gradleVersion = '6.7'
}

View File

@ -0,0 +1,14 @@
<?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" />
</content>
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="antlr-runtime-4.9" level="project" />
</component>
</module>

View File

@ -5,9 +5,9 @@ import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.toHex
import prog8.ast.walk.IAstVisitor
class AstToSourceCode(val output: (text: String) -> Unit, val program: Program): IAstVisitor {
private var scopelevel = 0
@ -121,8 +121,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(datatypeString(decl.datatype))
if(decl.arraysize!=null) {
decl.arraysize!!.indexNum?.accept(this)
decl.arraysize!!.indexVar?.accept(this)
decl.arraysize!!.indexExpr.accept(this)
}
if(decl.isArray)
output("]")
@ -365,8 +364,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(arrayIndexedExpression: ArrayIndexedExpression) {
arrayIndexedExpression.arrayvar.accept(this)
output("[")
arrayIndexedExpression.indexer.indexNum?.accept(this)
arrayIndexedExpression.indexer.indexVar?.accept(this)
arrayIndexedExpression.indexer.indexExpr.accept(this)
output("]")
}
@ -413,10 +411,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
outputlni("}}")
}
override fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder) {
output(builtinFunctionStatementPlaceholder.name)
}
override fun visit(whenStatement: WhenStatement) {
output("when ")
whenStatement.condition.accept(this)

View File

@ -1,14 +1,17 @@
package prog8.ast
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
import prog8.ast.walk.AstWalker
import prog8.ast.walk.IAstVisitor
import java.nio.file.Path
import kotlin.math.abs
interface IStringEncoding {
fun encodeString(str: String, altEncoding: Boolean): List<Short>
fun decodeString(bytes: List<Short>, altEncoding: Boolean): String
}
interface Node {
val position: Position
@ -238,16 +241,47 @@ interface IAssignable {
// just a tag for now
}
interface IMemSizer {
fun memorySize(dt: DataType): Int
}
interface IBuiltinFunctions {
val names: Set<String>
val purefunctionNames: Set<String>
fun constValue(name: String, args: List<Expression>, position: Position, memsizer: IMemSizer): NumericLiteralValue?
fun returnType(name: String, args: MutableList<Expression>): InferredTypes.InferredType
}
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String, val modules: MutableList<Module>): Node {
val namespace = GlobalNamespace(modules)
class Program(val name: String,
val modules: MutableList<Module>,
val builtinFunctions: IBuiltinFunctions,
val memsizer: IMemSizer): Node {
val namespace = GlobalNamespace(modules, builtinFunctions.names)
val mainModule: Module
get() = modules.first { it.name!=internedStringsModuleName }
val definedLoadAddress: Int
get() = modules.first().loadAddress
get() = mainModule.loadAddress
var actualLoadAddress: Int = 0
private val internedStrings = mutableMapOf<Pair<String, Boolean>, List<String>>()
val internedStringsModuleName = "prog8_interned_strings"
init {
// insert a container module for all interned strings later
if(modules.firstOrNull()?.name != internedStringsModuleName) {
val internedStringsModule = Module(internedStringsModuleName, mutableListOf(), Position.DUMMY, false, Path.of(""))
modules.add(0, internedStringsModule)
val block = Block(internedStringsModuleName, null, mutableListOf(), false, Position.DUMMY)
internedStringsModule.statements.add(block)
internedStringsModule.linkParents(this)
internedStringsModule.program = this
}
}
fun entrypoint(): Subroutine? {
val mainBlocks = allBlocks().filter { it.name=="main" }
@ -260,6 +294,22 @@ class Program(val name: String, val modules: MutableList<Module>): Node {
}
}
fun internString(string: StringLiteralValue): List<String> {
val key = Pair(string.value, string.altEncoding)
val existing = internedStrings[key]
if(existing!=null)
return existing
val decl = VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, "string_${internedStrings.size}", null, string,
isArray = false, autogeneratedDontRemove = true, position = string.position)
val internedStringsBlock = modules.first { it.name==internedStringsModuleName }.statements.first { it is Block && it.name == internedStringsModuleName}
(internedStringsBlock as Block).statements.add(decl)
decl.linkParents(internedStringsBlock)
val scopedName = listOf(internedStringsModuleName, decl.name)
internedStrings[key] = scopedName
return scopedName
}
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
override val position: Position = Position.DUMMY
@ -317,7 +367,7 @@ class Module(override val name: String,
}
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
class GlobalNamespace(val modules: List<Module>, private val builtinFunctionNames: Set<String>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
override val statements = mutableListOf<Statement>() // not used
@ -332,7 +382,7 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
}
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
if (scopedName.size == 1 && scopedName[0] in builtinFunctionNames) {
// builtin functions always exist, return a dummy localContext for them
val builtinPlaceholder = Label("builtin::${scopedName.last()}", localContext.position)
builtinPlaceholder.parent = ParentSentinel
@ -379,4 +429,21 @@ object BuiltinFunctionScopePlaceholder : INameScope {
// prefix for struct member variables
internal fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName"
fun mangledStructMemberName(varName: String, memberName: String) = "prog8struct_${varName}_$memberName"
fun Number.toHex(): String {
// 0..15 -> "0".."15"
// 16..255 -> "$10".."$ff"
// 256..65536 -> "$0100".."$ffff"
// negative values are prefixed with '-'.
val integer = this.toInt()
if(integer<0)
return '-' + abs(integer).toHex()
return when (integer) {
in 0 until 16 -> integer.toString()
in 0 until 0x100 -> "$"+integer.toString(16).padStart(2,'0')
in 0 until 0x10000 -> "$"+integer.toString(16).padStart(4,'0')
else -> throw IllegalArgumentException("number too large for 16 bits $this")
}
}

View File

@ -3,11 +3,11 @@ package prog8.ast.antlr
import org.antlr.v4.runtime.IntStream
import org.antlr.v4.runtime.ParserRuleContext
import org.antlr.v4.runtime.tree.TerminalNode
import prog8.ast.IStringEncoding
import prog8.ast.Module
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.parser.CustomLexer
import prog8.parser.prog8Parser
import java.io.CharConversionException
@ -19,10 +19,10 @@ import java.nio.file.Path
private data class NumericLiteral(val number: Number, val datatype: DataType)
internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path, encoding: IStringEncoding) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
val directives = this.directive().map { it.toAst() }
val blocks = this.block().map { it.toAst(isLibrary) }
val blocks = this.block().map { it.toAst(isLibrary, encoding) }
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), isLibrary, source)
}
@ -38,11 +38,11 @@ private fun ParserRuleContext.toPosition() : Position {
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
}
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement {
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean, encoding: IStringEncoding) : Statement {
val blockstatements = block_statement().map {
when {
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
it.variabledeclaration()!=null -> it.variabledeclaration().toAst(encoding)
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst(encoding)
it.directive()!=null -> it.directive().toAst()
it.inlineasm()!=null -> it.inlineasm().toAst()
else -> throw FatalAstException("weird block statement $it")
@ -51,11 +51,11 @@ private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement {
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
}
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList()
private fun prog8Parser.Statement_blockContext.toAst(encoding: IStringEncoding): MutableList<Statement> =
statement().asSequence().map { it.toAst(encoding) }.toMutableList()
private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
vardecl()?.let { return it.toAst() }
private fun prog8Parser.VariabledeclarationContext.toAst(encoding: IStringEncoding) : Statement {
vardecl()?.let { return it.toAst(encoding) }
varinitializer()?.let {
val vd = it.vardecl()
@ -63,10 +63,10 @@ private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
VarDeclType.VAR,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.arrayindex()?.toAst(encoding),
vd.varname.text,
null,
it.expression().toAst(),
it.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
it.toPosition()
@ -82,7 +82,7 @@ private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
null,
vd.varname.text,
vd.structname.text,
it.expression().toAst(),
it.expression().toAst(encoding),
isArray = false,
autogeneratedDontRemove = false,
position = it.toPosition()
@ -111,10 +111,10 @@ private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
VarDeclType.CONST,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.arrayindex()?.toAst(encoding),
vd.varname.text,
null,
cvarinit.expression().toAst(),
cvarinit.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
cvarinit.toPosition()
@ -128,10 +128,10 @@ private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
VarDeclType.MEMORY,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.arrayindex()?.toAst(encoding),
vd.varname.text,
null,
mvarinit.expression().toAst(),
mvarinit.expression().toAst(encoding),
vd.ARRAYSIG() != null || vd.arrayindex() != null,
false,
mvarinit.toPosition()
@ -140,40 +140,40 @@ private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
it.vardecl().map { vd->vd.toAst(encoding) }.toMutableList(),
toPosition())
}
throw FatalAstException("weird variable decl $this")
}
private fun prog8Parser.SubroutinedeclarationContext.toAst() : Subroutine {
private fun prog8Parser.SubroutinedeclarationContext.toAst(encoding: IStringEncoding) : Subroutine {
return when {
subroutine()!=null -> subroutine().toAst()
asmsubroutine()!=null -> asmsubroutine().toAst()
subroutine()!=null -> subroutine().toAst(encoding)
asmsubroutine()!=null -> asmsubroutine().toAst(encoding)
romsubroutine()!=null -> romsubroutine().toAst()
else -> throw FatalAstException("weird subroutine decl $this")
}
}
private fun prog8Parser.StatementContext.toAst() : Statement {
val vardecl = variabledeclaration()?.toAst()
private fun prog8Parser.StatementContext.toAst(encoding: IStringEncoding) : Statement {
val vardecl = variabledeclaration()?.toAst(encoding)
if(vardecl!=null) return vardecl
assignment()?.let {
return Assignment(it.assign_target().toAst(), it.expression().toAst(), it.toPosition())
return Assignment(it.assign_target().toAst(encoding), it.expression().toAst(encoding), it.toPosition())
}
augassignment()?.let {
// replace A += X with A = A + X
val target = it.assign_target().toAst()
val target = it.assign_target().toAst(encoding)
val oper = it.operator.text.substringBefore('=')
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(), it.expression().toPosition())
return Assignment(it.assign_target().toAst(), expression, it.toPosition())
val expression = BinaryExpression(target.toExpression(), oper, it.expression().toAst(encoding), it.expression().toPosition())
return Assignment(it.assign_target().toAst(encoding), expression, it.toPosition())
}
postincrdecr()?.let {
return PostIncrDecr(it.assign_target().toAst(), it.operator.text, it.toPosition())
return PostIncrDecr(it.assign_target().toAst(encoding), it.operator.text, it.toPosition())
}
val directive = directive()?.toAst()
@ -185,49 +185,49 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val jump = unconditionaljump()?.toAst()
if(jump!=null) return jump
val fcall = functioncall_stmt()?.toAst()
val fcall = functioncall_stmt()?.toAst(encoding)
if(fcall!=null) return fcall
val ifstmt = if_stmt()?.toAst()
val ifstmt = if_stmt()?.toAst(encoding)
if(ifstmt!=null) return ifstmt
val returnstmt = returnstmt()?.toAst()
val returnstmt = returnstmt()?.toAst(encoding)
if(returnstmt!=null) return returnstmt
val subroutine = subroutinedeclaration()?.toAst()
val subroutine = subroutinedeclaration()?.toAst(encoding)
if(subroutine!=null) return subroutine
val asm = inlineasm()?.toAst()
if(asm!=null) return asm
val branchstmt = branch_stmt()?.toAst()
val branchstmt = branch_stmt()?.toAst(encoding)
if(branchstmt!=null) return branchstmt
val forloop = forloop()?.toAst()
val forloop = forloop()?.toAst(encoding)
if(forloop!=null) return forloop
val untilloop = untilloop()?.toAst()
val untilloop = untilloop()?.toAst(encoding)
if(untilloop!=null) return untilloop
val whileloop = whileloop()?.toAst()
val whileloop = whileloop()?.toAst(encoding)
if(whileloop!=null) return whileloop
val repeatloop = repeatloop()?.toAst()
val repeatloop = repeatloop()?.toAst(encoding)
if(repeatloop!=null) return repeatloop
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val whenstmt = whenstmt()?.toAst()
val whenstmt = whenstmt()?.toAst(encoding)
if(whenstmt!=null) return whenstmt
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
private fun prog8Parser.AsmsubroutineContext.toAst(encoding: IStringEncoding): Subroutine {
val inline = this.inline()!=null
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
val statements = statement_block()?.toAst(encoding) ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, null, true, inline, statements, toPosition())
@ -308,28 +308,28 @@ private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParamete
AsmSubroutineParameter(vardecl.varname.text, datatype, registerorpair, statusregister, toPosition())
}
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
private fun prog8Parser.Functioncall_stmtContext.toAst(encoding: IStringEncoding): Statement {
val void = this.VOID() != null
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), void, toPosition())
else
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
FunctionCallStatement(location, expression_list().toAst(encoding).toMutableList(), void, toPosition())
}
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
private fun prog8Parser.FunctioncallContext.toAst(encoding: IStringEncoding): FunctionCall {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCall(location, mutableListOf(), toPosition())
else
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
FunctionCall(location, expression_list().toAst(encoding).toMutableList(), toPosition())
}
private fun prog8Parser.InlineasmContext.toAst() =
InlineAssembly(INLINEASMBLOCK().text, toPosition())
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
return Return(expression()?.toAst(), toPosition())
private fun prog8Parser.ReturnstmtContext.toAst(encoding: IStringEncoding) : Return {
return Return(expression()?.toAst(encoding), toPosition())
}
private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
@ -341,14 +341,14 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
private fun prog8Parser.LabeldefContext.toAst(): Statement =
Label(children[0].text, toPosition())
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
private fun prog8Parser.SubroutineContext.toAst(encoding: IStringEncoding) : Subroutine {
// non-asm subroutine
val inline = inline()!=null
val returntypes = sub_return_part()?.toAst() ?: emptyList()
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
returntypes,
statement_block()?.toAst() ?: mutableListOf(),
statement_block()?.toAst(encoding) ?: mutableListOf(),
inline,
toPosition())
}
@ -364,12 +364,12 @@ private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
SubroutineParameter(it.varname.text, datatype, it.toPosition())
}
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
private fun prog8Parser.Assign_targetContext.toAst(encoding: IStringEncoding) : AssignTarget {
val identifier = scoped_identifier()
return when {
identifier!=null -> AssignTarget(identifier.toAst(), null, null, toPosition())
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(), null, toPosition())
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(), toPosition()), toPosition())
arrayindexed()!=null -> AssignTarget(null, arrayindexed().toAst(encoding), null, toPosition())
directmemory()!=null -> AssignTarget(null, null, DirectMemoryWrite(directmemory().expression().toAst(encoding), toPosition()), toPosition())
else -> AssignTarget(scoped_identifier()?.toAst(), null, null, toPosition())
}
}
@ -381,8 +381,8 @@ private fun prog8Parser.ClobberContext.toAst() : Set<CpuRegister> {
private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperCase())
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
private fun prog8Parser.ArrayindexContext.toAst(encoding: IStringEncoding) : ArrayIndex =
ArrayIndex(expression().toAst(encoding), toPosition())
private fun prog8Parser.DirectiveContext.toAst() : Directive =
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
@ -446,7 +446,7 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
}
}
private fun prog8Parser.ExpressionContext.toAst() : Expression {
private fun prog8Parser.ExpressionContext.toAst(encoding: IStringEncoding) : Expression {
val litval = literalvalue()
if(litval!=null) {
@ -469,7 +469,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
litval.stringliteral()!=null -> litval.stringliteral().toAst()
litval.charliteral()!=null -> {
try {
NumericLiteralValue(DataType.UBYTE, CompilationTarget.instance.encodeString(
NumericLiteralValue(DataType.UBYTE, encoding.encodeString(
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
} catch (ce: CharConversionException) {
@ -477,7 +477,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
}
}
litval.arrayliteral()!=null -> {
val array = litval.arrayliteral().toAst()
val array = litval.arrayliteral().toAst(encoding)
// the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFold takes care of that and converts the type if needed.
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
@ -491,31 +491,31 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
return scoped_identifier().toAst()
if(bop!=null)
return BinaryExpression(left.toAst(), bop.text, right.toAst(), toPosition())
return BinaryExpression(left.toAst(encoding), bop.text, right.toAst(encoding), toPosition())
if(prefix!=null)
return PrefixExpression(prefix.text, expression(0).toAst(), toPosition())
return PrefixExpression(prefix.text, expression(0).toAst(encoding), toPosition())
val funcall = functioncall()?.toAst()
val funcall = functioncall()?.toAst(encoding)
if(funcall!=null) return funcall
if (rangefrom!=null && rangeto!=null) {
val defaultstep = if(rto.text == "to") 1 else -1
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
val step = rangestep?.toAst(encoding) ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
return RangeExpr(rangefrom.toAst(encoding), rangeto.toAst(encoding), step, encoding, toPosition())
}
if(childCount==3 && children[0].text=="(" && children[2].text==")")
return expression(0).toAst() // expression within ( )
return expression(0).toAst(encoding) // expression within ( )
if(arrayindexed()!=null)
return arrayindexed().toAst()
return arrayindexed().toAst(encoding)
if(typecast()!=null)
return TypecastExpression(expression(0).toAst(), typecast().datatype().toAst(), false, toPosition())
return TypecastExpression(expression(0).toAst(encoding), typecast().datatype().toAst(), false, toPosition())
if(directmemory()!=null)
return DirectMemoryRead(directmemory().expression().toAst(), toPosition())
return DirectMemoryRead(directmemory().expression().toAst(encoding), toPosition())
if(addressof()!=null)
return AddressOf(addressof().scoped_identifier().toAst(), toPosition())
@ -526,13 +526,13 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
private fun prog8Parser.StringliteralContext.toAst(): StringLiteralValue =
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
private fun prog8Parser.ArrayindexedContext.toAst(encoding: IStringEncoding): ArrayIndexedExpression {
return ArrayIndexedExpression(scoped_identifier().toAst(),
arrayindex().toAst(),
arrayindex().toAst(encoding),
toPosition())
}
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun prog8Parser.Expression_listContext.toAst(encoding: IStringEncoding) = expression().map{ it.toAst(encoding) }
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
IdentifierReference(listOf(text), toPosition())
@ -548,27 +548,27 @@ private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
else -> throw FatalAstException(text)
}
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
expression().map { it.toAst() }.toTypedArray()
private fun prog8Parser.ArrayliteralContext.toAst(encoding: IStringEncoding) : Array<Expression> =
expression().map { it.toAst(encoding) }.toTypedArray()
private fun prog8Parser.If_stmtContext.toAst(): IfStatement {
val condition = expression().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
private fun prog8Parser.If_stmtContext.toAst(encoding: IStringEncoding): IfStatement {
val condition = expression().toAst(encoding)
val trueStatements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
val elseStatements = else_part()?.toAst(encoding) ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
return IfStatement(condition, trueScope, elseScope, toPosition())
}
private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
private fun prog8Parser.Else_partContext.toAst(encoding: IStringEncoding): MutableList<Statement> {
return statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
}
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
private fun prog8Parser.Branch_stmtContext.toAst(encoding: IStringEncoding): BranchStatement {
val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val elseStatements = else_part()?.toAst() ?: mutableListOf()
val trueStatements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
val elseStatements = else_part()?.toAst(encoding) ?: mutableListOf()
val trueScope = AnonymousScope(trueStatements, statement_block()?.toPosition()
?: statement().toPosition())
val elseScope = AnonymousScope(elseStatements, else_part()?.toPosition() ?: toPosition())
@ -577,65 +577,65 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
private fun prog8Parser.ForloopContext.toAst(encoding: IStringEncoding): ForLoop {
val loopvar = identifier().toAst()
val iterable = expression()!!.toAst()
val iterable = expression()!!.toAst(encoding)
val scope =
if(statement()!=null)
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
AnonymousScope(mutableListOf(statement().toAst(encoding)), statement().toPosition())
else
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
AnonymousScope(statement_block().toAst(encoding), statement_block().toPosition())
return ForLoop(loopvar, iterable, scope, toPosition())
}
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
val condition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
private fun prog8Parser.WhileloopContext.toAst(encoding: IStringEncoding): WhileLoop {
val condition = expression().toAst(encoding)
val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return WhileLoop(condition, scope, toPosition())
}
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
val iterations = expression()?.toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
private fun prog8Parser.RepeatloopContext.toAst(encoding: IStringEncoding): RepeatLoop {
val iterations = expression()?.toAst(encoding)
val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return RepeatLoop(iterations, scope, toPosition())
}
private fun prog8Parser.UntilloopContext.toAst(): UntilLoop {
val untilCondition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
private fun prog8Parser.UntilloopContext.toAst(encoding: IStringEncoding): UntilLoop {
val untilCondition = expression().toAst(encoding)
val statements = statement_block()?.toAst(encoding) ?: mutableListOf(statement().toAst(encoding))
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return UntilLoop(scope, untilCondition, toPosition())
}
private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
val condition = expression().toAst()
val choices = this.when_choice()?.map { it.toAst() }?.toMutableList() ?: mutableListOf()
private fun prog8Parser.WhenstmtContext.toAst(encoding: IStringEncoding): WhenStatement {
val condition = expression().toAst(encoding)
val choices = this.when_choice()?.map { it.toAst(encoding) }?.toMutableList() ?: mutableListOf()
return WhenStatement(condition, choices, toPosition())
}
private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
val values = expression_list()?.toAst()
val stmt = statement()?.toAst()
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
private fun prog8Parser.When_choiceContext.toAst(encoding: IStringEncoding): WhenChoice {
val values = expression_list()?.toAst(encoding)
val stmt = statement()?.toAst(encoding)
val stmtBlock = statement_block()?.toAst(encoding)?.toMutableList() ?: mutableListOf()
if(stmt!=null)
stmtBlock.add(stmt)
val scope = AnonymousScope(stmtBlock, toPosition())
return WhenChoice(values?.toMutableList(), scope, toPosition())
}
private fun prog8Parser.VardeclContext.toAst(): VarDecl {
private fun prog8Parser.VardeclContext.toAst(encoding: IStringEncoding): VarDecl {
return VarDecl(
VarDeclType.VAR,
datatype()?.toAst() ?: DataType.STRUCT,
if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
arrayindex()?.toAst(),
arrayindex()?.toAst(encoding),
varname.text,
null,
null,
@ -644,48 +644,3 @@ private fun prog8Parser.VardeclContext.toAst(): VarDecl {
toPosition()
)
}
internal fun escape(str: String): String {
val es = str.map {
when(it) {
'\t' -> "\\t"
'\n' -> "\\n"
'\r' -> "\\r"
'"' -> "\\\""
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
in '\u0000'..'\u00ff' -> it.toString()
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
}
}
return es.joinToString("")
}
internal fun unescape(str: String, position: Position): String {
val result = mutableListOf<Char>()
val iter = str.iterator()
while(iter.hasNext()) {
val c = iter.nextChar()
if(c=='\\') {
val ec = iter.nextChar()
result.add(when(ec) {
'\\' -> '\\'
'n' -> '\n'
'r' -> '\r'
'"' -> '"'
'\'' -> '\''
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
'x' -> {
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
(0x8000 + hex).toChar()
}
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
})
} else {
result.add(c)
}
}
return result.joinToString("")
}

View File

@ -0,0 +1,49 @@
package prog8.ast.antlr
import prog8.ast.base.Position
import prog8.ast.base.SyntaxError
fun escape(str: String): String {
val es = str.map {
when(it) {
'\t' -> "\\t"
'\n' -> "\\n"
'\r' -> "\\r"
'"' -> "\\\""
in '\u8000'..'\u80ff' -> "\\x" + (it.toInt() - 0x8000).toString(16).padStart(2, '0')
in '\u0000'..'\u00ff' -> it.toString()
else -> "\\u" + it.toInt().toString(16).padStart(4, '0')
}
}
return es.joinToString("")
}
fun unescape(str: String, position: Position): String {
val result = mutableListOf<Char>()
val iter = str.iterator()
while(iter.hasNext()) {
val c = iter.nextChar()
if(c=='\\') {
val ec = iter.nextChar()
result.add(when(ec) {
'\\' -> '\\'
'n' -> '\n'
'r' -> '\r'
'"' -> '"'
'\'' -> '\''
'u' -> {
"${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}${iter.nextChar()}".toInt(16).toChar()
}
'x' -> {
// special hack 0x8000..0x80ff will be outputted verbatim without encoding
val hex = ("" + iter.nextChar() + iter.nextChar()).toInt(16)
(0x8000 + hex).toChar()
}
else -> throw SyntaxError("invalid escape char in string: \\$ec", position)
})
} else {
result.add(c)
}
}
return result.joinToString("")
}

View File

@ -1,7 +1,6 @@
package prog8.ast.base
import prog8.ast.Node
import prog8.compiler.target.CompilationTarget
/**************************** AST Data classes ****************************/
@ -44,7 +43,7 @@ enum class DataType {
this == other -> false
this in ByteDatatypes -> false
this in WordDatatypes -> other in ByteDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> false
this== STR && other== UWORD || this== UWORD && other== STR -> false
else -> true
}
@ -53,25 +52,21 @@ enum class DataType {
this == other -> true
this in ByteDatatypes -> other in ByteDatatypes
this in WordDatatypes -> other in WordDatatypes
this==STR && other==UWORD || this==UWORD && other==STR -> true
this== STR && other== UWORD || this== UWORD && other== STR -> true
else -> false
}
fun memorySize(): Int {
return when(this) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
FLOAT -> CompilationTarget.instance.machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> CompilationTarget.instance.machine.POINTER_MEM_SIZE
else -> -9999999
}
}
}
enum class CpuRegister {
A,
X,
Y
Y;
fun asRegisterOrPair(): RegisterOrPair = when(this) {
A -> RegisterOrPair.A
X -> RegisterOrPair.X
Y -> RegisterOrPair.Y
}
}
enum class RegisterOrPair {
@ -91,6 +86,13 @@ enum class RegisterOrPair {
val names by lazy { values().map { it.toString()} }
}
fun asCpuRegister(): CpuRegister = when(this) {
A -> CpuRegister.A
X -> CpuRegister.X
Y -> CpuRegister.Y
else -> throw IllegalArgumentException("no cpu hardware register for $this")
}
} // only used in parameter and return value specs in asm subroutines
enum class Statusflag {
@ -132,10 +134,11 @@ val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, Data
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
val StringlyDatatypes = setOf(DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B, DataType.UWORD)
val IterableDatatypes = setOf(
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F
)
val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
val ArrayElementTypes = mapOf(
@ -144,7 +147,8 @@ val ArrayElementTypes = mapOf(
DataType.ARRAY_UB to DataType.UBYTE,
DataType.ARRAY_W to DataType.WORD,
DataType.ARRAY_UW to DataType.UWORD,
DataType.ARRAY_F to DataType.FLOAT)
DataType.ARRAY_F to DataType.FLOAT
)
val ElementArrayTypes = mapOf(
DataType.BYTE to DataType.ARRAY_B,
DataType.UBYTE to DataType.ARRAY_UB,
@ -152,10 +156,12 @@ val ElementArrayTypes = mapOf(
DataType.UWORD to DataType.ARRAY_UW,
DataType.FLOAT to DataType.ARRAY_F
)
val Cx16VirtualRegisters = listOf(RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
val Cx16VirtualRegisters = listOf(
RegisterOrPair.R0, RegisterOrPair.R1, RegisterOrPair.R2, RegisterOrPair.R3,
RegisterOrPair.R4, RegisterOrPair.R5, RegisterOrPair.R6, RegisterOrPair.R7,
RegisterOrPair.R8, RegisterOrPair.R9, RegisterOrPair.R10, RegisterOrPair.R11,
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15)
RegisterOrPair.R12, RegisterOrPair.R13, RegisterOrPair.R14, RegisterOrPair.R15
)
// find the parent node of a specific type or interface

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