Compare commits

...

157 Commits

Author SHA1 Message Date
ac70ae6a76 scripts 2019-07-02 04:39:53 +02:00
d83f49d84f remove unused variables, subroutines, blocks 2019-07-02 04:29:51 +02:00
ff1294207e improved parameter name shadowing check 2019-07-02 00:32:55 +02:00
a56956797a chars can now have a color 2019-07-01 23:41:30 +02:00
3242495b0b slightly improved warning about implicit float casts 2019-07-01 18:43:39 +02:00
49eb7e7803 remove bogus 2019-07-01 18:11:16 +02:00
1d7f0d3537 streamline moving values to heap 2019-07-01 18:01:36 +02:00
31137743f0 simplified string handling a little in LiteralValue 2019-07-01 14:19:41 +02:00
2c69e10489 heapId writable 2019-07-01 14:10:52 +02:00
3a1fa9e069 fixed constantfolding of array values 2019-07-01 13:53:29 +02:00
2c08d2f9c6 fix array size in vardecls 2019-06-30 20:10:53 +02:00
4743cacb73 fix swap() 2019-06-30 18:06:11 +02:00
5f5a1447e0 array on heap fix 2019-06-30 17:58:08 +02:00
a3004555a8 branch 2019-06-30 17:07:08 +02:00
267c678292 more swap logic, some typing fixes 2019-06-28 22:10:01 +02:00
6c50043a4a swap isn't yet finished 2019-06-28 02:57:13 +02:00
3ee1b2efdd left and right of a binary expression should usually have the same datatype, insert typecast if needed 2019-06-28 02:39:55 +02:00
75d8c832ad implemented Jump 2019-06-28 01:21:31 +02:00
53a4379c45 implemented all builtin functions in the AstVm 2019-06-28 00:10:27 +02:00
29b3a7e94e optimize redundant typecasts, fix some runtime type casting errors 2019-06-27 21:09:21 +02:00
0782f6ecf1 function call arguments 2019-06-27 00:07:41 +02:00
595e58ec46 taking care of memory mapped vars 2019-06-26 03:28:34 +02:00
060e05c868 strlen and strings with zeros in them should terminate at the zero 2019-06-26 02:34:43 +02:00
f49eefad6f some builtin functions 2019-06-26 00:01:23 +02:00
d68360461b registers 2019-06-25 22:48:40 +02:00
343978d164 for loop and cleaner iteration over values 2019-06-25 21:49:02 +02:00
b11d10e2ff fix Return when dealing with non-subroutine scopes 2019-06-25 01:44:57 +02:00
268856823a got rid of old Value in favor of new RuntimeValue implementation 2019-06-24 22:45:27 +02:00
4bac5043b6 fix integer wraparounds for RuntimeValue 2019-06-24 22:18:50 +02:00
eb25b4c800 fix some initial value datatypes and type casting in assignments 2019-06-24 04:09:30 +02:00
a079e44b02 fix some initial value datatypes and type casting in assignments 2019-06-24 01:31:25 +02:00
e53c860f1a first go at ast-based virtual machine (rather than the stackvm that uses intermediate code) 2019-06-24 00:17:48 +02:00
99121004bf more sensible subroutine inlining 2019-06-23 20:06:35 +02:00
6dd3371781 some infix functions 2019-06-23 15:43:52 +02:00
f473be8951 simple cleaup script 2019-06-23 14:10:50 +02:00
ebd38f27e6 cleaned up some symbol visibilities 2019-06-23 13:49:35 +02:00
a6c3251668 simple subroutine inlining 2019-06-23 03:15:23 +02:00
560047adee variables init subroutine must never be optimized away (fixes primes example) 2019-06-21 23:56:45 +02:00
a86852874f readme 2019-06-21 23:41:20 +02:00
6d44d6a901 travis ci 2019-06-21 23:22:34 +02:00
968f02823f travis ci 2019-06-21 23:14:53 +02:00
5d321d759e travis ci 2019-06-21 23:12:25 +02:00
7de7d5234f callgraph fixed scanning asm subroutines, and deletion of unused subs and modules 2019-06-21 23:08:29 +02:00
b374af3526 remove unused/empty modules 2019-06-21 00:12:22 +02:00
b35430214b some more program node cleanups 2019-06-20 21:46:59 +02:00
e96d3d4455 update kotlin version
cleaning up the way the root of the Ast and the global namespace work (introduced ProgramAst node)
2019-06-20 20:15:18 +02:00
6a17f7a0ad Merge remote-tracking branch 'origin/master' 2019-05-30 16:04:09 +02:00
c559682c0b refresh IDE project files 2019-05-30 16:03:53 +02:00
6ce1277438 fix classpaths in windows command files 2019-05-06 17:14:13 +02:00
262e0bd6b9 fix avg() on float arrays 2019-04-21 03:04:36 +02:00
755af6010e fix some more issues with array vardecls without array size specifier 2019-04-21 03:04:13 +02:00
0298cf8b90 scripts use gradle build dirs 2019-04-20 13:26:04 +02:00
a6d0aecd66 fix invalid assignment reordering 2019-04-20 13:24:42 +02:00
ef6e364339 intellij idea config 2019-04-20 01:18:47 +02:00
3b37e0f99d new build scripts 2019-04-20 00:50:15 +02:00
78fbbf7119 %asmbinary implemented 2019-04-17 01:33:07 +02:00
0ee43294c4 check for file with %asmbinary, %asminclude 2019-04-17 00:55:42 +02:00
a81b82495c fix wrong values generated from range expression in array vardecl 2019-04-17 00:27:27 +02:00
390043e9e8 some fixes in syntaxchecking array initializer values 2019-04-16 01:50:12 +02:00
e384822b2c array size in vardecl is optional when initializer array value is given 2019-04-16 01:19:51 +02:00
730e08698d comment 2019-04-13 00:58:39 +02:00
5497de4234 optimize @( &thing )) in ast into just thing 2019-04-12 23:59:26 +02:00
c71b78dee6 use array of pointers to blocks instead of a large if statement 2019-04-12 23:35:27 +02:00
dfcb57a0b0 couple of small shortcuts on identifier ast to lookup what it is pointing to 2019-04-12 23:04:19 +02:00
f219ae43f7 more inspiring code example 2019-04-12 22:34:43 +02:00
a9bbe0bc40 removed the memory keyword instead use & now (reuse the address-of operator to reduce the number of different concepts in the grammar) 2019-04-12 22:00:32 +02:00
35aa954be8 doc 2019-04-12 01:06:46 +02:00
78ddcf9db7 address-of works the test program 2019-04-12 00:58:40 +02:00
cd0fa9405a comments 2019-04-12 00:54:04 +02:00
4462def8ea fix array processing and ASM code gen of arrays with addressOf in them 2019-04-12 00:37:33 +02:00
3f93b87745 fix array processing and ASM code gen of arrays with addressOf in them 2019-04-12 00:04:15 +02:00
9f302cc640 docs about '&' operator 2019-04-11 21:41:46 +02:00
0a73125606 fix auto-insertion of AddressOf expression in function call arguments 2019-04-11 21:32:23 +02:00
7780441524 fix build scripts to point to new IntelliJ version 2019-04-11 21:26:46 +02:00
8bec4eaa87 rename PointerOf to AddressOf 2019-04-11 21:01:02 +02:00
4434d31a3b upgrade to Kotlin 1.3.30 and increase memory settings for command line build script 2019-04-11 19:58:28 +02:00
51454c71c7 Merge branch 'master' into pointerto
# Conflicts:
#	compiler/res/prog8lib/c64flt.p8
2019-04-10 23:16:08 +02:00
fb2796ac06 truly fix min(f)/max(f) also fix ceil(f) 2019-04-10 23:14:28 +02:00
742b15357b fix all(f) 2019-04-10 22:42:48 +02:00
ac6ed27052 restore tweaks in c64flt.p8 2019-04-10 22:18:45 +02:00
f3c1783bf2 correct intermediate code output of pointers in arrayvalues 2019-04-10 22:08:21 +02:00
ce8853ab50 restore tweaks in c64flt.p8 2019-04-08 00:36:19 +02:00
5e3e00fbad fix stackvm 2019-04-08 00:29:10 +02:00
1dde49d644 Merge branch 'master' into pointerto
# Conflicts:
#	compiler/res/prog8lib/c64flt.p8
#	compiler/src/prog8/stackvm/StackVm.kt
2019-04-08 00:19:18 +02:00
fd19298a05 fixed stackvm pop signed byte into register 2019-04-08 00:08:23 +02:00
ede2b83ce4 got rid of unused avg syscalls and fixed stackvm iterable functions (min, max, avg, sum, any, all) 2019-04-08 00:00:43 +02:00
fc47d3feb8 repaired min(f) max(f) fixes #13 2019-04-07 23:19:31 +02:00
87446028e0 no more duplicate auto heap vars, attempt at automatic insertion of & expression for subroutine params 2019-04-05 13:14:19 +02:00
b200f9945f asmgen array with pointer values (w.i.p) 2019-04-04 23:51:22 +02:00
eebd4e5f18 fix float constants prefix mistakes, removed broken max_f/min_f (fix pending), tweaked sum_f 2019-04-04 23:39:28 +02:00
1069b5f5d5 w.i.p pointer-to 2019-04-04 21:45:30 +02:00
3e7e44acfe no hard crash anymore for invalid string escape sequences or unknown petscii characters 2019-04-03 22:25:26 +02:00
518c3bfd76 actually, get rid of integer pow() because a naive multiplication loop approach is way too slow 2019-03-31 18:05:41 +02:00
905d8a0c06 actually, get rid of integer pow() because a naive multiplication loop approach is way too slow 2019-03-31 18:04:19 +02:00
b57c02b0ba don't remove 'duplicate' assignments that aren't removable (i.e. not literalvalues) 2019-03-31 16:10:02 +02:00
03d0411679 pow_f implemented 2019-03-31 14:28:38 +02:00
83ace753b2 got rid of problematic signed POW operator, added compiler checks for this 2019-03-31 13:56:03 +02:00
ec2e7db23e doc fix 2019-03-30 00:40:09 +01:00
c4615591c9 fixing label names, fixes #11 2019-03-30 00:31:40 +01:00
25e3b599e7 fixing label names 2019-03-30 00:15:50 +01:00
5502a3e3ee optimized name checking, no longer depends on scopedname 2019-03-28 21:30:30 +01:00
62ceace941 block names are global (unscoped) 2019-03-25 23:46:58 +01:00
7114d3193c some cleanups in library asm code 2019-03-21 22:36:46 +01:00
f6bc69139d added some example images to the index page of the docs 2019-03-19 21:39:01 +01:00
f3fc2fe523 irq handler saves zeropage scratch registers, fixes #8 2019-03-19 01:22:26 +01:00
1e045b6a62 fixed multi-return value assignment 2019-03-18 04:44:20 +01:00
747c9604dd improve ast check for multiple returnvalues assignment 2019-03-18 04:01:25 +01:00
1e5b2e0be3 for loops can now be over an iterable literal value directly (don't require a variable to hold the iterable) 2019-03-17 23:58:07 +01:00
0820716e7b added sqrt16() integer square root 2019-03-16 19:25:47 +01:00
191707cd37 added new c64utils.str2(u)word that doesn't use kernel float routines
fixed processing of register pair return value of asmsub
2019-03-16 17:50:59 +01:00
223bab21aa less verbose anon label names 2019-03-16 00:11:04 +01:00
563122ac92 stricter argument check for boolean operator 2019-03-15 23:34:15 +01:00
bc9d00922e implemented difference between printing and writing text in vm screen 2019-03-15 23:27:54 +01:00
d9d83248fe implemented strlen() function 2019-03-15 23:10:26 +01:00
f2397527f1 improved text output in stackvm 2019-03-13 22:45:12 +01:00
bf3caaefe1 stackvm now uses a proper instruction pointer call stack instead of instruction linking 2019-03-13 22:00:41 +01:00
1aaf854ef7 identified issue with single instruction linking in vm 2019-03-12 21:59:40 +01:00
ce40f6f862 defined a few more sysasm routines 2019-03-11 22:30:32 +01:00
a349599943 serious endless for loop bug in stackvm because Z and N flags weren't set properly, now fixed 2019-03-11 22:02:00 +01:00
ac7faa8d25 stackvm can now intercept system asm calls (to a rom address) 2019-03-11 02:05:30 +01:00
747ee32e81 updated tehtriz screenshot 2019-03-10 20:22:33 +01:00
75fadaa24f added holding area 2019-03-10 20:17:58 +01:00
e4ea1f1014 tweaked controls, score, sounds 2019-03-10 19:24:11 +01:00
cd2c4e13da cleanups 2019-03-10 18:30:01 +01:00
f5ba072294 removed str_p and str_ps pascal string types, fixes #10 2019-03-10 18:11:26 +01:00
87d6312a37 tetriz screen 2019-03-10 05:38:14 +01:00
3af7d4c930 tweaked tetriz speedup 2019-03-10 05:24:07 +01:00
0fc3071a21 updated examples 2019-03-10 04:36:48 +01:00
7f36d08acc simple sound effects 2019-03-10 04:22:02 +01:00
b040e5ddad speedup at every 10 lines 2019-03-10 03:59:58 +01:00
f36ce5e0ee line clearing 2019-03-10 03:21:14 +01:00
ffbdac7e9a don't draw 8 pieces instead of 7. Implemented simple wall kick when rotating. 2019-03-09 00:42:56 +01:00
f2b03342ac tehtriz joystick input 2019-03-07 23:29:23 +01:00
52ff61470b fixed rotation of I piece to conform to current tetris guidelines 2019-03-07 22:41:59 +01:00
28277469b6 fixed a compiler crash because with noopt, strings weren't put on the heap 2019-03-07 22:04:00 +01:00
aa98104d54 doc 2019-03-07 02:46:24 +01:00
9be70bcbe7 tetris stuff 2019-03-07 02:28:01 +01:00
3a6fae4447 simplified tehtris collision check a bit 2019-03-07 01:46:38 +01:00
06f0984fa1 docs about irq handlers 2019-03-07 01:02:11 +01:00
77dc35dc6a added read_flags() function, uword2bcd routine no longer enables irq again if it wasn't enabled before calling it. 2019-03-05 23:10:00 +01:00
ed43f7cd9b grade: also include parser in fatJar to make it complete, and exclude the huge ic4j library that isn't used 2019-03-02 22:41:21 +01:00
32405a1637 Merge pull request #7 from fboldog/add-antlr4-runtime
possible solution for antlr4-runtime in the fatjar
2019-03-02 22:39:08 +01:00
43cab3f247 possible solution for antlr4-runtime in the fatjar 2019-02-28 15:02:10 +01:00
5ea2f2d4db docs about @zp tag 2019-02-28 00:13:59 +01:00
b8ae808b65 compiler was confused about resulting expression type 2019-02-27 23:58:08 +01:00
96ecbc9fe4 fixed too eager expression operand type adjustment 2019-02-27 23:07:12 +01:00
588133d418 fixed primes.p8 2019-02-25 01:37:05 +01:00
2f1249489b datatype cleanups 2019-02-25 01:22:56 +01:00
95f7c9bad0 asmsubroutines now also return their value on the evalstack (this fixes their use in expressions) 2019-02-24 18:54:25 +01:00
8811d2f7c5 fixed a compiler ast crash and added -noopt command line flag 2019-02-24 16:56:38 +01:00
d6ca1e6a12 fixed len() returntype 2019-02-24 15:25:46 +01:00
b0ad66bd04 added missing bitwise and/or/xor asm code 2019-02-23 23:06:46 +01:00
c1d2b4601b fixed/added logical and/or/xor 2019-02-23 22:13:42 +01:00
c265625ed1 gradle 2019-02-23 13:17:42 +01:00
52352d9d04 added c64scr.getchr/getclr 2019-02-21 01:31:33 +01:00
cc5898d010 more tetriz work 2019-02-15 01:53:20 +01:00
8684f0c8f5 clean exit mandelbrot 2019-02-12 23:24:47 +01:00
114 changed files with 9857 additions and 5743 deletions

3
.gitignore vendored
View File

@ -1,11 +1,10 @@
**/.idea/ .idea/workspace.xml
/build/ /build/
/dist/ /dist/
/output/ /output/
.*cache/ .*cache/
*.directory *.directory
*.prg *.prg
*.asm
*.bin *.bin
*.labels.txt *.labels.txt
*.vm.txt *.vm.txt

6
.idea/kotlinc.xml generated Normal file
View File

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

19
.idea/libraries/KotlinJavaRuntime.xml generated Normal file
View File

@ -0,0 +1,19 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime">
<CLASSES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-reflect-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-test-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk7-sources.jar!/" />
<root url="jar://$KOTLIN_BUNDLED$/lib/kotlin-stdlib-jdk8-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="antlr-4.7.2-complete">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-4.7.2-complete.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

9
.idea/libraries/antlr_runtime_4_7_2.xml generated Normal file
View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="antlr-runtime-4.7.2">
<CLASSES>
<root url="jar://$PROJECT_DIR$/parser/antlr/lib/antlr-runtime-4.7.2.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

10
.idea/libraries/unittest_libs.xml generated Normal file
View File

@ -0,0 +1,10 @@
<component name="libraryTable">
<library name="unittest-libs">
<CLASSES>
<root url="file://$PROJECT_DIR$/compiler/lib" />
</CLASSES>
<JAVADOC />
<SOURCES />
<jarDirectory url="file://$PROJECT_DIR$/compiler/lib" recursive="false" />
</library>
</component>

6
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

11
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.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" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
</modules>
</component>
</project>

124
.idea/uiDesigner.xml generated Normal file
View File

@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Palette2">
<group name="Swing">
<item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
</item>
<item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
</item>
<item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
<default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
</item>
<item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
<initial-values>
<property name="text" value="Button" />
</initial-values>
</item>
<item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="RadioButton" />
</initial-values>
</item>
<item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
<initial-values>
<property name="text" value="CheckBox" />
</initial-values>
</item>
<item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
<initial-values>
<property name="text" value="Label" />
</initial-values>
</item>
<item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
<preferred-size width="150" height="-1" />
</default-constraints>
</item>
<item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
</item>
<item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
<preferred-size width="150" height="50" />
</default-constraints>
</item>
<item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
<preferred-size width="200" height="200" />
</default-constraints>
</item>
<item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
</item>
<item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
</item>
<item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
</item>
<item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
<preferred-size width="-1" height="20" />
</default-constraints>
</item>
<item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
<default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
</item>
<item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
<default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
</item>
</group>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

11
.travis.yml Normal file
View File

@ -0,0 +1,11 @@
language: java
# jdk: openjdk8
# dist: xenial
# sudo: false
before_install:
- chmod +x gradlew
script:
- ./gradlew test

103
README.md
View File

@ -1,3 +1,6 @@
[![saythanks](https://img.shields.io/badge/say-thanks-ff69b4.svg)](https://saythanks.io/to/irmen)
[![Build Status](https://travis-ci.org/irmen/prog8.svg?branch=master)](https://travis-ci.org/irmen/prog8)
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
=========================================================================== ===========================================================================
@ -11,23 +14,29 @@ as used in many home computers from that era. It is a medium to low level progra
which aims to provide many conveniences over raw assembly code (even when using a macro assembler): which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
- reduction of source code length - reduction of source code length
- easier program understanding (because it's higher level, and more terse) - easier program understanding (because it's higher level, and way more compact)
- option to automatically run the compiled program in the Vice emulator
- modularity, symbol scoping, subroutines - modularity, symbol scoping, subroutines
- subroutines have enforced input- and output parameter definitions - subroutines have enforced input- and output parameter definitions
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs) - various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
- automatic variable allocations, automatic string variables and string sharing - automatic variable allocations, automatic string variables and string sharing
- constant folding in expressions (compile-time evaluation) - constant folding in expressions (compile-time evaluation)
- conditional branches
- automatic type conversions - automatic type conversions
- floating point operations - floating point operations (uses the C64 Basic ROM routines for this)
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses - abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
Rapid edit-compile-run-debug cycle:
- use modern PC to work on
- quick compilation times (less than 1 second)
- option to automatically run the program in the Vice emulator
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them - breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly - source code labels automatically loaded in Vice emulator so it can show them in disassembly
- conditional gotos
- various code optimizations (code structure, logical and numerical expressions, ...)
It is mainly targeted at the Commodore-64 machine at this time. It is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome.
Documentation is online at https://prog8.readthedocs.io/ Documentation is online at https://prog8.readthedocs.io/
@ -39,8 +48,8 @@ Required tools:
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project. A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
For other platforms it is very easy to compile it yourself (make ; make install). For other platforms it is very easy to compile it yourself (make ; make install).
A **Java runtime (jre or jdk), version 8 or newer** is required to run the packaged compiler. A **Java runtime (jre or jdk), version 8 or newer** is required to run a prepackaged version of the compiler.
If you want to build it from source, you'll need a Kotlin 1.3 SDK as well (or for instance, If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
IntelliJ IDEA with the Kotlin plugin). IntelliJ IDEA with the Kotlin plugin).
It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence
@ -50,55 +59,58 @@ of the [Vice emulator](http://vice-emu.sourceforge.net/)
Example code Example code
------------ ------------
When this code is compiled:: This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64lib
%import c64utils %import c64utils
%import c64flt %zeropage basicsafe
~ main { ~ main {
ubyte[256] sieve
ubyte candidate_prime = 2
sub start() { sub start() {
; set text color and activate lowercase charset memset(sieve, 256, false)
c64.COLOR = 13
c64.VMCSB |= 2
; use optimized routine to write text c64scr.print("prime numbers up to 255:\n\n")
c64scr.print("Hello!\n") ubyte amount=0
while true {
; use iteration to write text ubyte prime = find_next_prime()
str question = "How are you?\n" if prime==0
for ubyte char in question break
c64.CHROUT(char) c64scr.print_ub(prime)
c64scr.print(", ")
; use indexed loop to write characters amount++
str bye = "Goodbye!\n" }
for ubyte c in 0 to len(bye)
c64.CHROUT(bye[c])
float clock_seconds = ((mkword(c64.TIME_LO, c64.TIME_MID) as float)
+ (c64.TIME_HI as float)*65536.0)
/ 60
float hours = floor(clock_seconds / 3600)
clock_seconds -= hours*3600
float minutes = floor(clock_seconds / 60)
clock_seconds = floor(clock_seconds - minutes * 60.0)
c64scr.print("system time in ti$ is ")
c64flt.print_f(hours)
c64.CHROUT(':')
c64flt.print_f(minutes)
c64.CHROUT(':')
c64flt.print_f(clock_seconds)
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.print("number of primes (expected 54): ")
c64scr.print_ub(amount)
c64.CHROUT('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0
}
sieve[candidate_prime] = true
uword multiple = candidate_prime
while multiple < len(sieve) {
sieve[lsb(multiple)] = true
multiple += candidate_prime
}
return candidate_prime
} }
} }
when compiled an ran on a C-64 you'll get:
you get a program that outputs this when loaded on a C-64: ![c64 screen](docs/source/_static/primes_example.png)
![c64 screen](docs/source/_static/hello_screen.png)
One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this: One of the included examples (wizzine.p8) animates a bunch of sprite balloons and looks like this:
@ -109,3 +121,6 @@ Another example (cube3d-sprites.p8) draws the vertices of a rotating 3d cube:
![cube3d screen](docs/source/_static/cube3d.png) ![cube3d screen](docs/source/_static/cube3d.png)
If you want to play a video game, a fully working Tetris clone is included in the examples:
![tehtriz_screen](docs/source/_static/tehtriz.png)

5
clean.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env sh
rm *.jar *.asm *.prg *.vm.txt *.vice-mon-list
rm -r build

View File

@ -1,6 +1,7 @@
plugins { plugins {
id "org.jetbrains.kotlin.jvm" version "1.3.20" id "org.jetbrains.kotlin.jvm" version "1.3.40"
id 'application' id 'application'
id 'org.jetbrains.dokka' version "0.9.18"
} }
repositories { repositories {
@ -8,18 +9,29 @@ repositories {
jcenter() jcenter()
} }
def kotlinVersion = '1.3.20' def kotlinVersion = '1.3.40'
dependencies { dependencies {
implementation project(':parser') implementation project(':parser')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
runtime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" runtime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
runtime 'org.antlr:antlr4-runtime:4.7.2'
runtime project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion" testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0' testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
} }
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
sourceSets { sourceSets {
main { main {
@ -52,8 +64,8 @@ task p8vmScript(type: CreateStartScripts) {
} }
applicationDistribution.into("bin") { applicationDistribution.into("bin") {
from(p8vmScript) from(p8vmScript)
fileMode = 0755 fileMode = 0755
} }
task fatJar(type: Jar) { task fatJar(type: Jar) {
@ -61,8 +73,33 @@ task fatJar(type: Jar) {
attributes 'Main-Class': 'prog8.CompilerMainKt' attributes 'Main-Class': 'prog8.CompilerMainKt'
} }
archiveBaseName = 'prog8compiler' archiveBaseName = 'prog8compiler'
destinationDir = rootProject.projectDir destinationDirectory = rootProject.projectDir
from { project.configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) } } from {
project.configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) }
}
with jar with jar
} }
// build.finalizedBy(fatJar) // build.finalizedBy(fatJar)
// Java target version
sourceCompatibility = 1.8
test {
// Enable JUnit 5 (Gradle 4.6+).
useJUnitPlatform()
// Always run tests, even when nothing changed.
dependsOn 'cleanTest'
// Show test results.
testLogging {
events "passed", "skipped", "failed"
}
}
dokka {
outputFormat = 'html'
outputDirectory = "$buildDir/kdoc"
}

View File

@ -3,15 +3,17 @@
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content> </content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" /> <orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" /> <orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" /> <orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
<orderEntry type="library" name="testlibs" level="project" />
<orderEntry type="module" module-name="parser" /> <orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" />
</component> </component>
</module> </module>

View File

@ -20,20 +20,21 @@
; floats in memory (and rom) are stored in 5-byte MFLPT packed format. ; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
; constants in five-byte "mflpt" format in the BASIC ROM ; constants in five-byte "mflpt" format in the BASIC ROM
memory float FL_PIVAL = $aea8 ; 3.1415926... &float FL_PIVAL = $aea8 ; 3.1415926...
memory float FL_N32768 = $b1a5 ; -32768 &float FL_N32768 = $b1a5 ; -32768
memory float FL_FONE = $b9bc ; 1 &float FL_FONE = $b9bc ; 1
memory float FL_SQRHLF = $b9d6 ; SQR(2) / 2 &float FL_SQRHLF = $b9d6 ; SQR(2) / 2
memory float FL_SQRTWO = $b9db ; SQR(2) &float FL_SQRTWO = $b9db ; SQR(2)
memory float FL_NEGHLF = $b9e0 ; -.5 &float FL_NEGHLF = $b9e0 ; -.5
memory float FL_LOG2 = $b9e5 ; LOG(2) &float FL_LOG2 = $b9e5 ; LOG(2)
memory float FL_TENC = $baf9 ; 10 &float FL_TENC = $baf9 ; 10
memory float FL_NZMIL = $bdbd ; 1e9 (1 billion) &float FL_NZMIL = $bdbd ; 1e9 (1 billion)
memory float FL_FHALF = $bf11 ; .5 &float FL_FHALF = $bf11 ; .5
memory float FL_LOGEB2 = $bfbf ; 1 / LOG(2) &float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
memory float FL_PIHALF = $e2e0 ; PI / 2 &float FL_PIHALF = $e2e0 ; PI / 2
memory float FL_TWOPI = $e2e5 ; 2 * PI &float FL_TWOPI = $e2e5 ; 2 * PI
memory float FL_FR4 = $e2ea ; .25 &float FL_FR4 = $e2ea ; .25
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
; note: fac1/2 might get clobbered even if not mentioned in the function's name. ; note: fac1/2 might get clobbered even if not mentioned in the function's name.
@ -165,14 +166,14 @@ asmsub GIVAYFAY (uword value @ AY) -> clobbers(A,X,Y) -> () {
sta c64.SCRATCH_ZPREG sta c64.SCRATCH_ZPREG
tya tya
ldy c64.SCRATCH_ZPREG ldy c64.SCRATCH_ZPREG
jmp c64flt.GIVAYF ; this uses the inverse order, Y/A jmp GIVAYF ; this uses the inverse order, Y/A
}} }}
} }
asmsub FTOSWRDAY () -> clobbers(X) -> (uword @ AY) { asmsub FTOSWRDAY () -> clobbers(X) -> (uword @ AY) {
; ---- fac1 to signed word in A/Y ; ---- fac1 to signed word in A/Y
%asm {{ %asm {{
jsr c64flt.FTOSWORDYA ; note the inverse Y/A order jsr FTOSWORDYA ; note the inverse Y/A order
sta c64.SCRATCH_ZPREG sta c64.SCRATCH_ZPREG
tya tya
ldy c64.SCRATCH_ZPREG ldy c64.SCRATCH_ZPREG
@ -183,7 +184,7 @@ asmsub FTOSWRDAY () -> clobbers(X) -> (uword @ AY) {
asmsub GETADRAY () -> clobbers(X) -> (uword @ AY) { asmsub GETADRAY () -> clobbers(X) -> (uword @ AY) {
; ---- fac1 to unsigned word in A/Y ; ---- fac1 to unsigned word in A/Y
%asm {{ %asm {{
jsr c64flt.GETADR ; this uses the inverse order, Y/A jsr GETADR ; this uses the inverse order, Y/A
sta c64.SCRATCH_ZPB1 sta c64.SCRATCH_ZPB1
tya tya
ldy c64.SCRATCH_ZPB1 ldy c64.SCRATCH_ZPB1
@ -197,8 +198,8 @@ sub print_f (float value) {
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
lda #<print_f_value lda #<print_f_value
ldy #>print_f_value ldy #>print_f_value
jsr c64flt.MOVFM ; load float into fac1 jsr MOVFM ; load float into fac1
jsr c64flt.FOUT ; fac1 to string in A/Y jsr FOUT ; fac1 to string in A/Y
jsr c64.STROUT ; print string in A/Y jsr c64.STROUT ; print string in A/Y
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
rts rts
@ -211,8 +212,8 @@ sub print_fln (float value) {
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
lda #<print_fln_value lda #<print_fln_value
ldy #>print_fln_value ldy #>print_fln_value
jsr c64flt.MOVFM ; load float into fac1 jsr MOVFM ; load float into fac1
jsr c64flt.FPRINTLN ; print fac1 with newline jsr FPRINTLN ; print fac1 with newline
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
rts rts
}} }}
@ -229,10 +230,10 @@ ub2float .proc
sta c64.SCRATCH_ZPWORD2 sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1 sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPB1 ldy c64.SCRATCH_ZPB1
jsr c64flt.FREADUY jsr FREADUY
_fac_to_mem ldx c64.SCRATCH_ZPWORD2 _fac_to_mem ldx c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1 ldy c64.SCRATCH_ZPWORD2+1
jsr c64flt.MOVMF jsr MOVMF
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
rts rts
.pend .pend
@ -244,7 +245,7 @@ b2float .proc
sta c64.SCRATCH_ZPWORD2 sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1 sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPB1 lda c64.SCRATCH_ZPB1
jsr c64flt.FREADSA jsr FREADSA
jmp ub2float._fac_to_mem jmp ub2float._fac_to_mem
.pend .pend
@ -255,7 +256,7 @@ uw2float .proc
sty c64.SCRATCH_ZPWORD2+1 sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPWORD1 lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1 ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.GIVUAYFAY jsr GIVUAYFAY
jmp ub2float._fac_to_mem jmp ub2float._fac_to_mem
.pend .pend
@ -266,7 +267,7 @@ w2float .proc
sty c64.SCRATCH_ZPWORD2+1 sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPWORD1 ldy c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1 lda c64.SCRATCH_ZPWORD1+1
jsr c64flt.GIVAYF jsr GIVAYF
jmp ub2float._fac_to_mem jmp ub2float._fac_to_mem
.pend .pend
@ -275,7 +276,7 @@ stack_b2float .proc
inx inx
lda c64.ESTACK_LO,x lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.FREADSA jsr FREADSA
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -285,7 +286,7 @@ stack_w2float .proc
ldy c64.ESTACK_LO,x ldy c64.ESTACK_LO,x
lda c64.ESTACK_HI,x lda c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.GIVAYF jsr GIVAYF
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -295,7 +296,7 @@ stack_ub2float .proc
lda c64.ESTACK_LO,x lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
tay tay
jsr c64flt.FREADUY jsr FREADUY
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -305,14 +306,14 @@ stack_uw2float .proc
lda c64.ESTACK_LO,x lda c64.ESTACK_LO,x
ldy c64.ESTACK_HI,x ldy c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.GIVUAYFAY jsr GIVUAYFAY
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
stack_float2w .proc stack_float2w .proc
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.AYINT jsr AYINT
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
lda $64 lda $64
sta c64.ESTACK_HI,x sta c64.ESTACK_HI,x
@ -325,7 +326,7 @@ stack_float2w .proc
stack_float2uw .proc stack_float2uw .proc
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.GETADR jsr GETADR
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
sta c64.ESTACK_HI,x sta c64.ESTACK_HI,x
tya tya
@ -364,11 +365,11 @@ func_rndf .proc
; -- put a random floating point value on the stack ; -- put a random floating point value on the stack
stx c64.SCRATCH_ZPREG stx c64.SCRATCH_ZPREG
lda #1 lda #1
jsr c64flt.FREADSA jsr FREADSA
jsr c64flt.RND ; rng into fac1 jsr RND ; rng into fac1
ldx #<_rndf_rnum5 ldx #<_rndf_rnum5
ldy #>_rndf_rnum5 ldy #>_rndf_rnum5
jsr c64flt.MOVMF ; fac1 to mem X/Y jsr MOVMF ; fac1 to mem X/Y
ldx c64.SCRATCH_ZPREG ldx c64.SCRATCH_ZPREG
lda #<_rndf_rnum5 lda #<_rndf_rnum5
ldy #>_rndf_rnum5 ldy #>_rndf_rnum5
@ -420,7 +421,7 @@ pop_float_fac1 .proc
jsr pop_float jsr pop_float
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jmp c64flt.MOVFM jmp MOVFM
.pend .pend
pop_float_to_indexed_var .proc pop_float_to_indexed_var .proc
@ -462,13 +463,13 @@ inc_var_f .proc
sta c64.SCRATCH_ZPWORD1 sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1 sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.MOVFM jsr MOVFM
lda #<FL_FONE lda #<FL_FONE
ldy #>FL_FONE ldy #>FL_FONE
jsr c64flt.FADD jsr FADD
ldx c64.SCRATCH_ZPWORD1 ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1 ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.MOVMF jsr MOVMF
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
rts rts
.pend .pend
@ -480,13 +481,13 @@ dec_var_f .proc
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
lda #<FL_FONE lda #<FL_FONE
ldy #>FL_FONE ldy #>FL_FONE
jsr c64flt.MOVFM jsr MOVFM
lda c64.SCRATCH_ZPWORD1 lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1 ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.FSUB jsr FSUB
ldx c64.SCRATCH_ZPWORD1 ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1 ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.MOVMF jsr MOVMF
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
rts rts
.pend .pend
@ -538,9 +539,10 @@ pop_2_floats_f2_in_fac1 .proc
jsr pop_float jsr pop_float
lda #<fmath_float2 lda #<fmath_float2
ldy #>fmath_float2 ldy #>fmath_float2
jmp c64flt.MOVFM jmp MOVFM
.pend .pend
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
@ -548,13 +550,31 @@ push_fac1_as_result .proc
; -- push the float in FAC1 onto the stack, and return from calculation ; -- push the float in FAC1 onto the stack, and return from calculation
ldx #<fmath_float1 ldx #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr c64flt.MOVMF jsr MOVMF
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
jmp push_float jmp push_float
.pend .pend
pow_f .proc
; -- push f1 ** f2 on stack
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr CONUPK ; fac2 = float1
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
ldx c64.SCRATCH_ZPREGX
jmp push_fac1_as_result
.pend
div_f .proc div_f .proc
; -- push f1/f2 on stack ; -- push f1/f2 on stack
@ -562,7 +582,7 @@ div_f .proc
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr c64flt.FDIV jsr FDIV
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -572,7 +592,7 @@ add_f .proc
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr c64flt.FADD jsr FADD
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -582,7 +602,7 @@ sub_f .proc
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr c64flt.FSUB jsr FSUB
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -592,7 +612,7 @@ mul_f .proc
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr c64flt.FMULT jsr FMULT
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -600,7 +620,7 @@ neg_f .proc
; -- push -flt back on stack ; -- push -flt back on stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.NEGOP jsr NEGOP
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -608,7 +628,7 @@ abs_f .proc
; -- push abs(float) on stack (as float) ; -- push abs(float) on stack (as float)
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.ABS jsr ABS
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -695,11 +715,11 @@ compare_floats .proc
jsr pop_float jsr pop_float
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr c64flt.MOVFM ; fac1 = flt1 jsr MOVFM ; fac1 = flt1
lda #<fmath_float2 lda #<fmath_float2
ldy #>fmath_float2 ldy #>fmath_float2
stx c64.SCRATCH_ZPREG stx c64.SCRATCH_ZPREG
jsr c64flt.FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2) jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
ldx c64.SCRATCH_ZPREG ldx c64.SCRATCH_ZPREG
rts rts
_return_false lda #0 _return_false lda #0
@ -714,7 +734,7 @@ func_sin .proc
; -- push sin(f) back onto stack ; -- push sin(f) back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.SIN jsr SIN
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -722,7 +742,7 @@ func_cos .proc
; -- push cos(f) back onto stack ; -- push cos(f) back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.COS jsr COS
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -730,7 +750,7 @@ func_tan .proc
; -- push tan(f) back onto stack ; -- push tan(f) back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.TAN jsr TAN
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -738,7 +758,7 @@ func_atan .proc
; -- push atan(f) back onto stack ; -- push atan(f) back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.ATN jsr ATN
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -746,7 +766,7 @@ func_ln .proc
; -- push ln(f) back onto stack ; -- push ln(f) back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.LOG jsr LOG
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -754,19 +774,19 @@ func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack ; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.LOG jsr LOG
jsr c64flt.MOVEF jsr MOVEF
lda #<c64.FL_LOG2 lda #<c64.FL_LOG2
ldy #>c64.FL_LOG2 ldy #>c64.FL_LOG2
jsr c64flt.MOVFM jsr MOVFM
jsr c64flt.FDIVT jsr FDIVT
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
func_sqrt .proc func_sqrt .proc
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.SQR jsr SQR
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -776,7 +796,7 @@ func_rad .proc
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
lda #<_pi_div_180 lda #<_pi_div_180
ldy #>_pi_div_180 ldy #>_pi_div_180
jsr c64flt.FMULT jsr FMULT
jmp push_fac1_as_result jmp push_fac1_as_result
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180 _pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend .pend
@ -787,7 +807,7 @@ func_deg .proc
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
lda #<_one_over_pi_div_180 lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180 ldy #>_one_over_pi_div_180
jsr c64flt.FMULT jsr FMULT
jmp push_fac1_as_result jmp push_fac1_as_result
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180) _one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend .pend
@ -795,15 +815,15 @@ _one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
func_round .proc func_round .proc
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.FADDH jsr FADDH
jsr c64flt.INT jsr INT
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
func_floor .proc func_floor .proc
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr c64flt.INT jsr INT
jmp push_fac1_as_result jmp push_fac1_as_result
.pend .pend
@ -811,18 +831,18 @@ func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1 ; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1 jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1 lda #<fmath_float1
ldy #>fmath_float1 ldy #>fmath_float1
jsr c64flt.MOVMF jsr FCOMP
jsr c64flt.INT
lda #<fmath_float1
ldy #>fmath_float1
jsr c64flt.FCOMP
cmp #0 cmp #0
beq + beq +
lda #<FL_FONE lda #<FL_FONE
ldy #>FL_FONE ldy #>FL_FONE
jsr c64flt.FADD jsr FADD
+ jmp push_fac1_as_result + jmp push_fac1_as_result
.pend .pend
@ -834,98 +854,93 @@ func_any_f .proc
asl a asl a
clc clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float adc c64.SCRATCH_ZPB1 ; times 5 because of float
jmp func_any_b._entry jmp prog8_lib.func_any_b._entry
.pend .pend
func_all_f .proc func_all_f .proc
inx inx
jsr prog8_lib.peek_address
lda c64.ESTACK_LO,x ; array size lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1 sta c64.SCRATCH_ZPB1
asl a asl a
asl a asl a
clc clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float adc c64.SCRATCH_ZPB1 ; times 5 because of float
sta _cmp_mod+1 ; self-modifying code tay
jsr peek_address dey
ldy #0
- lda (c64.SCRATCH_ZPWORD1),y - lda (c64.SCRATCH_ZPWORD1),y
bne + clc
iny dey
lda (c64.SCRATCH_ZPWORD1),y adc (c64.SCRATCH_ZPWORD1),y
bne + dey
iny adc (c64.SCRATCH_ZPWORD1),y
lda (c64.SCRATCH_ZPWORD1),y dey
bne + adc (c64.SCRATCH_ZPWORD1),y
iny dey
lda (c64.SCRATCH_ZPWORD1),y adc (c64.SCRATCH_ZPWORD1),y
bne + dey
iny cmp #0
lda (c64.SCRATCH_ZPWORD1),y beq +
bne + cpy #255
lda #0 bne -
sta c64.ESTACK_LO+1,x
rts
+ iny
_cmp_mod cpy #255 ; modified
bne -
lda #1 lda #1
sta c64.ESTACK_LO+1,x sta c64.ESTACK_LO+1,x
rts rts
+ sta c64.ESTACK_LO+1,x
rts
.pend .pend
func_max_f .proc func_max_f .proc
lda #<_min_float
ldy #>_min_float
jsr c64flt.MOVFM ; fac1=min(float)
lda #255 lda #255
sta _cmp_mod+1 ; compare using 255 so we keep larger values sta _minmax_cmp+1
_minmax_entry jsr pop_array_and_lengthmin1Y lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG - sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1 lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1 ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.FCOMP jsr FCOMP
_cmp_mod cmp #255 ; will be modified _minmax_cmp cmp #255 ; modified
bne + bne +
; fac1 is smaller/larger, so store the new value instead
lda c64.SCRATCH_ZPWORD1 lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1 ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.MOVFM jsr MOVFM
ldy c64.SCRATCH_ZPREG + lda c64.SCRATCH_ZPWORD1
dey
cmp #255
beq +
lda c64.SCRATCH_ZPWORD1
clc clc
adc #5 adc #5
sta c64.SCRATCH_ZPWORD1 sta c64.SCRATCH_ZPWORD1
bcc - bcc +
inc c64.SCRATCH_ZPWORD1+1 inc c64.SCRATCH_ZPWORD1+1
+ ldy c64.SCRATCH_ZPREG
dey
cpy #255
bne - bne -
+ jmp push_fac1_as_result jmp push_fac1_as_result
_min_float .byte 255,255,255,255,255 ; -1.7014118345e+38 _largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend .pend
func_min_f .proc func_min_f .proc
lda #<_max_float
ldy #>_max_float
jsr c64flt.MOVFM ; fac1=max(float)
lda #1 lda #1
sta func_max_f._cmp_mod+1 ; compare using 1 so we keep smaller values sta func_max_f._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f._minmax_entry jmp func_max_f._minmax_entry
_max_float .byte 255,127,255,255,255 ; 1.7014118345e+38 _largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend .pend
func_sum_f .proc func_sum_f .proc
lda #<c64.FL_NEGHLF lda #<FL_ZERO
ldy #>c64.FL_NEGHLF ldy #>FL_ZERO
jsr c64flt.MOVFM jsr MOVFM
jsr pop_array_and_lengthmin1Y jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG - sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1 lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1 ldy c64.SCRATCH_ZPWORD1+1
jsr c64flt.FADD jsr FADD
ldy c64.SCRATCH_ZPREG ldy c64.SCRATCH_ZPREG
dey dey
cpy #255 cpy #255
@ -937,8 +952,7 @@ func_sum_f .proc
bcc - bcc -
inc c64.SCRATCH_ZPWORD1+1 inc c64.SCRATCH_ZPWORD1+1
bne - bne -
+ jsr c64flt.FADDH + jmp push_fac1_as_result
jmp push_fac1_as_result
.pend .pend
}} }}

View File

@ -7,178 +7,178 @@
~ c64 { ~ c64 {
memory ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
memory ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
memory ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
memory uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
memory uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb) const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
const uword ESTACK_HI = $cf00 ; evaluation stack (msb) const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
memory ubyte TIME_HI = $a0 ; software jiffy clock, hi byte &ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
memory ubyte TIME_MID = $a1 ; .. mid byte &ubyte TIME_MID = $a1 ; .. mid byte
memory ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec &ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
memory ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ) &ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
memory ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ) &ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
memory ubyte COLOR = $0286 ; cursor color &ubyte COLOR = $0286 ; cursor color
memory ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address) &ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
memory uword CINV = $0314 ; IRQ vector &uword CINV = $0314 ; IRQ vector
memory uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in &uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
memory uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in &uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
memory uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in &uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; the default addresses for the character screen chars and colors ; the default addresses for the character screen chars and colors
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255 const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255 const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
; the default locations of the 8 sprite pointers (store address of sprite / 64) ; the default locations of the 8 sprite pointers (store address of sprite / 64)
memory ubyte SPRPTR0 = 2040 &ubyte SPRPTR0 = 2040
memory ubyte SPRPTR1 = 2041 &ubyte SPRPTR1 = 2041
memory ubyte SPRPTR2 = 2042 &ubyte SPRPTR2 = 2042
memory ubyte SPRPTR3 = 2043 &ubyte SPRPTR3 = 2043
memory ubyte SPRPTR4 = 2044 &ubyte SPRPTR4 = 2044
memory ubyte SPRPTR5 = 2045 &ubyte SPRPTR5 = 2045
memory ubyte SPRPTR6 = 2046 &ubyte SPRPTR6 = 2046
memory ubyte SPRPTR7 = 2047 &ubyte SPRPTR7 = 2047
memory ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array. &ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
; ---- VIC-II 6567/6569/856x registers ---- ; ---- VIC-II 6567/6569/856x registers ----
memory ubyte SP0X = $d000 &ubyte SP0X = $d000
memory ubyte SP0Y = $d001 &ubyte SP0Y = $d001
memory ubyte SP1X = $d002 &ubyte SP1X = $d002
memory ubyte SP1Y = $d003 &ubyte SP1Y = $d003
memory ubyte SP2X = $d004 &ubyte SP2X = $d004
memory ubyte SP2Y = $d005 &ubyte SP2Y = $d005
memory ubyte SP3X = $d006 &ubyte SP3X = $d006
memory ubyte SP3Y = $d007 &ubyte SP3Y = $d007
memory ubyte SP4X = $d008 &ubyte SP4X = $d008
memory ubyte SP4Y = $d009 &ubyte SP4Y = $d009
memory ubyte SP5X = $d00a &ubyte SP5X = $d00a
memory ubyte SP5Y = $d00b &ubyte SP5Y = $d00b
memory ubyte SP6X = $d00c &ubyte SP6X = $d00c
memory ubyte SP6Y = $d00d &ubyte SP6Y = $d00d
memory ubyte SP7X = $d00e &ubyte SP7X = $d00e
memory ubyte SP7Y = $d00f &ubyte SP7Y = $d00f
memory ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array. &ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
memory uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array. &uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
memory ubyte MSIGX = $d010 &ubyte MSIGX = $d010
memory ubyte SCROLY = $d011 &ubyte SCROLY = $d011
memory ubyte RASTER = $d012 &ubyte RASTER = $d012
memory ubyte LPENX = $d013 &ubyte LPENX = $d013
memory ubyte LPENY = $d014 &ubyte LPENY = $d014
memory ubyte SPENA = $d015 &ubyte SPENA = $d015
memory ubyte SCROLX = $d016 &ubyte SCROLX = $d016
memory ubyte YXPAND = $d017 &ubyte YXPAND = $d017
memory ubyte VMCSB = $d018 &ubyte VMCSB = $d018
memory ubyte VICIRQ = $d019 &ubyte VICIRQ = $d019
memory ubyte IREQMASK = $d01a &ubyte IREQMASK = $d01a
memory ubyte SPBGPR = $d01b &ubyte SPBGPR = $d01b
memory ubyte SPMC = $d01c &ubyte SPMC = $d01c
memory ubyte XXPAND = $d01d &ubyte XXPAND = $d01d
memory ubyte SPSPCL = $d01e &ubyte SPSPCL = $d01e
memory ubyte SPBGCL = $d01f &ubyte SPBGCL = $d01f
memory ubyte EXTCOL = $d020 ; border color &ubyte EXTCOL = $d020 ; border color
memory ubyte BGCOL0 = $d021 ; screen color &ubyte BGCOL0 = $d021 ; screen color
memory ubyte BGCOL1 = $d022 &ubyte BGCOL1 = $d022
memory ubyte BGCOL2 = $d023 &ubyte BGCOL2 = $d023
memory ubyte BGCOL4 = $d024 &ubyte BGCOL4 = $d024
memory ubyte SPMC0 = $d025 &ubyte SPMC0 = $d025
memory ubyte SPMC1 = $d026 &ubyte SPMC1 = $d026
memory ubyte SP0COL = $d027 &ubyte SP0COL = $d027
memory ubyte SP1COL = $d028 &ubyte SP1COL = $d028
memory ubyte SP2COL = $d029 &ubyte SP2COL = $d029
memory ubyte SP3COL = $d02a &ubyte SP3COL = $d02a
memory ubyte SP4COL = $d02b &ubyte SP4COL = $d02b
memory ubyte SP5COL = $d02c &ubyte SP5COL = $d02c
memory ubyte SP6COL = $d02d &ubyte SP6COL = $d02d
memory ubyte SP7COL = $d02e &ubyte SP7COL = $d02e
memory ubyte[8] SPCOL = $d027 &ubyte[8] SPCOL = $d027
; ---- end of VIC-II registers ---- ; ---- end of VIC-II registers ----
; ---- CIA 6526 1 & 2 registers ---- ; ---- CIA 6526 1 & 2 registers ----
memory ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive &ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
memory ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port &ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
memory ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column &ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
memory ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row &ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
memory ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte &ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
memory ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte &ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
memory ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte &ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
memory ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte &ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
memory ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec. &ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
memory ubyte CIA1TODSEC = $DC09 ; time of day, seconds &ubyte CIA1TODSEC = $DC09 ; time of day, seconds
memory ubyte CIA1TODMMIN = $DC0A ; time of day, minutes &ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
memory ubyte CIA1TODHR = $DC0B ; time of day, hours &ubyte CIA1TODHR = $DC0B ; time of day, hours
memory ubyte CIA1SDR = $DC0C ; Serial Data Register &ubyte CIA1SDR = $DC0C ; Serial Data Register
memory ubyte CIA1ICR = $DC0D &ubyte CIA1ICR = $DC0D
memory ubyte CIA1CRA = $DC0E &ubyte CIA1CRA = $DC0E
memory ubyte CIA1CRB = $DC0F &ubyte CIA1CRB = $DC0F
memory ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address &ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
memory ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT &ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
memory ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address &ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
memory ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT &ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
memory ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte &ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
memory ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte &ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
memory ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte &ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
memory ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte &ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
memory ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec. &ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
memory ubyte CIA2TODSEC = $DD09 ; time of day, seconds &ubyte CIA2TODSEC = $DD09 ; time of day, seconds
memory ubyte CIA2TODMIN = $DD0A ; time of day, minutes &ubyte CIA2TODMIN = $DD0A ; time of day, minutes
memory ubyte CIA2TODHR = $DD0B ; time of day, hours &ubyte CIA2TODHR = $DD0B ; time of day, hours
memory ubyte CIA2SDR = $DD0C ; Serial Data Register &ubyte CIA2SDR = $DD0C ; Serial Data Register
memory ubyte CIA2ICR = $DD0D &ubyte CIA2ICR = $DD0D
memory ubyte CIA2CRA = $DD0E &ubyte CIA2CRA = $DD0E
memory ubyte CIA2CRB = $DD0F &ubyte CIA2CRB = $DD0F
; ---- end of CIA registers ---- ; ---- end of CIA registers ----
; ---- SID 6581/8580 registers ---- ; ---- SID 6581/8580 registers ----
memory ubyte FREQLO1 = $D400 ; channel 1 freq lo &ubyte FREQLO1 = $D400 ; channel 1 freq lo
memory ubyte FREQHI1 = $D401 ; channel 1 freq hi &ubyte FREQHI1 = $D401 ; channel 1 freq hi
memory uword FREQ1 = $D400 ; channel 1 freq (word) &uword FREQ1 = $D400 ; channel 1 freq (word)
memory ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0) &ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
memory ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8) &ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
memory uword PW1 = $D402 ; channel 1 pulse width (word) &uword PW1 = $D402 ; channel 1 pulse width (word)
memory ubyte CR1 = $D404 ; channel 1 voice control register &ubyte CR1 = $D404 ; channel 1 voice control register
memory ubyte AD1 = $D405 ; channel 1 attack & decay &ubyte AD1 = $D405 ; channel 1 attack & decay
memory ubyte SR1 = $D406 ; channel 1 sustain & release &ubyte SR1 = $D406 ; channel 1 sustain & release
memory ubyte FREQLO2 = $D407 ; channel 2 freq lo &ubyte FREQLO2 = $D407 ; channel 2 freq lo
memory ubyte FREQHI2 = $D408 ; channel 2 freq hi &ubyte FREQHI2 = $D408 ; channel 2 freq hi
memory uword FREQ2 = $D407 ; channel 2 freq (word) &uword FREQ2 = $D407 ; channel 2 freq (word)
memory ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0) &ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
memory ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8) &ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
memory uword PW2 = $D409 ; channel 2 pulse width (word) &uword PW2 = $D409 ; channel 2 pulse width (word)
memory ubyte CR2 = $D40B ; channel 2 voice control register &ubyte CR2 = $D40B ; channel 2 voice control register
memory ubyte AD2 = $D40C ; channel 2 attack & decay &ubyte AD2 = $D40C ; channel 2 attack & decay
memory ubyte SR2 = $D40D ; channel 2 sustain & release &ubyte SR2 = $D40D ; channel 2 sustain & release
memory ubyte FREQLO3 = $D40E ; channel 3 freq lo &ubyte FREQLO3 = $D40E ; channel 3 freq lo
memory ubyte FREQHI3 = $D40F ; channel 3 freq hi &ubyte FREQHI3 = $D40F ; channel 3 freq hi
memory uword FREQ3 = $D40E ; channel 3 freq (word) &uword FREQ3 = $D40E ; channel 3 freq (word)
memory ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0) &ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
memory ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8) &ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
memory uword PW3 = $D410 ; channel 3 pulse width (word) &uword PW3 = $D410 ; channel 3 pulse width (word)
memory ubyte CR3 = $D412 ; channel 3 voice control register &ubyte CR3 = $D412 ; channel 3 voice control register
memory ubyte AD3 = $D413 ; channel 3 attack & decay &ubyte AD3 = $D413 ; channel 3 attack & decay
memory ubyte SR3 = $D414 ; channel 3 sustain & release &ubyte SR3 = $D414 ; channel 3 sustain & release
memory ubyte FCLO = $D415 ; filter cutoff lo (2-0) &ubyte FCLO = $D415 ; filter cutoff lo (2-0)
memory ubyte FCHI = $D416 ; filter cutoff hi (10-3) &ubyte FCHI = $D416 ; filter cutoff hi (10-3)
memory uword FC = $D415 ; filter cutoff (word) &uword FC = $D415 ; filter cutoff (word)
memory ubyte RESFILT = $D417 ; filter resonance and routing &ubyte RESFILT = $D417 ; filter resonance and routing
memory ubyte MVOL = $D418 ; filter mode and main volume control &ubyte MVOL = $D418 ; filter mode and main volume control
memory ubyte POTX = $D419 ; potentiometer X &ubyte POTX = $D419 ; potentiometer X
memory ubyte POTY = $D41A ; potentiometer Y &ubyte POTY = $D41A ; potentiometer Y
memory ubyte OSC3 = $D41B ; channel 3 oscillator value read &ubyte OSC3 = $D41B ; channel 3 oscillator value read
memory ubyte ENV3 = $D41C ; channel 3 envelope value read &ubyte ENV3 = $D41C ; channel 3 envelope value read
; ---- end of SID registers ---- ; ---- end of SID registers ----
@ -235,7 +235,7 @@ asmsub GETIN () -> clobbers(X,Y) -> (ubyte @ A) = $FFE4 ; (via 810 ($32A))
asmsub CLALL () -> clobbers(A,X) -> () = $FFE7 ; (via 812 ($32C)) close all files asmsub CLALL () -> clobbers(A,X) -> () = $FFE7 ; (via 812 ($32C)) close all files
asmsub UDTIM () -> clobbers(A,X) -> () = $FFEA ; update the software clock asmsub UDTIM () -> clobbers(A,X) -> () = $FFEA ; update the software clock
asmsub SCREEN () -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFED ; read number of screen rows and columns asmsub SCREEN () -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFED ; read number of screen rows and columns
asmsub PLOT (ubyte dir @ Pc, ubyte col @ Y, ubyte row @ X) -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF0 ; read/set position of cursor on screen. See c64scr.PLOT for a 'safe' wrapper that preserves X. asmsub PLOT (ubyte dir @ Pc, ubyte col @ Y, ubyte row @ X) -> clobbers() -> (ubyte @ X, ubyte @ Y) = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
asmsub IOBASE () -> clobbers() -> (uword @ XY) = $FFF3 ; read base address of I/O devices asmsub IOBASE () -> clobbers() -> (uword @ XY) = $FFF3 ; read base address of I/O devices
; ---- end of C64 kernal routines ---- ; ---- end of C64 kernal routines ----

View File

@ -54,40 +54,39 @@ asmsub ubyte2hex (ubyte value @ A) -> clobbers() -> (ubyte @ A, ubyte @ Y) {
pha pha
and #$0f and #$0f
tax tax
ldy hex_digits,x ldy _hex_digits,x
pla pla
lsr a lsr a
lsr a lsr a
lsr a lsr a
lsr a lsr a
tax tax
lda hex_digits,x lda _hex_digits,x
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
rts rts
hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as well
}} }}
} }
str word2hex_output = "1234" ; 0-terminated, to make printing easier
asmsub uword2hex (uword value @ AY) -> clobbers(A,Y) -> () { asmsub uword2hex (uword value @ AY) -> clobbers(A,Y) -> () {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string into memory 'word2hex_output' ; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{ %asm {{
sta c64.SCRATCH_ZPREG sta c64.SCRATCH_ZPREG
tya tya
jsr ubyte2hex jsr ubyte2hex
sta word2hex_output sta output
sty word2hex_output+1 sty output+1
lda c64.SCRATCH_ZPREG lda c64.SCRATCH_ZPREG
jsr ubyte2hex jsr ubyte2hex
sta word2hex_output+2 sta output+2
sty word2hex_output+3 sty output+3
rts rts
output .text "0000", $00 ; 0-terminated output buffer (to make printing easier)
}} }}
} }
ubyte[3] word2bcd_bcdbuff = [0, 0, 0]
asmsub uword2bcd (uword value @ AY) -> clobbers(A,Y) -> () { asmsub uword2bcd (uword value @ AY) -> clobbers(A,Y) -> () {
; Convert an 16 bit binary value to BCD ; Convert an 16 bit binary value to BCD
; ;
@ -99,47 +98,55 @@ asmsub uword2bcd (uword value @ AY) -> clobbers(A,Y) -> () {
%asm {{ %asm {{
sta c64.SCRATCH_ZPB1 sta c64.SCRATCH_ZPB1
sty c64.SCRATCH_ZPREG sty c64.SCRATCH_ZPREG
php
pla ; read status register
and #%00000100
sta _had_irqd
sei ; disable interrupts because of bcd math sei ; disable interrupts because of bcd math
sed ; switch to decimal mode sed ; switch to decimal mode
lda #0 ; ensure the result is clear lda #0 ; ensure the result is clear
sta word2bcd_bcdbuff+0 sta bcdbuff+0
sta word2bcd_bcdbuff+1 sta bcdbuff+1
sta word2bcd_bcdbuff+2 sta bcdbuff+2
ldy #16 ; the number of source bits ldy #16 ; the number of source bits
- asl c64.SCRATCH_ZPB1 ; shift out one bit - asl c64.SCRATCH_ZPB1 ; shift out one bit
rol c64.SCRATCH_ZPREG rol c64.SCRATCH_ZPREG
lda word2bcd_bcdbuff+0 ; and add into result lda bcdbuff+0 ; and add into result
adc word2bcd_bcdbuff+0 adc bcdbuff+0
sta word2bcd_bcdbuff+0 sta bcdbuff+0
lda word2bcd_bcdbuff+1 ; propagating any carry lda bcdbuff+1 ; propagating any carry
adc word2bcd_bcdbuff+1 adc bcdbuff+1
sta word2bcd_bcdbuff+1 sta bcdbuff+1
lda word2bcd_bcdbuff+2 ; ... thru whole result lda bcdbuff+2 ; ... thru whole result
adc word2bcd_bcdbuff+2 adc bcdbuff+2
sta word2bcd_bcdbuff+2 sta bcdbuff+2
dey ; and repeat for next bit dey ; and repeat for next bit
bne - bne -
cld ; back to binary cld ; back to binary
cli ; enable interrupts again @todo don't re-enable if it wasn't enabled before lda _had_irqd
rts bne +
cli ; enable interrupts again (only if they were enabled before)
+ rts
_had_irqd .byte 0
bcdbuff .byte 0,0,0
}} }}
} }
ubyte[5] word2decimal_output = 0 asmsub uword2decimal (uword value @ AY) -> clobbers(A) -> (ubyte @ Y) {
asmsub uword2decimal (uword value @ AY) -> clobbers(A,Y) -> () { ; ---- convert 16 bit uword in A/Y into 0-terminated decimal string into memory 'uword2decimal.output'
; ---- convert 16 bit uword in A/Y into decimal string into memory 'word2decimal_output' ; returns length of resulting string in Y
%asm {{ %asm {{
jsr uword2bcd jsr uword2bcd
lda word2bcd_bcdbuff+2 lda uword2bcd.bcdbuff+2
clc clc
adc #'0' adc #'0'
sta word2decimal_output sta output
ldy #1 ldy #1
lda word2bcd_bcdbuff+1 lda uword2bcd.bcdbuff+1
jsr + jsr +
lda word2bcd_bcdbuff+0 lda uword2bcd.bcdbuff+0
+ pha + pha
lsr a lsr a
@ -148,153 +155,135 @@ asmsub uword2decimal (uword value @ AY) -> clobbers(A,Y) -> () {
lsr a lsr a
clc clc
adc #'0' adc #'0'
sta word2decimal_output,y sta output,y
iny iny
pla pla
and #$0f and #$0f
adc #'0' adc #'0'
sta word2decimal_output,y sta output,y
iny iny
rts
}}
}
asmsub str2byte (str string @ AY) -> clobbers(Y) -> (byte @ A) {
%asm {{
; -- convert string (address in A/Y) to byte in A
; doesn't use any kernal routines
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
cmp #'-'
beq +
jmp str2ubyte._enter
+ inc c64.SCRATCH_ZPWORD1
bne +
inc c64.SCRATCH_ZPWORD1+1
+ jsr str2ubyte._enter
eor #$ff
sec
adc #0
rts
}}
}
asmsub str2ubyte (str string @ AY) -> clobbers(Y) -> (ubyte @ A) {
%asm {{
; -- convert string (address in A/Y) to ubyte in A
; doesn't use any kernal routines
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
_enter jsr _numlen ; Y= slen
lda #0 lda #0
dey sta output,y
bpl +
rts rts
+ lda (c64.SCRATCH_ZPWORD1),y
sec
sbc #'0'
dey
bpl +
rts
+ sta c64.SCRATCH_ZPREG ;result
lda (c64.SCRATCH_ZPWORD1),y
sec
sbc #'0'
asl a
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
clc
adc c64.SCRATCH_ZPREG
dey
bpl +
rts
+ sta c64.SCRATCH_ZPREG
lda (c64.SCRATCH_ZPWORD1),y
tay
lda _hundreds-'0',y
clc
adc c64.SCRATCH_ZPREG
rts
_hundreds .byte 0, 100, 200
_numlen output .text "00000", $00 ; 0 terminated
;-- return the length of the numeric string at ZPWORD1, in Y
}}
}
asmsub str2uword(str string @ AY) -> clobbers() -> (uword @ AY) {
; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
%asm {{
_result = c64.SCRATCH_ZPWORD2
sta _mod+1
sty _mod+2
ldy #0 ldy #0
- lda (c64.SCRATCH_ZPWORD1),y sty _result
cmp #'0' sty _result+1
bmi + _mod lda $ffff,y ; modified
cmp #':' ; one after '9' sec
sbc #48
bpl + bpl +
iny _done ; return result
bne - lda _result
+ rts ldy _result+1
rts
+ cmp #10
bcs _done
; add digit to result
pha
jsr _result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _mod
; never reached
_result_times_10 ; (W*4 + W)*2
lda _result+1
sta c64.SCRATCH_ZPREG
lda _result
asl a
rol c64.SCRATCH_ZPREG
asl a
rol c64.SCRATCH_ZPREG
clc
adc _result
sta _result
lda c64.SCRATCH_ZPREG
adc _result+1
asl _result
rol a
sta _result+1
rts
}} }}
} }
asmsub c64flt_FREADSTR (ubyte length @ A) -> clobbers(A,X,Y) -> () = $b7b5 ; @todo needed for (slow) str conversion below asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) {
asmsub c64flt_GETADR () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b7f7 ; @todo needed for (slow) str conversion below ; -- returns the signed word value of the string number argument in AY
asmsub c64flt_FTOSWORDYA () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b1aa ; @todo needed for (slow) str conversion below ; the number may be preceded by a + or - sign but may NOT contain spaces
; (any non-digit character will terminate the number string that is parsed)
asmsub str2uword(str string @ AY) -> clobbers() -> (uword @ AY) {
%asm {{ %asm {{
;-- convert string (address in A/Y) to uword number in A/Y _result = c64.SCRATCH_ZPWORD2
; @todo don't use the (slow) kernel floating point conversion sta c64.SCRATCH_ZPWORD1
sta $22 sty c64.SCRATCH_ZPWORD1+1
sty $23
jsr _strlen2233
tya
stx c64.SCRATCH_ZPREGX
jsr c64flt_FREADSTR ; string to fac1
jsr c64flt_GETADR ; fac1 to unsigned word in Y/A
ldx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPREG
tya
ldy c64.SCRATCH_ZPREG
rts
_strlen2233
;-- return the length of the (zero-terminated) string at $22/$23, in Y
ldy #0 ldy #0
- lda ($22),y sty _result
sty _result+1
sty _negative
lda (c64.SCRATCH_ZPWORD1),y
cmp #'+'
bne +
iny
+ cmp #'-'
bne _parse
inc _negative
iny
_parse lda (c64.SCRATCH_ZPWORD1),y
sec
sbc #48
bpl _digit
_done ; return result
lda _negative
beq + beq +
iny sec
bne - lda #0
+ rts sbc _result
}} sta _result
} lda #0
sbc _result+1
asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) { sta _result+1
%asm {{ + lda _result
;-- convert string (address in A/Y) to signed word number in A/Y ldy _result+1
; @todo don't use the (slow) kernel floating point conversion
sta $22
sty $23
jsr str2uword._strlen2233
tya
stx c64.SCRATCH_ZPREGX
jsr c64flt_FREADSTR ; string to fac1
jsr c64flt_FTOSWORDYA ; fac1 to unsigned word in Y/A
ldx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPREG
tya
ldy c64.SCRATCH_ZPREG
rts rts
_digit cmp #10
bcs _done
; add digit to result
pha
jsr str2uword._result_times_10
pla
clc
adc _result
sta _result
bcc +
inc _result+1
+ iny
bne _parse
; never reached
_negative .byte 0
}} }}
} }
; @todo string to 32 bit unsigned integer http://www.6502.org/source/strings/ascii-to-32bit.html
asmsub set_irqvec_excl() -> clobbers(A) -> () { asmsub set_irqvec_excl() -> clobbers(A) -> () {
%asm {{ %asm {{
sei sei
@ -304,7 +293,9 @@ asmsub set_irqvec_excl() -> clobbers(A) -> () {
sta c64.CINV+1 sta c64.CINV+1
cli cli
rts rts
_irq_handler jsr irq.irq _irq_handler jsr set_irqvec._irq_handler_init
jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff lda #$ff
sta c64.VICIRQ ; acknowledge raster irq sta c64.VICIRQ ; acknowledge raster irq
lda c64.CIA1ICR ; acknowledge CIA1 interrupt lda c64.CIA1ICR ; acknowledge CIA1 interrupt
@ -321,10 +312,64 @@ asmsub set_irqvec() -> clobbers(A) -> () {
sta c64.CINV+1 sta c64.CINV+1
cli cli
rts rts
_irq_handler jsr irq.irq _irq_handler jsr _irq_handler_init
jsr irq.irq
jsr _irq_handler_end
jmp c64.IRQDFRT ; continue with normal kernel irq routine jmp c64.IRQDFRT ; continue with normal kernel irq routine
}} _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 c64.SCRATCH_ZPB1
sta IRQ_SCRATCH_ZPB1
lda c64.SCRATCH_ZPREG
sta IRQ_SCRATCH_ZPREG
lda c64.SCRATCH_ZPREGX
sta IRQ_SCRATCH_ZPREGX
lda c64.SCRATCH_ZPWORD1
sta IRQ_SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
sta IRQ_SCRATCH_ZPWORD1+1
lda c64.SCRATCH_ZPWORD2
sta IRQ_SCRATCH_ZPWORD2
lda c64.SCRATCH_ZPWORD2+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
rts
_irq_handler_end
; restore all zp scratch registers and the X register
lda IRQ_SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
lda IRQ_SCRATCH_ZPREG
sta c64.SCRATCH_ZPREG
lda IRQ_SCRATCH_ZPREGX
sta c64.SCRATCH_ZPREGX
lda IRQ_SCRATCH_ZPWORD1
sta c64.SCRATCH_ZPWORD1
lda IRQ_SCRATCH_ZPWORD1+1
sta c64.SCRATCH_ZPWORD1+1
lda IRQ_SCRATCH_ZPWORD2
sta c64.SCRATCH_ZPWORD2
lda IRQ_SCRATCH_ZPWORD2+1
sta c64.SCRATCH_ZPWORD2+1
ldx IRQ_X_REG
rts
IRQ_X_REG .byte 0
IRQ_SCRATCH_ZPB1 .byte 0
IRQ_SCRATCH_ZPREG .byte 0
IRQ_SCRATCH_ZPREGX .byte 0
IRQ_SCRATCH_ZPWORD1 .word 0
IRQ_SCRATCH_ZPWORD2 .word 0
}}
} }
@ -357,7 +402,9 @@ asmsub set_rasterirq(uword rasterpos @ AY) -> clobbers(A) -> () {
rts rts
_raster_irq_handler _raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff lda #$ff
sta c64.VICIRQ ; acknowledge raster irq sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFRT jmp c64.IRQDFRT
@ -396,7 +443,9 @@ asmsub set_rasterirq_excl(uword rasterpos @ AY) -> clobbers(A) -> () {
rts rts
_raster_irq_handler _raster_irq_handler
jsr set_irqvec._irq_handler_init
jsr irq.irq jsr irq.irq
jsr set_irqvec._irq_handler_end
lda #$ff lda #$ff
sta c64.VICIRQ ; acknowledge raster irq sta c64.VICIRQ ; acknowledge raster irq
jmp c64.IRQDFEND ; end irq processing - don't call kernel jmp c64.IRQDFEND ; end irq processing - don't call kernel
@ -473,7 +522,7 @@ _loop sta c64.Colors,y
} }
asmsub scroll_left_full (ubyte alsocolors @ Pc) -> clobbers(A, Y) -> () { asmsub scroll_left_full (ubyte alsocolors @ Pc) -> clobbers(A, Y) -> () {
; ---- scroll the whole screen 1 character to the left ; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself ; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
@ -534,7 +583,7 @@ _scroll_screen ; scroll the screen memory
} }
asmsub scroll_right_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () { asmsub scroll_right_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
; ---- scroll the whole screen 1 character to the right ; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself ; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
@ -587,7 +636,7 @@ _scroll_screen ; scroll the screen memory
} }
asmsub scroll_up_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () { asmsub scroll_up_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
; ---- scroll the whole screen 1 character up ; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself ; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
@ -640,7 +689,7 @@ _scroll_screen ; scroll the screen memory
} }
asmsub scroll_down_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () { asmsub scroll_down_full (ubyte alsocolors @ Pc) -> clobbers(A) -> () {
; ---- scroll the whole screen 1 character down ; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself ; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data must be scrolled too ; Carry flag determines if screen color data must be scrolled too
@ -713,27 +762,6 @@ asmsub print (str text @ AY) -> clobbers(A,Y) -> () {
} }
asmsub print_p (str_p text @ AY) -> clobbers(A) -> (ubyte @ Y) {
; ---- print pstring (length as first byte) from A/Y, returns str len in Y
%asm {{
sta c64.SCRATCH_ZPB1
sty c64.SCRATCH_ZPREG
stx c64.SCRATCH_ZPREGX
ldy #0
lda (c64.SCRATCH_ZPB1),y
beq +
tax
- iny
lda (c64.SCRATCH_ZPB1),y
jsr c64.CHROUT
dex
bne -
+ ldx c64.SCRATCH_ZPREGX
rts ; output string length is in Y
}}
}
asmsub print_ub0 (ubyte value @ A) -> clobbers(A,Y) -> () { asmsub print_ub0 (ubyte value @ A) -> clobbers(A,Y) -> () {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total) ; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{ %asm {{
@ -866,7 +894,7 @@ asmsub print_uw0 (uword value @ AY) -> clobbers(A,Y) -> () {
%asm {{ %asm {{
jsr c64utils.uword2decimal jsr c64utils.uword2decimal
ldy #0 ldy #0
- lda c64utils.word2decimal_output,y - lda c64utils.uword2decimal.output,y
jsr c64.CHROUT jsr c64.CHROUT
iny iny
cpy #5 cpy #5
@ -881,25 +909,25 @@ asmsub print_uw (uword value @ AY) -> clobbers(A,Y) -> () {
%asm {{ %asm {{
jsr c64utils.uword2decimal jsr c64utils.uword2decimal
ldy #0 ldy #0
lda c64utils.word2decimal_output lda c64utils.uword2decimal.output
cmp #'0' cmp #'0'
bne _pr_decimal bne _pr_decimal
iny iny
lda c64utils.word2decimal_output+1 lda c64utils.uword2decimal.output+1
cmp #'0' cmp #'0'
bne _pr_decimal bne _pr_decimal
iny iny
lda c64utils.word2decimal_output+2 lda c64utils.uword2decimal.output+2
cmp #'0' cmp #'0'
bne _pr_decimal bne _pr_decimal
iny iny
lda c64utils.word2decimal_output+3 lda c64utils.uword2decimal.output+3
cmp #'0' cmp #'0'
bne _pr_decimal bne _pr_decimal
iny iny
_pr_decimal _pr_decimal
lda c64utils.word2decimal_output,y lda c64utils.uword2decimal.output,y
jsr c64.CHROUT jsr c64.CHROUT
iny iny
cpy #5 cpy #5
@ -909,7 +937,7 @@ _pr_decimal
} }
asmsub print_w (word value @ AY) -> clobbers(A,Y) -> () { asmsub print_w (word value @ AY) -> clobbers(A,Y) -> () {
; ---- print the (signed) word in A/Y in decimal form, without left padding 0s ; ---- print the (signed) word in A/Y in decimal form, without left padding 0's
%asm {{ %asm {{
cpy #0 cpy #0
bpl + bpl +
@ -930,7 +958,7 @@ asmsub print_w (word value @ AY) -> clobbers(A,Y) -> () {
} }
asmsub input_chars (uword buffer @ AY) -> clobbers(A) -> (ubyte @ Y) { asmsub input_chars (uword buffer @ AY) -> clobbers(A) -> (ubyte @ Y) {
; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. ; ---- Input a string (max. 80 chars) from the keyboard. Returns length in Y. (string is terminated with a 0 byte as well)
; It assumes the keyboard is selected as I/O channel! ; It assumes the keyboard is selected as I/O channel!
%asm {{ %asm {{
@ -972,6 +1000,25 @@ _screenrows .word $0400 + range(0, 1000, 40)
}} }}
} }
asmsub getchr (ubyte col @Y, ubyte row @A) -> clobbers(Y) -> (ubyte @ A) {
; ---- get the character in the screen matrix at the given location
%asm {{
sty c64.SCRATCH_ZPB1
asl a
tay
lda setchr._screenrows+1,y
sta _mod+2
lda setchr._screenrows,y
clc
adc c64.SCRATCH_ZPB1
sta _mod+1
bcc _mod
inc _mod+2
_mod lda $ffff ; modified
rts
}}
}
asmsub setclr (ubyte col @Y, ubyte row @A) -> clobbers(A) -> () { asmsub setclr (ubyte col @Y, ubyte row @A) -> clobbers(A) -> () {
; ---- set the color in SCRATCH_ZPB1 on the screen matrix at the given position ; ---- set the color in SCRATCH_ZPB1 on the screen matrix at the given position
%asm {{ %asm {{
@ -994,6 +1041,24 @@ _colorrows .word $d800 + range(0, 1000, 40)
}} }}
} }
asmsub getclr (ubyte col @Y, ubyte row @A) -> clobbers(Y) -> (ubyte @ A) {
; ---- get the color in the screen color matrix at the given location
%asm {{
sty c64.SCRATCH_ZPB1
asl a
tay
lda setclr._colorrows+1,y
sta _mod+2
lda setclr._colorrows,y
clc
adc c64.SCRATCH_ZPB1
sta _mod+1
bcc _mod
inc _mod+2
_mod lda $ffff ; modified
rts
}}
}
sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) { sub setcc (ubyte column, ubyte row, ubyte char, ubyte color) {
; ---- set char+color at the given position on the screen ; ---- set char+color at the given position on the screen
@ -1021,7 +1086,7 @@ _colormod sta $ffff ; modified
}} }}
} }
asmsub PLOT (ubyte col @ Y, ubyte row @ A) -> clobbers(A) -> () { 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 kernel routine, to save the X register.
%asm {{ %asm {{
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX

View File

@ -58,29 +58,29 @@ multiply_words .proc
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
mult16 lda #$00 mult16 lda #$00
sta multiply_words_result+2 ; clear upper bits of product sta result+2 ; clear upper bits of product
sta multiply_words_result+3 sta result+3
ldx #16 ; for all 16 bits... ldx #16 ; for all 16 bits...
- lsr c64.SCRATCH_ZPWORD1+1 ; divide multiplier by 2 - lsr c64.SCRATCH_ZPWORD1+1 ; divide multiplier by 2
ror c64.SCRATCH_ZPWORD1 ror c64.SCRATCH_ZPWORD1
bcc + bcc +
lda multiply_words_result+2 ; get upper half of product and add multiplicand lda result+2 ; get upper half of product and add multiplicand
clc clc
adc c64.SCRATCH_ZPWORD2 adc c64.SCRATCH_ZPWORD2
sta multiply_words_result+2 sta result+2
lda multiply_words_result+3 lda result+3
adc c64.SCRATCH_ZPWORD2+1 adc c64.SCRATCH_ZPWORD2+1
+ ror a ; rotate partial product + ror a ; rotate partial product
sta multiply_words_result+3 sta result+3
ror multiply_words_result+2 ror result+2
ror multiply_words_result+1 ror result+1
ror multiply_words_result ror result
dex dex
bne - bne -
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
rts rts
multiply_words_result .byte 0,0,0,0 result .byte 0,0,0,0
.pend .pend

View File

@ -106,6 +106,161 @@ not_word .proc
rts rts
.pend .pend
bitand_b .proc
; -- bitwise and (of 2 bytes)
lda c64.ESTACK_LO+2,x
and c64.ESTACK_LO+1,x
inx
sta c64.ESTACK_LO+1,x
rts
.pend
bitor_b .proc
; -- bitwise or (of 2 bytes)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_LO+1,x
inx
sta c64.ESTACK_LO+1,x
rts
.pend
bitxor_b .proc
; -- bitwise xor (of 2 bytes)
lda c64.ESTACK_LO+2,x
eor c64.ESTACK_LO+1,x
inx
sta c64.ESTACK_LO+1,x
rts
.pend
bitand_w .proc
; -- bitwise and (of 2 words)
lda c64.ESTACK_LO+2,x
and c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+2,x
lda c64.ESTACK_HI+2,x
and c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+2,x
inx
rts
.pend
bitor_w .proc
; -- bitwise or (of 2 words)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+2,x
lda c64.ESTACK_HI+2,x
ora c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+2,x
inx
rts
.pend
bitxor_w .proc
; -- bitwise xor (of 2 bytes)
lda c64.ESTACK_LO+2,x
eor c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+2,x
lda c64.ESTACK_HI+2,x
eor c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+2,x
inx
rts
.pend
and_b .proc
; -- logical and (of 2 bytes)
lda c64.ESTACK_LO+2,x
beq +
lda #1
+ sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
beq +
lda #1
+ and c64.SCRATCH_ZPB1
inx
sta c64.ESTACK_LO+1,x
rts
.pend
or_b .proc
; -- logical or (of 2 bytes)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_LO+1,x
beq +
lda #1
+ inx
sta c64.ESTACK_LO+1,x
rts
.pend
xor_b .proc
; -- logical xor (of 2 bytes)
lda c64.ESTACK_LO+2,x
beq +
lda #1
+ sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
beq +
lda #1
+ eor c64.SCRATCH_ZPB1
inx
sta c64.ESTACK_LO+1,x
rts
.pend
and_w .proc
; -- logical and (word and word -> byte)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_HI+2,x
beq +
lda #1
+ sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
ora c64.ESTACK_HI+1,x
beq +
lda #1
+ and c64.SCRATCH_ZPB1
inx
sta c64.ESTACK_LO+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
or_w .proc
; -- logical or (word or word -> byte)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_LO+1,x
ora c64.ESTACK_HI+2,x
ora c64.ESTACK_HI+1,x
beq +
lda #1
+ inx
sta c64.ESTACK_LO+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
xor_w .proc
; -- logical xor (word xor word -> byte)
lda c64.ESTACK_LO+2,x
ora c64.ESTACK_HI+2,x
beq +
lda #1
+ sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
ora c64.ESTACK_HI+1,x
beq +
lda #1
+ eor c64.SCRATCH_ZPB1
inx
sta c64.ESTACK_LO+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
abs_b .proc abs_b .proc
; -- push abs(byte) on stack (as byte) ; -- push abs(byte) on stack (as byte)
lda c64.ESTACK_LO+1,x lda c64.ESTACK_LO+1,x
@ -167,9 +322,9 @@ mul_word .proc
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
jsr math.multiply_words jsr math.multiply_words
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
lda math.multiply_words.multiply_words_result lda math.multiply_words.result
sta c64.ESTACK_LO+1,x sta c64.ESTACK_LO+1,x
lda math.multiply_words.multiply_words_result+1 lda math.multiply_words.result+1
sta c64.ESTACK_HI+1,x sta c64.ESTACK_HI+1,x
rts rts
.pend .pend
@ -485,6 +640,65 @@ greatereq_w .proc
bmi equal_b._equal_b_false bmi equal_b._equal_b_false
.pend .pend
func_read_flags .proc
; -- put the processor status register on the stack
php
pla
sta c64.ESTACK_LO,x
dex
rts
.pend
func_sqrt16 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD2+1
stx c64.SCRATCH_ZPREGX
ldy #$00 ; r = 0
ldx #$07
clc ; clear bit 16 of m
_loop
tya
ora _stab-1,x
sta c64.SCRATCH_ZPB1 ; (r asl 8) | (d asl 7)
lda c64.SCRATCH_ZPWORD2+1
bcs _skip0 ; m >= 65536? then t <= m is always true
cmp c64.SCRATCH_ZPB1
bcc _skip1 ; t <= m
_skip0
sbc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPWORD2+1 ; m = m - t
tya
ora _stab,x
tay ; r = r or d
_skip1
asl c64.SCRATCH_ZPWORD2
rol c64.SCRATCH_ZPWORD2+1 ; m = m asl 1
dex
bne _loop
; last iteration
bcs _skip2
sty c64.SCRATCH_ZPB1
lda c64.SCRATCH_ZPWORD2
cmp #$80
lda c64.SCRATCH_ZPWORD2+1
sbc c64.SCRATCH_ZPB1
bcc _skip3
_skip2
iny ; r = r or d (d is 1 here)
_skip3
ldx c64.SCRATCH_ZPREGX
tya
sta c64.ESTACK_LO+1,x
lda #0
sta c64.ESTACK_HI+1,x
rts
_stab .byte $01,$02,$04,$08,$10,$20,$40,$80
.pend
func_sin8 .proc func_sin8 .proc
ldy c64.ESTACK_LO+1,x ldy c64.ESTACK_LO+1,x
@ -948,8 +1162,7 @@ _gtequ dey
_result_minw .word 0 _result_minw .word 0
.pend .pend
func_strlen .proc
func_len_str .proc
; -- push length of 0-terminated string on stack ; -- push length of 0-terminated string on stack
jsr peek_address jsr peek_address
ldy #0 ldy #0
@ -962,15 +1175,6 @@ func_len_str .proc
rts rts
.pend .pend
func_len_strp .proc
; -- push length of pascal-string on stack
jsr peek_address
ldy #0
lda (c64.SCRATCH_ZPWORD1),y ; first byte is length
sta c64.ESTACK_LO+1,x
rts
.pend
func_rnd .proc func_rnd .proc
; -- put a random ubyte on the estack ; -- put a random ubyte on the estack
jsr math.randbyte jsr math.randbyte

View File

@ -1 +1 @@
1.2 (beta) 1.8

View File

@ -1,6 +1,7 @@
package prog8 package prog8
import prog8.ast.* import prog8.ast.*
import prog8.astvm.AstVm
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.c64.AsmGen import prog8.compiler.target.c64.AsmGen
import prog8.compiler.target.c64.C64Zeropage import prog8.compiler.target.c64.C64Zeropage
@ -8,7 +9,9 @@ import prog8.optimizing.constantFold
import prog8.optimizing.optimizeStatements import prog8.optimizing.optimizeStatements
import prog8.optimizing.simplifyExpressions import prog8.optimizing.simplifyExpressions
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule
import prog8.parser.importModule import prog8.parser.importModule
import prog8.parser.moduleName
import java.io.File import java.io.File
import java.io.PrintStream import java.io.PrintStream
import java.lang.Exception import java.lang.Exception
@ -33,10 +36,9 @@ fun main(args: Array<String>) {
compileMain(args) compileMain(args)
} }
fun printSoftwareHeader(what: String) { internal fun printSoftwareHeader(what: String) {
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim() val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
println("\nProg8 $what by Irmen de Jong (irmen@razorvine.net)") println("\nProg8 $what v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
println("Version: $buildVersion")
println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n") println("This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html\n")
} }
@ -46,6 +48,9 @@ private fun compileMain(args: Array<String>) {
var moduleFile = "" var moduleFile = ""
var writeVmCode = false var writeVmCode = false
var writeAssembly = true var writeAssembly = true
var optimize = true
var optimizeInlining = true
var launchAstVm = false
for (arg in args) { for (arg in args) {
if(arg=="-emu") if(arg=="-emu")
emulatorToStart = "x64" emulatorToStart = "x64"
@ -55,6 +60,12 @@ private fun compileMain(args: Array<String>) {
writeVmCode = true writeVmCode = true
else if(arg=="-noasm") else if(arg=="-noasm")
writeAssembly = false writeAssembly = false
else if(arg=="-noopt")
optimize = false
else if(arg=="-nooptinline")
optimizeInlining = false
else if(arg=="-avm")
launchAstVm = true
else if(!arg.startsWith("-")) else if(!arg.startsWith("-"))
moduleFile = arg moduleFile = arg
else else
@ -65,63 +76,72 @@ private fun compileMain(args: Array<String>) {
val filepath = Paths.get(moduleFile).normalize() val filepath = Paths.get(moduleFile).normalize()
var programname = "?" var programname = "?"
lateinit var programAst: Program
try { try {
val totalTime = measureTimeMillis { val totalTime = measureTimeMillis {
// import main module and process additional imports // import main module and everything it needs
println("Parsing...") println("Parsing...")
val moduleAst = importModule(filepath) programAst = Program(moduleName(filepath.fileName), mutableListOf())
moduleAst.linkParents() importModule(programAst, filepath)
var namespace = moduleAst.definingScope()
// determine special compiler options
val compilerOptions = determineCompilationOptions(moduleAst)
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG) if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${moduleAst.position} BASIC launcher requires output type PRG.") throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
if(compilerOptions.launcher==LauncherType.BASIC || compilerOptions.output==OutputType.PRG) {
importLibraryModule(programAst, "c64lib")
importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importLibraryModule(programAst, "math")
importLibraryModule(programAst, "prog8lib")
// perform initial syntax checks and constant folding // perform initial syntax checks and constant folding
println("Syntax check...") println("Syntax check...")
val heap = HeapValues()
val time1= measureTimeMillis { val time1= measureTimeMillis {
moduleAst.checkIdentifiers(heap) programAst.checkIdentifiers()
} }
//println(" time1: $time1") //println(" time1: $time1")
val time2 = measureTimeMillis { val time2 = measureTimeMillis {
moduleAst.constantFold(namespace, heap) programAst.constantFold()
} }
//println(" time2: $time2") //println(" time2: $time2")
val time3 = measureTimeMillis { val time3 = measureTimeMillis {
moduleAst.reorderStatements(namespace,heap) // reorder statements to please the compiler later programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later
} }
//println(" time3: $time3") //println(" time3: $time3")
val time4 = measureTimeMillis { val time4 = measureTimeMillis {
moduleAst.checkValid(namespace, compilerOptions, heap) // check if tree is valid programAst.checkValid(compilerOptions) // check if tree is valid
} }
//println(" time4: $time4") //println(" time4: $time4")
// optimize the parse tree programAst.checkIdentifiers()
println("Optimizing...") if(optimize) {
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers(heap) // useful for checking symbol usage later? // optimize the parse tree
while (true) { println("Optimizing...")
// keep optimizing expressions and statements until no more steps remain while (true) {
val optsDone1 = moduleAst.simplifyExpressions(namespace, heap) // keep optimizing expressions and statements until no more steps remain
val optsDone2 = moduleAst.optimizeStatements(namespace, heap) val optsDone1 = programAst.simplifyExpressions()
if (optsDone1 + optsDone2 == 0) val optsDone2 = programAst.optimizeStatements(optimizeInlining)
break if (optsDone1 + optsDone2 == 0)
break
}
} }
namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime programAst.checkValid(compilerOptions) // check if final tree is valid
moduleAst.checkValid(namespace, compilerOptions, heap) // check if final tree is valid programAst.checkRecursion() // check if there are recursive subroutine calls
moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls
// namespace.debugPrint() // namespace.debugPrint()
// compile the syntax tree into stackvmProg form, and optimize that // compile the syntax tree into stackvmProg form, and optimize that
val compiler = Compiler(moduleAst, namespace, heap) val compiler = Compiler(programAst)
val intermediate = compiler.compile(compilerOptions) val intermediate = compiler.compile(compilerOptions)
intermediate.optimize() if(optimize)
intermediate.optimize()
if(writeVmCode) { if(writeVmCode) {
val stackVmFilename = intermediate.name + ".vm.txt" val stackVmFilename = intermediate.name + ".vm.txt"
@ -134,7 +154,7 @@ private fun compileMain(args: Array<String>) {
if(writeAssembly) { if(writeAssembly) {
val zeropage = C64Zeropage(compilerOptions) val zeropage = C64Zeropage(compilerOptions)
intermediate.allocateZeropage(zeropage) intermediate.allocateZeropage(zeropage)
val assembly = AsmGen(compilerOptions, intermediate, heap, zeropage).compileToAssembly() val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize)
assembly.assemble(compilerOptions) assembly.assemble(compilerOptions)
programname = assembly.name programname = assembly.name
} }
@ -146,6 +166,11 @@ private fun compileMain(args: Array<String>) {
System.err.println(px.message) System.err.println(px.message)
System.err.print("\u001b[0m") // reset System.err.print("\u001b[0m") // reset
exitProcess(1) exitProcess(1)
} catch (ax: AstException) {
System.err.print("\u001b[91m") // bright red
System.err.println(ax.toString())
System.err.print("\u001b[0m") // reset
exitProcess(1)
} catch (x: Exception) { } catch (x: Exception) {
print("\u001b[91m") // bright red print("\u001b[91m") // bright red
println("\n* internal error *") println("\n* internal error *")
@ -160,6 +185,12 @@ private fun compileMain(args: Array<String>) {
throw x throw x
} }
if(launchAstVm) {
println("\nLaunching AST-based vm...")
val vm = AstVm(programAst)
vm.run()
}
if(emulatorToStart.isNotEmpty()) { if(emulatorToStart.isNotEmpty()) {
println("\nStarting C-64 emulator $emulatorToStart...") println("\nStarting C-64 emulator $emulatorToStart...")
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programname.vice-mon-list", val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programname.vice-mon-list",
@ -169,17 +200,19 @@ private fun compileMain(args: Array<String>) {
} }
} }
fun determineCompilationOptions(moduleAst: Module): CompilationOptions {
val options = moduleAst.statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet() private fun determineCompilationOptions(program: Program): CompilationOptions {
val outputType = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%output" } val mainModule = program.modules.first()
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
as? Directive)?.args?.single()?.name?.toUpperCase() as? Directive)?.args?.single()?.name?.toUpperCase()
val launcherType = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%launcher" } val launcherType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%launcher" }
as? Directive)?.args?.single()?.name?.toUpperCase() as? Directive)?.args?.single()?.name?.toUpperCase()
moduleAst.loadAddress = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%address" } mainModule.loadAddress = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%address" }
as? Directive)?.args?.single()?.int ?: 0 as? Directive)?.args?.single()?.int ?: 0
val zpoption: String? = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%zeropage" } val zpoption: String? = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%zeropage" }
as? Directive)?.args?.single()?.name?.toUpperCase() as? Directive)?.args?.single()?.name?.toUpperCase()
val floatsEnabled = options.any { it.name == "enable_floats" } 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 zpType: ZeropageType = val zpType: ZeropageType =
if (zpoption == null) if (zpoption == null)
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
@ -190,7 +223,7 @@ fun determineCompilationOptions(moduleAst: Module): CompilationOptions {
ZeropageType.KERNALSAFE ZeropageType.KERNALSAFE
// error will be printed by the astchecker // error will be printed by the astchecker
} }
val zpReserved = moduleAst.statements val zpReserved = mainModule.statements
.asSequence() .asSequence()
.filter { it is Directive && it.directive == "%zpreserved" } .filter { it is Directive && it.directive == "%zpreserved" }
.map { (it as Directive).args } .map { (it as Directive).args }
@ -206,11 +239,14 @@ fun determineCompilationOptions(moduleAst: Module): CompilationOptions {
private fun usage() { private fun usage() {
System.err.println("Missing argument(s):") System.err.println("Missing argument(s):")
System.err.println(" [-emu] auto-start the 'x64' C-64 emulator after successful compilation") System.err.println(" [-emu] auto-start the 'x64' C-64 emulator after successful compilation")
System.err.println(" [-emu2] auto-start the 'x64sc' C-64 emulator after successful compilation") System.err.println(" [-emu2] auto-start the 'x64sc' C-64 emulator after successful compilation")
System.err.println(" [-writevm] write intermediate vm code to a file as well") System.err.println(" [-writevm] write intermediate vm code to a file as well")
System.err.println(" [-noasm] don't create assembly code") System.err.println(" [-noasm] don't create assembly code")
System.err.println(" [-vm] launch the prog8 virtual machine instead of the compiler") System.err.println(" [-vm] launch the prog8 virtual machine instead of the compiler")
System.err.println(" modulefile main module file to compile") System.err.println(" [-avm] launch the prog8 ast-based virtual machine after compilation")
System.err.println(" [-noopt] don't perform any optimizations")
System.err.println(" [-nooptinline] don't perform subroutine inlining optimizations")
System.err.println(" modulefile main module file to compile")
exitProcess(1) exitProcess(1)
} }

File diff suppressed because it is too large Load Diff

View File

@ -5,16 +5,16 @@ import prog8.compiler.HeapValues
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import prog8.optimizing.same
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
import java.io.File
/** /**
* General checks on the Ast * General checks on the Ast
*/ */
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions, heap: HeapValues) { internal fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(globalNamespace, compilerOptions, heap) val checker = AstChecker(this, compilerOptions)
this.process(checker) checker.process(this)
printErrors(checker.result(), name) printErrors(checker.result(), name)
} }
@ -51,20 +51,71 @@ fun printWarning(msg: String) {
print("\u001b[0m\n") // normal print("\u001b[0m\n") // normal
} }
private class AstChecker(private val namespace: INameScope, private class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions, private val compilerOptions: CompilationOptions) : IAstProcessor {
private val heap: HeapValues) : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf() private val checkResult: MutableList<AstException> = mutableListOf()
private val heapStringSentinel: Int private val heapStringSentinel: Int
init { init {
val stringSentinel = heap.allEntries().firstOrNull {it.value.str==""} val stringSentinel = program.heap.allEntries().firstOrNull {it.value.str==""}
heapStringSentinel = stringSentinel?.key ?: heap.add(DataType.STR, "") heapStringSentinel = stringSentinel?.key ?: program.heap.addString(DataType.STR, "")
} }
fun result(): List<AstException> { fun result(): List<AstException> {
return checkResult return checkResult
} }
override fun process(program: Program) {
assert(program === this.program)
// there must be a single 'main' block with a 'start' subroutine for the program entry point.
val mainBlocks = program.modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
if(mainBlocks.size>1)
checkResult.add(SyntaxError("more than one 'main' block", mainBlocks[0].position))
for(mainBlock in mainBlocks) {
val startSub = mainBlock.subScopes()["start"] as? Subroutine
if (startSub == null) {
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position))
} else {
if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", startSub.position))
}
// the main module cannot contain 'regular' statements (they will never be executed!)
for (statement in mainBlock.statements) {
val ok = when (statement) {
is Block -> true
is Directive -> true
is Label -> true
is VarDecl -> true
is InlineAssembly -> true
is INameScope -> true
is VariableInitializationAssignment -> true
is NopStatement -> true
else -> false
}
if (!ok) {
checkResult.add(SyntaxError("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position))
break
}
}
}
// 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)
checkResult.add(SyntaxError("more than one 'irq' block", irqBlocks[0].position))
for(irqBlock in irqBlocks) {
val irqSub = irqBlock.subScopes()["irq"] as? Subroutine
if (irqSub != null) {
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position))
}
}
super.process(program)
}
override fun process(module: Module) { override fun process(module: Module) {
super.process(module) super.process(module)
val directives = module.statements.filterIsInstance<Directive>().groupBy { it.directive } val directives = module.statements.filterIsInstance<Directive>().groupBy { it.directive }
@ -74,45 +125,6 @@ private class AstChecker(private val namespace: INameScope,
entry.value.mapTo(checkResult) { SyntaxError("directive can just occur once", it.position) } entry.value.mapTo(checkResult) { SyntaxError("directive can just occur once", it.position) }
} }
} }
// there must be a 'main' block with a 'start' subroutine for the program entry point.
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" } as? Block?
val startSub = mainBlock?.subScopes()?.get("start") as? Subroutine
if(startSub==null) {
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", module.position))
} else {
if(startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", startSub.position))
}
if(mainBlock!=null) {
// the main module cannot contain 'regular' statements (they will never be executed!)
for (statement in mainBlock.statements) {
val ok = when(statement) {
is Block->true
is Directive->true
is Label->true
is VarDecl->true
is InlineAssembly->true
is INameScope->true
is VariableInitializationAssignment->true
else->false
}
if(!ok) {
checkResult.add(SyntaxError("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position))
break
}
}
}
// there can be an optional '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 irqBlock = module.statements.singleOrNull { it is Block && it.name=="irq" } as? Block?
val irqSub = irqBlock?.subScopes()?.get("irq") as? Subroutine
if(irqSub!=null) {
if(irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position))
}
} }
override fun process(returnStmt: Return): IStatement { override fun process(returnStmt: Return): IStatement {
@ -120,7 +132,7 @@ private class AstChecker(private val namespace: INameScope,
if(expectedReturnValues.size != returnStmt.values.size) { if(expectedReturnValues.size != returnStmt.values.size) {
// if the return value is a function call, check the result of that call instead // if the return value is a function call, check the result of that call instead
if(returnStmt.values.size==1 && returnStmt.values[0] is FunctionCall) { if(returnStmt.values.size==1 && returnStmt.values[0] is FunctionCall) {
val dt = (returnStmt.values[0] as FunctionCall).resultingDatatype(namespace, heap) val dt = (returnStmt.values[0] as FunctionCall).inferType(program)
if(dt!=null && expectedReturnValues.isEmpty()) if(dt!=null && expectedReturnValues.isEmpty())
checkResult.add(SyntaxError("invalid number of return values", returnStmt.position)) checkResult.add(SyntaxError("invalid number of return values", returnStmt.position))
} else } else
@ -128,7 +140,7 @@ private class AstChecker(private val namespace: INameScope,
} }
for (rv in expectedReturnValues.withIndex().zip(returnStmt.values)) { for (rv in expectedReturnValues.withIndex().zip(returnStmt.values)) {
val valueDt=rv.second.resultingDatatype(namespace, heap) val valueDt=rv.second.inferType(program)
if(rv.first.value!=valueDt) if(rv.first.value!=valueDt)
checkResult.add(ExpressionError("type $valueDt of return value #${rv.first.index+1} doesn't match subroutine return type ${rv.first.value}", rv.second.position)) checkResult.add(ExpressionError("type $valueDt of return value #${rv.first.index+1} doesn't match subroutine return type ${rv.first.value}", rv.second.position))
} }
@ -136,16 +148,13 @@ private class AstChecker(private val namespace: INameScope,
} }
override fun process(forLoop: ForLoop): IStatement { override fun process(forLoop: ForLoop): IStatement {
if(forLoop.body.isEmpty()) if(forLoop.body.containsNoCodeNorVars())
printWarning("for loop body is empty", forLoop.position) printWarning("for loop body is empty", forLoop.position)
if(forLoop.iterable is LiteralValue) val iterableDt = forLoop.iterable.inferType(program)
checkResult.add(SyntaxError("currently not possible to loop over a literal value directly, define it as a variable instead", forLoop.position)) // todo loop over literals (by creating a generated variable) if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
if(!forLoop.iterable.isIterable(namespace, heap)) {
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position)) checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
} else { } else {
val iterableDt = forLoop.iterable.resultingDatatype(namespace, heap)
if (forLoop.loopRegister != null) { if (forLoop.loopRegister != null) {
printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position) printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position)
// loop register // loop register
@ -153,7 +162,7 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position)) checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position))
} else { } else {
// loop variable // loop variable
val loopvar = forLoop.loopVar!!.targetStatement(namespace) as? VarDecl val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
if(loopvar==null || loopvar.type==VarDeclType.CONST) { if(loopvar==null || loopvar.type==VarDeclType.CONST) {
checkResult.add(SyntaxError("for loop requires a variable to loop with", forLoop.position)) checkResult.add(SyntaxError("for loop requires a variable to loop with", forLoop.position))
@ -260,9 +269,9 @@ private class AstChecker(private val namespace: INameScope,
if(subroutine.isAsmSubroutine) { if(subroutine.isAsmSubroutine) {
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size) if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
err("number of asm parameter registers is not the same as number of parameters") err("number of asm parameter registers is not the isSameAs as number of parameters")
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size) if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
err("number of return registers is not the same as number of return values") err("number of return registers is not the isSameAs as number of return values")
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) { for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
if(param.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) { if(param.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE) if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE)
@ -336,7 +345,7 @@ private class AstChecker(private val namespace: INameScope,
if(subroutine.asmClobbers.intersect(regCounts.keys).isNotEmpty()) if(subroutine.asmClobbers.intersect(regCounts.keys).isNotEmpty())
err("a return register is also in the clobber list") err("a return register is also in the clobber list")
} else { } else {
// TODO: non-asm subroutines can only take numeric arguments for now. (not strings and arrays) // TODO: non-asm subroutines can only take numeric arguments for now. (not strings and arrays) Maybe this can be improved now that we have '&' ?
// the way string params are treated is almost okay (their address is passed) but the receiving subroutine treats it as an integer rather than referring back to the original string. // the way string params are treated is almost okay (their address is passed) but the receiving subroutine treats it as an integer rather than referring back to the original string.
// the way array params are treated is buggy; it thinks the subroutine needs a byte parameter in place of a byte[] ... // the way array params are treated is buggy; it thinks the subroutine needs a byte parameter in place of a byte[] ...
// This is not easy to fix because strings and arrays are treated a bit simplistic (a "virtual" pointer to the value on the heap) // This is not easy to fix because strings and arrays are treated a bit simplistic (a "virtual" pointer to the value on the heap)
@ -357,24 +366,18 @@ private class AstChecker(private val namespace: INameScope,
// assigning from a functioncall COULD return multiple values (from an asm subroutine) // assigning from a functioncall COULD return multiple values (from an asm subroutine)
if(assignment.value is FunctionCall) { if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(namespace) val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if (stmt is Subroutine && stmt.returntypes.size > 1) { if (stmt is Subroutine) {
if (stmt.isAsmSubroutine) { if (stmt.isAsmSubroutine) {
if (stmt.returntypes.size != assignment.targets.size) if (stmt.returntypes.size != assignment.targets.size)
checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position)) checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position))
else { else {
if (assignment.targets.all { it.register != null }) {
val returnRegisters = registerSet(stmt.asmReturnvaluesRegisters)
val targetRegisters = assignment.targets.filter { it.register != null }.map { it.register }.toSet()
if (returnRegisters != targetRegisters)
checkResult.add(ExpressionError("asmsub return registers $returnRegisters don't match assignment target registers", assignment.position))
}
for (thing in stmt.returntypes.zip(assignment.targets)) { for (thing in stmt.returntypes.zip(assignment.targets)) {
if (thing.second.determineDatatype(namespace, heap, assignment) != thing.first) if (thing.second.inferType(program, assignment) != thing.first)
checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position)) checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position))
} }
} }
} else } else if(assignment.targets.size>1)
checkResult.add(ExpressionError("only asmsub subroutines can return multiple values", assignment.value.position)) checkResult.add(ExpressionError("only asmsub subroutines can return multiple values", assignment.value.position))
} }
} }
@ -387,7 +390,7 @@ private class AstChecker(private val namespace: INameScope,
} }
private fun processAssignmentTarget(assignment: Assignment, target: AssignTarget): Assignment { private fun processAssignmentTarget(assignment: Assignment, target: AssignTarget): Assignment {
val memAddr = target.memoryAddress?.addressExpression?.constValue(namespace, heap)?.asIntegerValue val memAddr = target.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue
if(memAddr!=null) { if(memAddr!=null) {
if(memAddr<0 || memAddr>=65536) if(memAddr<0 || memAddr>=65536)
checkResult.add(ExpressionError("address out of range", target.position)) checkResult.add(ExpressionError("address out of range", target.position))
@ -395,7 +398,7 @@ private class AstChecker(private val namespace: INameScope,
if(target.identifier!=null) { if(target.identifier!=null) {
val targetName = target.identifier.nameInSource val targetName = target.identifier.nameInSource
val targetSymbol = namespace.lookup(targetName, assignment) val targetSymbol = program.namespace.lookup(targetName, assignment)
when (targetSymbol) { when (targetSymbol) {
null -> { null -> {
checkResult.add(ExpressionError("undefined symbol: ${targetName.joinToString(".")}", assignment.position)) checkResult.add(ExpressionError("undefined symbol: ${targetName.joinToString(".")}", assignment.position))
@ -410,23 +413,10 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(ExpressionError("cannot assign new value to a constant", assignment.position)) checkResult.add(ExpressionError("cannot assign new value to a constant", assignment.position))
return assignment return assignment
} }
if(assignment.value.resultingDatatype(namespace, heap) in ArrayDatatypes) {
if(targetSymbol.datatype==DataType.UWORD)
return assignment // array can be assigned to UWORD (it's address should be taken as the value then)
}
} }
} }
} }
// it is only possible to assign an array to something that is an UWORD or UWORD array (in which case the address of the array value is used as the value)
if(assignment.value.resultingDatatype(namespace, heap) in ArrayDatatypes) {
// the UWORD case has been handled above already, check for UWORD array
val arrayVar = target.arrayindexed?.identifier?.targetStatement(namespace)
if(arrayVar is VarDecl && arrayVar.datatype==DataType.ARRAY_UW)
return assignment
checkResult.add(SyntaxError("it's not possible to assign an array to something other than an UWORD, use it as a variable decl initializer instead", assignment.position))
}
if(assignment.aug_op!=null) { if(assignment.aug_op!=null) {
// check augmented assignment (and convert it into a normal assignment!) // check augmented assignment (and convert it into a normal assignment!)
// A /= 3 -> check as if it was A = A / 3 // A /= 3 -> check as if it was A = A / 3
@ -446,23 +436,23 @@ private class AstChecker(private val namespace: INameScope,
return assignment2 return assignment2
} }
val targetDatatype = target.determineDatatype(namespace, heap, assignment) val targetDatatype = target.inferType(program, assignment)
if(targetDatatype!=null) { if(targetDatatype!=null) {
val constVal = assignment.value.constValue(namespace, heap) val constVal = assignment.value.constValue(program)
if(constVal!=null) { if(constVal!=null) {
val arrayspec = if(target.identifier!=null) { val arrayspec = if(target.identifier!=null) {
val targetVar = namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl val targetVar = program.namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
targetVar?.arrayspec targetVar?.arraysize
} else null } else null
checkValueTypeAndRange(targetDatatype, checkValueTypeAndRange(targetDatatype,
arrayspec ?: ArraySpec(LiteralValue.optimalInteger(-1, assignment.position), assignment.position), arrayspec ?: ArrayIndex(LiteralValue.optimalInteger(-1, assignment.position), assignment.position),
constVal, heap) constVal, program.heap)
} else { } else {
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap) val sourceDatatype: DataType? = assignment.value.inferType(program)
if(sourceDatatype==null) { if(sourceDatatype==null) {
if(assignment.targets.size<=1) { if(assignment.targets.size<=1) {
if (assignment.value is FunctionCall) { if (assignment.value is FunctionCall) {
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(namespace) val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if(targetStmt!=null) if(targetStmt!=null)
checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position)) checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position))
} }
@ -477,6 +467,18 @@ private class AstChecker(private val namespace: INameScope,
return assignment return assignment
} }
override fun process(addressOf: AddressOf): IExpression {
val variable=addressOf.identifier.targetVarDecl(program.namespace)
if(variable==null)
checkResult.add(ExpressionError("pointer-of operand must be the name of a heap variable", addressOf.position))
else {
if(variable.datatype !in ArrayDatatypes && variable.datatype !in StringDatatypes)
checkResult.add(ExpressionError("pointer-of operand must be the name of a string or array heap variable", addressOf.position))
}
if(addressOf.scopedname==null)
throw FatalAstException("the scopedname of AddressOf should have been set by now $addressOf")
return super.process(addressOf)
}
/** /**
* Check the variable declarations (values within range etc) * Check the variable declarations (values within range etc)
@ -487,7 +489,7 @@ private class AstChecker(private val namespace: INameScope,
} }
// the initializer value can't refer to the variable itself (recursive definition) // the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arrayspec?.x?.referencesIdentifier(decl.name) == true) { if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
err("recursive var declaration") err("recursive var declaration")
} }
@ -498,22 +500,43 @@ private class AstChecker(private val namespace: INameScope,
} }
// FLOATS // FLOATS
if(!compilerOptions.floats && decl.datatype==DataType.FLOAT && decl.type!=VarDeclType.MEMORY) { if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!=VarDeclType.MEMORY) {
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", decl.position)) checkResult.add(SyntaxError("floating point used, but that is not enabled via options", decl.position))
} }
// ARRAY without size specifier MUST have an iterable initializer value
if(decl.isArray && decl.arraysize==null) {
if(decl.type==VarDeclType.MEMORY)
checkResult.add(SyntaxError("memory mapped array must have a size specification", decl.position))
if(decl.value==null) {
checkResult.add(SyntaxError("array variable is missing a size specification or an initialization value", decl.position))
return decl
}
if(decl.value is LiteralValue && !(decl.value as LiteralValue).isArray) {
checkResult.add(SyntaxError("unsized array declaration cannot use a single literal initialization value", decl.position))
return decl
}
if(decl.value is RangeExpr)
throw FatalAstException("range expressions in vardecls should have been converted into array values during constFolding $decl")
}
when(decl.type) { when(decl.type) {
VarDeclType.VAR, VarDeclType.CONST -> { VarDeclType.VAR, VarDeclType.CONST -> {
if (decl.value == null) { if (decl.value == null) {
when { when {
decl.datatype in NumericDatatypes -> { decl.datatype in NumericDatatypes -> {
// initialize numeric var with value zero by default. // initialize numeric var with value zero by default.
val litVal = LiteralValue(DataType.UBYTE, 0, position = decl.position) val litVal =
when {
decl.datatype in ByteDatatypes -> LiteralValue(decl.datatype, bytevalue=0, position = decl.position)
decl.datatype in WordDatatypes -> LiteralValue(decl.datatype, wordvalue=0, position = decl.position)
else -> LiteralValue(decl.datatype, floatvalue=0.0, position = decl.position)
}
litVal.parent = decl litVal.parent = decl
decl.value = litVal decl.value = litVal
} }
decl.type==VarDeclType.VAR -> { decl.type==VarDeclType.VAR -> {
val litVal = LiteralValue(decl.datatype, heapId = heapStringSentinel, position=decl.position) // point to the sentinel heap value instead val litVal = LiteralValue(decl.datatype, initHeapId = heapStringSentinel, position=decl.position) // point to the sentinel heap value instead
litVal.parent=decl litVal.parent=decl
decl.value = litVal decl.value = litVal
} }
@ -523,15 +546,18 @@ private class AstChecker(private val namespace: INameScope,
return super.process(decl) return super.process(decl)
} }
when { when {
decl.value is RangeExpr -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as RangeExpr) decl.value is RangeExpr -> {
if(decl.arraysize!=null)
checkValueTypeAndRange(decl.datatype, decl.arraysize!!, decl.value as RangeExpr)
}
decl.value is LiteralValue -> { decl.value is LiteralValue -> {
val arraySpec = decl.arrayspec ?: ( val arraySpec = decl.arraysize ?: (
if((decl.value as LiteralValue).isArray) if((decl.value as LiteralValue).isArray)
ArraySpec.forArray(decl.value as LiteralValue, heap) ArrayIndex.forArray(decl.value as LiteralValue, program.heap)
else else
ArraySpec(LiteralValue.optimalInteger(-2, decl.position), decl.position) ArrayIndex(LiteralValue.optimalInteger(-2, decl.position), decl.position)
) )
checkValueTypeAndRange(decl.datatype, arraySpec, decl.value as LiteralValue, heap) checkValueTypeAndRange(decl.datatype, arraySpec, decl.value as LiteralValue, program.heap)
} }
else -> { else -> {
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!::class.simpleName}") err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!::class.simpleName}")
@ -540,8 +566,8 @@ private class AstChecker(private val namespace: INameScope,
} }
} }
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
if(decl.arrayspec!=null) { if(decl.arraysize!=null) {
val arraySize = decl.arrayspec.size() ?: 1 val arraySize = decl.arraysize!!.size() ?: 1
when(decl.datatype) { when(decl.datatype) {
DataType.ARRAY_B, DataType.ARRAY_UB -> DataType.ARRAY_B, DataType.ARRAY_UB ->
if(arraySize > 256) if(arraySize > 256)
@ -623,6 +649,7 @@ private class AstChecker(private val namespace: INameScope,
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block") if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
if(directive.args.size!=2 || directive.args[0].str==null || directive.args[1].str==null) if(directive.args.size!=2 || directive.args[0].str==null || directive.args[1].str==null)
err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"") err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"")
checkFileExists(directive, directive.args[0].str!!)
} }
"%asmbinary" -> { "%asmbinary" -> {
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block") if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
@ -632,6 +659,7 @@ private class AstChecker(private val namespace: INameScope,
if(directive.args.size>=2 && directive.args[1].int==null) err(errormsg) if(directive.args.size>=2 && directive.args[1].int==null) err(errormsg)
if(directive.args.size==3 && directive.args[2].int==null) err(errormsg) if(directive.args.size==3 && directive.args[2].int==null) err(errormsg)
if(directive.args.size>3) err(errormsg) if(directive.args.size>3) err(errormsg)
checkFileExists(directive, directive.args[0].str!!)
} }
"%option" -> { "%option" -> {
if(directive.parent !is Block && directive.parent !is Module) err("this directive may only occur in a block or at module level") if(directive.parent !is Block && directive.parent !is Module) err("this directive may only occur in a block or at module level")
@ -645,16 +673,24 @@ private class AstChecker(private val namespace: INameScope,
return super.process(directive) return super.process(directive)
} }
private fun checkFileExists(directive: Directive, filename: String) {
var definingModule = directive.parent
while (definingModule !is Module)
definingModule = definingModule.parent
if (!(filename.startsWith("library:") || definingModule.source.resolveSibling(filename).toFile().isFile || File(filename).isFile))
checkResult.add(NameError("included file not found: $filename", directive.position))
}
override fun process(literalValue: LiteralValue): LiteralValue { override fun process(literalValue: LiteralValue): LiteralValue {
if(!compilerOptions.floats && literalValue.type==DataType.FLOAT) { if(!compilerOptions.floats && literalValue.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", literalValue.position)) checkResult.add(SyntaxError("floating point used, but that is not enabled via options", literalValue.position))
} }
val arrayspec = val arrayspec =
if(literalValue.isArray) if(literalValue.isArray)
ArraySpec.forArray(literalValue, heap) ArrayIndex.forArray(literalValue, program.heap)
else else
ArraySpec(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position) ArrayIndex(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position)
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, heap) checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, program.heap)
val lv = super.process(literalValue) val lv = super.process(literalValue)
when(lv.type) { when(lv.type) {
@ -673,7 +709,7 @@ private class AstChecker(private val namespace: INameScope,
override fun process(expr: PrefixExpression): IExpression { override fun process(expr: PrefixExpression): IExpression {
if(expr.operator=="-") { if(expr.operator=="-") {
val dt = expr.resultingDatatype(namespace, heap) val dt = expr.inferType(program)
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) { if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
checkResult.add(ExpressionError("can only take negative of a signed number type", expr.position)) checkResult.add(ExpressionError("can only take negative of a signed number type", expr.position))
} }
@ -682,30 +718,40 @@ private class AstChecker(private val namespace: INameScope,
} }
override fun process(expr: BinaryExpression): IExpression { override fun process(expr: BinaryExpression): IExpression {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
when(expr.operator){ when(expr.operator){
"/", "%" -> { "/", "%" -> {
val constvalRight = expr.right.constValue(namespace, heap) val constvalRight = expr.right.constValue(program)
val divisor = constvalRight?.asNumericValue?.toDouble() val divisor = constvalRight?.asNumericValue?.toDouble()
if(divisor==0.0) if(divisor==0.0)
checkResult.add(ExpressionError("division by zero", expr.right.position)) checkResult.add(ExpressionError("division by zero", expr.right.position))
if(expr.operator=="%") { if(expr.operator=="%") {
val rightDt = constvalRight?.resultingDatatype(namespace, heap)
val leftDt = expr.left.resultingDatatype(namespace, heap)
if ((rightDt != DataType.UBYTE && rightDt != DataType.UWORD) || (leftDt!=DataType.UBYTE && leftDt!=DataType.UWORD)) if ((rightDt != DataType.UBYTE && rightDt != DataType.UWORD) || (leftDt!=DataType.UBYTE && leftDt!=DataType.UWORD))
checkResult.add(ExpressionError("remainder can only be used on unsigned integer operands", expr.right.position)) checkResult.add(ExpressionError("remainder can only be used on unsigned integer operands", expr.right.position))
} }
} }
"and", "or", "xor", "&", "|", "^" -> { "**" -> {
// only integer numeric operands accepted if(leftDt in IntegerDatatypes)
val rightDt = expr.right.resultingDatatype(namespace, heap) checkResult.add(ExpressionError("power operator requires floating point", expr.position))
val leftDt = expr.left.resultingDatatype(namespace, heap) }
"and", "or", "xor" -> {
// only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1)
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes) if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
checkResult.add(ExpressionError("logical or bitwise operator can only be used on integer operands", expr.right.position)) checkResult.add(ExpressionError("logical operator can only be used on boolean operands", expr.right.position))
val constLeft = expr.left.constValue(program)
val constRight = expr.right.constValue(program)
if(constLeft!=null && constLeft.asIntegerValue !in 0..1 || constRight!=null && constRight.asIntegerValue !in 0..1)
checkResult.add(ExpressionError("const literal argument of logical operator must be boolean (0 or 1)", expr.position))
}
"&", "|", "^" -> {
// only integer numeric operands accepted
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
checkResult.add(ExpressionError("bitwise operator can only be used on integer operands", expr.right.position))
} }
} }
val leftDt = expr.left.resultingDatatype(namespace, heap)!!
val rightDt = expr.right.resultingDatatype(namespace, heap)!!
if(leftDt !in NumericDatatypes) if(leftDt !in NumericDatatypes)
checkResult.add(ExpressionError("left operand is not numeric", expr.left.position)) checkResult.add(ExpressionError("left operand is not numeric", expr.left.position))
if(rightDt!in NumericDatatypes) if(rightDt!in NumericDatatypes)
@ -716,12 +762,6 @@ private class AstChecker(private val namespace: INameScope,
override fun process(typecast: TypecastExpression): IExpression { override fun process(typecast: TypecastExpression): IExpression {
if(typecast.type in IterableDatatypes) if(typecast.type in IterableDatatypes)
checkResult.add(ExpressionError("cannot type cast to string or array type", typecast.position)) checkResult.add(ExpressionError("cannot type cast to string or array type", typecast.position))
val funcTarget = (typecast.expression as? IFunctionCall)?.target?.targetStatement(namespace)
if(funcTarget is Subroutine &&
funcTarget.asmReturnvaluesRegisters.isNotEmpty() &&
funcTarget.asmReturnvaluesRegisters.all { it.stack!=true }) {
checkResult.add(ExpressionError("cannot type cast a call to an asmsub that returns value in register - use a variable to store it first", typecast.position))
}
return super.process(typecast) return super.process(typecast)
} }
@ -730,9 +770,9 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(SyntaxError(msg, range.position)) checkResult.add(SyntaxError(msg, range.position))
} }
super.process(range) super.process(range)
val from = range.from.constValue(namespace, heap) val from = range.from.constValue(program)
val to = range.to.constValue(namespace, heap) val to = range.to.constValue(program)
val stepLv = range.step.constValue(namespace, heap) ?: LiteralValue(DataType.UBYTE, 1, position = range.position) val stepLv = range.step.constValue(program) ?: LiteralValue(DataType.UBYTE, 1, position = range.position)
if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) { if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) {
err("range step must be an integer != 0") err("range step must be an integer != 0")
return range return range
@ -749,8 +789,8 @@ private class AstChecker(private val namespace: INameScope,
err("descending range requires step < 0") err("descending range requires step < 0")
} }
from.isString && to.isString -> { from.isString && to.isString -> {
val fromString = from.strvalue(heap) val fromString = from.strvalue!!
val toString = to.strvalue(heap) val toString = to.strvalue!!
if(fromString.length!=1 || toString.length!=1) if(fromString.length!=1 || toString.length!=1)
err("range from and to must be a single character") err("range from and to must be a single character")
if(fromString[0] == toString[0]) if(fromString[0] == toString[0])
@ -797,20 +837,20 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(SyntaxError("invalid number of arguments", position)) checkResult.add(SyntaxError("invalid number of arguments", position))
else { else {
for (arg in args.withIndex().zip(func.parameters)) { for (arg in args.withIndex().zip(func.parameters)) {
val argDt=arg.first.value.resultingDatatype(namespace, heap) val argDt=arg.first.value.inferType(program)
if(argDt!=null && !argDt.assignableTo(arg.second.possibleDatatypes)) { if(argDt!=null && !(argDt isAssignableTo arg.second.possibleDatatypes)) {
checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position)) checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
} }
} }
if(target.name=="swap") { if(target.name=="swap") {
// swap() is a bit weird because this one is translated into a sequence of bytecodes, instead of being an actual function call // swap() is a bit weird because this one is translated into a sequence of bytecodes, instead of being an actual function call
val dt1 = args[0].resultingDatatype(namespace, heap)!! val dt1 = args[0].inferType(program)!!
val dt2 = args[1].resultingDatatype(namespace, heap)!! val dt2 = args[1].inferType(program)!!
if (dt1 != dt2) if (dt1 != dt2)
checkResult.add(ExpressionError("swap requires 2 args of identical type", position)) checkResult.add(ExpressionError("swap requires 2 args of identical type", position))
else if (args[0].constValue(namespace, heap) != null || args[1].constValue(namespace, heap) != null) else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
checkResult.add(ExpressionError("swap requires 2 variables, not constant value(s)", position)) checkResult.add(ExpressionError("swap requires 2 variables, not constant value(s)", position))
else if(same(args[0], args[1])) else if(args[0] isSameAs args[1])
checkResult.add(ExpressionError("swap should have 2 different args", position)) checkResult.add(ExpressionError("swap should have 2 different args", position))
else if(dt1 !in NumericDatatypes) else if(dt1 !in NumericDatatypes)
checkResult.add(ExpressionError("swap requires args of numerical type", position)) checkResult.add(ExpressionError("swap requires args of numerical type", position))
@ -821,15 +861,31 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(SyntaxError("invalid number of arguments", position)) checkResult.add(SyntaxError("invalid number of arguments", position))
else { else {
for (arg in args.withIndex().zip(target.parameters)) { for (arg in args.withIndex().zip(target.parameters)) {
val argDt = arg.first.value.resultingDatatype(namespace, heap) val argDt = arg.first.value.inferType(program)
if(argDt!=null && !argDt.assignableTo(arg.second.type)) if(argDt!=null && !(argDt isAssignableTo arg.second.type)) {
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} has invalid type $argDt, expected ${arg.second.type}", position)) // for asm subroutines having STR param it's okay to provide a UWORD too (pointer value)
if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt==DataType.UWORD))
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position))
}
if(target.isAsmSubroutine) { if(target.isAsmSubroutine) {
if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) { if (target.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
if (arg.first.value !is LiteralValue && arg.first.value !is IdentifierReference) if (arg.first.value !is LiteralValue && arg.first.value !is IdentifierReference)
printWarning("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position) printWarning("calling a subroutine that expects X as a parameter is problematic, more so when providing complex arguments. If you see a compiler error/crash about this later, try to simplify this call", position)
} }
// check if the argument types match the register(pairs)
val asmParamReg = target.asmParameterRegisters[arg.first.index]
if(asmParamReg.statusflag!=null) {
if(argDt !in ByteDatatypes)
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be byte type for statusflag", position))
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if(argDt !in ByteDatatypes)
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be byte type for single register", position))
} else if(asmParamReg.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if(argDt !in WordDatatypes+ IterableDatatypes)
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index+1} must be word type for register pair", position))
}
} }
} }
} }
@ -839,7 +895,7 @@ private class AstChecker(private val namespace: INameScope,
override fun process(postIncrDecr: PostIncrDecr): IStatement { override fun process(postIncrDecr: PostIncrDecr): IStatement {
if(postIncrDecr.target.identifier != null) { if(postIncrDecr.target.identifier != null) {
val targetName = postIncrDecr.target.identifier!!.nameInSource val targetName = postIncrDecr.target.identifier!!.nameInSource
val target = namespace.lookup(targetName, postIncrDecr) val target = program.namespace.lookup(targetName, postIncrDecr)
if(target==null) { if(target==null) {
checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position)) checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position))
} else { } else {
@ -850,7 +906,7 @@ private class AstChecker(private val namespace: INameScope,
} }
} }
} else if(postIncrDecr.target.arrayindexed != null) { } else if(postIncrDecr.target.arrayindexed != null) {
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(namespace) val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
if(target==null) { if(target==null) {
checkResult.add(SyntaxError("undefined symbol", postIncrDecr.position)) checkResult.add(SyntaxError("undefined symbol", postIncrDecr.position))
} }
@ -866,24 +922,21 @@ private class AstChecker(private val namespace: INameScope,
} }
override fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression { override fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
val target = arrayIndexedExpression.identifier.targetStatement(namespace) val target = arrayIndexedExpression.identifier.targetStatement(program.namespace)
if(target is VarDecl) { if(target is VarDecl) {
if(target.datatype !in IterableDatatypes) if(target.datatype !in IterableDatatypes)
checkResult.add(SyntaxError("indexing requires an iterable variable", arrayIndexedExpression.position)) checkResult.add(SyntaxError("indexing requires an iterable variable", arrayIndexedExpression.position))
val arraysize = target.arrayspec?.size() val arraysize = target.arraysize?.size()
if(arraysize!=null) { if(arraysize!=null) {
// check out of bounds // check out of bounds
val index = (arrayIndexedExpression.arrayspec.x as? LiteralValue)?.asIntegerValue val index = (arrayIndexedExpression.arrayspec.index as? LiteralValue)?.asIntegerValue
if(index!=null && (index<0 || index>=arraysize)) if(index!=null && (index<0 || index>=arraysize))
checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position)) checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position))
} else if(target.datatype in StringDatatypes) { } else if(target.datatype in StringDatatypes) {
// check supported string tyep
if(target.datatype == DataType.STR_P || target.datatype==DataType.STR_PS)
checkResult.add(ExpressionError("indexing pascal-strings is not supported, use regular str type instead", arrayIndexedExpression.arrayspec.position))
// check string lengths // check string lengths
val heapId = (target.value as LiteralValue).heapId!! val heapId = (target.value as LiteralValue).heapId!!
val stringLen = heap.get(heapId).str!!.length val stringLen = program.heap.get(heapId).str!!.length
val index = (arrayIndexedExpression.arrayspec.x as? LiteralValue)?.asIntegerValue val index = (arrayIndexedExpression.arrayspec.index as? LiteralValue)?.asIntegerValue
if(index!=null && (index<0 || index>=stringLen)) if(index!=null && (index<0 || index>=stringLen))
checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position)) checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position))
} }
@ -891,7 +944,7 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position)) checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position))
// check index value 0..255 // check index value 0..255
val dtx = arrayIndexedExpression.arrayspec.x.resultingDatatype(namespace, heap) val dtx = arrayIndexedExpression.arrayspec.index.inferType(program)
if(dtx!=DataType.UBYTE && dtx!=DataType.BYTE) if(dtx!=DataType.UBYTE && dtx!=DataType.BYTE)
checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)) checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position))
@ -899,16 +952,16 @@ private class AstChecker(private val namespace: INameScope,
} }
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? { private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? {
val targetStatement = target.targetStatement(namespace) val targetStatement = target.targetStatement(program.namespace)
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder) if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
return targetStatement return targetStatement
checkResult.add(SyntaxError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position)) checkResult.add(NameError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
return null return null
} }
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, range: RangeExpr) : Boolean { private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArrayIndex, range: RangeExpr) : Boolean {
val from = range.from.constValue(namespace, heap) val from = range.from.constValue(program)
val to = range.to.constValue(namespace, heap) val to = range.to.constValue(program)
if(from==null || to==null) { if(from==null || to==null) {
checkResult.add(SyntaxError("range from and to values must be constants", range.position)) checkResult.add(SyntaxError("range from and to values must be constants", range.position))
return false return false
@ -925,7 +978,7 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(ExpressionError("range for string must have single characters from and to values", range.position)) checkResult.add(ExpressionError("range for string must have single characters from and to values", range.position))
return false return false
} }
val rangeSize=range.size(heap) val rangeSize=range.size()
if(rangeSize!=null && (rangeSize<0 || rangeSize>255)) { if(rangeSize!=null && (rangeSize<0 || rangeSize>255)) {
checkResult.add(ExpressionError("size of range for string must be 0..255, instead of $rangeSize", range.position)) checkResult.add(ExpressionError("size of range for string must be 0..255, instead of $rangeSize", range.position))
return false return false
@ -934,8 +987,8 @@ private class AstChecker(private val namespace: INameScope,
} }
in ArrayDatatypes -> { in ArrayDatatypes -> {
// range and length check bytes // range and length check bytes
val expectedSize = arrayspec!!.size() val expectedSize = arrayspec.size()
val rangeSize=range.size(heap) val rangeSize=range.size()
if(rangeSize!=null && rangeSize != expectedSize) { if(rangeSize!=null && rangeSize != expectedSize) {
checkResult.add(ExpressionError("range size doesn't match array size, expected $expectedSize found $rangeSize", range.position)) checkResult.add(ExpressionError("range size doesn't match array size, expected $expectedSize found $rangeSize", range.position))
return false return false
@ -946,7 +999,7 @@ private class AstChecker(private val namespace: INameScope,
} }
} }
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec, value: LiteralValue, heap: HeapValues) : Boolean { private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArrayIndex, value: LiteralValue, heap: HeapValues) : Boolean {
fun err(msg: String) : Boolean { fun err(msg: String) : Boolean {
checkResult.add(ExpressionError(msg, value.position)) checkResult.add(ExpressionError(msg, value.position))
return false return false
@ -954,8 +1007,8 @@ private class AstChecker(private val namespace: INameScope,
when (targetDt) { when (targetDt) {
DataType.FLOAT -> { DataType.FLOAT -> {
val number = when(value.type) { val number = when(value.type) {
DataType.UBYTE, DataType.BYTE -> value.bytevalue!!.toDouble() in ByteDatatypes -> value.bytevalue!!.toDouble()
DataType.UWORD, DataType.WORD -> value.wordvalue!!.toDouble() in WordDatatypes -> value.wordvalue!!.toDouble()
DataType.FLOAT -> value.floatvalue!! DataType.FLOAT -> value.floatvalue!!
else -> return err("numeric value expected") else -> return err("numeric value expected")
} }
@ -979,8 +1032,6 @@ private class AstChecker(private val namespace: INameScope,
return err("value '$number' out of range for byte") return err("value '$number' out of range for byte")
} }
DataType.UWORD -> { DataType.UWORD -> {
if(value.isString || value.isArray) // string or array are assignable to uword; their memory address is used.
return true
val number = value.asIntegerValue ?: return if (value.floatvalue!=null) val number = value.asIntegerValue ?: return if (value.floatvalue!=null)
err("unsigned word value expected instead of float; possible loss of precision") err("unsigned word value expected instead of float; possible loss of precision")
else else
@ -996,15 +1047,14 @@ private class AstChecker(private val namespace: INameScope,
if (number < -32768 || number > 32767) if (number < -32768 || number > 32767)
return err("value '$number' out of range for word") return err("value '$number' out of range for word")
} }
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { DataType.STR, DataType.STR_S -> {
if(!value.isString) if(!value.isString)
return err("string value expected") return err("string value expected")
val str = value.strvalue(heap) if (value.strvalue!!.length > 255)
if (str.length > 255)
return err("string length must be 0-255") return err("string length must be 0-255")
} }
DataType.ARRAY_UB, DataType.ARRAY_B -> { DataType.ARRAY_UB, DataType.ARRAY_B -> {
// value may be either a single byte, or a byte arrayspec (of all constant values) // value may be either a single byte, or a byte arraysize (of all constant values), or a range
if(value.type==targetDt) { if(value.type==targetDt) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
@ -1013,7 +1063,7 @@ private class AstChecker(private val namespace: INameScope,
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>256) if(arraySpecSize<1 || arraySpecSize>256)
return err("byte array length must be 1-256") return err("byte array length must be 1-256")
val constX = arrayspec.x.constValue(namespace, heap) val constX = arrayspec.index.constValue(program)
if(constX?.asIntegerValue==null) if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value") return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue val expectedSize = constX.asIntegerValue
@ -1026,7 +1076,7 @@ private class AstChecker(private val namespace: INameScope,
return err("invalid byte array initialization value ${value.type}, expected $targetDt") return err("invalid byte array initialization value ${value.type}, expected $targetDt")
} }
DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UW, DataType.ARRAY_W -> {
// value may be either a single word, or a word arrayspec // value may be either a single word, or a word arraysize, or a range
if(value.type==targetDt) { if(value.type==targetDt) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
@ -1035,7 +1085,7 @@ private class AstChecker(private val namespace: INameScope,
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>128) if(arraySpecSize<1 || arraySpecSize>128)
return err("word array length must be 1-128") return err("word array length must be 1-128")
val constX = arrayspec.x.constValue(namespace, heap) val constX = arrayspec.index.constValue(program)
if(constX?.asIntegerValue==null) if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value") return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue val expectedSize = constX.asIntegerValue
@ -1048,7 +1098,7 @@ private class AstChecker(private val namespace: INameScope,
return err("invalid word array initialization value ${value.type}, expected $targetDt") return err("invalid word array initialization value ${value.type}, expected $targetDt")
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
// value may be either a single float, or a float arrayspec // value may be either a single float, or a float arraysize
if(value.type==targetDt) { if(value.type==targetDt) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
@ -1057,7 +1107,7 @@ private class AstChecker(private val namespace: INameScope,
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize < 1 || arraySpecSize>51) if(arraySpecSize < 1 || arraySpecSize>51)
return err("float array length must be 1-51") return err("float array length must be 1-51")
val constX = arrayspec.x.constValue(namespace, heap) val constX = arrayspec.index.constValue(program)
if(constX?.asIntegerValue==null) if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value") return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue val expectedSize = constX.asIntegerValue
@ -1068,7 +1118,7 @@ private class AstChecker(private val namespace: INameScope,
// check if the floating point values are all within range // check if the floating point values are all within range
val doubles = if(value.arrayvalue!=null) val doubles = if(value.arrayvalue!=null)
value.arrayvalue.map {it.constValue(namespace, heap)?.asNumericValue!!.toDouble()}.toDoubleArray() value.arrayvalue.map {it.constValue(program)?.asNumericValue!!.toDouble()}.toDoubleArray()
else else
heap.get(value.heapId!!).doubleArray!! heap.get(value.heapId!!).doubleArray!!
if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE}) if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE})
@ -1082,20 +1132,45 @@ private class AstChecker(private val namespace: INameScope,
} }
private fun checkArrayValues(value: LiteralValue, type: DataType): Boolean { private fun checkArrayValues(value: LiteralValue, type: DataType): Boolean {
val array = heap.get(value.heapId!!) if(value.isArray && value.heapId==null) {
// TODO weird, array literal that hasn't been moved to the heap yet?
val array = value.arrayvalue!!.map { it.constValue(program)!! }
val correct: Boolean
when(type) {
DataType.ARRAY_UB -> {
correct=array.all { it.bytevalue!=null && it.bytevalue in 0..255 }
}
DataType.ARRAY_B -> {
correct=array.all { it.bytevalue!=null && it.bytevalue in -128..127 }
}
DataType.ARRAY_UW -> {
correct=array.all { it.wordvalue!=null && it.wordvalue in 0..65535 }
}
DataType.ARRAY_W -> {
correct=array.all { it.wordvalue!=null && it.wordvalue in -32768..32767}
}
DataType.ARRAY_F -> correct = true
else -> throw AstException("invalid array type $type")
}
if(!correct)
checkResult.add(ExpressionError("array value out of range for type $type", value.position))
return correct
}
val array = program.heap.get(value.heapId!!)
val correct: Boolean val correct: Boolean
when(type) { when(type) {
DataType.ARRAY_UB -> { DataType.ARRAY_UB -> {
correct=array.array!=null && array.array.all { it in 0..255 } correct=array.array!=null && array.array.all { it.integer!=null && it.integer in 0..255 }
} }
DataType.ARRAY_B -> { DataType.ARRAY_B -> {
correct=array.array!=null && array.array.all { it in -128..127 } correct=array.array!=null && array.array.all { it.integer!=null && it.integer in -128..127 }
} }
DataType.ARRAY_UW -> { DataType.ARRAY_UW -> {
correct=array.array!=null && array.array.all { it in 0..65535 } correct=array.array!=null && array.array.all { (it.integer!=null && it.integer in 0..65535) || it.addressOf!=null}
} }
DataType.ARRAY_W -> { DataType.ARRAY_W -> {
correct=array.array!=null && array.array.all { it in -32768..32767 } correct=array.array!=null && array.array.all { it.integer!=null && it.integer in -32768..32767 }
} }
DataType.ARRAY_F -> correct = array.doubleArray!=null DataType.ARRAY_F -> correct = array.doubleArray!=null
else -> throw AstException("invalid array type $type") else -> throw AstException("invalid array type $type")
@ -1118,12 +1193,10 @@ private class AstChecker(private val namespace: INameScope,
DataType.BYTE -> sourceDatatype==DataType.BYTE DataType.BYTE -> sourceDatatype==DataType.BYTE
DataType.UBYTE -> sourceDatatype==DataType.UBYTE DataType.UBYTE -> sourceDatatype==DataType.UBYTE
DataType.WORD -> sourceDatatype==DataType.BYTE || sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.WORD DataType.WORD -> sourceDatatype==DataType.BYTE || sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.WORD
DataType.UWORD -> sourceDatatype in setOf(DataType.UBYTE, DataType.UWORD, DataType.STR, DataType.STR_S) || sourceDatatype in ArrayDatatypes DataType.UWORD -> sourceDatatype==DataType.UBYTE || sourceDatatype==DataType.UWORD
DataType.FLOAT -> sourceDatatype in NumericDatatypes DataType.FLOAT -> sourceDatatype in NumericDatatypes
DataType.STR -> sourceDatatype==DataType.STR DataType.STR -> sourceDatatype==DataType.STR
DataType.STR_S -> sourceDatatype==DataType.STR_S DataType.STR_S -> sourceDatatype==DataType.STR_S
DataType.STR_P -> sourceDatatype==DataType.STR_P
DataType.STR_PS -> sourceDatatype==DataType.STR_PS
else -> checkResult.add(SyntaxError("cannot assign new value to variable of type $targetDatatype", position)) else -> checkResult.add(SyntaxError("cannot assign new value to variable of type $targetDatatype", position))
} }
@ -1137,9 +1210,9 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position)) checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position))
} }
else if(sourceDatatype==DataType.FLOAT && targetDatatype in IntegerDatatypes) else if(sourceDatatype==DataType.FLOAT && targetDatatype in IntegerDatatypes)
checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.toString().toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position)) checkResult.add(ExpressionError("cannot assign float to ${targetDatatype.name.toLowerCase()}; possible loss of precision. Suggestion: round the value or revert to integer arithmetic", position))
else else
checkResult.add(ExpressionError("cannot assign ${sourceDatatype.toString().toLowerCase()} to ${targetDatatype.toString().toLowerCase()}", position)) checkResult.add(ExpressionError("cannot assign ${sourceDatatype.name.toLowerCase()} to ${targetDatatype.name.toLowerCase()}", position))
return false return false
} }

View File

@ -1,74 +1,85 @@
package prog8.ast package prog8.ast
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
/** /**
* Checks the validity of all identifiers (no conflicts) * Checks the validity of all identifiers (no conflicts)
* Also builds a list of all (scoped) symbol definitions
* Also makes sure that subroutine's parameters also become local variable decls in the subroutine's scope. * Also makes sure that subroutine's parameters also become local variable decls in the subroutine's scope.
* Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly. * Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly.
*/ */
fun Module.checkIdentifiers(heap: HeapValues): MutableMap<String, IStatement> { internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(heap) val checker = AstIdentifiersChecker(namespace)
this.process(checker) checker.process(this)
// add any anonymous variables for heap values that are used, and replace literalvalue by identifierref if(modules.map {it.name}.toSet().size != modules.size) {
for (variable in checker.anonymousVariablesFromHeap) { throw FatalAstException("modules should all be unique")
}
// add any anonymous variables for heap values that are used,
// and replace an iterable literalvalue by identifierref to new local variable
for (variable in checker.anonymousVariablesFromHeap.values) {
val scope = variable.first.definingScope() val scope = variable.first.definingScope()
scope.statements.add(variable.second) scope.statements.add(variable.second)
val parent = variable.first.parent val parent = variable.first.parent
when { when {
parent is Assignment && parent.value === variable.first -> { parent is Assignment && parent.value === variable.first -> {
val idref = IdentifierReference(listOf("auto_heap_value_${variable.first.heapId}"), variable.first.position) val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent) idref.linkParents(parent)
parent.value = idref parent.value = idref
} }
parent is IFunctionCall -> { parent is IFunctionCall -> {
val parameterPos = parent.arglist.indexOf(variable.first) val parameterPos = parent.arglist.indexOf(variable.first)
val idref = IdentifierReference(listOf("auto_heap_value_${variable.first.heapId}"), variable.first.position) val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent) idref.linkParents(parent)
parent.arglist[parameterPos] = idref parent.arglist[parameterPos] = idref
} }
parent is ForLoop -> {
val idref = IdentifierReference(listOf("$autoHeapValuePrefix${variable.first.heapId}"), variable.first.position)
idref.linkParents(parent)
parent.iterable = idref
}
else -> TODO("replace literalvalue by identifierref: $variable (in $parent)") else -> TODO("replace literalvalue by identifierref: $variable (in $parent)")
} }
variable.second.linkParents(scope as Node)
} }
printErrors(checker.result(), name) printErrors(checker.result(), name)
return checker.symbols
} }
private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor { private class AstIdentifiersChecker(private val namespace: INameScope) : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf() private val checkResult: MutableList<AstException> = mutableListOf()
var symbols: MutableMap<String, IStatement> = mutableMapOf() private var blocks: MutableMap<String, Block> = mutableMapOf()
private set
fun result(): List<AstException> { internal fun result(): List<AstException> {
return checkResult return checkResult
} }
private fun nameError(name: String, position: Position, existing: IStatement) { private fun nameError(name: String, position: Position, existing: IStatement) {
checkResult.add(NameError("name conflict '$name', first defined in ${existing.position.file} line ${existing.position.line}", position)) checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
}
override fun process(module: Module) {
blocks.clear() // blocks may be redefined within a different module
super.process(module)
} }
override fun process(block: Block): IStatement { override fun process(block: Block): IStatement {
val scopedName = block.scopedname val existing = blocks[block.name]
val existing = symbols[scopedName] if(existing!=null)
if(existing!=null) {
nameError(block.name, block.position, existing) nameError(block.name, block.position, existing)
} else { else
symbols[scopedName] = block blocks[block.name] = block
}
return super.process(block) return super.process(block)
} }
override fun process(functionCall: FunctionCall): IExpression { override fun process(functionCall: FunctionCall): IExpression {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") { if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte" // lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, functionCall.position) val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position)
typecast.linkParents(functionCall.parent) typecast.linkParents(functionCall.parent)
return super.process(typecast) return super.process(typecast)
} }
@ -84,13 +95,10 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
// the builtin functions can't be redefined // the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position)) checkResult.add(NameError("builtin function cannot be redefined", decl.position))
val scopedName = decl.scopedname val existing = namespace.lookup(listOf(decl.name), decl)
val existing = symbols[scopedName] if (existing != null && existing !== decl)
if(existing!=null) {
nameError(decl.name, decl.position, existing) nameError(decl.name, decl.position, existing)
} else {
symbols[scopedName] = decl
}
return super.process(decl) return super.process(decl)
} }
@ -102,22 +110,22 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
if (subroutine.parameters.any { it.name in BuiltinFunctions }) if (subroutine.parameters.any { it.name in BuiltinFunctions })
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position)) checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
val scopedName = subroutine.scopedname val existing = namespace.lookup(listOf(subroutine.name), subroutine)
val existing = symbols[scopedName] if (existing != null && existing !== subroutine)
if (existing != null) {
nameError(subroutine.name, subroutine.position, existing) nameError(subroutine.name, subroutine.position, existing)
} else {
symbols[scopedName] = subroutine
}
// check that there are no local variables that redefine the subroutine's parameters // check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
val allDefinedNames = subroutine.allLabelsAndVariables() val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
val paramNames = subroutine.parameters.map { it.name }.toSet() val paramNames = subroutine.parameters.map { it.name }.toSet()
val paramsToCheck = paramNames.intersect(allDefinedNames) val paramsToCheck = paramNames.intersect(namesInSub)
for(name in paramsToCheck) { for(name in paramsToCheck) {
val thing = subroutine.getLabelOrVariable(name)!! val labelOrVar = subroutine.getLabelOrVariable(name)
if(thing.position != subroutine.position) if(labelOrVar!=null && labelOrVar.position != subroutine.position)
nameError(name, thing.position, subroutine) nameError(name, labelOrVar.position, subroutine)
val sub = subroutine.statements.singleOrNull { it is Subroutine && it.name==name}
if(sub!=null)
nameError(name, sub.position, subroutine)
} }
// inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters) // inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters)
@ -128,9 +136,10 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
if(subroutine.asmAddress==null && !subroutine.canBeAsmSubroutine) { if(subroutine.asmAddress==null && !subroutine.canBeAsmSubroutine) {
if(subroutine.asmParameterRegisters.isEmpty()) { if(subroutine.asmParameterRegisters.isEmpty()) {
subroutine.parameters subroutine.parameters
.filter { it.name !in allDefinedNames } .filter { it.name !in namesInSub }
.forEach { .forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null, subroutine.position) val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null,
isArray = false, autoGenerated = true, position = subroutine.position)
vardecl.linkParents(subroutine) vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl) subroutine.statements.add(0, vardecl)
} }
@ -145,13 +154,9 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
// the builtin functions can't be redefined // the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position)) checkResult.add(NameError("builtin function cannot be redefined", label.position))
} else { } else {
val scopedName = label.scopedname val existing = namespace.lookup(listOf(label.name), label)
val existing = symbols[scopedName] if (existing != null && existing !== label)
if (existing != null) {
nameError(label.name, label.position, existing) nameError(label.name, label.position, existing)
} else {
symbols[scopedName] = label
}
} }
return super.process(label) return super.process(label)
} }
@ -163,16 +168,17 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
// additional interation count variable in their scope. // additional interation count variable in their scope.
if(forLoop.loopRegister!=null) { if(forLoop.loopRegister!=null) {
if(forLoop.decltype!=null) if(forLoop.decltype!=null)
checkResult.add(SyntaxError("register loop variables cannot be explicitly declared with a datatype", forLoop.position)) checkResult.add(SyntaxError("register loop variables have a fixed implicit datatype", forLoop.position))
if(forLoop.loopRegister == Register.X) if(forLoop.loopRegister == Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position) printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
} else if(forLoop.loopVar!=null) { } else if(forLoop.loopVar!=null) {
val varName = forLoop.loopVar.nameInSource.last() val varName = forLoop.loopVar.nameInSource.last()
if(forLoop.decltype!=null) { if(forLoop.decltype!=null) {
val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first()) val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
if(existing==null) { if(existing==null) {
// create the local scoped for loop variable itself // create the local scoped for loop variable itself
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, true, null, varName, null, forLoop.loopVar.position) val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null,
isArray = false, autoGenerated = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body) vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl) forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body' forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@ -181,10 +187,11 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
} }
if(forLoop.iterable !is RangeExpr) { if(forLoop.iterable !is RangeExpr) {
val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first()) val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first())
if(existing==null) { if(existing==null) {
// create loop iteration counter variable (without value, to avoid an assignment) // create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null, forLoop.loopVar.position) val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null,
isArray = false, autoGenerated = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body) vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl) forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body' forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@ -225,15 +232,27 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
} }
internal val anonymousVariablesFromHeap = mutableSetOf<Pair<LiteralValue, VarDecl>>() internal val anonymousVariablesFromHeap = mutableMapOf<String, Pair<LiteralValue, VarDecl>>()
override fun process(literalValue: LiteralValue): LiteralValue { override fun process(literalValue: LiteralValue): LiteralValue {
if(literalValue.heapId!=null && literalValue.parent !is VarDecl) { if(literalValue.heapId!=null && literalValue.parent !is VarDecl) {
// a literal value that's not declared as a variable, which refers to something on the heap. // a literal value that's not declared as a variable, which refers to something on the heap.
// we need to introduce an auto-generated variable for this to be able to refer to the value! // we need to introduce an auto-generated variable for this to be able to refer to the value!
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "auto_heap_value_${literalValue.heapId}", literalValue, literalValue.position) val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue,
anonymousVariablesFromHeap.add(Pair(literalValue, variable)) isArray = false, autoGenerated = false, position = literalValue.position)
anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable)
} }
return super.process(literalValue) return super.process(literalValue)
} }
override fun process(addressOf: AddressOf): IExpression {
// register the scoped name of the referenced identifier
val variable= addressOf.identifier.targetVarDecl(namespace) ?: return addressOf
addressOf.scopedname = variable.scopedname
return super.process(addressOf)
}
} }
internal const val autoHeapValuePrefix = "auto_heap_value_"

View File

@ -4,9 +4,9 @@ package prog8.ast
* Checks for the occurrence of recursive subroutine calls * Checks for the occurrence of recursive subroutine calls
*/ */
fun Module.checkRecursion(namespace: INameScope) { internal fun Program.checkRecursion() {
val checker = AstRecursionChecker(namespace) val checker = AstRecursionChecker(namespace)
this.process(checker) checker.process(this)
printErrors(checker.result(), name) printErrors(checker.result(), name)
} }
@ -30,7 +30,7 @@ private class DirectedGraph<VT> {
fun print() { fun print() {
println("#vertices: $numVertices") println("#vertices: $numVertices")
graph.forEach { from, to -> graph.forEach { (from, to) ->
println("$from CALLS:") println("$from CALLS:")
to.forEach { println(" $it") } to.forEach { println(" $it") }
} }
@ -41,8 +41,8 @@ private class DirectedGraph<VT> {
} }
fun checkForCycle(): MutableList<VT> { fun checkForCycle(): MutableList<VT> {
val visited = uniqueVertices.associate { it to false }.toMutableMap() val visited = uniqueVertices.associateWith { false }.toMutableMap()
val recStack = uniqueVertices.associate { it to false }.toMutableMap() val recStack = uniqueVertices.associateWith { false }.toMutableMap()
val cycle = mutableListOf<VT>() val cycle = mutableListOf<VT>()
for(node in uniqueVertices) { for(node in uniqueVertices) {
if(isCyclicUntil(node, visited, recStack, cycle)) if(isCyclicUntil(node, visited, recStack, cycle))
@ -84,7 +84,7 @@ private class DirectedGraph<VT> {
private class AstRecursionChecker(private val namespace: INameScope) : IAstProcessor { private class AstRecursionChecker(private val namespace: INameScope) : IAstProcessor {
private val callGraph = DirectedGraph<INameScope>() private val callGraph = DirectedGraph<INameScope>()
fun result(): List<AstException> { internal fun result(): List<AstException> {
val cycle = callGraph.checkForCycle() val cycle = callGraph.checkForCycle()
if(cycle.isEmpty()) if(cycle.isEmpty())
return emptyList() return emptyList()

View File

@ -5,10 +5,9 @@ package prog8.ast
* Checks that are specific for imported modules. * Checks that are specific for imported modules.
*/ */
fun Module.checkImportedValid() { internal fun Module.checkImportedValid() {
val checker = ImportedAstChecker() val checker = ImportedAstChecker()
this.linkParents() checker.process(this)
this.process(checker)
printErrors(checker.result(), name) printErrors(checker.result(), name)
} }
@ -16,7 +15,7 @@ fun Module.checkImportedValid() {
private class ImportedAstChecker : IAstProcessor { private class ImportedAstChecker : IAstProcessor {
private val checkResult: MutableList<SyntaxError> = mutableListOf() private val checkResult: MutableList<SyntaxError> = mutableListOf()
fun result(): List<SyntaxError> { internal fun result(): List<SyntaxError> {
return checkResult return checkResult
} }

View File

@ -1,19 +1,19 @@
package prog8.ast package prog8.ast
import prog8.compiler.HeapValues import prog8.functions.BuiltinFunctions
fun Module.reorderStatements(namespace: INameScope, heap: HeapValues) { internal fun Program.reorderStatements() {
val initvalueCreator = VarInitValueCreator() val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
this.process(initvalueCreator) initvalueCreator.process(this)
val checker = StatementReorderer(namespace, heap) val checker = StatementReorderer(this)
this.process(checker) checker.process(this)
} }
const val initvarsSubName="prog8_init_vars" // the name of the subroutine that should be called for every block to initialize its variables internal const val initvarsSubName="prog8_init_vars" // the name of the subroutine that should be called for every block to initialize its variables
private class StatementReorderer(private val namespace: INameScope, private val heap: HeapValues): IAstProcessor { private class StatementReorderer(private val program: Program): IAstProcessor {
// Reorders the statements in a way the compiler needs. // Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set. // - 'main' block must be the very first statement UNLESS it has an address set.
// - blocks are ordered by address, where blocks without address are put at the end. // - blocks are ordered by address, where blocks without address are put at the end.
@ -24,6 +24,9 @@ private class StatementReorderer(private val namespace: INameScope, private val
// //
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives. // - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
// - all other subroutines will be moved to the end of their block. // - all other subroutines will be moved to the end of their block.
//
// Also, makes 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)
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option") private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
@ -42,9 +45,9 @@ private class StatementReorderer(private val namespace: INameScope, private val
module.statements.removeAt(nonLibBlock.first) module.statements.removeAt(nonLibBlock.first)
for(nonLibBlock in nonLibraryBlocks) for(nonLibBlock in nonLibraryBlocks)
module.statements.add(0, nonLibBlock.second) module.statements.add(0, nonLibBlock.second)
val mainBlock = module.statements.single { it is Block && it.name=="main" } val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
if((mainBlock as Block).address==null) { if(mainBlock!=null && (mainBlock as Block).address==null) {
module.statements.remove(mainBlock) module.remove(mainBlock)
module.statements.add(0, mainBlock) module.statements.add(0, mainBlock)
} }
@ -66,7 +69,7 @@ private class StatementReorderer(private val namespace: INameScope, private val
// move all subroutines to the end of the block // move all subroutines to the end of the block
for (subroutine in subroutines) { for (subroutine in subroutines) {
if(subroutine.name!="start" || block.name!="main") { if(subroutine.name!="start" || block.name!="main") {
block.statements.remove(subroutine) block.remove(subroutine)
block.statements.add(subroutine) block.statements.add(subroutine)
} }
numSubroutinesAtEnd++ numSubroutinesAtEnd++
@ -74,7 +77,7 @@ private class StatementReorderer(private val namespace: INameScope, private val
// move the "start" subroutine to the top // move the "start" subroutine to the top
if(block.name=="main") { if(block.name=="main") {
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let { block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
block.statements.remove(it) block.remove(it)
block.statements.add(0, it) block.statements.add(0, it)
numSubroutinesAtEnd-- numSubroutinesAtEnd--
} }
@ -97,20 +100,23 @@ private class StatementReorderer(private val namespace: INameScope, private val
} }
} }
val varDecls = block.statements.filter { it is VarDecl } val varDecls = block.statements.filterIsInstance<VarDecl>()
block.statements.removeAll(varDecls) block.statements.removeAll(varDecls)
block.statements.addAll(0, varDecls) block.statements.addAll(0, varDecls)
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove} val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
block.statements.removeAll(directives) block.statements.removeAll(directives)
block.statements.addAll(0, directives) block.statements.addAll(0, directives)
block.linkParents(block.parent)
sortConstantAssignments(block.statements) sortConstantAssignments(block.statements)
// create subroutine that initializes the block's variables (if any)
val varInits = block.statements.withIndex().filter { it.value is VariableInitializationAssignment } val varInits = block.statements.withIndex().filter { it.value is VariableInitializationAssignment }
if(varInits.isNotEmpty()) { if(varInits.isNotEmpty()) {
val statements = varInits.map{it.value}.toMutableList() val statements = varInits.map{it.value}.toMutableList()
val varInitSub = Subroutine(initvarsSubName, emptyList(), emptyList(), emptyList(), emptyList(), val varInitSub = Subroutine(initvarsSubName, emptyList(), emptyList(), emptyList(), emptyList(),
emptySet(), null, false, statements, block.position) emptySet(), null, false, statements, block.position)
varInitSub.keepAlways = true
varInitSub.linkParents(block) varInitSub.linkParents(block)
block.statements.add(varInitSub) block.statements.add(varInitSub)
@ -155,13 +161,36 @@ private class StatementReorderer(private val namespace: INameScope, private val
return scope return scope
} }
override fun process(expr: BinaryExpression): IExpression {
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if(leftDt!=null && rightDt!=null && leftDt!=rightDt) {
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right)
if(toFix!=null) {
when {
toFix===expr.left -> {
expr.left = TypecastExpression(expr.left, commonDt, true, expr.left.position)
expr.left.linkParents(expr)
}
toFix===expr.right -> {
expr.right = TypecastExpression(expr.right, commonDt, true, expr.right.position)
expr.right.linkParents(expr)
}
else -> throw FatalAstException("confused binary expression side")
}
}
}
return super.process(expr)
}
private fun sortConstantAssignments(statements: MutableList<IStatement>) { private fun sortConstantAssignments(statements: MutableList<IStatement>) {
// sort assignments by datatype and value, so multiple initializations with the same value can be optimized (to load the value just once) // sort assignments by datatype and value, so multiple initializations with the isSameAs value can be optimized (to load the value just once)
val result = mutableListOf<IStatement>() val result = mutableListOf<IStatement>()
val stmtIter = statements.iterator() val stmtIter = statements.iterator()
for(stmt in stmtIter) { for(stmt in stmtIter) {
if(stmt is Assignment) { if(stmt is Assignment && !stmt.targets.any { it.isMemoryMapped(program.namespace) }) {
val constval = stmt.value.constValue(namespace, heap) val constval = stmt.value.constValue(program)
if(constval!=null) { if(constval!=null) {
val (sorted, trailing) = sortConstantAssignmentSequence(stmt, stmtIter) val (sorted, trailing) = sortConstantAssignmentSequence(stmt, stmtIter)
result.addAll(sorted) result.addAll(sorted)
@ -178,13 +207,88 @@ private class StatementReorderer(private val namespace: INameScope, private val
statements.addAll(result) statements.addAll(result)
} }
override fun process(assignment: Assignment): IStatement {
val target=assignment.singleTarget
if(target!=null) {
// see if a typecast is needed to convert the value's type into the proper target type
val valuetype = assignment.value.inferType(program)
val targettype = target.inferType(program, assignment)
if(targettype!=null && valuetype!=null && valuetype!=targettype) {
if(valuetype isAssignableTo targettype) {
assignment.value = TypecastExpression(assignment.value, targettype, true, assignment.value.position)
assignment.value.linkParents(assignment)
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
} else TODO("multi-target assign")
return super.process(assignment)
}
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
return super.process(functionCallStatement)
}
override fun process(functionCall: FunctionCall): IExpression {
checkFunctionCallArguments(functionCall, functionCall.definingScope())
return super.process(functionCall)
}
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
// see if a typecast is needed to convert the arguments into the required parameter's type
val sub = call.target.targetStatement(scope)
when(sub) {
is Subroutine -> {
for(arg in sub.parameters.zip(call.arglist.withIndex())) {
val argtype = arg.second.value.inferType(program)
if(argtype!=null) {
val requiredType = arg.first.type
if (requiredType != argtype) {
if (argtype isAssignableTo requiredType) {
val typecasted = TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position)
typecasted.linkParents(arg.second.value.parent)
call.arglist[arg.second.index] = typecasted
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
}
}
is BuiltinFunctionStatementPlaceholder -> {
// if(sub.name in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "memset", "memcopy", "memsetw", "swap"))
val func = BuiltinFunctions.getValue(sub.name)
if(func.pure) {
// non-pure functions don't get automatic typecasts because sometimes they act directly on their parameters
for (arg in func.parameters.zip(call.arglist.withIndex())) {
val argtype = arg.second.value.inferType(program)
if (argtype != null) {
if (arg.first.possibleDatatypes.any { argtype == it })
continue
for (possibleType in arg.first.possibleDatatypes) {
if (argtype isAssignableTo possibleType) {
val typecasted = TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position)
typecasted.linkParents(arg.second.value.parent)
call.arglist[arg.second.index] = typecasted
break
}
}
}
}
}
}
null -> {}
else -> TODO("call to something weird $sub ${call.target}")
}
}
private fun sortConstantAssignmentSequence(first: Assignment, stmtIter: MutableIterator<IStatement>): Pair<List<Assignment>, IStatement?> { private fun sortConstantAssignmentSequence(first: Assignment, stmtIter: MutableIterator<IStatement>): Pair<List<Assignment>, IStatement?> {
val sequence= mutableListOf(first) val sequence= mutableListOf(first)
var trailing: IStatement? = null var trailing: IStatement? = null
while(stmtIter.hasNext()) { while(stmtIter.hasNext()) {
val next = stmtIter.next() val next = stmtIter.next()
if(next is Assignment) { if(next is Assignment) {
val constValue = next.value.constValue(namespace, heap) val constValue = next.value.constValue(program)
if(constValue==null) { if(constValue==null) {
trailing = next trailing = next
break break
@ -196,31 +300,43 @@ private class StatementReorderer(private val namespace: INameScope, private val
break break
} }
} }
val sorted = sequence.sortedWith(compareBy({it.value.resultingDatatype(namespace, heap)}, {it.singleTarget?.shortString(true)})) val sorted = sequence.sortedWith(compareBy({it.value.inferType(program)}, {it.singleTarget?.shortString(true)}))
return Pair(sorted, trailing) return Pair(sorted, trailing)
} }
override fun process(typecast: TypecastExpression): IExpression {
// warn about any implicit type casts to Float, because that may not be intended
if(typecast.implicit && typecast.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
printWarning("byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic", typecast.position)
}
return super.process(typecast)
}
} }
private class VarInitValueCreator: IAstProcessor { private class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstProcessor {
// Replace the var decl with an assignment and add a new vardecl with the default constant value. // For VarDecls that declare an initialization value:
// Replace the vardecl with an assignment (to set the initial value),
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
// This makes sure the variables get reset to the intended value on a next run of the program. // This makes sure the variables get reset to the intended value on a next run of the program.
// Variable decls without a value don't get this treatment, which means they retain the last // Variable decls without a value don't get this treatment, which means they retain the last
// value they had when restarting the program. // value they had when restarting the program.
// This is done in a separate step because it interferes with the namespace lookup of symbols // This is done in a separate step because it interferes with the namespace lookup of symbols
// in other ast processors. // in other ast processors.
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>() // Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val vardeclsToAdd = mutableMapOf<INameScope, MutableMap<String, VarDecl>>()
override fun process(module: Module) { override fun process(module: Module) {
vardeclsToAdd.clear()
super.process(module) super.process(module)
// add any new vardecls to the various scopes // add any new vardecls to the various scopes
for(decl in vardeclsToAdd) for(decl in vardeclsToAdd)
for(d in decl.value) { for(d in decl.value) {
d.linkParents(decl.key as Node) d.value.linkParents(decl.key as Node)
decl.key.statements.add(0, d) decl.key.statements.add(0, d.value)
} }
} }
@ -231,9 +347,7 @@ private class VarInitValueCreator: IAstProcessor {
if(decl.datatype in NumericDatatypes) { if(decl.datatype in NumericDatatypes) {
val scope = decl.definingScope() val scope = decl.definingScope()
if(scope !in vardeclsToAdd) addVarDecl(scope, decl.asDefaultValueDecl(null))
vardeclsToAdd[scope] = mutableListOf()
vardeclsToAdd[scope]!!.add(decl.asDefaultValueDecl(null))
val declvalue = decl.value!! val declvalue = decl.value!!
val value = val value =
if(declvalue is LiteralValue) { if(declvalue is LiteralValue) {
@ -242,8 +356,9 @@ private class VarInitValueCreator: IAstProcessor {
} }
else else
declvalue declvalue
val identifierName = listOf(decl.name) // // TODO this was: (scoped name) decl.scopedname.split(".")
return VariableInitializationAssignment( return VariableInitializationAssignment(
AssignTarget(null, IdentifierReference(decl.scopedname.split("."), decl.position), null, null, decl.position), AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
null, null,
value, value,
decl.position decl.position
@ -252,4 +367,64 @@ private class VarInitValueCreator: IAstProcessor {
return decl return decl
} }
override fun process(functionCall: FunctionCall): IExpression {
val targetStatement = functionCall.target.targetSubroutine(namespace)
if(targetStatement!=null) {
var node: Node = functionCall
while(node !is IStatement)
node=node.parent
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, node)
}
return functionCall
}
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val targetStatement = functionCallStatement.target.targetSubroutine(namespace)
if(targetStatement!=null)
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
return functionCallStatement
}
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<IExpression>, parent: IStatement) {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) {
if(argparam.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? LiteralValue
if(idref!=null) {
val variable = idref.targetVarDecl(namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
val pointerExpr = AddressOf(idref, idref.position)
pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single())
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
}
else if(strvalue!=null) {
if(strvalue.isString) {
// replace the argument with &autovar
val autoVarName = "$autoHeapValuePrefix${strvalue.heapId}"
val autoHeapvarRef = IdentifierReference(listOf(autoVarName), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
pointerExpr.scopedname = parent.makeScopedName(autoVarName)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue,
isArray = false, autoGenerated = false, position=strvalue.position)
addVarDecl(strvalue.definingScope(), variable)
}
}
}
}
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableMapOf()
vardeclsToAdd.getValue(scope)[variable.name]=variable
}
} }

View File

@ -0,0 +1,563 @@
package prog8.astvm
import prog8.ast.*
import prog8.compiler.RuntimeValue
import prog8.compiler.RuntimeValueRange
import prog8.compiler.target.c64.Petscii
import java.awt.EventQueue
class VmExecutionException(msg: String?) : Exception(msg)
class VmTerminationException(msg: String?) : Exception(msg)
class VmBreakpointException : Exception("breakpoint")
class StatusFlags {
var carry: Boolean = false
var zero: Boolean = true
var negative: Boolean = false
var irqd: Boolean = false
private fun setFlags(value: LiteralValue?) {
if (value != null) {
when (value.type) {
DataType.UBYTE -> {
val v = value.bytevalue!!.toInt()
negative = v > 127
zero = v == 0
}
DataType.BYTE -> {
val v = value.bytevalue!!.toInt()
negative = v < 0
zero = v == 0
}
DataType.UWORD -> {
val v = value.wordvalue!!
negative = v > 32767
zero = v == 0
}
DataType.WORD -> {
val v = value.wordvalue!!
negative = v < 0
zero = v == 0
}
DataType.FLOAT -> {
val flt = value.floatvalue!!
negative = flt < 0.0
zero = flt == 0.0
}
else -> {
// no flags for non-numeric type
}
}
}
}
}
class RuntimeVariables {
fun define(scope: INameScope, name: String, initialValue: RuntimeValue) {
val where = vars.getValue(scope)
where[name] = initialValue
vars[scope] = where
}
fun defineMemory(scope: INameScope, name: String, address: Int) {
val where = memvars.getValue(scope)
where[name] = address
memvars[scope] = where
}
fun set(scope: INameScope, name: String, value: RuntimeValue) {
val where = vars.getValue(scope)
val existing = where[name]
if(existing==null) {
if(memvars.getValue(scope)[name]!=null)
throw NoSuchElementException("this is a memory mapped var, not a normal var: ${scope.name}.$name")
throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
}
if(existing.type!=value.type)
throw VmExecutionException("new value is of different datatype ${value.type} expected ${existing.type} for $name")
where[name] = value
vars[scope] = where
}
fun get(scope: INameScope, name: String): RuntimeValue {
val where = vars.getValue(scope)
val value = where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
return value
}
fun getMemoryAddress(scope: INameScope, name: String): Int {
val where = memvars.getValue(scope)
val address = where[name] ?: throw NoSuchElementException("no such runtime memory-variable: ${scope.name}.$name")
return address
}
fun swap(a1: VarDecl, a2: VarDecl) = swap(a1.definingScope(), a1.name, a2.definingScope(), a2.name)
fun swap(scope1: INameScope, name1: String, scope2: INameScope, name2: String) {
val v1 = get(scope1, name1)
val v2 = get(scope2, name2)
set(scope1, name1, v2)
set(scope2, name2, v1)
}
private val vars = mutableMapOf<INameScope, MutableMap<String, RuntimeValue>>().withDefault { mutableMapOf() }
private val memvars = mutableMapOf<INameScope, MutableMap<String, Int>>().withDefault { mutableMapOf() }
}
class AstVm(val program: Program) {
val mem = Memory()
val statusflags = StatusFlags()
private var dialog = ScreenDialog()
var instructionCounter = 0
init {
dialog.requestFocusInWindow()
EventQueue.invokeLater {
dialog.pack()
dialog.isVisible = true
dialog.start()
}
}
fun run() {
try {
val init = VariablesCreator(runtimeVariables, program.heap)
init.process(program)
// initialize all global variables
for (m in program.modules) {
for (b in m.statements.filterIsInstance<Block>()) {
for (s in b.statements.filterIsInstance<Subroutine>()) {
if (s.name == initvarsSubName) {
try {
executeSubroutine(s, emptyList(), null)
} catch (x: LoopControlReturn) {
// regular return
}
}
}
}
}
var entrypoint: Subroutine? = program.entrypoint() ?: throw VmTerminationException("no valid entrypoint found")
var startlabel: Label? = null
while(entrypoint!=null) {
try {
executeSubroutine(entrypoint, emptyList(), startlabel)
entrypoint = null
} catch (rx: LoopControlReturn) {
// regular return
} catch (jx: LoopControlJump) {
if (jx.address != null)
throw VmTerminationException("doesn't support jumping to machine address ${jx.address}")
when {
jx.generatedLabel != null -> {
val label = entrypoint.getLabelOrVariable(jx.generatedLabel) as Label
TODO("generatedlabel $label")
}
jx.identifier != null -> {
when (val jumptarget = entrypoint.lookup(jx.identifier.nameInSource, jx.identifier.parent)) {
is Label -> {
startlabel = jumptarget
entrypoint = jumptarget.definingSubroutine()
}
is Subroutine -> entrypoint = jumptarget
else -> throw VmTerminationException("weird jump target $jumptarget")
}
}
else -> throw VmTerminationException("unspecified jump target")
}
}
}
println("PROGRAM EXITED!")
dialog.title = "PROGRAM EXITED"
} catch (tx: VmTerminationException) {
println("Execution halted: ${tx.message}")
} catch (xx: VmExecutionException) {
println("Execution error: ${xx.message}")
throw xx
}
}
private val runtimeVariables = RuntimeVariables()
private val functions = BuiltinFunctions()
private val evalCtx = EvalContext(program, mem, statusflags, runtimeVariables, functions, ::executeSubroutine)
class LoopControlBreak : Exception()
class LoopControlContinue : Exception()
class LoopControlReturn(val returnvalues: List<RuntimeValue>) : Exception()
class LoopControlJump(val identifier: IdentifierReference?, val address: Int?, val generatedLabel: String?) : Exception()
internal fun executeSubroutine(sub: Subroutine, arguments: List<RuntimeValue>, startlabel: Label?=null): List<RuntimeValue> {
assert(!sub.isAsmSubroutine)
if (sub.statements.isEmpty())
throw VmTerminationException("scope contains no statements: $sub")
if (arguments.size != sub.parameters.size)
throw VmTerminationException("number of args doesn't match number of required parameters")
for (arg in sub.parameters.zip(arguments)) {
val idref = IdentifierReference(listOf(arg.first.name), sub.position)
performAssignment(AssignTarget(null, idref, null, null, idref.position),
arg.second, sub.statements.first(), evalCtx)
}
val statements = sub.statements.iterator()
if(startlabel!=null) {
do {
val stmt = statements.next()
} while(stmt!==startlabel)
}
try {
while(statements.hasNext()) {
val s = statements.next()
try {
executeStatement(sub, s)
}
catch (b: VmBreakpointException) {
print("BREAKPOINT HIT at ${s.position} - Press enter to continue:")
readLine()
}
}
} catch (r: LoopControlReturn) {
return r.returnvalues
}
throw VmTerminationException("instruction pointer overflow, is a return missing? $sub")
}
internal fun executeAnonymousScope(scope: INameScope) {
for (s in scope.statements) {
executeStatement(scope, s)
}
}
private fun executeStatement(sub: INameScope, stmt: IStatement) {
instructionCounter++
if (instructionCounter % 100 == 0)
Thread.sleep(1)
when (stmt) {
is NopStatement, is Label, is Subroutine -> {
// do nothing, skip this instruction
}
is Directive -> {
if (stmt.directive == "%breakpoint")
throw VmBreakpointException()
else if (stmt.directive == "%asm")
throw VmExecutionException("can't execute assembly code")
}
is VarDecl -> {
// should have been defined already when the program started
}
is FunctionCallStatement -> {
val target = stmt.target.targetStatement(program.namespace)
when (target) {
is Subroutine -> {
val args = evaluate(stmt.arglist)
if (target.isAsmSubroutine) {
performSyscall(target, args)
} else {
executeSubroutine(target, args, null)
// any return value(s) are discarded
}
}
is BuiltinFunctionStatementPlaceholder -> {
if(target.name=="swap") {
// swap cannot be implemented as a function, so inline it here
executeSwap(stmt)
} else {
val args = evaluate(stmt.arglist)
functions.performBuiltinFunction(target.name, args, statusflags)
}
}
else -> {
TODO("weird call $target")
}
}
}
is Return -> throw LoopControlReturn(stmt.values.map { evaluate(it, evalCtx) })
is Continue -> throw LoopControlContinue()
is Break -> throw LoopControlBreak()
is Assignment -> {
if (stmt.aug_op != null)
throw VmExecutionException("augmented assignment should have been converted into regular one $stmt")
val target = stmt.singleTarget
if (target != null) {
val value = evaluate(stmt.value, evalCtx)
performAssignment(target, value, stmt, evalCtx)
} else TODO("assign multitarget $stmt")
}
is PostIncrDecr -> {
when {
stmt.target.identifier != null -> {
val ident = stmt.definingScope().lookup(stmt.target.identifier!!.nameInSource, stmt) as VarDecl
val identScope = ident.definingScope()
var value = runtimeVariables.get(identScope, ident.name)
value = when {
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1))
stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1))
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
runtimeVariables.set(identScope, ident.name, value)
}
stmt.target.memoryAddress != null -> {
TODO("postincrdecr memory $stmt")
}
stmt.target.arrayindexed != null -> {
TODO("postincrdecr array $stmt")
}
}
}
is Jump -> throw LoopControlJump(stmt.identifier, stmt.address, stmt.generatedLabel)
is InlineAssembly -> {
if (sub is Subroutine) {
val args = sub.parameters.map { runtimeVariables.get(sub, it.name) }
performSyscall(sub, args)
throw LoopControlReturn(emptyList())
}
throw VmExecutionException("can't execute inline assembly in $sub")
}
is AnonymousScope -> executeAnonymousScope(stmt)
is IfStatement -> {
val condition = evaluate(stmt.condition, evalCtx)
if (condition.asBoolean)
executeAnonymousScope(stmt.truepart)
else
executeAnonymousScope(stmt.elsepart)
}
is BranchStatement -> {
when(stmt.condition) {
BranchCondition.CS -> if(statusflags.carry) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.CC -> if(!statusflags.carry) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.EQ, BranchCondition.Z -> if(statusflags.zero) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.NE, BranchCondition.NZ -> if(statusflags.zero) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.MI, BranchCondition.NEG -> if(statusflags.negative) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.PL, BranchCondition.POS -> if(statusflags.negative) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.VS, BranchCondition.VC -> TODO("overflow status")
}
}
is ForLoop -> {
val iterable = evaluate(stmt.iterable, evalCtx)
if (iterable.type !in IterableDatatypes && iterable !is RuntimeValueRange)
throw VmExecutionException("can only iterate over an iterable value: $stmt")
val loopvarDt: DataType
val loopvar: IdentifierReference
if (stmt.loopRegister != null) {
loopvarDt = DataType.UBYTE
loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position)
} else {
loopvarDt = stmt.loopVar!!.inferType(program)!!
loopvar = stmt.loopVar
}
val iterator = iterable.iterator()
for (loopvalue in iterator) {
try {
oneForCycle(stmt, loopvarDt, loopvalue, loopvar)
} catch (b: LoopControlBreak) {
break
} catch (c: LoopControlContinue) {
continue
}
}
}
is WhileLoop -> {
var condition = evaluate(stmt.condition, evalCtx)
while (condition.asBoolean) {
try {
executeAnonymousScope(stmt.body)
condition = evaluate(stmt.condition, evalCtx)
} catch (b: LoopControlBreak) {
break
} catch (c: LoopControlContinue) {
continue
}
}
}
is RepeatLoop -> {
do {
val condition = evaluate(stmt.untilCondition, evalCtx)
try {
executeAnonymousScope(stmt.body)
} catch (b: LoopControlBreak) {
break
} catch (c: LoopControlContinue) {
continue
}
} while (!condition.asBoolean)
}
else -> {
TODO("implement $stmt")
}
}
}
private fun executeSwap(swap: FunctionCallStatement) {
val v1 = swap.arglist[0]
val v2 = swap.arglist[1]
val value1 = evaluate(v1, evalCtx)
val value2 = evaluate(v2, evalCtx)
val target1 = AssignTarget.fromExpr(v1)
val target2 = AssignTarget.fromExpr(v2)
performAssignment(target1, value2, swap, evalCtx)
performAssignment(target2, value1, swap, evalCtx)
}
fun performAssignment(target: AssignTarget, value: RuntimeValue, contextStmt: IStatement, evalCtx: EvalContext) {
when {
target.identifier != null -> {
val decl = contextStmt.definingScope().lookup(target.identifier.nameInSource, contextStmt) as? VarDecl
?: throw VmExecutionException("can't find assignment target ${target.identifier}")
if (decl.type == VarDeclType.MEMORY) {
val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name)
when (decl.datatype) {
DataType.UBYTE -> mem.setUByte(address, value.byteval!!)
DataType.BYTE -> mem.setSByte(address, value.byteval!!)
DataType.UWORD -> mem.setUWord(address, value.wordval!!)
DataType.WORD -> mem.setSWord(address, value.wordval!!)
DataType.FLOAT -> mem.setFloat(address, value.floatval!!)
DataType.STR -> mem.setString(address, value.str!!)
DataType.STR_S -> mem.setScreencodeString(address, value.str!!)
else -> TODO("set memvar $decl")
}
} else
runtimeVariables.set(decl.definingScope(), decl.name, value)
}
target.memoryAddress != null -> {
val address = evaluate(target.memoryAddress!!.addressExpression, evalCtx).wordval!!
evalCtx.mem.setUByte(address, value.byteval!!)
}
target.arrayindexed != null -> {
val array = evaluate(target.arrayindexed.identifier, evalCtx)
val index = evaluate(target.arrayindexed.arrayspec.index, evalCtx)
when (array.type) {
DataType.ARRAY_UB -> {
if (value.type != DataType.UBYTE)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_B -> {
if (value.type != DataType.BYTE)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_UW -> {
if (value.type != DataType.UWORD)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_W -> {
if (value.type != DataType.WORD)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.ARRAY_F -> {
if (value.type != DataType.FLOAT)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.STR, DataType.STR_S -> {
if (value.type !in ByteDatatypes)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
else -> throw VmExecutionException("strange array type ${array.type}")
}
if (array.type in ArrayDatatypes)
array.array!![index.integerValue()] = value.numericValue()
else if (array.type in StringDatatypes) {
val indexInt = index.integerValue()
val newchr = Petscii.decodePetscii(listOf(value.numericValue().toShort()), true)
val newstr = array.str!!.replaceRange(indexInt, indexInt + 1, newchr)
val ident = contextStmt.definingScope().lookup(target.arrayindexed.identifier.nameInSource, contextStmt) as? VarDecl
?: throw VmExecutionException("can't find assignment target ${target.identifier}")
val identScope = ident.definingScope()
program.heap.update(array.heapId!!, newstr)
runtimeVariables.set(identScope, ident.name, RuntimeValue(array.type, str = newstr, heapId = array.heapId))
}
}
target.register != null -> {
runtimeVariables.set(program.namespace, target.register.name, value)
}
else -> TODO("assign $target")
}
}
private fun oneForCycle(stmt: ForLoop, loopvarDt: DataType, loopValue: Number, loopVar: IdentifierReference) {
// assign the new loop value to the loopvar, and run the code
performAssignment(AssignTarget(null, loopVar, null, null, loopVar.position),
RuntimeValue(loopvarDt, loopValue), stmt.body.statements.first(), evalCtx)
executeAnonymousScope(stmt.body)
}
private fun evaluate(args: List<IExpression>) = args.map { evaluate(it, evalCtx) }
private fun performSyscall(sub: Subroutine, args: List<RuntimeValue>) {
assert(sub.isAsmSubroutine)
when (sub.scopedname) {
"c64scr.print" -> {
// if the argument is an UWORD, consider it to be the "address" of the string (=heapId)
if (args[0].wordval != null) {
val str = program.heap.get(args[0].wordval!!).str!!
dialog.canvas.printText(str, 1, true)
} else
dialog.canvas.printText(args[0].str!!, 1, true)
}
"c64scr.print_ub" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), 1, true)
}
"c64scr.print_b" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), 1, true)
}
"c64scr.print_uw" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), 1, true)
}
"c64scr.print_w" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), 1, true)
}
"c64scr.print_ubhex" -> {
val prefix = if (args[0].asBoolean) "$" else ""
val number = args[1].byteval!!
dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", 1, true)
}
"c64scr.print_uwhex" -> {
val prefix = if (args[0].asBoolean) "$" else ""
val number = args[1].wordval!!
dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", 1, true)
}
"c64scr.print_uwbin" -> {
val prefix = if (args[0].asBoolean) "%" else ""
val number = args[1].wordval!!
dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", 1, true)
}
"c64scr.print_ubbin" -> {
val prefix = if (args[0].asBoolean) "%" else ""
val number = args[1].byteval!!
dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", 1, true)
}
"c64scr.clear_screenchars" -> {
dialog.canvas.clearScreen(6)
}
"c64scr.clear_screen" -> {
dialog.canvas.clearScreen(args[0].integerValue().toShort())
}
"c64scr.setcc" -> {
dialog.canvas.setChar(args[0].integerValue(), args[1].integerValue(), args[2].integerValue().toShort(), args[3].integerValue().toShort())
}
"c64scr.plot" -> {
dialog.canvas.setCursorPos(args[0].integerValue(), args[1].integerValue())
}
"c64.CHROUT" -> {
dialog.canvas.printChar(args[0].byteval!!)
}
"c64flt.print_f" -> {
dialog.canvas.printText(args[0].floatval.toString(), 1, true)
}
else -> TODO("syscall ${sub.scopedname} $sub")
}
}
}

View File

@ -0,0 +1,204 @@
package prog8.astvm
import prog8.ast.DataType
import prog8.compiler.RuntimeValue
import java.lang.Math.toDegrees
import java.lang.Math.toRadians
import java.util.*
import kotlin.math.*
import kotlin.random.Random
class BuiltinFunctions {
private val rnd = Random(0)
private val statusFlagsSave = Stack<StatusFlags>()
fun performBuiltinFunction(name: String, args: List<RuntimeValue>, statusflags: StatusFlags): RuntimeValue? {
return when (name) {
"rnd" -> RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255)
"rndw" -> RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535)
"rndf" -> RuntimeValue(DataType.FLOAT, rnd.nextDouble())
"lsb" -> RuntimeValue(DataType.UBYTE, args[0].integerValue() and 255)
"msb" -> RuntimeValue(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
"sin" -> RuntimeValue(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
"sin8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * sin(rad)).toShort())
}
"sin8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
}
"sin16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * sin(rad)).toShort())
}
"sin16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort())
}
"cos" -> RuntimeValue(DataType.FLOAT, cos(args[0].numericValue().toDouble()))
"cos8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * cos(rad)).toShort())
}
"cos8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort())
}
"cos16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * cos(rad)).toShort())
}
"cos16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * cos(rad)).toShort())
}
"tan" -> RuntimeValue(DataType.FLOAT, tan(args[0].numericValue().toDouble()))
"atan" -> RuntimeValue(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
"ln" -> RuntimeValue(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
"log2" -> RuntimeValue(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
"sqrt" -> RuntimeValue(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
"sqrt16" -> RuntimeValue(DataType.UBYTE, sqrt(args[0].wordval!!.toDouble()).toInt())
"rad" -> RuntimeValue(DataType.FLOAT, toRadians(args[0].numericValue().toDouble()))
"deg" -> RuntimeValue(DataType.FLOAT, toDegrees(args[0].numericValue().toDouble()))
"round" -> RuntimeValue(DataType.FLOAT, round(args[0].numericValue().toDouble()))
"floor" -> RuntimeValue(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
"ceil" -> RuntimeValue(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
"rol" -> {
val (result, newCarry) = args[0].rol(statusflags.carry)
statusflags.carry = newCarry
return result
}
"rol2" -> args[0].rol2()
"ror" -> {
val (result, newCarry) = args[0].ror(statusflags.carry)
statusflags.carry = newCarry
return result
}
"ror2" -> args[0].ror2()
"lsl" -> args[0].shl()
"lsr" -> args[0].shr()
"abs" -> {
when (args[0].type) {
DataType.UBYTE -> args[0]
DataType.BYTE -> RuntimeValue(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
DataType.UWORD -> args[0]
DataType.WORD -> RuntimeValue(DataType.UWORD, abs(args[0].numericValue().toDouble()))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
else -> TODO("strange abs type")
}
}
"max" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(args[0].type, numbers.max())
}
"min" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(args[0].type, numbers.min())
}
"avg" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.FLOAT, numbers.average())
}
"sum" -> {
val sum = args.map { it.numericValue().toDouble() }.sum()
when (args[0].type) {
DataType.UBYTE -> RuntimeValue(DataType.UWORD, sum)
DataType.BYTE -> RuntimeValue(DataType.WORD, sum)
DataType.UWORD -> RuntimeValue(DataType.UWORD, sum)
DataType.WORD -> RuntimeValue(DataType.WORD, sum)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, sum)
else -> TODO("weird sum type")
}
}
"any" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
}
"all" -> {
val numbers = args.map { it.numericValue().toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
}
"swap" ->
throw VmExecutionException("swap() cannot be implemented as a function")
"strlen" -> {
val zeroIndex = args[0].str!!.indexOf(0.toChar())
if (zeroIndex >= 0)
RuntimeValue(DataType.UBYTE, zeroIndex)
else
RuntimeValue(DataType.UBYTE, args[0].str!!.length)
}
"memset" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount) {
target[i] = value
}
null
}
"memsetw" -> {
val target = args[0].array!!
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount step 2) {
target[i * 2] = value and 255
target[i * 2 + 1] = value ushr 8
}
null
}
"memcopy" -> {
val source = args[0].array!!
val dest = args[1].array!!
val amount = args[2].integerValue()
for(i in 0 until amount) {
dest[i] = source[i]
}
null
}
"mkword" -> {
val result = (args[0].integerValue() shl 8) or args[1].integerValue()
RuntimeValue(DataType.UWORD, result)
}
"set_carry" -> {
statusflags.carry=true
null
}
"clear_carry" -> {
statusflags.carry=false
null
}
"set_irqd" -> {
statusflags.irqd=true
null
}
"clear_irqd" -> {
statusflags.irqd=false
null
}
"read_flags" -> {
val carry = if(statusflags.carry) 1 else 0
val zero = if(statusflags.zero) 2 else 0
val irqd = if(statusflags.irqd) 4 else 0
val negative = if(statusflags.negative) 128 else 0
RuntimeValue(DataType.UBYTE, carry or zero or irqd or negative)
}
"rsave" -> {
statusFlagsSave.push(statusflags)
null
}
"rrestore" -> {
val flags = statusFlagsSave.pop()
statusflags.carry = flags.carry
statusflags.negative = flags.negative
statusflags.zero = flags.zero
statusflags.irqd = flags.irqd
null
}
else -> TODO("builtin function $name")
}
}
}

View File

@ -0,0 +1,18 @@
package prog8.astvm
import prog8.ast.INameScope
import java.util.*
class CallStack {
private val stack = Stack<Pair<INameScope, Int>>()
fun pop(): Pair<INameScope, Int> {
return stack.pop()
}
fun push(scope: INameScope, index: Int) {
stack.push(Pair(scope, index))
}
}

View File

@ -0,0 +1,154 @@
package prog8.astvm
import prog8.ast.*
import prog8.compiler.RuntimeValue
import prog8.compiler.RuntimeValueRange
import kotlin.math.abs
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
val runtimeVars: RuntimeVariables, val functions: BuiltinFunctions,
val executeSubroutine: (sub: Subroutine, args: List<RuntimeValue>, startlabel: Label?) -> List<RuntimeValue>)
fun evaluate(expr: IExpression, ctx: EvalContext): RuntimeValue {
val constval = expr.constValue(ctx.program)
if(constval!=null)
return RuntimeValue.from(constval, ctx.program.heap)
when(expr) {
is LiteralValue -> {
return RuntimeValue.from(expr, ctx.program.heap)
}
is PrefixExpression -> {
return when(expr.operator) {
"-" -> evaluate(expr.expression, ctx).neg()
"~" -> evaluate(expr.expression, ctx).inv()
"not" -> evaluate(expr.expression, ctx).not()
// unary '+' should have been optimized away
else -> TODO("prefixexpr ${expr.operator}")
}
}
is BinaryExpression -> {
val left = evaluate(expr.left, ctx)
val right = evaluate(expr.right, ctx)
return when(expr.operator) {
"<" -> RuntimeValue(DataType.UBYTE, if (left < right) 1 else 0)
"<=" -> RuntimeValue(DataType.UBYTE, if (left <= right) 1 else 0)
">" -> RuntimeValue(DataType.UBYTE, if (left > right) 1 else 0)
">=" -> RuntimeValue(DataType.UBYTE, if (left >= right) 1 else 0)
"==" -> RuntimeValue(DataType.UBYTE, if (left == right) 1 else 0)
"!=" -> RuntimeValue(DataType.UBYTE, if (left != right) 1 else 0)
"+" -> left.add(right)
"-" -> left.sub(right)
"*" -> left.mul(right)
"/" -> left.div(right)
"**" -> left.pow(right)
"<<" -> {
var result = left
repeat(right.integerValue()) {result = result.shl()}
result
}
">>" -> {
var result = left
repeat(right.integerValue()) {result = result.shr()}
result
}
"%" -> left.remainder(right)
"|" -> left.bitor(right)
"&" -> left.bitand(right)
"^" -> left.bitxor(right)
"and" -> left.and(right)
"or" -> left.or(right)
"xor" -> left.xor(right)
else -> TODO("binexpression operator ${expr.operator}")
}
}
is ArrayIndexedExpression -> {
val array = evaluate(expr.identifier, ctx)
val index = evaluate(expr.arrayspec.index, ctx)
val value = array.array!![index.integerValue()]
return RuntimeValue(ArrayElementTypes.getValue(array.type), value)
}
is TypecastExpression -> {
return evaluate(expr.expression, ctx).cast(expr.type)
}
is AddressOf -> {
// we support: address of heap var -> the heap id
val heapId = expr.identifier.heapId(ctx.program.namespace)
return RuntimeValue(DataType.UWORD, heapId)
}
is DirectMemoryRead -> {
val address = evaluate(expr.addressExpression, ctx).wordval!!
return RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
}
is DirectMemoryWrite -> {
TODO("memorywrite $expr")
}
is RegisterExpr -> return ctx.runtimeVars.get(ctx.program.namespace, expr.register.name)
is IdentifierReference -> {
val scope = expr.definingScope()
val variable = scope.lookup(expr.nameInSource, expr)
if(variable is VarDecl) {
if(variable.type==VarDeclType.VAR)
return ctx.runtimeVars.get(variable.definingScope(), variable.name)
else {
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
return when(variable.datatype) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address))
DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address))
DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address))
DataType.STR -> RuntimeValue(DataType.STR, str=ctx.mem.getString(address))
DataType.STR_S -> RuntimeValue(DataType.STR_S, str=ctx.mem.getScreencodeString(address))
else -> TODO("memvar $variable")
}
}
} else
TODO("weird ref $variable")
}
is FunctionCall -> {
val sub = expr.target.targetStatement(ctx.program.namespace)
val args = expr.arglist.map { evaluate(it, ctx) }
return when(sub) {
is Subroutine -> {
val results = ctx.executeSubroutine(sub, args, null)
if(results.size!=1)
throw VmExecutionException("expected 1 result from functioncall $expr")
results[0]
}
is BuiltinFunctionStatementPlaceholder -> {
val result = ctx.functions.performBuiltinFunction(sub.name, args, ctx.statusflags)
?: throw VmExecutionException("expected 1 result from functioncall $expr")
result
}
else -> {
TODO("call expr function ${expr.target}")
}
}
}
is RangeExpr -> {
val cRange = expr.toConstantIntegerRange()
if(cRange!=null)
return RuntimeValueRange(expr.inferType(ctx.program)!!, cRange)
val fromVal = evaluate(expr.from, ctx).integerValue()
val toVal = evaluate(expr.to, ctx).integerValue()
val stepVal = evaluate(expr.step, ctx).integerValue()
val range = when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
return RuntimeValueRange(expr.inferType(ctx.program)!!, range)
}
else -> {
TODO("implement eval $expr")
}
}
}

View File

@ -0,0 +1,122 @@
package prog8.astvm
import prog8.compiler.target.c64.Mflpt5
import prog8.compiler.target.c64.Petscii
import kotlin.math.abs
class Memory {
private val mem = ShortArray(65536) // shorts because byte is signed and we store values 0..255
fun getUByte(address: Int): Short {
return mem[address]
}
fun getSByte(address: Int): Short {
val ubyte = getUByte(address)
if(ubyte <= 127)
return ubyte
return (-((ubyte.toInt() xor 255)+1)).toShort() // 2's complement
}
fun setUByte(address: Int, value: Short) {
if(value !in 0..255)
throw VmExecutionException("ubyte value out of range")
mem[address] = value
}
fun setSByte(address: Int, value: Short) {
if(value !in -128..127) throw VmExecutionException("byte value out of range")
if(value>=0)
mem[address] = value
else
mem[address] = ((abs(value.toInt()) xor 255)+1).toShort() // 2's complement
}
fun getUWord(address: Int): Int {
return mem[address] + 256*mem[address+1]
}
fun getSWord(address: Int): Int {
val uword = getUWord(address)
if(uword <= 32767)
return uword
return -((uword xor 65535)+1) // 2's complement
}
fun setUWord(address: Int, value: Int) {
if(value !in 0..65535)
throw VmExecutionException("uword value out of range")
mem[address] = value.and(255).toShort()
mem[address+1] = (value / 256).toShort()
}
fun setSWord(address: Int, value: Int) {
if(value !in -32768..32767) throw VmExecutionException("word value out of range")
if(value>=0)
setUWord(address, value)
else
setUWord(address, (abs(value) xor 65535)+1) // 2's complement
}
fun setFloat(address: Int, value: Double) {
val mflpt5 = Mflpt5.fromNumber(value)
mem[address] = mflpt5.b0
mem[address+1] = mflpt5.b1
mem[address+2] = mflpt5.b2
mem[address+3] = mflpt5.b3
mem[address+4] = mflpt5.b4
}
fun getFloat(address: Int): Double {
return Mflpt5(mem[address], mem[address + 1], mem[address + 2], mem[address + 3], mem[address + 4]).toDouble()
}
fun setString(address: Int, str: String) {
// lowercase PETSCII
val petscii = Petscii.encodePetscii(str, true)
var addr = address
for (c in petscii) mem[addr++] = c
mem[addr] = 0
}
fun getString(strAddress: Int): String {
// lowercase PETSCII
val petscii = mutableListOf<Short>()
var addr = strAddress
while(true) {
val byte = mem[addr++]
if(byte==0.toShort()) break
petscii.add(byte)
}
return Petscii.decodePetscii(petscii, true)
}
fun clear() {
for(i in 0..65535) mem[i]=0
}
fun copy(from: Int, to: Int, numbytes: Int) {
for(i in 0 until numbytes)
mem[to+i] = mem[from+i]
}
fun getScreencodeString(strAddress: Int): String? {
// lowercase Screencodes
val screencodes = mutableListOf<Short>()
var addr = strAddress
while(true) {
val byte = mem[addr++]
if(byte==0.toShort()) break
screencodes.add(byte)
}
return Petscii.decodeScreencode(screencodes, true)
}
fun setScreencodeString(address: Int, str: String) {
// lowercase screencodes
val screencodes = Petscii.encodeScreencode(str, true)
var addr = address
for (c in screencodes) mem[addr++] = c
mem[addr] = 0
}
}

View File

@ -0,0 +1,192 @@
package prog8.astvm
import prog8.compiler.target.c64.Charset
import prog8.compiler.target.c64.Colors
import prog8.compiler.target.c64.Petscii
import java.awt.*
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import java.awt.image.BufferedImage
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.Timer
class BitmapScreenPanel : KeyListener, JPanel() {
private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB)
private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0
private var cursorY: Int=0
init {
val size = Dimension(image.width * SCALING, image.height * SCALING)
minimumSize = size
maximumSize = size
preferredSize = size
clearScreen(6)
isFocusable = true
requestFocusInWindow()
addKeyListener(this)
}
override fun keyTyped(p0: KeyEvent?) {}
override fun keyPressed(p0: KeyEvent?) {
println("pressed: $p0.k")
}
override fun keyReleased(p0: KeyEvent?) {
println("released: $p0")
}
override fun paint(graphics: Graphics?) {
val g2d = graphics as Graphics2D?
g2d!!.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF)
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE)
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
g2d.drawImage(image, 0, 0, image.width * 3, image.height * 3, null)
}
fun clearScreen(color: Short) {
g2d.background = Colors.palette[color % Colors.palette.size]
g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT)
cursorX = 0
cursorY = 0
}
fun setPixel(x: Int, y: Int, color: Short) {
image.setRGB(x, y, Colors.palette[color % Colors.palette.size].rgb)
}
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) {
g2d.color = Colors.palette[color % Colors.palette.size]
g2d.drawLine(x1, y1, x2, y2)
}
fun printText(text: String, color: Short, lowercase: Boolean) {
val t2 = text.substringBefore(0.toChar())
val lines = t2.split('\n')
for(line in lines.withIndex()) {
printTextSingleLine(line.value, color, lowercase)
if(line.index<lines.size-1) {
cursorX=0
cursorY++
}
}
}
private fun printTextSingleLine(text: String, color: Short, lowercase: Boolean) {
for(clearx in cursorX until cursorX+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(cursorX, cursorY, sc, color)
cursorX++
if(cursorX>=(SCREENWIDTH/8)) {
cursorY++
cursorX=0
}
}
}
fun printChar(char: Short) {
if(char==13.toShort() || char==141.toShort()) {
cursorX=0
cursorY++
} else {
setChar(cursorX, cursorY, char, 1)
cursorX++
if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++
cursorX = 0
}
}
}
fun setChar(x: Int, y: Int, screenCode: Short, color: Short) {
g2d.clearRect(8*x, 8*y, 8, 8)
val colorIdx = (color % Colors.palette.size).toShort()
val coloredImage = Charset.getColoredChar(screenCode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null)
}
fun setCursorPos(x: Int, y: Int) {
cursorX = x
cursorY = y
}
fun getCursorPos(): Pair<Int, Int> {
return Pair(cursorX, cursorY)
}
fun writeText(x: Int, y: Int, text: String, color: Short, lowercase: Boolean) {
val colorIdx = (color % Colors.palette.size).toShort()
var xx=x
for(clearx in xx until xx+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
if(sc==0.toShort())
break
setChar(xx++, y, sc, colorIdx)
}
}
companion object {
const val SCREENWIDTH = 320
const val SCREENHEIGHT = 200
const val SCALING = 3
}
}
class ScreenDialog : JFrame() {
val canvas = BitmapScreenPanel()
init {
val borderWidth = 16
title = "AstVm graphics. Text I/O goes to console."
layout = GridBagLayout()
defaultCloseOperation = JFrame.EXIT_ON_CLOSE
isResizable = false
// the borders (top, left, right, bottom)
val borderTop = JPanel().apply {
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = Colors.palette[14]
}
val borderBottom = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = Colors.palette[14]
}
val borderLeft = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = Colors.palette[14]
}
val borderRight = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = Colors.palette[14]
}
var c = GridBagConstraints()
c.gridx=0; c.gridy=1; c.gridwidth=3
add(borderTop, c)
c = GridBagConstraints()
c.gridx=0; c.gridy=2
add(borderLeft, c)
c = GridBagConstraints()
c.gridx=2; c.gridy=2
add(borderRight, c)
c = GridBagConstraints()
c.gridx=0; c.gridy=3; c.gridwidth=3
add(borderBottom, c)
// the screen canvas(bitmap)
c = GridBagConstraints()
c.gridx = 1; c.gridy = 2
add(canvas, c)
canvas.requestFocusInWindow()
}
fun start() {
val repaintTimer = Timer(1000 / 60) { repaint() }
repaintTimer.start()
}
}

View File

@ -0,0 +1,73 @@
package prog8.astvm
import prog8.ast.*
import prog8.compiler.HeapValues
import prog8.compiler.RuntimeValue
class VariablesCreator(private val runtimeVariables: RuntimeVariables, private val heap: HeapValues) : IAstProcessor {
override fun process(program: Program) {
// define the three registers as global variables
runtimeVariables.define(program.namespace, Register.A.name, RuntimeValue(DataType.UBYTE, 0))
runtimeVariables.define(program.namespace, Register.X.name, RuntimeValue(DataType.UBYTE, 255))
runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValue(DataType.UBYTE, 0))
val globalpos = Position("<<global>>", 0, 0, 0)
val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.A.name, LiteralValue.optimalInteger(0, globalpos), isArray = false, autoGenerated = true, position = globalpos)
val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.X.name, LiteralValue.optimalInteger(255, globalpos), isArray = false, autoGenerated = true, position = globalpos)
val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, false, null, Register.Y.name, LiteralValue.optimalInteger(0, globalpos), isArray = false, autoGenerated = true, position = globalpos)
vdA.linkParents(program.namespace)
vdX.linkParents(program.namespace)
vdY.linkParents(program.namespace)
program.namespace.statements.add(vdA)
program.namespace.statements.add(vdX)
program.namespace.statements.add(vdY)
super.process(program)
}
override fun process(decl: VarDecl): IStatement {
when(decl.type) {
VarDeclType.VAR -> {
val value = when (decl.datatype) {
in NumericDatatypes -> {
if(decl.value !is LiteralValue) {
TODO("evaluate vardecl expression $decl")
//RuntimeValue(decl.datatype, num = evaluate(decl.value!!, program, runtimeVariables, executeSubroutine).numericValue())
} else {
RuntimeValue.from(decl.value as LiteralValue, heap)
}
}
in StringDatatypes -> {
RuntimeValue.from(decl.value as LiteralValue, heap)
}
in ArrayDatatypes -> {
RuntimeValue.from(decl.value as LiteralValue, heap)
}
else -> throw VmExecutionException("weird type ${decl.datatype}")
}
runtimeVariables.define(decl.definingScope(), decl.name, value)
}
VarDeclType.MEMORY -> {
if(decl.value !is LiteralValue) {
TODO("evaluate vardecl expression $decl")
//RuntimeValue(decl.datatype, num = evaluate(decl.value!!, program, runtimeVariables, executeSubroutine).numericValue())
} else {
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as LiteralValue).asIntegerValue!!)
}
}
VarDeclType.CONST -> {
// consts should have been const-folded away
}
}
return super.process(decl)
}
// override fun process(assignment: Assignment): IStatement {
// if(assignment is VariableInitializationAssignment) {
// println("INIT VAR $assignment")
// }
// return super.process(assignment)
// }
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,528 @@
package prog8.compiler
import prog8.ast.*
import prog8.compiler.target.c64.Petscii
import kotlin.math.abs
import kotlin.math.pow
/**
* Rather than a literal value (LiteralValue) that occurs in the parsed source code,
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form)
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
*/
open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null, val array: Array<Number>?=null, val heapId: Int?=null) {
val byteval: Short?
val wordval: Int?
val floatval: Double?
val asBoolean: Boolean
companion object {
fun from(literalValue: LiteralValue, heap: HeapValues): RuntimeValue {
return when(literalValue.type) {
in NumericDatatypes -> RuntimeValue(literalValue.type, num = literalValue.asNumericValue!!)
in StringDatatypes -> from(literalValue.heapId!!, heap)
in ArrayDatatypes -> from(literalValue.heapId!!, heap)
else -> TODO("type")
}
}
fun from(heapId: Int, heap: HeapValues): RuntimeValue {
val value = heap.get(heapId)
return when {
value.type in StringDatatypes ->
RuntimeValue(value.type, str = value.str!!, heapId = heapId)
value.type in ArrayDatatypes ->
if (value.type == DataType.ARRAY_F) {
RuntimeValue(value.type, array = value.doubleArray!!.toList().toTypedArray(), heapId = heapId)
} else {
val array = value.array!!
if (array.any { it.addressOf != null })
TODO("addressof values")
RuntimeValue(value.type, array = array.map { it.integer!! }.toTypedArray(), heapId = heapId)
}
else -> TODO("weird type on heap")
}
}
}
init {
when(type) {
DataType.UBYTE -> {
byteval = (num!!.toInt() and 255).toShort()
wordval = null
floatval = null
asBoolean = byteval != 0.toShort()
}
DataType.BYTE -> {
val v = num!!.toInt() and 255
byteval = (if(v<128) v else v-256).toShort()
wordval = null
floatval = null
asBoolean = byteval != 0.toShort()
}
DataType.UWORD -> {
wordval = num!!.toInt() and 65535
byteval = null
floatval = null
asBoolean = wordval != 0
}
DataType.WORD -> {
val v = num!!.toInt() and 65535
wordval = if(v<32768) v else v - 65536
byteval = null
floatval = null
asBoolean = wordval != 0
}
DataType.FLOAT -> {
floatval = num!!.toDouble()
byteval = null
wordval = null
asBoolean = floatval != 0.0
}
else -> {
if(heapId==null)
throw IllegalArgumentException("for non-numeric types, a heapId should be given")
byteval = null
wordval = null
floatval = null
asBoolean = true
}
}
}
fun asLiteralValue(): LiteralValue {
return when(type) {
in ByteDatatypes -> LiteralValue(type, byteval, position = Position("", 0, 0, 0))
in WordDatatypes -> LiteralValue(type, wordvalue = wordval, position = Position("", 0, 0, 0))
DataType.FLOAT -> LiteralValue(type, floatvalue = floatval, position = Position("", 0, 0, 0))
in StringDatatypes -> LiteralValue(type, strvalue = str, position = Position("", 0, 0, 0))
in ArrayDatatypes -> LiteralValue(type,
arrayvalue = array?.map { LiteralValue.optimalNumeric(it, Position("", 0, 0, 0)) }?.toTypedArray(),
position = Position("", 0, 0, 0))
else -> TODO("strange type")
}
}
override fun toString(): String {
return when(type) {
DataType.UBYTE -> "ub:%02x".format(byteval)
DataType.BYTE -> {
if(byteval!!<0)
"b:-%02x".format(abs(byteval.toInt()))
else
"b:%02x".format(byteval)
}
DataType.UWORD -> "uw:%04x".format(wordval)
DataType.WORD -> {
if(wordval!!<0)
"w:-%04x".format(abs(wordval))
else
"w:%04x".format(wordval)
}
DataType.FLOAT -> "f:$floatval"
else -> "heap:$heapId"
}
}
fun numericValue(): Number {
return when(type) {
in ByteDatatypes -> byteval!!
in WordDatatypes -> wordval!!
DataType.FLOAT -> floatval!!
else -> throw ArithmeticException("invalid datatype for numeric value: $type")
}
}
fun integerValue(): Int {
return when(type) {
in ByteDatatypes -> byteval!!.toInt()
in WordDatatypes -> wordval!!
DataType.FLOAT -> throw ArithmeticException("float to integer loss of precision")
else -> throw ArithmeticException("invalid datatype for integer value: $type")
}
}
override fun hashCode(): Int {
val bh = byteval?.hashCode() ?: 0x10001234
val wh = wordval?.hashCode() ?: 0x01002345
val fh = floatval?.hashCode() ?: 0x00103456
return bh xor wh xor fh xor heapId.hashCode() xor type.hashCode()
}
override fun equals(other: Any?): Boolean {
if(other==null || other !is RuntimeValue)
return false
if(type==other.type)
return if (type in IterableDatatypes) heapId==other.heapId else compareTo(other)==0
return compareTo(other)==0 // note: datatype doesn't matter
}
operator fun compareTo(other: RuntimeValue): Int {
return if (type in NumericDatatypes && other.type in NumericDatatypes)
numericValue().toDouble().compareTo(other.numericValue().toDouble())
else throw ArithmeticException("comparison can only be done between two numeric values")
}
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): RuntimeValue {
if(leftDt!=rightDt)
throw ArithmeticException("left and right datatypes are not the same")
if(result.toDouble() < 0 ) {
return when(leftDt) {
DataType.UBYTE, DataType.UWORD -> {
// storing a negative number in an unsigned one is done by storing the 2's complement instead
val number = abs(result.toDouble().toInt())
if(leftDt==DataType.UBYTE)
RuntimeValue(DataType.UBYTE, (number xor 255) + 1)
else
RuntimeValue(DataType.UBYTE, (number xor 65535) + 1)
}
DataType.BYTE -> RuntimeValue(DataType.BYTE, result.toInt())
DataType.WORD -> RuntimeValue(DataType.WORD, result.toInt())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
return when(leftDt) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result.toInt())
DataType.BYTE -> RuntimeValue(DataType.BYTE, result.toInt())
DataType.UWORD -> RuntimeValue(DataType.UWORD, result.toInt())
DataType.WORD -> RuntimeValue(DataType.WORD, result.toInt())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
fun add(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() + v2.toDouble()
return arithResult(type, result, other.type, "add")
}
fun sub(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() - v2.toDouble()
return arithResult(type, result, other.type, "sub")
}
fun mul(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() * v2.toDouble()
return arithResult(type, result, other.type, "mul")
}
fun div(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
if(v2.toDouble()==0.0) {
when (type) {
DataType.UBYTE -> return RuntimeValue(DataType.UBYTE, 255)
DataType.BYTE -> return RuntimeValue(DataType.BYTE, 127)
DataType.UWORD -> return RuntimeValue(DataType.UWORD, 65535)
DataType.WORD -> return RuntimeValue(DataType.WORD, 32767)
else -> {}
}
}
val result = v1.toDouble() / v2.toDouble()
// NOTE: integer division returns integer result!
return when(type) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result)
DataType.BYTE -> RuntimeValue(DataType.BYTE, result)
DataType.UWORD -> RuntimeValue(DataType.UWORD, result)
DataType.WORD -> RuntimeValue(DataType.WORD, result)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
else -> throw ArithmeticException("div on non-numeric type")
}
}
fun remainder(other: RuntimeValue): RuntimeValue {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() % v2.toDouble()
return arithResult(type, result, other.type, "remainder")
}
fun pow(other: RuntimeValue): RuntimeValue {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble())
return arithResult(type, result, other.type,"pow")
}
fun shl(): RuntimeValue {
val v = integerValue()
return when (type) {
DataType.UBYTE,
DataType.BYTE,
DataType.UWORD,
DataType.WORD -> RuntimeValue(type, v shl 1)
else -> throw ArithmeticException("invalid type for shl: $type")
}
}
fun shr(): RuntimeValue {
val v = integerValue()
return when(type){
DataType.UBYTE -> RuntimeValue(type, v ushr 1)
DataType.BYTE -> RuntimeValue(type, v shr 1)
DataType.UWORD -> RuntimeValue(type, v ushr 1)
DataType.WORD -> RuntimeValue(type, v shr 1)
else -> throw ArithmeticException("invalid type for shr: $type")
}
}
fun rol(carry: Boolean): Pair<RuntimeValue, Boolean> {
// 9 or 17 bit rotate left (with carry))
return when(type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val newCarry = (v and 0x8000) != 0
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
Pair(RuntimeValue(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("rol can only work on byte/word")
}
}
fun ror(carry: Boolean): Pair<RuntimeValue, Boolean> {
// 9 or 17 bit rotate right (with carry)
return when(type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
Pair(RuntimeValue(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun rol2(): RuntimeValue {
// 8 or 16 bit rotate left
return when(type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry
RuntimeValue(DataType.UBYTE, newval)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val carry = (v and 0x8000) ushr 15
val newval = (v and 0x7fff shl 1) or carry
RuntimeValue(DataType.UWORD, newval)
}
else -> throw ArithmeticException("rol2 can only work on byte/word")
}
}
fun ror2(): RuntimeValue {
// 8 or 16 bit rotate right
return when(type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = v and 1 shl 7
val newval = (v ushr 1) or carry
RuntimeValue(DataType.UBYTE, newval)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val carry = v and 1 shl 15
val newval = (v ushr 1) or carry
RuntimeValue(DataType.UWORD, newval)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun neg(): RuntimeValue {
return when(type) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, -(byteval!!))
DataType.WORD -> RuntimeValue(DataType.WORD, -(wordval!!))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, -(floatval)!!)
else -> throw ArithmeticException("neg can only work on byte/word/float")
}
}
fun abs(): RuntimeValue {
return when(type) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, abs(byteval!!.toInt()))
DataType.WORD -> RuntimeValue(DataType.WORD, abs(wordval!!))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(floatval!!))
else -> throw ArithmeticException("abs can only work on byte/word/float")
}
}
fun bitand(other: RuntimeValue): RuntimeValue {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 and v2
return RuntimeValue(type, result)
}
fun bitor(other: RuntimeValue): RuntimeValue {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 or v2
return RuntimeValue(type, result)
}
fun bitxor(other: RuntimeValue): RuntimeValue {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 xor v2
return RuntimeValue(type, result)
}
fun and(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean && other.asBoolean) 1 else 0)
fun or(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean || other.asBoolean) 1 else 0)
fun xor(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean xor other.asBoolean) 1 else 0)
fun not() = RuntimeValue(DataType.UBYTE, if (this.asBoolean) 0 else 1)
fun inv(): RuntimeValue {
return when(type) {
in ByteDatatypes -> RuntimeValue(type, byteval!!.toInt().inv())
in WordDatatypes -> RuntimeValue(type, wordval!!.inv())
else -> throw ArithmeticException("inv can only work on byte/word")
}
}
fun inc(): RuntimeValue {
return when(type) {
in ByteDatatypes -> RuntimeValue(type, byteval!! + 1)
in WordDatatypes -> RuntimeValue(type, wordval!! + 1)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! + 1)
else -> throw ArithmeticException("inc can only work on numeric types")
}
}
fun dec(): RuntimeValue {
return when(type) {
in ByteDatatypes -> RuntimeValue(type, byteval!! - 1)
in WordDatatypes -> RuntimeValue(type, wordval!! - 1)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! - 1)
else -> throw ArithmeticException("dec can only work on numeric types")
}
}
fun msb(): RuntimeValue {
return when(type) {
in ByteDatatypes -> RuntimeValue(DataType.UBYTE, 0)
in WordDatatypes -> RuntimeValue(DataType.UBYTE, wordval!! ushr 8 and 255)
else -> throw ArithmeticException("msb can only work on (u)byte/(u)word")
}
}
fun cast(targetType: DataType): RuntimeValue {
return when (type) {
DataType.UBYTE -> {
when (targetType) {
DataType.UBYTE -> this
DataType.BYTE -> RuntimeValue(DataType.BYTE, byteval)
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue())
DataType.WORD -> RuntimeValue(DataType.WORD, numericValue())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.BYTE -> {
when (targetType) {
DataType.BYTE -> this
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue())
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue())
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.UWORD -> {
when (targetType) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, integerValue())
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue())
DataType.UWORD -> this
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.WORD -> {
when (targetType) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, integerValue())
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue())
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue())
DataType.WORD -> this
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.FLOAT -> {
when (targetType) {
DataType.BYTE -> {
val integer=numericValue().toInt()
if(integer in -128..127)
RuntimeValue(DataType.BYTE, integer)
else
throw ArithmeticException("overflow when casting float to byte: $this")
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, numericValue().toInt())
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue().toInt())
DataType.WORD -> {
val integer=numericValue().toInt()
if(integer in -32768..32767)
RuntimeValue(DataType.WORD, integer)
else
throw ArithmeticException("overflow when casting float to word: $this")
}
DataType.FLOAT -> this
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
open fun iterator(): Iterator<Number> {
return when (type) {
in StringDatatypes -> {
Petscii.encodePetscii(str!!, true).iterator()
}
in ArrayDatatypes -> {
array!!.iterator()
}
else -> throw IllegalArgumentException("cannot iterate over $this")
}
}
}
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, 0) {
override fun iterator(): Iterator<Number> {
return range.iterator()
}
}

View File

@ -16,12 +16,12 @@ abstract class Zeropage(protected val options: CompilationOptions) {
fun available() = free.size fun available() = free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int { fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"same scopedname can't be allocated twice"} assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
val size = val size =
when (datatype) { when (datatype) {
DataType.UBYTE, DataType.BYTE -> 1 in ByteDatatypes -> 1
DataType.UWORD, DataType.WORD -> 2 in WordDatatypes -> 2
DataType.FLOAT -> { DataType.FLOAT -> {
if (options.floats) { if (options.floats) {
if(position!=null) if(position!=null)

View File

@ -1,32 +1,42 @@
package prog8.compiler.intermediate package prog8.compiler.intermediate
import prog8.compiler.RuntimeValue
import prog8.stackvm.Syscall import prog8.stackvm.Syscall
open class Instruction(val opcode: Opcode, open class Instruction(val opcode: Opcode,
val arg: Value? = null, val arg: RuntimeValue? = null,
val arg2: Value? = null, val arg2: RuntimeValue? = null,
val callLabel: String? = null, val callLabel: String? = null,
val callLabel2: String? = null) val callLabel2: String? = null)
{ {
lateinit var next: Instruction var branchAddress: Int? = null
var nextAlt: Instruction? = null
override fun toString(): String { override fun toString(): String {
val argStr = arg?.toString() ?: "" val argStr = arg?.toString() ?: ""
val result = val result =
when { when {
opcode==Opcode.LINE -> "_line $callLabel" opcode==Opcode.LINE -> "_line $callLabel"
opcode==Opcode.INLINE_ASSEMBLY -> "inline_assembly" opcode==Opcode.INLINE_ASSEMBLY -> {
// inline assembly is not written out (it can't be processed as intermediate language)
// instead, it is converted into a system call that can be intercepted by the vm
if(callLabel!=null)
"syscall SYSASM.$callLabel\n return"
else
"inline_assembly"
}
opcode==Opcode.INCLUDE_FILE -> {
"include_file \"$callLabel\" $arg $arg2"
}
opcode==Opcode.SYSCALL -> { opcode==Opcode.SYSCALL -> {
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() } val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
"syscall $syscall" "syscall $syscall"
} }
opcode in opcodesWithVarArgument -> { opcode in opcodesWithVarArgument -> {
// opcodes that manipulate a variable // opcodes that manipulate a variable
"${opcode.toString().toLowerCase()} ${callLabel?:""} ${callLabel2?:""}".trimEnd() "${opcode.name.toLowerCase()} ${callLabel?:""} ${callLabel2?:""}".trimEnd()
} }
callLabel==null -> "${opcode.toString().toLowerCase()} $argStr" callLabel==null -> "${opcode.name.toLowerCase()} $argStr"
else -> "${opcode.toString().toLowerCase()} $callLabel $argStr" else -> "${opcode.name.toLowerCase()} $callLabel $argStr"
} }
.trimEnd() .trimEnd()

View File

@ -1,6 +1,7 @@
package prog8.compiler.intermediate package prog8.compiler.intermediate
import prog8.ast.* import prog8.ast.*
import prog8.compiler.RuntimeValue
import prog8.compiler.CompilerException import prog8.compiler.CompilerException
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.Zeropage import prog8.compiler.Zeropage
@ -9,15 +10,14 @@ import java.io.PrintStream
import java.nio.file.Path import java.nio.file.Path
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val importedFrom: Path) { class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
class ProgramBlock(val scopedname: String, class ProgramBlock(val name: String,
val shortname: String,
var address: Int?, var address: Int?,
val instructions: MutableList<Instruction> = mutableListOf(), val instructions: MutableList<Instruction> = mutableListOf(),
val variables: MutableMap<String, Value> = mutableMapOf(), val variables: MutableMap<String, RuntimeValue> = mutableMapOf(), // names are fully scoped
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(), val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
val labels: MutableMap<String, Instruction> = mutableMapOf(), val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
val force_output: Boolean) val force_output: Boolean)
{ {
val numVariables: Int val numVariables: Int
@ -29,7 +29,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>() val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
val blocks = mutableListOf<ProgramBlock>() val blocks = mutableListOf<ProgramBlock>()
val memory = mutableMapOf<Int, List<Value>>() val memory = mutableMapOf<Int, List<RuntimeValue>>()
private lateinit var currentBlock: ProgramBlock private lateinit var currentBlock: ProgramBlock
val numVariables: Int val numVariables: Int
@ -70,7 +70,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
optimizeMultipleSequentialLineInstrs() optimizeMultipleSequentialLineInstrs()
optimizeCallReturnIntoJump() optimizeCallReturnIntoJump()
optimizeConditionalBranches() optimizeConditionalBranches()
// todo: add more optimizations to stackvm code // todo: add more optimizations to intermediate code!
optimizeRemoveNops() // must be done as the last step optimizeRemoveNops() // must be done as the last step
optimizeMultipleSequentialLineInstrs() // once more optimizeMultipleSequentialLineInstrs() // once more
@ -89,7 +89,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
blk.instructions.asSequence().withIndex().filter {it.value.opcode!=Opcode.LINE}.windowed(2).toList().forEach { blk.instructions.asSequence().withIndex().filter {it.value.opcode!=Opcode.LINE}.windowed(2).toList().forEach {
if (it[1].value.opcode in branchOpcodes) { if (it[1].value.opcode in branchOpcodes) {
if (it[0].value.opcode in pushvalue) { if (it[0].value.opcode in pushvalue) {
val value = it[0].value.arg!!.asBooleanValue val value = it[0].value.arg!!.asBoolean
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP) instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
val replacement: Instruction = val replacement: Instruction =
if (value) { if (value) {
@ -257,17 +257,17 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
instructionsToReplace[index1] = Instruction(Opcode.NOP) instructionsToReplace[index1] = Instruction(Opcode.NOP)
} }
Opcode.CAST_W_TO_UB, Opcode.CAST_UW_TO_UB -> { Opcode.CAST_W_TO_UB, Opcode.CAST_UW_TO_UB -> {
val ins = Instruction(Opcode.PUSH_BYTE, Value(DataType.UBYTE, ins0.arg!!.integerValue() and 255)) val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() and 255))
instructionsToReplace[index0] = ins instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP) instructionsToReplace[index1] = Instruction(Opcode.NOP)
} }
Opcode.MSB -> { Opcode.MSB -> {
val ins = Instruction(Opcode.PUSH_BYTE, Value(DataType.UBYTE, ins0.arg!!.integerValue() ushr 8 and 255)) val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() ushr 8 and 255))
instructionsToReplace[index0] = ins instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP) instructionsToReplace[index1] = Instruction(Opcode.NOP)
} }
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> { Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> {
val ins = Instruction(Opcode.PUSH_FLOAT, Value(DataType.FLOAT, ins0.arg!!.integerValue().toDouble())) val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP) instructionsToReplace[index1] = Instruction(Opcode.NOP)
} }
@ -297,12 +297,12 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
Opcode.CAST_UW_TO_B, Opcode.CAST_UW_TO_UB -> instructionsToReplace[index1] = Instruction(Opcode.NOP) Opcode.CAST_UW_TO_B, Opcode.CAST_UW_TO_UB -> instructionsToReplace[index1] = Instruction(Opcode.NOP)
Opcode.MSB -> throw CompilerException("msb of a byte") Opcode.MSB -> throw CompilerException("msb of a byte")
Opcode.CAST_UB_TO_UW -> { Opcode.CAST_UB_TO_UW -> {
val ins = Instruction(Opcode.PUSH_WORD, Value(DataType.UWORD, ins0.arg!!.integerValue())) val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.UWORD, ins0.arg!!.integerValue()))
instructionsToReplace[index0] = ins instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP) instructionsToReplace[index1] = Instruction(Opcode.NOP)
} }
Opcode.CAST_B_TO_W -> { Opcode.CAST_B_TO_W -> {
val ins = Instruction(Opcode.PUSH_WORD, Value(DataType.WORD, ins0.arg!!.integerValue())) val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.WORD, ins0.arg!!.integerValue()))
instructionsToReplace[index0] = ins instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP) instructionsToReplace[index1] = Instruction(Opcode.NOP)
} }
@ -317,7 +317,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
instructionsToReplace[index1] = Instruction(Opcode.NOP) instructionsToReplace[index1] = Instruction(Opcode.NOP)
} }
Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F-> { Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F-> {
val ins = Instruction(Opcode.PUSH_FLOAT, Value(DataType.FLOAT, ins0.arg!!.integerValue().toDouble())) val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP) instructionsToReplace[index1] = Instruction(Opcode.NOP)
} }
@ -327,6 +327,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
instructionsToReplace[index1] = Instruction(Opcode.NOP) instructionsToReplace[index1] = Instruction(Opcode.NOP)
} }
Opcode.DISCARD_WORD, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte") Opcode.DISCARD_WORD, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
Opcode.MKWORD -> {}
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode}") else -> throw CompilerException("invalid conversion opcode ${ins1.opcode}")
} }
} }
@ -389,20 +390,20 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
when(decl.type) { when(decl.type) {
VarDeclType.VAR -> { VarDeclType.VAR -> {
val value = when(decl.datatype) { val value = when(decl.datatype) {
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!) in NumericDatatypes -> RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { in StringDatatypes -> {
val litval = (decl.value as LiteralValue) val litval = (decl.value as LiteralValue)
if(litval.heapId==null) if(litval.heapId==null)
throw CompilerException("string should already be in the heap") throw CompilerException("string should already be in the heap")
Value(decl.datatype, litval.heapId) RuntimeValue(decl.datatype, heapId = litval.heapId)
} }
DataType.ARRAY_B, DataType.ARRAY_W, in ArrayDatatypes -> {
DataType.ARRAY_UB, DataType.ARRAY_UW, DataType.ARRAY_F -> {
val litval = (decl.value as LiteralValue) val litval = (decl.value as LiteralValue)
if(litval.heapId==null) if(litval.heapId==null)
throw CompilerException("array should already be in the heap") throw CompilerException("array should already be in the heap")
Value(decl.datatype, litval.heapId) RuntimeValue(decl.datatype, heapId = litval.heapId)
} }
else -> throw CompilerException("weird datatype")
} }
currentBlock.variables[scopedname] = value currentBlock.variables[scopedname] = value
if(decl.zeropage) if(decl.zeropage)
@ -425,7 +426,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
} }
} }
fun instr(opcode: Opcode, arg: Value? = null, arg2: Value? = null, callLabel: String? = null, callLabel2: String? = null) { fun instr(opcode: Opcode, arg: RuntimeValue? = null, arg2: RuntimeValue? = null, callLabel: String? = null, callLabel2: String? = null) {
currentBlock.instructions.add(Instruction(opcode, arg, arg2, callLabel, callLabel2)) currentBlock.instructions.add(Instruction(opcode, arg, arg2, callLabel, callLabel2))
} }
@ -447,8 +448,8 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
currentBlock.memoryPointers[name] = Pair(address, datatype) currentBlock.memoryPointers[name] = Pair(address, datatype)
} }
fun newBlock(scopedname: String, shortname: String, address: Int?, options: Set<String>) { fun newBlock(name: String, address: Int?, options: Set<String>) {
currentBlock = ProgramBlock(scopedname, shortname, address, force_output="force_output" in options) currentBlock = ProgramBlock(name, address, force_output="force_output" in options)
blocks.add(currentBlock) blocks.add(currentBlock)
} }
@ -460,29 +461,44 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
out.println("%end_memory") out.println("%end_memory")
out.println("%heap") out.println("%heap")
heap.allEntries().forEach { heap.allEntries().forEach {
out.print("${it.key} ${it.value.type.name.toLowerCase()} ")
when { when {
it.value.str!=null -> it.value.str!=null ->
out.println("${it.key} ${it.value.type.toString().toLowerCase()} \"${escape(it.value.str!!)}\"") out.println("\"${escape(it.value.str!!)}\"")
it.value.array!=null -> it.value.array!=null -> {
out.println("${it.key} ${it.value.type.toString().toLowerCase()} ${it.value.array!!.toList()}") // this array can contain both normal integers, and pointer values
val arrayvalues = it.value.array!!.map { av ->
when {
av.integer!=null -> av.integer.toString()
av.addressOf!=null -> {
if(av.addressOf.scopedname==null)
throw CompilerException("AddressOf scopedname should have been set")
else
"&${av.addressOf.scopedname}"
}
else -> throw CompilerException("weird array value")
}
}
out.println(arrayvalues)
}
it.value.doubleArray!=null -> it.value.doubleArray!=null ->
out.println("${it.key} ${it.value.type.toString().toLowerCase()} ${it.value.doubleArray!!.toList()}") out.println(it.value.doubleArray!!.toList())
else -> throw CompilerException("invalid heap entry $it") else -> throw CompilerException("invalid heap entry $it")
} }
} }
out.println("%end_heap") out.println("%end_heap")
for(blk in blocks) { for(blk in blocks) {
out.println("\n%block ${blk.scopedname} ${blk.address?.toString(16) ?: ""}") out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
out.println("%variables") out.println("%variables")
for(variable in blk.variables) { for(variable in blk.variables) {
val valuestr = variable.value.toString() val valuestr = variable.value.toString()
out.println("${variable.key} ${variable.value.type.toString().toLowerCase()} $valuestr") out.println("${variable.key} ${variable.value.type.name.toLowerCase()} $valuestr")
} }
out.println("%end_variables") out.println("%end_variables")
out.println("%memorypointers") out.println("%memorypointers")
for(iconst in blk.memoryPointers) { for(iconst in blk.memoryPointers) {
out.println("${iconst.key} ${iconst.value.second.toString().toLowerCase()} uw:${iconst.value.first.toString(16)}") out.println("${iconst.key} ${iconst.value.second.name.toLowerCase()} uw:${iconst.value.first.toString(16)}")
} }
out.println("%end_memorypointers") out.println("%end_memorypointers")
out.println("%instructions") out.println("%instructions")

View File

@ -58,10 +58,6 @@ enum class Opcode {
DIV_F, DIV_F,
REMAINDER_UB, // signed remainder is undefined/unimplemented REMAINDER_UB, // signed remainder is undefined/unimplemented
REMAINDER_UW, // signed remainder is undefined/unimplemented REMAINDER_UW, // signed remainder is undefined/unimplemented
POW_UB,
POW_B,
POW_UW,
POW_W,
POW_F, POW_F,
NEG_B, NEG_B,
NEG_W, NEG_W,
@ -244,7 +240,6 @@ enum class Opcode {
JZW, // branch if value is zero (word) JZW, // branch if value is zero (word)
JNZW, // branch if value is not zero (word) JNZW, // branch if value is not zero (word)
// subroutines // subroutines
CALL, CALL,
RETURN, RETURN,
@ -257,6 +252,7 @@ enum class Opcode {
CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
SEI, // set irq-disable status flag SEI, // set irq-disable status flag
CLI, // clear irq-disable status flag CLI, // clear irq-disable status flag
CARRY_TO_A, // load var/register A with carry status bit
RSAVE, // save all internal registers and status flags RSAVE, // save all internal registers and status flags
RSAVEX, // save just X (the evaluation stack pointer) RSAVEX, // save just X (the evaluation stack pointer)
RRESTORE, // restore all internal registers and status flags RRESTORE, // restore all internal registers and status flags
@ -266,7 +262,8 @@ enum class Opcode {
BREAKPOINT, // breakpoint BREAKPOINT, // breakpoint
TERMINATE, // end the program TERMINATE, // end the program
LINE, // track source file line number LINE, // track source file line number
INLINE_ASSEMBLY // container to hold inline raw assembly code INLINE_ASSEMBLY, // container to hold inline raw assembly code
INCLUDE_FILE // directive to include a file at this position in the memory of the program
} }
val opcodesWithVarArgument = setOf( val opcodesWithVarArgument = setOf(

View File

@ -1,478 +0,0 @@
package prog8.compiler.intermediate
import prog8.ast.*
import java.lang.Exception
import kotlin.math.abs
import kotlin.math.pow
class ValueException(msg: String?) : Exception(msg)
class Value(val type: DataType, numericvalueOrHeapId: Number) {
private var byteval: Short? = null
private var wordval: Int? = null
private var floatval: Double? = null
var heapId: Int = -1
private set
val asBooleanValue: Boolean
init {
when(type) {
DataType.UBYTE -> {
if(numericvalueOrHeapId.toInt() !in 0..255)
throw ValueException("value out of range: $numericvalueOrHeapId")
byteval = numericvalueOrHeapId.toShort()
asBooleanValue = byteval != (0.toShort())
}
DataType.BYTE -> {
if(numericvalueOrHeapId.toInt() !in -128..127)
throw ValueException("value out of range: $numericvalueOrHeapId")
byteval = numericvalueOrHeapId.toShort()
asBooleanValue = byteval != (0.toShort())
}
DataType.UWORD -> {
if(numericvalueOrHeapId.toInt() !in 0..65535)
throw ValueException("value out of range: $numericvalueOrHeapId")
wordval = numericvalueOrHeapId.toInt()
asBooleanValue = wordval != 0
}
DataType.WORD -> {
if(numericvalueOrHeapId.toInt() !in -32768..32767)
throw ValueException("value out of range: $numericvalueOrHeapId")
wordval = numericvalueOrHeapId.toInt()
asBooleanValue = wordval != 0
}
DataType.FLOAT -> {
floatval = numericvalueOrHeapId.toDouble()
asBooleanValue = floatval != 0.0
}
else -> {
if(numericvalueOrHeapId !is Int || numericvalueOrHeapId<0)
throw ValueException("for non-numeric types, the value should be a integer heapId >= 0")
heapId = numericvalueOrHeapId
asBooleanValue=true
}
}
}
override fun toString(): String {
return when(type) {
DataType.UBYTE -> "ub:%02x".format(byteval)
DataType.BYTE -> {
if(byteval!!<0)
"b:-%02x".format(abs(byteval!!.toInt()))
else
"b:%02x".format(byteval)
}
DataType.UWORD -> "uw:%04x".format(wordval)
DataType.WORD -> {
if(wordval!!<0)
"w:-%04x".format(abs(wordval!!))
else
"w:%04x".format(wordval)
}
DataType.FLOAT -> "f:$floatval"
else -> "heap:$heapId"
}
}
fun numericValue(): Number {
return when(type) {
DataType.UBYTE, DataType.BYTE -> byteval!!
DataType.UWORD, DataType.WORD -> wordval!!
DataType.FLOAT -> floatval!!
else -> throw ValueException("invalid datatype for numeric value: $type")
}
}
fun integerValue(): Int {
return when(type) {
DataType.UBYTE, DataType.BYTE -> byteval!!.toInt()
DataType.UWORD, DataType.WORD -> wordval!!
DataType.FLOAT -> throw ValueException("float to integer loss of precision")
else -> throw ValueException("invalid datatype for integer value: $type")
}
}
override fun hashCode(): Int {
val bh = byteval?.hashCode() ?: 0x10001234
val wh = wordval?.hashCode() ?: 0x01002345
val fh = floatval?.hashCode() ?: 0x00103456
return bh xor wh xor fh xor heapId.hashCode() xor type.hashCode()
}
override fun equals(other: Any?): Boolean {
if(other==null || other !is Value)
return false
if(type==other.type)
return if (type in IterableDatatypes) heapId==other.heapId else compareTo(other)==0
return compareTo(other)==0 // note: datatype doesn't matter
}
operator fun compareTo(other: Value): Int {
return if (type in NumericDatatypes && other.type in NumericDatatypes)
numericValue().toDouble().compareTo(other.numericValue().toDouble())
else throw ValueException("comparison can only be done between two numeric values")
}
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): Value {
if(leftDt!=rightDt)
throw ValueException("left and right datatypes are not the same")
if(result.toDouble() < 0 ) {
return when(leftDt) {
DataType.UBYTE, DataType.UWORD -> {
// storing a negative number in an unsigned one is done by storing the 2's complement instead
val number = abs(result.toDouble().toInt())
if(leftDt==DataType.UBYTE)
Value(DataType.UBYTE, (number xor 255) + 1)
else
Value(DataType.UBYTE, (number xor 65535) + 1)
}
DataType.BYTE -> Value(DataType.BYTE, result.toInt())
DataType.WORD -> Value(DataType.WORD, result.toInt())
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw ValueException("$op on non-numeric type")
}
}
return when(leftDt) {
DataType.UBYTE -> Value(DataType.UBYTE, result.toInt() and 255)
DataType.BYTE -> Value(DataType.BYTE, result.toInt())
DataType.UWORD -> Value(DataType.UWORD, result.toInt() and 65535)
DataType.WORD -> Value(DataType.WORD, result.toInt())
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw ValueException("$op on non-numeric type")
}
}
fun add(other: Value): Value {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ValueException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() + v2.toDouble()
return arithResult(type, result, other.type, "add")
}
fun sub(other: Value): Value {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ValueException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() - v2.toDouble()
return arithResult(type, result, other.type, "sub")
}
fun mul(other: Value): Value {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ValueException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() * v2.toDouble()
return arithResult(type, result, other.type, "mul")
}
fun div(other: Value): Value {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
throw ValueException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
if(v2.toDouble()==0.0) {
when (type) {
DataType.UBYTE -> return Value(DataType.UBYTE, 255)
DataType.BYTE -> return Value(DataType.BYTE, 127)
DataType.UWORD -> return Value(DataType.UWORD, 65535)
DataType.WORD -> return Value(DataType.WORD, 32767)
else -> {}
}
}
val result = v1.toDouble() / v2.toDouble()
// NOTE: integer division returns integer result!
return when(type) {
DataType.UBYTE -> Value(DataType.UBYTE, result)
DataType.BYTE -> Value(DataType.BYTE, result)
DataType.UWORD -> Value(DataType.UWORD, result)
DataType.WORD -> Value(DataType.WORD, result)
DataType.FLOAT -> Value(DataType.FLOAT, result)
else -> throw ValueException("div on non-numeric type")
}
}
fun remainder(other: Value): Value? {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() % v2.toDouble()
return arithResult(type, result, other.type, "remainder")
}
fun pow(other: Value): Value {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble())
return arithResult(type, result, other.type,"pow")
}
fun shl(): Value {
val v = integerValue()
return when (type) {
DataType.UBYTE -> return Value(type, (v shl 1) and 255)
DataType.BYTE -> {
if(v<0)
Value(type, -((-v shl 1) and 255))
else
Value(type, (v shl 1) and 255)
}
DataType.UWORD -> return Value(type, (v shl 1) and 65535)
DataType.WORD -> {
if(v<0)
Value(type, -((-v shl 1) and 65535))
else
Value(type, (v shl 1) and 65535)
}
else -> throw ValueException("invalid type for shl: $type")
}
}
fun shr(): Value {
val v = integerValue()
return when(type){
DataType.UBYTE -> Value(type, (v ushr 1) and 255)
DataType.BYTE -> Value(type, v shr 1)
DataType.UWORD -> Value(type, (v ushr 1) and 65535)
DataType.WORD -> Value(type, v shr 1)
else -> throw ValueException("invalid type for shr: $type")
}
}
fun rol(carry: Boolean): Pair<Value, Boolean> {
// 9 or 17 bit rotate left (with carry))
return when(type) {
DataType.UBYTE -> {
val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
Pair(Value(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD -> {
val v = wordval!!
val newCarry = (v and 0x8000) != 0
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
Pair(Value(DataType.UWORD, newval), newCarry)
}
else -> throw ValueException("rol can only work on byte/word")
}
}
fun ror(carry: Boolean): Pair<Value, Boolean> {
// 9 or 17 bit rotate right (with carry)
return when(type) {
DataType.UBYTE -> {
val v = byteval!!.toInt()
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
Pair(Value(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD -> {
val v = wordval!!
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
Pair(Value(DataType.UWORD, newval), newCarry)
}
else -> throw ValueException("ror2 can only work on byte/word")
}
}
fun rol2(): Value {
// 8 or 16 bit rotate left
return when(type) {
DataType.UBYTE -> {
val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry
Value(DataType.UBYTE, newval)
}
DataType.UWORD -> {
val v = wordval!!
val carry = (v and 0x8000) ushr 15
val newval = (v and 0x7fff shl 1) or carry
Value(DataType.UWORD, newval)
}
else -> throw ValueException("rol2 can only work on byte/word")
}
}
fun ror2(): Value {
// 8 or 16 bit rotate right
return when(type) {
DataType.UBYTE -> {
val v = byteval!!.toInt()
val carry = v and 1 shl 7
val newval = (v ushr 1) or carry
Value(DataType.UBYTE, newval)
}
DataType.UWORD -> {
val v = wordval!!
val carry = v and 1 shl 15
val newval = (v ushr 1) or carry
Value(DataType.UWORD, newval)
}
else -> throw ValueException("ror2 can only work on byte/word")
}
}
fun neg(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, -(byteval!!))
DataType.WORD -> Value(DataType.WORD, -(wordval!!))
DataType.FLOAT -> Value(DataType.FLOAT, -(floatval)!!)
else -> throw ValueException("neg can only work on byte/word/float")
}
}
fun abs(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, abs(byteval!!.toInt()))
DataType.WORD -> Value(DataType.WORD, abs(wordval!!))
DataType.FLOAT -> Value(DataType.FLOAT, abs(floatval!!))
else -> throw ValueException("abs can only work on byte/word/float")
}
}
fun bitand(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 and v2
return Value(type, result)
}
fun bitor(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 or v2
return Value(type, result)
}
fun bitxor(other: Value): Value {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 xor v2
return Value(type, result)
}
fun and(other: Value) = Value(DataType.UBYTE, if (this.asBooleanValue && other.asBooleanValue) 1 else 0)
fun or(other: Value) = Value(DataType.UBYTE, if (this.asBooleanValue || other.asBooleanValue) 1 else 0)
fun xor(other: Value) = Value(DataType.UBYTE, if (this.asBooleanValue xor other.asBooleanValue) 1 else 0)
fun not() = Value(DataType.UBYTE, if (this.asBooleanValue) 0 else 1)
fun inv(): Value {
return when(type) {
DataType.UBYTE -> Value(DataType.UBYTE, byteval!!.toInt().inv() and 255)
DataType.UWORD -> Value(DataType.UWORD, wordval!!.inv() and 65535)
else -> throw ValueException("inv can only work on byte/word")
}
}
fun inc(): Value {
return when(type) {
DataType.UBYTE -> Value(DataType.UBYTE, (byteval!! + 1) and 255)
DataType.UWORD -> Value(DataType.UWORD, (wordval!! + 1) and 65535)
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! + 1)
else -> throw ValueException("inc can only work on byte/word/float")
}
}
fun dec(): Value {
return when(type) {
DataType.UBYTE -> Value(DataType.UBYTE, (byteval!! - 1) and 255)
DataType.UWORD -> Value(DataType.UWORD, (wordval!! - 1) and 65535)
DataType.FLOAT -> Value(DataType.FLOAT, floatval!! - 1)
else -> throw ValueException("dec can only work on byte/word/float")
}
}
fun msb(): Value {
return when(type) {
DataType.UBYTE, DataType.BYTE -> Value(DataType.UBYTE, 0)
DataType.UWORD, DataType.WORD -> Value(DataType.UBYTE, wordval!! ushr 8 and 255)
else -> throw ValueException("msb can only work on (u)byte/(u)word")
}
}
fun cast(targetType: DataType): Value {
return when (type) {
DataType.UBYTE -> {
when (targetType) {
DataType.UBYTE -> this
DataType.BYTE -> {
if(byteval!!<=127)
Value(DataType.BYTE, byteval!!)
else
Value(DataType.BYTE, -(256-byteval!!))
}
DataType.UWORD -> Value(DataType.UWORD, numericValue())
DataType.WORD -> Value(DataType.WORD, numericValue())
DataType.FLOAT -> Value(DataType.FLOAT, numericValue())
else -> throw ValueException("invalid type cast from $type to $targetType")
}
}
DataType.BYTE -> {
when (targetType) {
DataType.BYTE -> this
DataType.UBYTE -> Value(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> Value(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> Value(DataType.WORD, integerValue())
DataType.FLOAT -> Value(DataType.FLOAT, numericValue())
else -> throw ValueException("invalid type cast from $type to $targetType")
}
}
DataType.UWORD -> {
when (targetType) {
DataType.BYTE, DataType.UBYTE -> Value(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> this
DataType.WORD -> {
if(integerValue()<=32767)
Value(DataType.WORD, integerValue())
else
Value(DataType.WORD, -(65536-integerValue()))
}
DataType.FLOAT -> Value(DataType.FLOAT, numericValue())
else -> throw ValueException("invalid type cast from $type to $targetType")
}
}
DataType.WORD -> {
when (targetType) {
DataType.BYTE, DataType.UBYTE -> Value(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> Value(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> this
DataType.FLOAT -> Value(DataType.FLOAT, numericValue())
else -> throw ValueException("invalid type cast from $type to $targetType")
}
}
DataType.FLOAT -> {
when (targetType) {
DataType.BYTE -> {
val integer=numericValue().toInt()
if(integer in -128..127)
Value(DataType.BYTE, integer)
else
throw ValueException("overflow when casting float to byte: $this")
}
DataType.UBYTE -> Value(DataType.UBYTE, numericValue().toInt() and 255)
DataType.UWORD -> Value(DataType.UWORD, numericValue().toInt() and 65535)
DataType.WORD -> {
val integer=numericValue().toInt()
if(integer in -32768..32767)
Value(DataType.WORD, integer)
else
throw ValueException("overflow when casting float to word: $this")
}
DataType.FLOAT -> this
else -> throw ValueException("invalid type cast from $type to $targetType")
}
}
else -> throw ValueException("invalid type cast from $type to $targetType")
}
}
}

View File

@ -4,6 +4,7 @@ package prog8.compiler.target.c64
// possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles // possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles
import prog8.ast.* import prog8.ast.*
import prog8.compiler.RuntimeValue
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.intermediate.* import prog8.compiler.intermediate.*
import prog8.stackvm.Syscall import prog8.stackvm.Syscall
@ -16,20 +17,15 @@ import kotlin.math.abs
class AssemblyError(msg: String) : RuntimeException(msg) class AssemblyError(msg: String) : RuntimeException(msg)
// TODO: code generation for POW instruction class AsmGen(private val options: CompilationOptions, private val program: IntermediateProgram,
private val heap: HeapValues, private val zeropage: Zeropage) {
class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, val heap: HeapValues, val zeropage: Zeropage) {
private val globalFloatConsts = mutableMapOf<Double, String>() private val globalFloatConsts = mutableMapOf<Double, String>()
private val assemblyLines = mutableListOf<String>() private val assemblyLines = mutableListOf<String>()
private lateinit var block: IntermediateProgram.ProgramBlock private lateinit var block: IntermediateProgram.ProgramBlock
private var breakpointCounter = 0 private var breakpointCounter = 0
init { init {
// Because 64tass understands scoped names via .proc / .block, // Convert invalid label names (such as "<anon-1>") to something that's allowed.
// we'll strip the block prefix from all scoped names in the program.
// Also, convert invalid label names (such as "<<<anonymous-1>>>") to something that's allowed.
// Also have to do that for the variablesMarkedForZeropage!
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>() val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
for(block in program.blocks) { for(block in program.blocks) {
val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap() val newvars = block.variables.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
@ -45,14 +41,13 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
callLabel2 = if (it.callLabel2 != null) symname(it.callLabel2, block) else null) callLabel2 = if (it.callLabel2 != null) symname(it.callLabel2, block) else null)
} }
}.toMutableList() }.toMutableList()
val newConstants = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap() val newMempointers = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
val newblock = IntermediateProgram.ProgramBlock( val newblock = IntermediateProgram.ProgramBlock(
block.scopedname, block.name,
block.shortname,
block.address, block.address,
newinstructions, newinstructions,
newvars, newvars,
newConstants, newMempointers,
newlabels, newlabels,
force_output = block.force_output) force_output = block.force_output)
newblock.variablesMarkedForZeropage.clear() newblock.variablesMarkedForZeropage.clear()
@ -76,7 +71,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
} }
} }
fun compileToAssembly(): AssemblyProgram { fun compileToAssembly(optimize: Boolean): AssemblyProgram {
println("Generating assembly code from intermediate code... ") println("Generating assembly code from intermediate code... ")
assemblyLines.clear() assemblyLines.clear()
@ -84,9 +79,11 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
for(b in program.blocks) for(b in program.blocks)
block2asm(b) block2asm(b)
var optimizationsDone=1 if(optimize) {
while(optimizationsDone>0) { var optimizationsDone = 1
optimizationsDone = optimizeAssembly(assemblyLines) while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
} }
File("${program.name}.asm").printWriter().use { File("${program.name}.asm").printWriter().use {
@ -107,29 +104,20 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
} }
// convert a fully scoped name (defined in the given block) to a valid assembly symbol name
private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String { private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String {
if(' ' in scoped) if(' ' in scoped)
return scoped return scoped
val blockLocal: Boolean val blockLocal: Boolean
var name = when { var name = if (block!=null && scoped.startsWith("${block.name}.")) {
block==null -> { blockLocal = true
blockLocal=true scoped.substring(block.name.length+1)
scoped
}
scoped.startsWith("${block.shortname}.") -> {
blockLocal = true
scoped.substring(block.shortname.length+1)
}
scoped.startsWith("block.") -> {
blockLocal = false
scoped
}
else -> {
blockLocal = false
scoped
}
} }
name = name.replace("<<<", "prog8_").replace(">>>", "") else {
blockLocal = false
scoped
}
name = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
if(name=="-") if(name=="-")
return "-" return "-"
if(blockLocal) if(blockLocal)
@ -200,7 +188,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
for(block in program.blocks) { for(block in program.blocks) {
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name==initvarsSubName } as? LabelInstr val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name==initvarsSubName } as? LabelInstr
if(initVarsLabel!=null) if(initVarsLabel!=null)
out(" jsr ${block.scopedname}.${initVarsLabel.name}") out(" jsr ${block.name}.${initVarsLabel.name}")
} }
out(" clc") out(" clc")
when(zeropage.exitProgramStrategy) { when(zeropage.exitProgramStrategy) {
@ -223,9 +211,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
private fun block2asm(blk: IntermediateProgram.ProgramBlock) { private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
block = blk block = blk
out("\n; ---- block: '${block.shortname}' ----") out("\n; ---- block: '${block.name}' ----")
if(!blk.force_output) if(!blk.force_output)
out("${block.shortname}\t.proc\n") out("${block.name}\t.proc\n")
if(block.address!=null) { if(block.address!=null) {
out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'") out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'")
out("* = ${block.address?.toHex()}") out("* = ${block.address?.toHex()}")
@ -233,14 +221,14 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
// deal with zeropage variables // deal with zeropage variables
for(variable in blk.variables) { for(variable in blk.variables) {
val sym = symname(blk.scopedname+"."+variable.key, null) val sym = symname(blk.name+"."+variable.key, null)
val zpVar = program.allocatedZeropageVariables[sym] val zpVar = program.allocatedZeropageVariables[sym]
if(zpVar==null) { if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space) // This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) { if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) {
try { try {
val address = zeropage.allocate(sym, variable.value.type, null) val address = zeropage.allocate(sym, variable.value.type, null)
out("${variable.key} = $address\t; zp ${variable.value.type}") out("${variable.key} = $address\t; auto zp ${variable.value.type}")
// make sure we add the var to the set of zpvars for this block // make sure we add the var to the set of zpvars for this block
blk.variablesMarkedForZeropage.add(variable.key) blk.variablesMarkedForZeropage.add(variable.key)
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type) program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
@ -294,18 +282,15 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
DataType.UWORD -> out("${v.first}\t.word 0") DataType.UWORD -> out("${v.first}\t.word 0")
DataType.WORD -> out("${v.first}\t.sint 0") DataType.WORD -> out("${v.first}\t.sint 0")
DataType.FLOAT -> out("${v.first}\t.byte 0,0,0,0,0 ; float") DataType.FLOAT -> out("${v.first}\t.byte 0,0,0,0,0 ; float")
DataType.STR, DataType.STR, DataType.STR_S -> {
DataType.STR_P, val rawStr = heap.get(v.second.heapId!!).str!!
DataType.STR_S,
DataType.STR_PS -> {
val rawStr = heap.get(v.second.heapId).str!!
val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') } val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') }
out("${v.first}\t; ${v.second.type} \"${escape(rawStr)}\"") out("${v.first}\t; ${v.second.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
for (chunk in bytes.chunked(16)) for (chunk in bytes.chunked(16))
out(" .byte " + chunk.joinToString()) out(" .byte " + chunk.joinToString())
} }
DataType.ARRAY_UB -> { DataType.ARRAY_UB -> {
// unsigned integer byte arrayspec // unsigned integer byte arraysize
val data = makeArrayFillDataUnsigned(v.second) val data = makeArrayFillDataUnsigned(v.second)
if (data.size <= 16) if (data.size <= 16)
out("${v.first}\t.byte ${data.joinToString()}") out("${v.first}\t.byte ${data.joinToString()}")
@ -316,7 +301,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
} }
} }
DataType.ARRAY_B -> { DataType.ARRAY_B -> {
// signed integer byte arrayspec // signed integer byte arraysize
val data = makeArrayFillDataSigned(v.second) val data = makeArrayFillDataSigned(v.second)
if (data.size <= 16) if (data.size <= 16)
out("${v.first}\t.char ${data.joinToString()}") out("${v.first}\t.char ${data.joinToString()}")
@ -327,7 +312,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
} }
} }
DataType.ARRAY_UW -> { DataType.ARRAY_UW -> {
// unsigned word arrayspec // unsigned word arraysize
val data = makeArrayFillDataUnsigned(v.second) val data = makeArrayFillDataUnsigned(v.second)
if (data.size <= 16) if (data.size <= 16)
out("${v.first}\t.word ${data.joinToString()}") out("${v.first}\t.word ${data.joinToString()}")
@ -338,7 +323,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
} }
} }
DataType.ARRAY_W -> { DataType.ARRAY_W -> {
// signed word arrayspec // signed word arraysize
val data = makeArrayFillDataSigned(v.second) val data = makeArrayFillDataSigned(v.second)
if (data.size <= 16) if (data.size <= 16)
out("${v.first}\t.sint ${data.joinToString()}") out("${v.first}\t.sint ${data.joinToString()}")
@ -349,8 +334,8 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
} }
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
// float arrayspec // float arraysize
val array = heap.get(v.second.heapId).doubleArray!! val array = heap.get(v.second.heapId!!).doubleArray!!
val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) } val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) }
out(v.first) out(v.first)
for(f in array.zip(floatFills)) for(f in array.zip(floatFills))
@ -361,48 +346,48 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
} }
private fun encodeStr(str: String, dt: DataType): List<Short> { private fun encodeStr(str: String, dt: DataType): List<Short> {
when(dt) { return when(dt) {
DataType.STR -> { DataType.STR -> {
val bytes = Petscii.encodePetscii(str, true) val bytes = Petscii.encodePetscii(str, true)
return bytes.plus(0) bytes.plus(0)
}
DataType.STR_P -> {
val result = listOf(str.length.toShort())
val bytes = Petscii.encodePetscii(str, true)
return result.plus(bytes)
} }
DataType.STR_S -> { DataType.STR_S -> {
val bytes = Petscii.encodeScreencode(str, true) val bytes = Petscii.encodeScreencode(str, true)
return bytes.plus(0) bytes.plus(0)
}
DataType.STR_PS -> {
val result = listOf(str.length.toShort())
val bytes = Petscii.encodeScreencode(str, true)
return result.plus(bytes)
} }
else -> throw AssemblyError("invalid str type") else -> throw AssemblyError("invalid str type")
} }
} }
private fun makeArrayFillDataUnsigned(value: Value): List<String> { private fun makeArrayFillDataUnsigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId).array!! val array = heap.get(value.heapId!!).array!!
return if (value.type == DataType.ARRAY_UB || value.type == DataType.ARRAY_UW) return when {
array.map { "$"+it.toString(16).padStart(2, '0') } value.type==DataType.ARRAY_UB ->
else // byte array can never contain pointer-to types, so treat values as all integers
throw AssemblyError("invalid arrayspec type") array.map { "$"+it.integer!!.toString(16).padStart(2, '0') }
value.type==DataType.ARRAY_UW -> array.map {
when {
it.integer!=null -> "$"+it.integer.toString(16).padStart(2, '0')
it.addressOf!=null -> symname(it.addressOf.scopedname!!, block)
else -> throw AssemblyError("weird type in array")
}
}
else -> throw AssemblyError("invalid arraysize type")
}
} }
private fun makeArrayFillDataSigned(value: Value): List<String> { private fun makeArrayFillDataSigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId).array!! val array = heap.get(value.heapId!!).array!!
// note: array of signed value can never contain pointer-to type, so simply process values as being all integers
return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W) { return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W) {
array.map { array.map {
if(it>=0) if(it.integer!!>=0)
"$"+it.toString(16).padStart(2, '0') "$"+it.integer.toString(16).padStart(2, '0')
else else
"-$"+abs(it).toString(16).padStart(2, '0') "-$"+abs(it.integer).toString(16).padStart(2, '0')
} }
} }
else throw AssemblyError("invalid arrayspec type") else throw AssemblyError("invalid arraysize type")
} }
private fun instr2asm(ins: List<Instruction>): Int { private fun instr2asm(ins: List<Instruction>): Int {
@ -434,19 +419,16 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
} }
} }
private fun getFloatConst(value: Value): String = private fun getFloatConst(value: RuntimeValue): String =
globalFloatConsts[value.numericValue().toDouble()] globalFloatConsts[value.numericValue().toDouble()]
?: throw AssemblyError("should have a global float const for number $value") ?: throw AssemblyError("should have a global float const for number $value")
private fun simpleInstr2Asm(ins: Instruction): String? { private fun simpleInstr2Asm(ins: Instruction): String? {
// a label 'instruction' is simply translated into a asm label // a label 'instruction' is simply translated into a asm label
if(ins is LabelInstr) { if(ins is LabelInstr) {
if(ins.name.startsWith("block."))
return ""
val labelresult = val labelresult =
if(ins.name.startsWith("${block.shortname}.")) if(ins.name.startsWith("${block.name}."))
ins.name.substring(block.shortname.length+1) ins.name.substring(block.name.length+1)
else else
ins.name ins.name
return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult
@ -463,6 +445,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.CLC -> " clc" Opcode.CLC -> " clc"
Opcode.SEI -> " sei" Opcode.SEI -> " sei"
Opcode.CLI -> " cli" Opcode.CLI -> " cli"
Opcode.CARRY_TO_A -> " lda #0 | adc #0"
Opcode.JUMP -> { Opcode.JUMP -> {
if(ins.callLabel!=null) if(ins.callLabel!=null)
" jmp ${ins.callLabel}" " jmp ${ins.callLabel}"
@ -490,7 +473,12 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.DISCARD_BYTE -> " inx" Opcode.DISCARD_BYTE -> " inx"
Opcode.DISCARD_WORD -> " inx" Opcode.DISCARD_WORD -> " inx"
Opcode.DISCARD_FLOAT -> " inx | inx | inx" Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel ?: "") // All of the inline assembly is stored in the calllabel property. the '@inline@' is a special marker to process it. Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to process it.
Opcode.INCLUDE_FILE -> {
val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}"
val length = if(ins.arg2==null) "" else ", ${ins.arg2.integerValue()}"
" .binary \"${ins.callLabel}\" $offset $length"
}
Opcode.SYSCALL -> { Opcode.SYSCALL -> {
if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr }) if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr })
throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}") throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}")
@ -514,9 +502,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Syscall.FUNC_ALL_F, Syscall.FUNC_ALL_F,
Syscall.FUNC_MAX_F, Syscall.FUNC_MAX_F,
Syscall.FUNC_MIN_F, Syscall.FUNC_MIN_F,
Syscall.FUNC_AVG_F, Syscall.FUNC_SUM_F -> " jsr c64flt.${call.name.toLowerCase()}"
Syscall.FUNC_SUM_F -> " jsr c64flt.${call.toString().toLowerCase()}" null -> ""
else -> " jsr prog8_lib.${call.toString().toLowerCase()}" else -> " jsr prog8_lib.${call.name.toLowerCase()}"
} }
} }
Opcode.BREAKPOINT -> { Opcode.BREAKPOINT -> {
@ -757,6 +745,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.ABS_B -> " jsr prog8_lib.abs_b" Opcode.ABS_B -> " jsr prog8_lib.abs_b"
Opcode.ABS_W -> " jsr prog8_lib.abs_w" Opcode.ABS_W -> " jsr prog8_lib.abs_w"
Opcode.ABS_F -> " jsr c64flt.abs_f" Opcode.ABS_F -> " jsr c64flt.abs_f"
Opcode.POW_F -> " jsr c64flt.pow_f"
Opcode.INV_BYTE -> { Opcode.INV_BYTE -> {
""" """
lda ${(ESTACK_LO + 1).toHex()},x lda ${(ESTACK_LO + 1).toHex()},x
@ -887,30 +876,20 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.IDIV_W -> " jsr prog8_lib.idiv_w" Opcode.IDIV_W -> " jsr prog8_lib.idiv_w"
Opcode.IDIV_UW -> " jsr prog8_lib.idiv_uw" Opcode.IDIV_UW -> " jsr prog8_lib.idiv_uw"
Opcode.AND_BYTE -> { Opcode.AND_BYTE -> " jsr prog8_lib.and_b"
""" Opcode.OR_BYTE -> " jsr prog8_lib.or_b"
lda ${(ESTACK_LO + 2).toHex()},x Opcode.XOR_BYTE -> " jsr prog8_lib.xor_b"
and ${(ESTACK_LO + 1).toHex()},x Opcode.AND_WORD -> " jsr prog8_lib.and_w"
inx Opcode.OR_WORD -> " jsr prog8_lib.or_w"
sta ${(ESTACK_LO + 1).toHex()},x Opcode.XOR_WORD -> " jsr prog8_lib.xor_w"
"""
} Opcode.BITAND_BYTE -> " jsr prog8_lib.bitand_b"
Opcode.OR_BYTE -> { Opcode.BITOR_BYTE -> " jsr prog8_lib.bitor_b"
""" Opcode.BITXOR_BYTE -> " jsr prog8_lib.bitxor_b"
lda ${(ESTACK_LO + 2).toHex()},x Opcode.BITAND_WORD -> " jsr prog8_lib.bitand_w"
ora ${(ESTACK_LO + 1).toHex()},x Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
inx Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
sta ${(ESTACK_LO + 1).toHex()},x
"""
}
Opcode.XOR_BYTE -> {
"""
lda ${(ESTACK_LO + 2).toHex()},x
eor ${(ESTACK_LO + 1).toHex()},x
inx
sta ${(ESTACK_LO + 1).toHex()},x
"""
}
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub" Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw" Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
@ -960,23 +939,21 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
if(mulIns.opcode == Opcode.MUL_B || mulIns.opcode==Opcode.MUL_UB) { if(mulIns.opcode == Opcode.MUL_B || mulIns.opcode==Opcode.MUL_UB) {
if(amount in setOf(0,1,2,4,8,16,32,64,128,256)) if(amount in setOf(0,1,2,4,8,16,32,64,128,256))
throw AssemblyError("multiplication by power of 2 should have been converted into a left shift instruction already") printWarning("multiplication by power of 2 should have been optimized into a left shift instruction: $mulIns $amount")
if(amount in setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40)) if(amount in setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40))
return " jsr math.mul_byte_$amount" return " jsr math.mul_byte_$amount"
if(mulIns.opcode == Opcode.MUL_B && amount in setOf(-3,-5,-6,-7,-9,-10,-11,-12,-13,-14,-15,-20,-25,-40)) if(mulIns.opcode == Opcode.MUL_B && amount in setOf(-3,-5,-6,-7,-9,-10,-11,-12,-13,-14,-15,-20,-25,-40))
return " jsr prog8_lib.neg_b | jsr math.mul_byte_${-amount}" return " jsr prog8_lib.neg_b | jsr math.mul_byte_${-amount}"
} }
else if(mulIns.opcode == Opcode.MUL_UW) { else if(mulIns.opcode == Opcode.MUL_UW) {
if(amount in setOf(0,1,2,4,8,16,32,64,128,256)) if(amount in setOf(0,1,2,4,8,16,32,64,128,256))
throw AssemblyError("multiplication by power of 2 should have been converted into a left shift instruction already") printWarning("multiplication by power of 2 should have been optimized into a left shift instruction: $mulIns $amount")
if(amount in setOf(3,5,6,7,9,10,12,15,20,25,40)) if(amount in setOf(3,5,6,7,9,10,12,15,20,25,40))
return " jsr math.mul_word_$amount" return " jsr math.mul_word_$amount"
} }
else if(mulIns.opcode == Opcode.MUL_W) { else if(mulIns.opcode == Opcode.MUL_W) {
if(amount in setOf(0,1,2,4,8,16,32,64,128,256)) if(amount in setOf(0,1,2,4,8,16,32,64,128,256))
throw AssemblyError("multiplication by power of 2 should have been converted into a left shift instruction already") printWarning("multiplication by power of 2 should have been optimized into a left shift instruction: $mulIns $amount")
if(amount in setOf(3,5,6,7,9,10,12,15,20,25,40)) if(amount in setOf(3,5,6,7,9,10,12,15,20,25,40))
return " jsr math.mul_word_$amount" return " jsr math.mul_word_$amount"
if(amount in setOf(-3,-5,-6,-7,-9,-10,-12,-15,-20,-25,-40)) if(amount in setOf(-3,-5,-6,-7,-9,-10,-12,-15,-20,-25,-40))
@ -1278,9 +1255,6 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
Opcode.ROR2_WORD -> { Opcode.ROR2_WORD -> {
AsmFragment(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+", 30) AsmFragment(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+", 30)
} }
// Opcode.SYSCALL -> {
// TODO("optimize SYSCALL $ins in-place on variable $variable")
// }
else -> null else -> null
} }
} }
@ -3327,44 +3301,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
null null
}, },
// 16 bit addition avoiding excessive stack usage // @todo optimize 8 and 16 bit adds and subs (avoid stack use altogether on most common operations)
// @todo optimize 8 and 16 bit adds and subs even more with longer asmpatterns (avoid stack use altogether on most common operations)
AsmPattern(listOf(Opcode.PUSH_VAR_WORD, Opcode.ADD_UW),
listOf(Opcode.PUSH_VAR_WORD, Opcode.ADD_W)) { segment ->
"""
clc
lda ${segment[0].callLabel}
adc ${(ESTACK_LO+1).toHex()},x
sta ${(ESTACK_LO+1).toHex()},x
lda ${segment[0].callLabel}+1
adc ${(ESTACK_HI+1).toHex()},x
sta ${(ESTACK_HI+1).toHex()},x
"""
},
AsmPattern(listOf(Opcode.PUSH_MEM_UW, Opcode.ADD_UW),
listOf(Opcode.PUSH_MEM_W, Opcode.ADD_W)) { segment ->
"""
clc
lda ${hexVal(segment[0])}
adc ${(ESTACK_LO + 1).toHex()},x
sta ${(ESTACK_LO + 1).toHex()},x
lda ${hexValPlusOne(segment[0])}
adc ${(ESTACK_HI + 1).toHex()},x
sta ${(ESTACK_HI + 1).toHex()},x
"""
},
AsmPattern(listOf(Opcode.PUSH_WORD, Opcode.ADD_UW),
listOf(Opcode.PUSH_WORD, Opcode.ADD_W)) { segment ->
"""
clc
lda #<${hexVal(segment[0])}
adc ${(ESTACK_LO+1).toHex()},x
sta ${(ESTACK_LO+1).toHex()},x
lda #>${hexVal(segment[0])}
adc ${(ESTACK_HI+1).toHex()},x
sta ${(ESTACK_HI+1).toHex()},x
"""
},
AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_B), listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_UB)) { segment -> AsmPattern(listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_B), listOf(Opcode.PUSH_VAR_BYTE, Opcode.CMP_UB)) { segment ->
// this pattern is encountered as part of the loop bound condition in for loops (var + cmp + jz/jnz) // this pattern is encountered as part of the loop bound condition in for loops (var + cmp + jz/jnz)

View File

@ -64,7 +64,7 @@ fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>
fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Int> { fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Int> {
// optimize sequential assignments of the same value to various targets (bytes, words, floats) // optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check... // the float one is the one that requires 2*7=14 lines of code to check...
// @todo a better place to do this is in the Compiler instead and work on opcodes, and never even create the inefficient asm... // @todo a better place to do this is in the Compiler instead and work on opcodes, and never even create the inefficient asm...
@ -86,7 +86,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
val thirdvalue = fifth.substring(4) val thirdvalue = fifth.substring(4)
val fourthvalue = sixth.substring(4) val fourthvalue = sixth.substring(4)
if(firstvalue==thirdvalue && secondvalue==fourthvalue) { if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines) // lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
removeLines.add(pair[4].index) removeLines.add(pair[4].index)
removeLines.add(pair[5].index) removeLines.add(pair[5].index)
} }
@ -96,7 +96,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
val firstvalue = first.substring(4) val firstvalue = first.substring(4)
val secondvalue = third.substring(4) val secondvalue = third.substring(4)
if(firstvalue==secondvalue) { if(firstvalue==secondvalue) {
// lda value / sta ? / lda same-value / sta ? -> remove second lda (third line) // lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
removeLines.add(pair[2].index) removeLines.add(pair[2].index)
} }
} }

View File

@ -183,4 +183,60 @@ object Charset {
val normalChars = scanChars(normalImg) val normalChars = scanChars(normalImg)
val shiftedChars = scanChars(shiftedImg) val shiftedChars = scanChars(shiftedImg)
private val coloredNormalChars = mutableMapOf<Short, Array<BufferedImage>>()
fun getColoredChar(screenCode: Short, color: Short): BufferedImage {
val colorIdx = (color % Colors.palette.size).toShort()
val chars = coloredNormalChars[colorIdx]
if(chars!=null)
return chars[screenCode.toInt()]
val coloredChars = mutableListOf<BufferedImage>()
val transparent = Color(0,0,0,0).rgb
val rgb = Colors.palette[colorIdx.toInt()].rgb
for(c in normalChars) {
val colored = c.copy()
for(y in 0 until colored.height)
for(x in 0 until colored.width) {
if(colored.getRGB(x, y)!=transparent) {
colored.setRGB(x, y, rgb)
}
}
coloredChars.add(colored)
}
coloredNormalChars[colorIdx] = coloredChars.toTypedArray()
return coloredNormalChars.getValue(colorIdx)[screenCode.toInt()]
}
}
private fun BufferedImage.copy(): BufferedImage {
val bcopy = BufferedImage(this.width, this.height, this.type)
val g = bcopy.graphics
g.drawImage(this, 0, 0, null)
g.dispose()
return bcopy
}
object Colors {
val palette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
)
} }

View File

@ -9,7 +9,7 @@ class Petscii {
// character tables used from https://github.com/dj51d/cbmcodecs // character tables used from https://github.com/dj51d/cbmcodecs
private val decodingPetsciiLowercase = arrayOf( private val decodingPetsciiLowercase = arrayOf(
'\ufffe', // 0x00 -> UNDEFINED '\u0000', // 0x00 -> \u0000
'\ufffe', // 0x01 -> UNDEFINED '\ufffe', // 0x01 -> UNDEFINED
'\ufffe', // 0x02 -> UNDEFINED '\ufffe', // 0x02 -> UNDEFINED
'\ufffe', // 0x03 -> UNDEFINED '\ufffe', // 0x03 -> UNDEFINED
@ -268,7 +268,7 @@ class Petscii {
) )
private val decodingPetsciiUppercase = arrayOf( private val decodingPetsciiUppercase = arrayOf(
'\ufffe', // 0x00 -> UNDEFINED '\u0000', // 0x00 -> \u0000
'\ufffe', // 0x01 -> UNDEFINED '\ufffe', // 0x01 -> UNDEFINED
'\ufffe', // 0x02 -> UNDEFINED '\ufffe', // 0x02 -> UNDEFINED
'\ufffe', // 0x03 -> UNDEFINED '\ufffe', // 0x03 -> UNDEFINED
@ -1055,11 +1055,12 @@ class Petscii {
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
return text.map { return text.map {
val petscii = lookup[it] val petscii = lookup[it]
if(petscii==null) { petscii?.toShort() ?: if(it=='\u0000')
val case = if(lowercase) "lower" else "upper" 0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}case Petscii character for '$it'") throw CharConversionException("no ${case}case Petscii character for '$it'")
} }
petscii.toShort()
} }
} }
@ -1072,11 +1073,12 @@ class Petscii {
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
return text.map{ return text.map{
val screencode = lookup[it] val screencode = lookup[it]
if(screencode==null) { screencode?.toShort() ?: if(it=='\u0000')
val case = if(lowercase) "lower" else "upper" 0.toShort()
else {
val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it'") throw CharConversionException("no ${case}Screencode character for '$it'")
} }
screencode.toShort()
} }
} }

View File

@ -2,11 +2,7 @@ package prog8.functions
import prog8.ast.* import prog8.ast.*
import prog8.compiler.CompilerException import prog8.compiler.CompilerException
import prog8.compiler.HeapValues import kotlin.math.*
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.log2
import kotlin.math.sin
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>) class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
@ -14,7 +10,7 @@ class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType
class FunctionSignature(val pure: Boolean, // does it have side effects? class FunctionSignature(val pure: Boolean, // does it have side effects?
val parameters: List<BuiltinFunctionParam>, val parameters: List<BuiltinFunctionParam>,
val returntype: DataType?, val returntype: DataType?,
val constExpressionFunc: ((args: List<IExpression>, position: Position, namespace: INameScope, heap: HeapValues) -> LiteralValue)? = null) val constExpressionFunc: ((args: List<IExpression>, position: Position, program: Program) -> LiteralValue)? = null)
val BuiltinFunctions = mapOf( val BuiltinFunctions = mapOf(
@ -26,38 +22,38 @@ val BuiltinFunctions = mapOf(
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null), "lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null), "lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
// these few have a return value depending on the argument(s): // these few have a return value depending on the argument(s):
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.max()!! }}, // type depends on args "max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.max()!! }}, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.min()!! }}, // type depends on args "min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.min()!! }}, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.sum() }}, // type depends on args "sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.sum() }}, // type depends on args
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument "abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
// normal functions follow: // normal functions follow:
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sin) }, "sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ), "sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ), "sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ), "sin16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ), "sin16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::cos) }, "cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ), "cos8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ), "cos8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ), "cos16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ), "cos16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::tan) }, "tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::atan) }, "atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::log) }, "ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, ::log2) }, "log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
// TODO: sqrt() should have integer versions too "sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sqrt) }, "sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toRadians) }, "rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toDegrees) }, "deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT, ::builtinAvg), "avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT, ::builtinAvg),
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::round) }, "round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::floor) }, "floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::ceil) }, "ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), DataType.UWORD, ::builtinLen), "any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.any { v -> v != 0.0} }},
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.any { v -> v != 0.0} }}, "all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.all { v -> v != 0.0} }},
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.all { v -> v != 0.0} }}, "lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x and 255 }}, "msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x ushr 8 and 255}},
"mkword" to FunctionSignature(true, listOf( "mkword" to FunctionSignature(true, listOf(
BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)), BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)),
BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword), BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
@ -70,19 +66,21 @@ val BuiltinFunctions = mapOf(
"clear_carry" to FunctionSignature(false, emptyList(), null), "clear_carry" to FunctionSignature(false, emptyList(), null),
"set_irqd" to FunctionSignature(false, emptyList(), null), "set_irqd" to FunctionSignature(false, emptyList(), null),
"clear_irqd" to FunctionSignature(false, emptyList(), null), "clear_irqd" to FunctionSignature(false, emptyList(), null),
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null), "swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
"memcopy" to FunctionSignature(false, listOf( "memcopy" to FunctionSignature(false, listOf(
BuiltinFunctionParam("from", IterableDatatypes + setOf(DataType.UWORD)), BuiltinFunctionParam("from", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("to", IterableDatatypes + setOf(DataType.UWORD)), BuiltinFunctionParam("to", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numbytes", IntegerDatatypes)), null), BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FunctionSignature(false, listOf( "memset" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)), BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)), BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
BuiltinFunctionParam("bytevalue", setOf(DataType.UBYTE, DataType.BYTE))), null), BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FunctionSignature(false, listOf( "memsetw" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)), BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)), BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null), BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", StringDatatypes)), DataType.UBYTE, ::builtinStrlen),
"vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null), "vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null), "vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null), "vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null),
@ -109,14 +107,14 @@ val BuiltinFunctions = mapOf(
) )
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope, heap: HeapValues): DataType? { fun builtinFunctionReturnType(function: String, args: List<IExpression>, program: Program): DataType? {
fun datatypeFromIterableArg(arglist: IExpression): DataType { fun datatypeFromIterableArg(arglist: IExpression): DataType {
if(arglist is LiteralValue) { if(arglist is LiteralValue) {
if(arglist.type==DataType.ARRAY_UB || arglist.type==DataType.ARRAY_UW || arglist.type==DataType.ARRAY_F) { if(arglist.type==DataType.ARRAY_UB || arglist.type==DataType.ARRAY_UW || arglist.type==DataType.ARRAY_F) {
val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace, heap)} val dt = arglist.arrayvalue!!.map {it.inferType(program)}
if(dt.any { it!=DataType.UBYTE && it!=DataType.UWORD && it!=DataType.FLOAT}) { if(dt.any { it!=DataType.UBYTE && it!=DataType.UWORD && it!=DataType.FLOAT}) {
throw FatalAstException("fuction $function only accepts arrayspec of numeric values") throw FatalAstException("fuction $function only accepts arraysize of numeric values")
} }
if(dt.any { it==DataType.FLOAT }) return DataType.FLOAT if(dt.any { it==DataType.FLOAT }) return DataType.FLOAT
if(dt.any { it==DataType.UWORD }) return DataType.UWORD if(dt.any { it==DataType.UWORD }) return DataType.UWORD
@ -124,16 +122,12 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
} }
} }
if(arglist is IdentifierReference) { if(arglist is IdentifierReference) {
val dt = arglist.resultingDatatype(namespace, heap) val dt = arglist.inferType(program)
return when(dt) { return when(dt) {
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT, in NumericDatatypes -> dt!!
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> dt in StringDatatypes -> dt!!
DataType.ARRAY_UB -> DataType.UBYTE in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!)
DataType.ARRAY_B -> DataType.BYTE else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
null -> throw FatalAstException("function '$function' requires one argument which is an iterable")
} }
} }
throw FatalAstException("function '$function' requires one argument which is an iterable") throw FatalAstException("function '$function' requires one argument which is an iterable")
@ -146,10 +140,10 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
return when (function) { return when (function) {
"abs" -> { "abs" -> {
val dt = args.single().resultingDatatype(namespace, heap) val dt = args.single().inferType(program)
when(dt) { when(dt) {
DataType.UBYTE, DataType.BYTE -> DataType.UBYTE in ByteDatatypes -> DataType.UBYTE
DataType.UWORD, DataType.WORD -> DataType.UWORD in WordDatatypes -> DataType.UWORD
DataType.FLOAT -> DataType.FLOAT DataType.FLOAT -> DataType.FLOAT
else -> throw FatalAstException("weird datatype passed to abs $dt") else -> throw FatalAstException("weird datatype passed to abs $dt")
} }
@ -157,27 +151,29 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
"max", "min" -> { "max", "min" -> {
val dt = datatypeFromIterableArg(args.single()) val dt = datatypeFromIterableArg(args.single())
when(dt) { when(dt) {
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> dt in NumericDatatypes -> dt
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.UBYTE in StringDatatypes -> DataType.UBYTE
DataType.ARRAY_UB -> DataType.UBYTE in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
DataType.ARRAY_B -> DataType.BYTE else -> null
DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
} }
} }
"sum" -> { "sum" -> {
val dt=datatypeFromIterableArg(args.single()) when(datatypeFromIterableArg(args.single())) {
when(dt) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD DataType.UBYTE, DataType.UWORD -> DataType.UWORD
DataType.BYTE, DataType.WORD -> DataType.WORD DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT DataType.FLOAT -> DataType.FLOAT
DataType.ARRAY_UB, DataType.ARRAY_UW -> DataType.UWORD DataType.ARRAY_UB, DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_B, DataType.ARRAY_W -> DataType.WORD DataType.ARRAY_B, DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT DataType.ARRAY_F -> DataType.FLOAT
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.UWORD in StringDatatypes -> DataType.UWORD
else -> null
} }
} }
"len" -> {
// a length can be >255 so in that case, the result is an UWORD instead of an UBYTE
// but to avoid a lot of code duplication we simply assume UWORD in all cases for now
return DataType.UWORD
}
else -> return null else -> return null
} }
} }
@ -186,10 +182,10 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
class NotConstArgumentException: AstException("not a const argument to a built-in function") class NotConstArgumentException: AstException("not a const argument to a built-in function")
private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue { private fun oneDoubleArg(args: List<IExpression>, position: Position, program: Program, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position) throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type!=DataType.FLOAT) if(constval.type!=DataType.FLOAT)
throw SyntaxError("built-in function requires one floating point argument", position) throw SyntaxError("built-in function requires one floating point argument", position)
@ -197,19 +193,19 @@ private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:
return numericLiteral(function(float), args[0].position) return numericLiteral(function(float), args[0].position)
} }
private fun oneDoubleArgOutputWord(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue { private fun oneDoubleArgOutputWord(args: List<IExpression>, position: Position, program: Program, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position) throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type!=DataType.FLOAT) if(constval.type!=DataType.FLOAT)
throw SyntaxError("built-in function requires one floating point argument", position) throw SyntaxError("built-in function requires one floating point argument", position)
return LiteralValue(DataType.WORD, wordvalue=function(constval.asNumericValue!!.toDouble()).toInt(), position=args[0].position) return LiteralValue(DataType.WORD, wordvalue=function(constval.asNumericValue!!.toDouble()).toInt(), position=args[0].position)
} }
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Int)->Number): LiteralValue { private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, program: Program, function: (arg: Int)->Number): LiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position) throw SyntaxError("built-in function requires one integer argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type!=DataType.UBYTE && constval.type!=DataType.UWORD) if(constval.type!=DataType.UBYTE && constval.type!=DataType.UWORD)
throw SyntaxError("built-in function requires one integer argument", position) throw SyntaxError("built-in function requires one integer argument", position)
@ -218,14 +214,14 @@ private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, name
} }
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position, private fun collectionArgOutputNumber(args: List<IExpression>, position: Position,
namespace:INameScope, heap: HeapValues, program: Program,
function: (arg: Collection<Double>)->Number): LiteralValue { function: (arg: Collection<Double>)->Number): LiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position) throw SyntaxError("builtin function requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
val result = if(iterable.arrayvalue != null) { val result = if(iterable.arrayvalue != null) {
val constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue } val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
if(null in constants) if(null in constants)
throw NotConstArgumentException() throw NotConstArgumentException()
function(constants.map { it!!.toDouble() }).toDouble() function(constants.map { it!!.toDouble() }).toDouble()
@ -233,10 +229,14 @@ private fun collectionArgOutputNumber(args: List<IExpression>, position: Positio
when(iterable.type) { when(iterable.type) {
DataType.UBYTE, DataType.UWORD, DataType.FLOAT -> throw SyntaxError("function expects an iterable type", position) DataType.UBYTE, DataType.UWORD, DataType.FLOAT -> throw SyntaxError("function expects an iterable type", position)
else -> { else -> {
if(iterable.heapId==null) val heapId = iterable.heapId ?: throw FatalAstException("iterable value should be on the heap")
throw FatalAstException("iterable value should be on the heap") val array = program.heap.get(heapId).array ?: throw SyntaxError("function expects an iterable type", position)
val array = heap.get(iterable.heapId).array ?: throw SyntaxError("function expects an iterable type", position) function(array.map {
function(array.map { it.toDouble() }) if(it.integer!=null)
it.integer.toDouble()
else
throw FatalAstException("cannot perform function over array that contains other values besides constant integers")
})
} }
} }
} }
@ -244,164 +244,195 @@ private fun collectionArgOutputNumber(args: List<IExpression>, position: Positio
} }
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position, private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position,
namespace:INameScope, heap: HeapValues, program: Program,
function: (arg: Collection<Double>)->Boolean): LiteralValue { function: (arg: Collection<Double>)->Boolean): LiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position) throw SyntaxError("builtin function requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
val result = if(iterable.arrayvalue != null) { val result = if(iterable.arrayvalue != null) {
val constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue } val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
if(null in constants) if(null in constants)
throw NotConstArgumentException() throw NotConstArgumentException()
function(constants.map { it!!.toDouble() }) function(constants.map { it!!.toDouble() })
} else { } else {
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array argument", position) val array = program.heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array argument", position)
function(array.map { it.toDouble() }) function(array.map {
if(it.integer!=null)
it.integer.toDouble()
else
throw FatalAstException("cannot perform function over array that contains other values besides constant integers")
})
} }
return LiteralValue.fromBoolean(result, position) return LiteralValue.fromBoolean(result, position)
} }
private fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinAbs(args: List<IExpression>, position: Position, program: Program): LiteralValue {
// 1 arg, type = float or int, result type= same as argument type // 1 arg, type = float or int, result type= isSameAs as argument type
if(args.size!=1) if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position) throw SyntaxError("abs requires one numeric argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val number = constval.asNumericValue val number = constval.asNumericValue
return when (number) { return when (number) {
is Int, is Byte, is Short -> numericLiteral(Math.abs(number.toInt()), args[0].position) is Int, is Byte, is Short -> numericLiteral(abs(number.toInt()), args[0].position)
is Double -> numericLiteral(Math.abs(number.toDouble()), args[0].position) is Double -> numericLiteral(abs(number.toDouble()), args[0].position)
else -> throw SyntaxError("abs requires one numeric argument", position) else -> throw SyntaxError("abs requires one numeric argument", position)
} }
} }
private fun builtinAvg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinAvg(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("avg requires array argument", position) throw SyntaxError("avg requires array argument", position)
val iterable = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
val result = if(iterable.arrayvalue!=null) { val result = if(iterable.arrayvalue!=null) {
val constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue } val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
if (null in constants) if (null in constants)
throw NotConstArgumentException() throw NotConstArgumentException()
(constants.map { it!!.toDouble() }).average() (constants.map { it!!.toDouble() }).average()
} }
else { else {
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("avg requires array argument", position) val heapId = iterable.heapId!!
array.average() val integerarray = program.heap.get(heapId).array
if(integerarray!=null) {
if (integerarray.all { it.integer != null }) {
integerarray.map { it.integer!! }.average()
} else {
throw ExpressionError("cannot avg() over array that does not only contain constant numerical values", position)
}
} else {
val doublearray = program.heap.get(heapId).doubleArray
doublearray?.average() ?: throw SyntaxError("avg requires array argument", position)
}
} }
return numericLiteral(result, args[0].position) return numericLiteral(result, args[0].position)
} }
private fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinStrlen(args: List<IExpression>, position: Position, program: Program): LiteralValue {
// note: in some cases the length is > 255 and so we have to return a UWORD type instead of a byte. if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
if(argument.type !in StringDatatypes)
throw SyntaxError("strlen must have string argument", position)
val string = argument.strvalue!!
val zeroIdx = string.indexOf('\u0000')
return if(zeroIdx>=0)
LiteralValue.optimalInteger(zeroIdx, position=position)
else
LiteralValue.optimalInteger(string.length, position=position)
}
private fun builtinLen(args: List<IExpression>, position: Position, program: Program): LiteralValue {
// 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) if(args.size!=1)
throw SyntaxError("len requires one argument", position) throw SyntaxError("len requires one argument", position)
var argument = args[0].constValue(namespace, heap) var argument = args[0].constValue(program)
if(argument==null) { if(argument==null) {
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
val arraySize = directMemVar?.arraysize?.size()
if(arraySize != null)
return LiteralValue.optimalInteger(arraySize, position)
if(args[0] !is IdentifierReference) if(args[0] !is IdentifierReference)
throw SyntaxError("len over weird argument ${args[0]}", position) throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetStatement(namespace) val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
val argValue = (target as? VarDecl)?.value val argValue = (target as? VarDecl)?.value
argument = argValue?.constValue(namespace, heap) argument = argValue?.constValue(program)
?: throw NotConstArgumentException() ?: throw NotConstArgumentException()
} }
return when(argument.type) { return when(argument.type) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize
if(arraySize>256) if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${argument.position}") throw CompilerException("array length exceeds byte limit ${argument.position}")
LiteralValue(DataType.UWORD, wordvalue=arraySize, position=args[0].position) LiteralValue.optimalInteger(arraySize, args[0].position)
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize
if(arraySize>256) if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${argument.position}") throw CompilerException("array length exceeds byte limit ${argument.position}")
LiteralValue(DataType.UWORD, wordvalue=arraySize, position=args[0].position) LiteralValue.optimalInteger(arraySize, args[0].position)
} }
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> { in StringDatatypes -> {
val str = argument.strvalue(heap) val str = argument.strvalue!!
if(str.length>255) if(str.length>255)
throw CompilerException("string length exceeds byte limit ${argument.position}") throw CompilerException("string length exceeds byte limit ${argument.position}")
LiteralValue(DataType.UWORD, wordvalue=str.length, position=args[0].position) LiteralValue.optimalInteger(str.length, args[0].position)
} }
DataType.UBYTE, DataType.BYTE, in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
DataType.UWORD, DataType.WORD, else -> throw CompilerException("weird datatype")
DataType.FLOAT -> throw SyntaxError("len of weird argument ${args[0]}", position)
} }
} }
private fun builtinMkword(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinMkword(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 2) if (args.size != 2)
throw SyntaxError("mkword requires lsb and msb arguments", position) throw SyntaxError("mkword requires lsb and msb arguments", position)
val constLsb = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constMsb = args[1].constValue(namespace, heap) ?: throw NotConstArgumentException() val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsb.asIntegerValue!! shl 8) or constLsb.asIntegerValue!! val result = (constMsb.asIntegerValue!! shl 8) or constLsb.asIntegerValue!!
return LiteralValue(DataType.UWORD, wordvalue = result, position = position) return LiteralValue(DataType.UWORD, wordvalue = result, position = position)
} }
private fun builtinSin8(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinSin8(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin8 requires one argument", position) throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.BYTE, bytevalue = (127.0* sin(rad)).toShort(), position = position) return LiteralValue(DataType.BYTE, bytevalue = (127.0* sin(rad)).toShort(), position = position)
} }
private fun builtinSin8u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinSin8u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin8u requires one argument", position) throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UBYTE, bytevalue = (128.0+127.5*sin(rad)).toShort(), position = position) return LiteralValue(DataType.UBYTE, bytevalue = (128.0+127.5*sin(rad)).toShort(), position = position)
} }
private fun builtinCos8(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinCos8(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos8 requires one argument", position) throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.BYTE, bytevalue = (127.0* cos(rad)).toShort(), position = position) return LiteralValue(DataType.BYTE, bytevalue = (127.0* cos(rad)).toShort(), position = position)
} }
private fun builtinCos8u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinCos8u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos8u requires one argument", position) throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5*cos(rad)).toShort(), position = position) return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5*cos(rad)).toShort(), position = position)
} }
private fun builtinSin16(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinSin16(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin16 requires one argument", position) throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.WORD, wordvalue = (32767.0* sin(rad)).toInt(), position = position) return LiteralValue(DataType.WORD, wordvalue = (32767.0* sin(rad)).toInt(), position = position)
} }
private fun builtinSin16u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinSin16u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("sin16u requires one argument", position) throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5*sin(rad)).toInt(), position = position) return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5*sin(rad)).toInt(), position = position)
} }
private fun builtinCos16(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinCos16(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos16 requires one argument", position) throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.WORD, wordvalue = (32767.0* cos(rad)).toInt(), position = position) return LiteralValue(DataType.WORD, wordvalue = (32767.0* cos(rad)).toInt(), position = position)
} }
private fun builtinCos16u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue { private fun builtinCos16u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1) if (args.size != 1)
throw SyntaxError("cos16u requires one argument", position) throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException() val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5* cos(rad)).toInt(), position = position) return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5* cos(rad)).toInt(), position = position)
} }
@ -409,7 +440,7 @@ private fun builtinCos16u(args: List<IExpression>, position: Position, namespace
private fun numericLiteral(value: Number, position: Position): LiteralValue { private fun numericLiteral(value: Number, position: Position): LiteralValue {
val floatNum=value.toDouble() val floatNum=value.toDouble()
val tweakedValue: Number = val tweakedValue: Number =
if(floatNum==Math.floor(floatNum) && (floatNum>=-32768 && floatNum<=65535)) if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
floatNum.toInt() // we have an integer disguised as a float. floatNum.toInt() // we have an integer disguised as a float.
else else
floatNum floatNum

View File

@ -0,0 +1,192 @@
package prog8.optimizing
import prog8.ast.*
import prog8.compiler.loadAsmIncludeFile
class CallGraph(private val program: Program): IAstProcessor {
val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
val usedSymbols = mutableSetOf<IStatement>()
init {
process(program)
}
fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
fun findSubs(scope: INameScope) {
scope.statements.forEach {
if(it is Subroutine)
sub(it)
if(it is INameScope)
findSubs(it)
}
}
findSubs(scope)
}
override fun process(program: Program) {
super.process(program)
program.modules.forEach {
it.importedBy.clear()
it.imports.clear()
it.importedBy.addAll(modulesImportedBy.getValue(it))
it.imports.addAll(modulesImporting.getValue(it))
forAllSubroutines(it) { sub ->
sub.calledBy.clear()
sub.calls.clear()
sub.calledBy.addAll(subroutinesCalledBy.getValue(sub))
sub.calls.addAll(subroutinesCalling.getValue(sub))
}
}
val rootmodule = program.modules.first()
rootmodule.importedBy.add(rootmodule) // don't discard root module
}
override fun process(block: Block): IStatement {
if(block.definingModule().isLibraryModule) {
// make sure the block is not removed
addNodeAndParentScopes(block)
}
return super.process(block)
}
override fun process(directive: Directive): IStatement {
val thisModule = directive.definingModule()
if(directive.directive=="%import") {
val importedModule: Module = program.modules.single { it.name==directive.args[0].name }
modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule)
modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive=="%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val scope = directive.definingScope()
scanAssemblyCode(asm, directive, scope)
}
return super.process(directive)
}
override fun process(identifier: IdentifierReference): IExpression {
// track symbol usage
val target = identifier.targetStatement(this.program.namespace)
if(target!=null) {
addNodeAndParentScopes(target)
}
return super.process(identifier)
}
private fun addNodeAndParentScopes(stmt: IStatement) {
usedSymbols.add(stmt)
var node: Node=stmt
do {
if(node is INameScope && node is IStatement) {
usedSymbols.add(node)
}
node=node.parent
} while (node !is Module && node !is ParentSentinel)
}
override fun process(subroutine: Subroutine): IStatement {
if((subroutine.name=="start" && subroutine.definingScope().name=="main")
|| subroutine.name==initvarsSubName || subroutine.definingModule().isLibraryModule) {
// make sure the entrypoint is mentioned in the used symbols
addNodeAndParentScopes(subroutine)
}
return super.process(subroutine)
}
override fun process(decl: VarDecl): IStatement {
if(decl.autoGenerated || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
// make sure autogenerated vardecls are in the used symbols
addNodeAndParentScopes(decl)
}
return super.process(decl)
}
override fun process(functionCall: FunctionCall): IExpression {
val otherSub = functionCall.target.targetSubroutine(program.namespace)
if(otherSub!=null) {
functionCall.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCall)
}
}
return super.process(functionCall)
}
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
if(otherSub!=null) {
functionCallStatement.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCallStatement)
}
}
return super.process(functionCallStatement)
}
override fun process(jump: Jump): IStatement {
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
if(otherSub!=null) {
jump.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(jump)
}
}
return super.process(jump)
}
override fun process(inlineAssembly: InlineAssembly): IStatement {
// parse inline asm for subroutine calls (jmp, jsr)
val scope = inlineAssembly.definingScope()
scanAssemblyCode(inlineAssembly.assembly, inlineAssembly, scope)
return super.process(inlineAssembly)
}
private fun scanAssemblyCode(asm: String, context: IStatement, scope: INameScope) {
val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
asm.lines().forEach { line ->
val matches = asmJumpRx.matchEntire(line)
if (matches != null) {
val jumptarget = matches.groups[2]?.value
if (jumptarget != null && (jumptarget[0].isLetter() || jumptarget[0] == '_')) {
val node = program.namespace.lookup(jumptarget.split('.'), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
} else if(jumptarget.contains('.')) {
// maybe only the first part already refers to a subroutine
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
if (node2 is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node2)
subroutinesCalledBy[node2] = subroutinesCalledBy.getValue(node2).plus(context)
}
}
}
} else {
val matches2 = asmRefRx.matchEntire(line)
if (matches2 != null) {
val target= matches2.groups[2]?.value
if (target != null && (target[0].isLetter() || target[0] == '_')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
}
}
}
}
}
}
}

View File

@ -10,11 +10,11 @@ val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "=
class ConstExprEvaluator { class ConstExprEvaluator {
fun evaluate(left: LiteralValue, operator: String, right: LiteralValue, heap: HeapValues): IExpression { fun evaluate(left: LiteralValue, operator: String, right: LiteralValue): IExpression {
return when(operator) { return when(operator) {
"+" -> plus(left, right, heap) "+" -> plus(left, right)
"-" -> minus(left, right) "-" -> minus(left, right)
"*" -> multiply(left, right, heap) "*" -> multiply(left, right)
"/" -> divide(left, right) "/" -> divide(left, right)
"%" -> remainder(left, right) "%" -> remainder(left, right)
"**" -> power(left, right) "**" -> power(left, right)
@ -161,7 +161,7 @@ class ConstExprEvaluator {
} }
} }
private fun plus(left: LiteralValue, right: LiteralValue, heap: HeapValues): LiteralValue { private fun plus(left: LiteralValue, right: LiteralValue): LiteralValue {
val error = "cannot add $left and $right" val error = "cannot add $left and $right"
return when { return when {
left.asIntegerValue!=null -> when { left.asIntegerValue!=null -> when {
@ -176,7 +176,7 @@ class ConstExprEvaluator {
} }
left.isString -> when { left.isString -> when {
right.isString -> { right.isString -> {
val newStr = left.strvalue(heap) + right.strvalue(heap) val newStr = left.strvalue!! + right.strvalue!!
if(newStr.length > 255) throw ExpressionError("string too long", left.position) if(newStr.length > 255) throw ExpressionError("string too long", left.position)
LiteralValue(DataType.STR, strvalue = newStr, position = left.position) LiteralValue(DataType.STR, strvalue = newStr, position = left.position)
} }
@ -203,15 +203,15 @@ class ConstExprEvaluator {
} }
} }
private fun multiply(left: LiteralValue, right: LiteralValue, heap: HeapValues): LiteralValue { private fun multiply(left: LiteralValue, right: LiteralValue): LiteralValue {
val error = "cannot multiply ${left.type} and ${right.type}" val error = "cannot multiply ${left.type} and ${right.type}"
return when { return when {
left.asIntegerValue!=null -> when { left.asIntegerValue!=null -> when {
right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue * right.asIntegerValue, left.position) right.asIntegerValue!=null -> LiteralValue.optimalNumeric(left.asIntegerValue * right.asIntegerValue, left.position)
right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue * right.floatvalue, position = left.position) right.floatvalue!=null -> LiteralValue(DataType.FLOAT, floatvalue = left.asIntegerValue * right.floatvalue, position = left.position)
right.isString -> { right.isString -> {
if(right.strvalue(heap).length * left.asIntegerValue > 255) throw ExpressionError("string too long", left.position) if(right.strvalue!!.length * left.asIntegerValue > 255) throw ExpressionError("string too long", left.position)
LiteralValue(DataType.STR, strvalue = right.strvalue(heap).repeat(left.asIntegerValue), position = left.position) LiteralValue(DataType.STR, strvalue = right.strvalue.repeat(left.asIntegerValue), position = left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }

View File

@ -3,19 +3,20 @@ package prog8.optimizing
import prog8.ast.* import prog8.ast.*
import prog8.compiler.CompilerException import prog8.compiler.CompilerException
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
import kotlin.math.floor import kotlin.math.floor
class ConstantFolding(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor { class ConstantFolding(private val program: Program) : IAstProcessor {
var optimizationsDone: Int = 0 var optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf() var errors : MutableList<AstException> = mutableListOf()
private val reportedErrorMessages = mutableSetOf<String>() private val reportedErrorMessages = mutableSetOf<String>()
fun addError(x: AstException) { fun addError(x: AstException) {
// check that we don't add the same error more than once // check that we don't add the isSameAs error more than once
if(x.toString() !in reportedErrorMessages) { if(x.toString() !in reportedErrorMessages) {
reportedErrorMessages.add(x.toString()) reportedErrorMessages.add(x.toString())
errors.add(x) errors.add(x)
@ -24,31 +25,73 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
override fun process(decl: VarDecl): IStatement { override fun process(decl: VarDecl): IStatement {
// the initializer value can't refer to the variable itself (recursive definition) // the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arrayspec?.x?.referencesIdentifier(decl.name) == true) { if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
errors.add(ExpressionError("recursive var declaration", decl.position)) errors.add(ExpressionError("recursive var declaration", decl.position))
return decl return decl
} }
val result = super.process(decl)
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) { if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
val litval = decl.value as? LiteralValue val litval = decl.value as? LiteralValue
if(litval!=null && litval.isArray && litval.heapId!=null) if(litval!=null && litval.isArray && litval.heapId!=null)
fixupArrayTypeOnHeap(decl, litval) fixupArrayTypeOnHeap(decl, litval)
if(decl.isArray){
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
if(decl.arraysize==null) {
val arrayval = (decl.value as? LiteralValue)?.arrayvalue
if(arrayval!=null) {
decl.arraysize = ArrayIndex(LiteralValue.optimalInteger(arrayval.size, decl.position), decl.position)
optimizationsDone++
}
}
else if(decl.arraysize?.size()==null) {
val size = decl.arraysize!!.index.process(this)
if(size is LiteralValue) {
decl.arraysize = ArrayIndex(size, decl.position)
optimizationsDone++
}
}
}
when(decl.datatype) { when(decl.datatype) {
DataType.FLOAT -> { DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats // vardecl: for scalar float vars, promote constant integer initialization values to floats
if (litval != null && litval.type in IntegerDatatypes) { if (litval != null && litval.type in IntegerDatatypes) {
val newValue = LiteralValue(DataType.FLOAT, floatvalue = litval.asNumericValue!!.toDouble(), position = litval.position) val newValue = LiteralValue(DataType.FLOAT, floatvalue = litval.asNumericValue!!.toDouble(), position = litval.position)
decl.value = newValue decl.value = newValue
optimizationsDone++
return decl
} }
} }
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array (will be put on heap later)
val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program)!!
if(eltType in ByteDatatypes) {
decl.value = LiteralValue(decl.datatype,
arrayvalue = constRange.map { LiteralValue(eltType, bytevalue=it.toShort(), position = decl.value!!.position ) }
.toTypedArray(), position=decl.value!!.position)
} else {
decl.value = LiteralValue(decl.datatype,
arrayvalue = constRange.map { LiteralValue(eltType, wordvalue= it, position = decl.value!!.position ) }
.toTypedArray(), position=decl.value!!.position)
}
decl.value!!.linkParents(decl)
optimizationsDone++
return decl
}
}
if(litval?.type==DataType.FLOAT) if(litval?.type==DataType.FLOAT)
errors.add(ExpressionError("arrayspec requires only integers here", litval.position)) errors.add(ExpressionError("arraysize requires only integers here", litval.position))
val size = decl.arrayspec!!.size() val size = decl.arraysize?.size() ?: return decl
if ((litval==null || !litval.isArray) && size != null) { if ((litval==null || !litval.isArray) && rangeExpr==null) {
// arrayspec initializer is empty or a single int, and we know the size; create the arrayspec. // arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = if (litval == null) 0 else litval.asIntegerValue ?: 0 val fillvalue = if (litval == null) 0 else litval.asIntegerValue ?: 0
when(decl.datatype){ when(decl.datatype){
DataType.ARRAY_UB -> { DataType.ARRAY_UB -> {
@ -69,29 +112,34 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
} }
else -> {} else -> {}
} }
val fillArray = IntArray(size) { fillvalue } val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) })
val heapId = heap.add(decl.datatype, fillArray) decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval?.position ?: decl.position)
decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval?.position ?: decl.position) optimizationsDone++
return decl
} }
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
val size = decl.arrayspec!!.size() val size = decl.arraysize?.size() ?: return decl
if ((litval==null || !litval.isArray) && size != null) { if (litval==null || !litval.isArray) {
// arrayspec initializer is empty or a single int, and we know the size; create the arrayspec. // arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0 val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0
if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE) if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("float value overflow", litval?.position ?: decl.position)) errors.add(ExpressionError("float value overflow", litval?.position ?: decl.position))
else { else {
val fillArray = DoubleArray(size) { fillvalue } val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue })
val heapId = heap.add(decl.datatype, fillArray) decl.value = LiteralValue(DataType.ARRAY_F, initHeapId = heapId, position = litval?.position ?: decl.position)
decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval?.position ?: decl.position) optimizationsDone++
return decl
} }
} }
} }
else -> return result else -> {
// nothing to do for this type
}
} }
} }
return result
return super.process(decl)
} }
private fun fixupArrayTypeOnHeap(decl: VarDecl, litval: LiteralValue) { private fun fixupArrayTypeOnHeap(decl: VarDecl, litval: LiteralValue) {
@ -101,23 +149,23 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
if(decl.datatype==litval.type) if(decl.datatype==litval.type)
return // already correct datatype return // already correct datatype
val heapId = litval.heapId ?: throw FatalAstException("expected array to be on heap $litval") val heapId = litval.heapId ?: throw FatalAstException("expected array to be on heap $litval")
val array=heap.get(heapId) val array = program.heap.get(heapId)
when(decl.datatype) { when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
if(array.array!=null) { if(array.array!=null) {
heap.update(heapId, HeapValues.HeapValue(decl.datatype, null, array.array, null)) program.heap.update(heapId, HeapValues.HeapValue(decl.datatype, null, array.array, null))
decl.value = LiteralValue(decl.datatype, heapId=heapId, position = litval.position) decl.value = LiteralValue(decl.datatype, initHeapId=heapId, position = litval.position)
} }
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
if(array.array!=null) { if(array.array!=null) {
// convert a non-float array to floats // convert a non-float array to floats
val doubleArray = array.array.map { it.toDouble() }.toDoubleArray() val doubleArray = array.array.map { it.integer!!.toDouble() }.toDoubleArray()
heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray)) program.heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray))
decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval.position) decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval.position)
} }
} }
else -> throw AstException("invalid array vardecl type") else -> throw FatalAstException("invalid array vardecl type ${decl.datatype}")
} }
} }
@ -126,7 +174,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
*/ */
override fun process(identifier: IdentifierReference): IExpression { override fun process(identifier: IdentifierReference): IExpression {
return try { return try {
val cval = identifier.constValue(namespace, heap) ?: return identifier val cval = identifier.constValue(program) ?: return identifier
return if(cval.isNumeric) { return if(cval.isNumeric) {
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, null, cval.arrayvalue, position = identifier.position) val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, null, cval.arrayvalue, position = identifier.position)
copy.parent = identifier.parent copy.parent = identifier.parent
@ -143,7 +191,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
return try { return try {
super.process(functionCall) super.process(functionCall)
typeCastConstArguments(functionCall) typeCastConstArguments(functionCall)
functionCall.constValue(namespace, heap) ?: functionCall functionCall.constValue(program) ?: functionCall
} catch (ax: AstException) { } catch (ax: AstException) {
addError(ax) addError(ax)
functionCall functionCall
@ -157,12 +205,12 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
} }
private fun typeCastConstArguments(functionCall: IFunctionCall) { private fun typeCastConstArguments(functionCall: IFunctionCall) {
val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) { if(subroutine!=null) {
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter // if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
for(arg in functionCall.arglist.withIndex().zip(subroutine.parameters)) { for(arg in functionCall.arglist.withIndex().zip(subroutine.parameters)) {
val expectedDt = arg.second.type val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(namespace, heap) val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) { if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.intoDatatype(expectedDt) val convertedValue = argConst.intoDatatype(expectedDt)
if(convertedValue!=null) { if(convertedValue!=null) {
@ -174,6 +222,21 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
} }
} }
override fun process(memread: DirectMemoryRead): IExpression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
return super.process(addrOf.identifier)
return super.process(memread)
}
override fun process(memwrite: DirectMemoryWrite): IExpression {
// @( &thing ) --> thing
val addrOf = memwrite.addressExpression as? AddressOf
if(addrOf!=null)
return super.process(addrOf.identifier)
return super.process(memwrite)
}
/** /**
* Try to process a unary prefix expression. * Try to process a unary prefix expression.
@ -248,8 +311,8 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
override fun process(expr: BinaryExpression): IExpression { override fun process(expr: BinaryExpression): IExpression {
return try { return try {
super.process(expr) super.process(expr)
val leftconst = expr.left.constValue(namespace, heap) val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(namespace, heap) val rightconst = expr.right.constValue(program)
val subExpr: BinaryExpression? = when { val subExpr: BinaryExpression? = when {
leftconst!=null -> expr.right as? BinaryExpression leftconst!=null -> expr.right as? BinaryExpression
@ -257,8 +320,8 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
else -> null else -> null
} }
if(subExpr!=null) { if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(namespace, heap) val subleftconst = subExpr.left.constValue(program)
val subrightconst = subExpr.right.constValue(namespace, heap) val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) { if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
// try reordering. // try reordering.
return groupTwoConstsTogether(expr, subExpr, return groupTwoConstsTogether(expr, subExpr,
@ -272,7 +335,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
return when { return when {
leftconst != null && rightconst != null -> { leftconst != null && rightconst != null -> {
optimizationsDone++ optimizationsDone++
evaluator.evaluate(leftconst, expr.operator, rightconst, heap) evaluator.evaluate(leftconst, expr.operator, rightconst)
} }
else -> expr else -> expr
} }
@ -291,7 +354,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
{ {
// @todo this implements only a small set of possible reorderings for now // @todo this implements only a small set of possible reorderings for now
if(expr.operator==subExpr.operator) { if(expr.operator==subExpr.operator) {
// both operators are the same. // both operators are the isSameAs.
// If + or *, we can simply swap the const of expr and Var in subexpr. // If + or *, we can simply swap the const of expr and Var in subexpr.
if(expr.operator=="+" || expr.operator=="*") { if(expr.operator=="+" || expr.operator=="*") {
if(leftIsConst) { if(leftIsConst) {
@ -494,7 +557,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
val rangeTo = iterableRange.to as? LiteralValue val rangeTo = iterableRange.to as? LiteralValue
if(rangeFrom==null || rangeTo==null) return resultStmt if(rangeFrom==null || rangeTo==null) return resultStmt
val loopvar = resultStmt.loopVar!!.targetStatement(namespace) as? VarDecl val loopvar = resultStmt.loopVar?.targetVarDecl(program.namespace)
if(loopvar!=null) { if(loopvar!=null) {
val stepLiteral = iterableRange.step as? LiteralValue val stepLiteral = iterableRange.step as? LiteralValue
when(loopvar.datatype) { when(loopvar.datatype) {
@ -529,79 +592,69 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
} }
override fun process(literalValue: LiteralValue): LiteralValue { override fun process(literalValue: LiteralValue): LiteralValue {
if(literalValue.isString) { val litval = super.process(literalValue)
if(litval.isString) {
// intern the string; move it into the heap // intern the string; move it into the heap
if(literalValue.strvalue(heap).length !in 1..255) if(litval.strvalue!!.length !in 1..255)
addError(ExpressionError("string literal length must be between 1 and 255", literalValue.position)) addError(ExpressionError("string literal length must be between 1 and 255", litval.position))
else { else {
val heapId = heap.add(literalValue.type, literalValue.strvalue(heap)) // TODO: we don't know the actual string type yet, STR != STR_P etc... litval.addToHeap(program.heap) // TODO: we don't know the actual string type yet, STR != STR_S etc...
val newValue = LiteralValue(literalValue.type, heapId = heapId, position = literalValue.position)
return super.process(newValue)
} }
} else if(literalValue.arrayvalue!=null) { } else if(litval.arrayvalue!=null) {
return moveArrayToHeap(literalValue) // first, adjust the array datatype
val litval2 = adjustArrayValDatatype(litval)
litval2.addToHeap(program.heap)
return litval2
} }
return litval
return super.process(literalValue)
} }
private fun moveArrayToHeap(arraylit: LiteralValue): LiteralValue { private fun adjustArrayValDatatype(litval: LiteralValue): LiteralValue {
val array: Array<IExpression> = arraylit.arrayvalue!!.map { it.process(this) }.toTypedArray() val array = litval.arrayvalue!!
val allElementsAreConstant = array.fold(true) { c, expr-> c and (expr is LiteralValue)} val typesInArray = array.mapNotNull { it.inferType(program) }.toSet()
if(!allElementsAreConstant) { val arrayDt =
addError(ExpressionError("array literal can contain only constant values", arraylit.position)) when {
return arraylit array.any { it is AddressOf} -> DataType.ARRAY_UW
} else { DataType.FLOAT in typesInArray -> DataType.ARRAY_F
val valuesInArray = array.map { it.constValue(namespace, heap)!!.asNumericValue!! } DataType.WORD in typesInArray -> DataType.ARRAY_W
val integerArray = valuesInArray.map{it.toInt()}.toIntArray() else -> {
val doubleArray = valuesInArray.map{it.toDouble()}.toDoubleArray() val allElementsAreConstantOrAddressOf = array.fold(true) { c, expr-> c and (expr is LiteralValue || expr is AddressOf)}
val typesInArray: Set<DataType> = array.mapNotNull { it.resultingDatatype(namespace, heap) }.toSet() if(!allElementsAreConstantOrAddressOf) {
addError(ExpressionError("array literal can only consist of constant primitive numerical values or memory pointers", litval.position))
// Take an educated guess about the array type. return litval
// This may be altered (if needed & if possible) to suit an array declaration type later!
// Also, the check if all values are valid for the given datatype is done later, in the AstChecker.
val arrayDt =
if(DataType.FLOAT in typesInArray)
DataType.ARRAY_F
else if(DataType.WORD in typesInArray) {
DataType.ARRAY_W
} else {
val maxValue = integerArray.max()!!
val minValue = integerArray.min()!!
if (minValue >= 0) {
// unsigned
if (maxValue <= 255)
DataType.ARRAY_UB
else
DataType.ARRAY_UW
} else { } else {
// signed val integerArray = array.map { it.constValue(program)!!.asIntegerValue!! }
if (maxValue <= 127) val maxValue = integerArray.max()!!
DataType.ARRAY_B val minValue = integerArray.min()!!
else if (minValue >= 0) {
DataType.ARRAY_W // unsigned
if (maxValue <= 255)
DataType.ARRAY_UB
else
DataType.ARRAY_UW
} else {
// signed
if (maxValue <= 127)
DataType.ARRAY_B
else
DataType.ARRAY_W
}
} }
} }
}
val heapId = when(arrayDt) { if(arrayDt!=litval.type) {
DataType.ARRAY_UB, return LiteralValue(arrayDt, arrayvalue = litval.arrayvalue, position = litval.position)
DataType.ARRAY_B,
DataType.ARRAY_UW,
DataType.ARRAY_W -> heap.add(arrayDt, integerArray)
DataType.ARRAY_F -> heap.add(arrayDt, doubleArray)
else -> throw CompilerException("invalid arrayspec type")
}
return LiteralValue(arrayDt, heapId = heapId, position = arraylit.position)
} }
return litval
} }
override fun process(assignment: Assignment): IStatement { override fun process(assignment: Assignment): IStatement {
super.process(assignment) super.process(assignment)
val lv = assignment.value as? LiteralValue val lv = assignment.value as? LiteralValue
if(lv!=null) { if(lv!=null) {
val targetDt = assignment.singleTarget?.determineDatatype(namespace, heap, assignment)
// see if we can promote/convert a literal value to the required datatype // see if we can promote/convert a literal value to the required datatype
when(targetDt) { when(assignment.singleTarget?.inferType(program, assignment)) {
DataType.UWORD -> { DataType.UWORD -> {
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535, // we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
if(lv.type==DataType.UBYTE) if(lv.type==DataType.UBYTE)
@ -662,5 +715,3 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
return assignment return assignment
} }
} }

View File

@ -1,48 +1,49 @@
package prog8.optimizing package prog8.optimizing
import prog8.ast.AstException import prog8.ast.*
import prog8.ast.INameScope
import prog8.ast.Module
import prog8.compiler.HeapValues
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
fun Module.constantFold(globalNamespace: INameScope, heap: HeapValues) { internal fun Program.constantFold() {
val optimizer = ConstantFolding(globalNamespace, heap) val optimizer = ConstantFolding(this)
try { try {
this.process(optimizer) optimizer.process(this)
} catch (ax: AstException) { } catch (ax: AstException) {
optimizer.addError(ax) optimizer.addError(ax)
} }
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) { while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
optimizer.optimizationsDone = 0 optimizer.optimizationsDone = 0
this.process(optimizer) optimizer.process(this)
} }
if(optimizer.errors.isNotEmpty()) { if(optimizer.errors.isNotEmpty()) {
optimizer.errors.forEach { System.err.println(it) } optimizer.errors.forEach { System.err.println(it) }
throw ParsingFailedError("There are ${optimizer.errors.size} errors.") throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
} else { } else {
this.linkParents() // re-link in final configuration modules.forEach { it.linkParents(namespace) } // re-link in final configuration
} }
} }
fun Module.optimizeStatements(globalNamespace: INameScope, heap: HeapValues): Int { internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int {
val optimizer = StatementOptimizer(globalNamespace, heap) val optimizer = StatementOptimizer(this, optimizeInlining)
this.process(optimizer) optimizer.process(this)
for(stmt in optimizer.statementsToRemove) { for(scope in optimizer.scopesToFlatten.reversed()) {
val scope=stmt.definingScope() val namescope = scope.parent as INameScope
scope.remove(stmt) val idx = namescope.statements.indexOf(scope as IStatement)
if(idx>=0) {
namescope.statements[idx] = NopStatement(scope.position)
namescope.statements.addAll(idx, scope.statements)
}
} }
this.linkParents() // re-link in final configuration modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
return optimizer.optimizationsDone return optimizer.optimizationsDone
} }
fun Module.simplifyExpressions(namespace: INameScope, heap: HeapValues) : Int { internal fun Program.simplifyExpressions() : Int {
val optimizer = SimplifyExpressions(namespace, heap) val optimizer = SimplifyExpressions(this)
this.process(optimizer) optimizer.process(this)
return optimizer.optimizationsDone return optimizer.optimizationsDone
} }

View File

@ -1,15 +1,17 @@
package prog8.optimizing package prog8.optimizing
import prog8.ast.* import prog8.ast.*
import prog8.compiler.HeapValues
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.log2 import kotlin.math.log2
/* /*
todo advanced expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call + introduce variable to hold it) todo advanced expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call + introduce variable to hold it)
Also see https://egorbo.com/peephole-optimizations.html
*/ */
class SimplifyExpressions(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor { internal class SimplifyExpressions(private val program: Program) : IAstProcessor {
var optimizationsDone: Int = 0 var optimizationsDone: Int = 0
override fun process(assignment: Assignment): IStatement { override fun process(assignment: Assignment): IStatement {
@ -18,6 +20,43 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
return super.process(assignment) return super.process(assignment)
} }
override fun process(memread: DirectMemoryRead): IExpression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
return super.process(addrOf.identifier)
return super.process(memread)
}
override fun process(memwrite: DirectMemoryWrite): IExpression {
// @( &thing ) --> thing
val addrOf = memwrite.addressExpression as? AddressOf
if(addrOf!=null)
return super.process(addrOf.identifier)
return super.process(memwrite)
}
override fun process(typecast: TypecastExpression): IExpression {
// remove redundant typecasts
var tc = typecast
while(true) {
val expr = tc.expression
if(expr !is TypecastExpression || expr.type!=tc.type) {
val assignment = typecast.parent as? Assignment
if(assignment!=null) {
val targetDt = assignment.singleTarget?.inferType(program, assignment)
if(tc.expression.inferType(program)==targetDt) {
optimizationsDone++
return tc.expression
}
}
return super.process(tc)
}
optimizationsDone++
tc = expr
}
}
override fun process(expr: PrefixExpression): IExpression { override fun process(expr: PrefixExpression): IExpression {
if (expr.operator == "+") { if (expr.operator == "+") {
// +X --> X // +X --> X
@ -67,15 +106,15 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
override fun process(expr: BinaryExpression): IExpression { override fun process(expr: BinaryExpression): IExpression {
super.process(expr) super.process(expr)
val leftVal = expr.left.constValue(namespace, heap) val leftVal = expr.left.constValue(program)
val rightVal = expr.right.constValue(namespace, heap) val rightVal = expr.right.constValue(program)
val constTrue = LiteralValue.fromBoolean(true, expr.position) val constTrue = LiteralValue.fromBoolean(true, expr.position)
val constFalse = LiteralValue.fromBoolean(false, expr.position) val constFalse = LiteralValue.fromBoolean(false, expr.position)
val leftDt = expr.left.resultingDatatype(namespace, heap) val leftDt = expr.left.inferType(program)
val rightDt = expr.right.resultingDatatype(namespace, heap) val rightDt = expr.right.inferType(program)
if (leftDt != null && rightDt != null && leftDt != rightDt) { if (leftDt != null && rightDt != null && leftDt != rightDt) {
// try to convert a datatype into the other // try to convert a datatype into the other (where ddd
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) { if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
optimizationsDone++ optimizationsDone++
return expr return expr
@ -280,8 +319,8 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
private fun determineY(x: IExpression, subBinExpr: BinaryExpression): IExpression? { private fun determineY(x: IExpression, subBinExpr: BinaryExpression): IExpression? {
return when { return when {
same(subBinExpr.left, x) -> subBinExpr.right subBinExpr.left isSameAs x -> subBinExpr.right
same(subBinExpr.right, x) -> subBinExpr.left subBinExpr.right isSameAs x -> subBinExpr.left
else -> null else -> null
} }
} }
@ -347,27 +386,30 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
} }
if(leftConstVal==null && rightConstVal!=null) { if(leftConstVal==null && rightConstVal!=null) {
val (adjusted, newValue) = adjust(rightConstVal, leftDt) if(leftDt biggerThan rightDt) {
if(adjusted) { val (adjusted, newValue) = adjust(rightConstVal, leftDt)
expr.right = newValue if (adjusted) {
optimizationsDone++ expr.right = newValue
return true optimizationsDone++
return true
}
} }
return false return false
} else if(leftConstVal!=null && rightConstVal==null) { } else if(leftConstVal!=null && rightConstVal==null) {
val (adjusted, newValue) = adjust(leftConstVal, rightDt) if(rightDt biggerThan leftDt) {
if(adjusted) { val (adjusted, newValue) = adjust(leftConstVal, rightDt)
expr.left = newValue if (adjusted) {
optimizationsDone++ expr.left = newValue
return true optimizationsDone++
return true
}
} }
return false return false
} else { } else {
return false return false // two const values, don't adjust (should have been const-folded away)
} }
} }
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: LiteralValue?, val rightVal: LiteralValue?) private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: LiteralValue?, val rightVal: LiteralValue?)
private fun reorderAssociative(expr: BinaryExpression, leftVal: LiteralValue?): ReorderedAssociativeBinaryExpr { private fun reorderAssociative(expr: BinaryExpression, leftVal: LiteralValue?): ReorderedAssociativeBinaryExpr {
@ -377,9 +419,9 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
expr.left = expr.right expr.left = expr.right
expr.right = tmp expr.right = tmp
optimizationsDone++ optimizationsDone++
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(namespace, heap), leftVal) return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(program), leftVal)
} }
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(namespace, heap)) return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
} }
private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression { private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: LiteralValue?, prightVal: LiteralValue?): IExpression {
@ -523,7 +565,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
"%" -> { "%" -> {
if (cv == 1.0) { if (cv == 1.0) {
optimizationsDone++ optimizationsDone++
return LiteralValue.fromNumber(0, expr.resultingDatatype(namespace, heap)!!, expr.position) return LiteralValue.fromNumber(0, expr.inferType(program)!!, expr.position)
} else if (cv == 2.0) { } else if (cv == 2.0) {
optimizationsDone++ optimizationsDone++
expr.operator = "&" expr.operator = "&"
@ -546,7 +588,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: LiteralValue = rightVal val rightConst: LiteralValue = rightVal
val cv = rightConst.asNumericValue?.toDouble() val cv = rightConst.asNumericValue?.toDouble()
val leftDt = expr.left.resultingDatatype(namespace, heap) val leftDt = expr.left.inferType(program)
when(cv) { when(cv) {
-1.0 -> { -1.0 -> {
// '/' -> -left // '/' -> -left
@ -617,8 +659,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val leftValue: IExpression = expr.left val leftValue: IExpression = expr.left
val rightConst: LiteralValue = rightVal val rightConst: LiteralValue = rightVal
val cv = rightConst.asNumericValue?.toDouble() when(val cv = rightConst.asNumericValue?.toDouble()) {
when(cv) {
-1.0 -> { -1.0 -> {
// -left // -left
optimizationsDone++ optimizationsDone++
@ -635,7 +676,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
return expr.left return expr.left
} }
2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0 -> { 2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0 -> {
if(leftValue.resultingDatatype(namespace, heap) in IntegerDatatypes) { if(leftValue.inferType(program) in IntegerDatatypes) {
// times a power of two => shift left // times a power of two => shift left
optimizationsDone++ optimizationsDone++
val numshifts = log2(cv).toInt() val numshifts = log2(cv).toInt()
@ -643,7 +684,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
} }
} }
-2.0, -4.0, -8.0, -16.0, -32.0, -64.0, -128.0, -256.0, -512.0, -1024.0, -2048.0, -4096.0, -8192.0, -16384.0, -32768.0, -65536.0 -> { -2.0, -4.0, -8.0, -16.0, -32.0, -64.0, -128.0, -256.0, -512.0, -1024.0, -2048.0, -4096.0, -8192.0, -16384.0, -32768.0, -65536.0 -> {
if(leftValue.resultingDatatype(namespace, heap) in IntegerDatatypes) { if(leftValue.inferType(program) in IntegerDatatypes) {
// times a negative power of two => negate, then shift left // times a negative power of two => negate, then shift left
optimizationsDone++ optimizationsDone++
val numshifts = log2(-cv).toInt() val numshifts = log2(-cv).toInt()

View File

@ -1,7 +1,6 @@
package prog8.optimizing package prog8.optimizing
import prog8.ast.* import prog8.ast.*
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import kotlin.math.floor import kotlin.math.floor
@ -9,44 +8,151 @@ import kotlin.math.floor
/* /*
todo: subroutines with 1 or 2 byte args or 1 word arg can be converted to asm sub calling convention (args in registers) todo: subroutines with 1 or 2 byte args or 1 word arg can be converted to asm sub calling convention (args in registers)
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this
todo: implement usage counters for blocks, variables, subroutines, heap variables. Then:
todo remove unused blocks
todo remove unused variables
todo remove unused subroutines
todo remove unused strings and arrays from the heap
todo inline subroutines that are called exactly once (regardless of their size)
todo inline subroutines that are only called a few times (3?) and that are "sufficiently small" (0-3 statements)
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to)
*/ */
class StatementOptimizer(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor { internal class StatementOptimizer(private val program: Program, private val optimizeInlining: Boolean) : IAstProcessor {
var optimizationsDone: Int = 0 var optimizationsDone: Int = 0
private set private set
var statementsToRemove = mutableListOf<IStatement>() var scopesToFlatten = mutableListOf<INameScope>()
private set
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure } private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
private val callgraph = CallGraph(program)
companion object {
private var generatedLabelSequenceNumber = 0
}
override fun process(program: Program) {
removeUnusedCode(callgraph)
if(optimizeInlining) {
inlineSubroutines(callgraph)
}
super.process(program)
}
private fun inlineSubroutines(callgraph: CallGraph) {
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if(sub!==entrypoint && !sub.isAsmSubroutine) {
if (sub.statements.size <= 3 && !sub.expensiveToInline) {
sub.calledBy.toList().forEach { caller -> inlineSubroutine(sub, caller) }
} else if (sub.calledBy.size==1 && sub.statements.size < 50) {
inlineSubroutine(sub, sub.calledBy[0])
} else if(sub.calledBy.size<=3 && sub.statements.size < 10 && !sub.expensiveToInline) {
sub.calledBy.toList().forEach { caller -> inlineSubroutine(sub, caller) }
}
}
}
}
}
private fun inlineSubroutine(sub: Subroutine, caller: Node) {
// if the sub is called multiple times from the isSameAs scope, we can't inline (would result in duplicate definitions)
// (unless we add a sequence number to all vars/labels and references to them in the inlined code, but I skip that for now)
val scope = caller.definingScope()
if(sub.calledBy.count { it.definingScope()===scope } > 1)
return
if(caller !is IFunctionCall || caller !is IStatement || sub.statements.any { it is Subroutine })
return
if(sub.parameters.isEmpty() && sub.returntypes.isEmpty()) {
// sub without params and without return value can be easily inlined
val parent = caller.parent as INameScope
val inlined = AnonymousScope(sub.statements.toMutableList(), caller.position)
parent.statements[parent.statements.indexOf(caller)] = inlined
// replace return statements in the inlined sub by a jump to the end of it
var endlabel = inlined.statements.last() as? Label
if(endlabel==null) {
endlabel = makeLabel("_prog8_auto_sub_end", inlined.statements.last().position)
inlined.statements.add(endlabel)
endlabel.parent = inlined
}
val returns = inlined.statements.withIndex().filter { iv -> iv.value is Return }.map { iv -> Pair(iv.index, iv.value as Return)}
for(returnIdx in returns) {
assert(returnIdx.second.values.isEmpty())
val jump = Jump(null, IdentifierReference(listOf(endlabel.name), returnIdx.second.position), null, returnIdx.second.position)
inlined.statements[returnIdx.first] = jump
}
inlined.linkParents(caller.parent)
sub.calledBy.remove(caller) // if there are no callers left, the sub will be removed automatically later
optimizationsDone++
} else {
// TODO inline subroutine that has params or returnvalues or both
}
}
private fun makeLabel(name: String, position: Position): Label {
generatedLabelSequenceNumber++
return Label("${name}_$generatedLabelSequenceNumber", position)
}
private fun removeUnusedCode(callgraph: CallGraph) {
// remove all subroutines that aren't called, or are empty
val removeSubroutines = mutableSetOf<Subroutine>()
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
removeSubroutines.add(sub)
}
}
if (removeSubroutines.isNotEmpty()) {
removeSubroutines.forEach {
it.definingScope().remove(it)
}
}
val removeBlocks = mutableSetOf<Block>()
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removeBlocks.add(block)
}
if (removeBlocks.isNotEmpty()) {
removeBlocks.forEach { it.definingScope().remove(it) }
}
// remove modules that are not imported, or are empty (unless it's a library modules)
val removeModules = mutableSetOf<Module>()
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removeModules.add(it)
}
if (removeModules.isNotEmpty()) {
program.modules.removeAll(removeModules)
}
}
override fun process(block: Block): IStatement { override fun process(block: Block): IStatement {
if(block.statements.isEmpty()) { if("force_output" !in block.options()) {
// remove empty block if (block.containsNoCodeNorVars()) {
optimizationsDone++ optimizationsDone++
statementsToRemove.add(block) printWarning("removing empty block '${block.name}'", block.position)
return NopStatement(block.position)
}
if (block !in callgraph.usedSymbols) {
optimizationsDone++
printWarning("removing unused block '${block.name}'", block.position)
return NopStatement(block.position) // remove unused block
}
} }
return super.process(block) return super.process(block)
} }
override fun process(subroutine: Subroutine): IStatement { override fun process(subroutine: Subroutine): IStatement {
super.process(subroutine) super.process(subroutine)
val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null) { if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.statements.isEmpty()) { if(subroutine.containsNoCodeNorVars()) {
// remove empty subroutine printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++ optimizationsDone++
statementsToRemove.add(subroutine) return NopStatement(subroutine.position)
} }
} }
@ -67,24 +173,42 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
} }
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement(subroutine.position) // remove unused subroutine
}
return subroutine return subroutine
} }
override fun process(decl: VarDecl): IStatement {
val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) {
if(decl.type!=VarDeclType.CONST)
printWarning("removing unused variable '${decl.name}'", decl.position)
optimizationsDone++
return NopStatement(decl.position) // remove unused variable
}
return super.process(decl)
}
private fun deduplicateAssignments(statements: List<IStatement>): MutableList<Int> { private fun deduplicateAssignments(statements: List<IStatement>): MutableList<Int> {
// removes 'duplicate' assignments that assign the same target // removes 'duplicate' assignments that assign the isSameAs target
val linesToRemove = mutableListOf<Int>() val linesToRemove = mutableListOf<Int>()
var previousAssignmentLine: Int? = null var previousAssignmentLine: Int? = null
for (i in 0 until statements.size) { for (i in 0 until statements.size) {
val stmt = statements[i] as? Assignment val stmt = statements[i] as? Assignment
if (stmt != null) { if (stmt != null && stmt.value is LiteralValue) {
if (previousAssignmentLine == null) { if (previousAssignmentLine == null) {
previousAssignmentLine = i previousAssignmentLine = i
continue continue
} else { } else {
val prev = statements[previousAssignmentLine] as Assignment val prev = statements[previousAssignmentLine] as Assignment
if (prev.targets.size == 1 && stmt.targets.size == 1 && same(prev.targets[0], stmt.targets[0])) { if (prev.targets.size == 1 && stmt.targets.size == 1 && prev.targets[0].isSameAs(stmt.targets[0], program)) {
// get rid of the previous assignment, if the target is not MEMORY // get rid of the previous assignment, if the target is not MEMORY
if (isNotMemory(prev.targets[0])) if (prev.targets[0].isNotMemory(program.namespace))
linesToRemove.add(previousAssignmentLine) linesToRemove.add(previousAssignmentLine)
} }
previousAssignmentLine = i previousAssignmentLine = i
@ -95,32 +219,13 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
return linesToRemove return linesToRemove
} }
private fun isNotMemory(target: AssignTarget): Boolean {
if(target.register!=null)
return true
if(target.memoryAddress!=null)
return false
if(target.arrayindexed!=null) {
val targetStmt = target.arrayindexed.identifier.targetStatement(namespace) as? VarDecl
if(targetStmt!=null)
return targetStmt.type!=VarDeclType.MEMORY
}
if(target.identifier!=null) {
val targetStmt = target.identifier.targetStatement(namespace) as? VarDecl
if(targetStmt!=null)
return targetStmt.type!=VarDeclType.MEMORY
}
return false
}
override fun process(functionCallStatement: FunctionCallStatement): IStatement { override fun process(functionCallStatement: FunctionCallStatement): IStatement {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) { if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0] val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) { if (functionName in pureBuiltinFunctions) {
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position) printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
statementsToRemove.add(functionCallStatement) optimizationsDone++
return functionCallStatement return NopStatement(functionCallStatement.position)
} }
} }
@ -131,8 +236,8 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
throw AstException("string argument should be on heap already") throw AstException("string argument should be on heap already")
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference val stringVar = functionCallStatement.arglist.single() as? IdentifierReference
if(stringVar!=null) { if(stringVar!=null) {
val heapId = stringVar.heapId(namespace) val heapId = stringVar.heapId(program.namespace)
val string = heap.get(heapId).str!! val string = program.heap.get(heapId).str!!
if(string.length==1) { if(string.length==1) {
val petscii = Petscii.encodePetscii(string, true)[0] val petscii = Petscii.encodePetscii(string, true)[0]
functionCallStatement.arglist.clear() functionCallStatement.arglist.clear()
@ -156,7 +261,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
// if it calls a subroutine, // if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead // and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement, replace with a nop instruction // if the first instruction in the subroutine is a return statement, replace with a nop instruction
val subroutine = functionCallStatement.target.targetStatement(namespace) as? Subroutine val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
if(subroutine!=null) { if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) { if(first is Jump && first.identifier!=null) {
@ -176,7 +281,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
// if it calls a subroutine, // if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead // and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value // if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) { if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) { if(first is Jump && first.identifier!=null) {
@ -184,7 +289,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
return FunctionCall(first.identifier, functionCall.arglist, functionCall.position) return FunctionCall(first.identifier, functionCall.arglist, functionCall.position)
} }
if(first is Return && first.values.size==1) { if(first is Return && first.values.size==1) {
val constval = first.values[0].constValue(namespace, heap) val constval = first.values[0].constValue(program)
if(constval!=null) if(constval!=null)
return constval return constval
} }
@ -195,13 +300,12 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
override fun process(ifStatement: IfStatement): IStatement { override fun process(ifStatement: IfStatement): IStatement {
super.process(ifStatement) super.process(ifStatement)
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty()) { if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
statementsToRemove.add(ifStatement)
optimizationsDone++ optimizationsDone++
return ifStatement return NopStatement(ifStatement.position)
} }
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isNotEmpty()) { if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
// invert the condition and move else part to true part // invert the condition and move else part to true part
ifStatement.truepart = ifStatement.elsepart ifStatement.truepart = ifStatement.elsepart
ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position) ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
@ -210,7 +314,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
return ifStatement return ifStatement
} }
val constvalue = ifStatement.condition.constValue(namespace, heap) val constvalue = ifStatement.condition.constValue(program)
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue){ return if(constvalue.asBooleanValue){
// always true -> keep only if-part // always true -> keep only if-part
@ -229,25 +333,23 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
override fun process(forLoop: ForLoop): IStatement { override fun process(forLoop: ForLoop): IStatement {
super.process(forLoop) super.process(forLoop)
if(forLoop.body.isEmpty()) { if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop // remove empty for loop
statementsToRemove.add(forLoop)
optimizationsDone++ optimizationsDone++
return forLoop return NopStatement(forLoop.position)
} else if(forLoop.body.statements.size==1) { } else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) { if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
// remove empty for loop // remove empty for loop
statementsToRemove.add(forLoop)
optimizationsDone++ optimizationsDone++
return forLoop return NopStatement(forLoop.position)
} }
} }
val range = forLoop.iterable as? RangeExpr val range = forLoop.iterable as? RangeExpr
if(range!=null) { if(range!=null) {
if(range.size(heap)==1) { if(range.size()==1) {
// for loop over a (constant) range of just a single value-- optimize the loop away // for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block // loopvar/reg = range value , follow by block
val assignment = Assignment(listOf(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position)), null, range.from, forLoop.position) val assignment = Assignment(listOf(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position)), null, range.from, forLoop.position)
@ -261,7 +363,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
override fun process(whileLoop: WhileLoop): IStatement { override fun process(whileLoop: WhileLoop): IStatement {
super.process(whileLoop) super.process(whileLoop)
val constvalue = whileLoop.condition.constValue(namespace, heap) val constvalue = whileLoop.condition.constValue(program)
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue){ return if(constvalue.asBooleanValue){
// always true -> print a warning, and optimize into body + jump (if there are no continue and break statements) // always true -> print a warning, and optimize into body + jump (if there are no continue and break statements)
@ -287,7 +389,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
override fun process(repeatLoop: RepeatLoop): IStatement { override fun process(repeatLoop: RepeatLoop): IStatement {
super.process(repeatLoop) super.process(repeatLoop)
val constvalue = repeatLoop.untilCondition.constValue(namespace, heap) val constvalue = repeatLoop.untilCondition.constValue(program)
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue){ return if(constvalue.asBooleanValue){
// always true -> keep only the statement block (if there are no continue and break statements) // always true -> keep only the statement block (if there are no continue and break statements)
@ -341,7 +443,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
} }
override fun process(jump: Jump): IStatement { override fun process(jump: Jump): IStatement {
val subroutine = jump.identifier?.targetStatement(namespace) as? Subroutine val subroutine = jump.identifier?.targetSubroutine(program.namespace)
if(subroutine!=null) { if(subroutine!=null) {
// if the first instruction in the subroutine is another jump, shortcut this one // if the first instruction in the subroutine is another jump, shortcut this one
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
@ -350,6 +452,17 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
return first return first
} }
} }
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
if(label!=null) {
if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) {
optimizationsDone++
return NopStatement(jump.position)
}
}
return jump return jump
} }
@ -359,17 +472,17 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
if(assignment.targets.size==1) { if(assignment.targets.size==1) {
val target=assignment.targets[0] val target=assignment.targets[0]
if(same(target, assignment.value)) { if(target isSameAs assignment.value) {
optimizationsDone++ optimizationsDone++
return NopStatement(assignment.position) return NopStatement(assignment.position)
} }
val targetDt = target.determineDatatype(namespace, heap, assignment)!! val targetDt = target.inferType(program, assignment)
val bexpr=assignment.value as? BinaryExpression val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) { if(bexpr!=null) {
val cv = bexpr.right.constValue(namespace, heap)?.asNumericValue?.toDouble() val cv = bexpr.right.constValue(program)?.asNumericValue?.toDouble()
if(cv==null) { if(cv==null) {
if(bexpr.operator=="+" && targetDt!=DataType.FLOAT) { if(bexpr.operator=="+" && targetDt!=DataType.FLOAT) {
if (same(bexpr.left, bexpr.right) && same(target, bexpr.left)) { if (bexpr.left isSameAs bexpr.right && target isSameAs bexpr.left) {
bexpr.operator = "*" bexpr.operator = "*"
bexpr.right = LiteralValue.optimalInteger(2, assignment.value.position) bexpr.right = LiteralValue.optimalInteger(2, assignment.value.position)
optimizationsDone++ optimizationsDone++
@ -377,10 +490,10 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
} }
} }
} else { } else {
if (same(target, bexpr.left)) { if (target isSameAs bexpr.left) {
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc // remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
// A = A <operator> B // A = A <operator> B
val vardeclDt = (target.identifier?.targetStatement(namespace) as? VarDecl)?.type val vardeclDt = (target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) { when (bexpr.operator) {
"+" -> { "+" -> {
@ -486,71 +599,29 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
return super.process(assignment) return super.process(assignment)
} }
override fun process(scope: AnonymousScope): AnonymousScope { override fun process(scope: AnonymousScope): IStatement {
val linesToRemove = deduplicateAssignments(scope.statements) val linesToRemove = deduplicateAssignments(scope.statements)
if(linesToRemove.isNotEmpty()) { if(linesToRemove.isNotEmpty()) {
linesToRemove.reversed().forEach{scope.statements.removeAt(it)} linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
} }
if(scope.parent is INameScope) {
scopesToFlatten.add(scope) // get rid of the anonymous scope
}
return super.process(scope) return super.process(scope)
} }
private fun same(target: AssignTarget, value: IExpression): Boolean { override fun process(label: Label): IStatement {
return when { // remove duplicate labels
target.memoryAddress!=null -> false val stmts = label.definingScope().statements
target.register!=null -> value is RegisterExpr && value.register==target.register val startIdx = stmts.indexOf(label)
target.identifier!=null -> value is IdentifierReference && value.nameInSource==target.identifier.nameInSource if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label)
target.arrayindexed!=null -> value is ArrayIndexedExpression && return NopStatement(label.position)
value.identifier.nameInSource==target.arrayindexed.identifier.nameInSource &&
value.arrayspec.size()!=null &&
target.arrayindexed.arrayspec.size()!=null &&
value.arrayspec.size()==target.arrayindexed.arrayspec.size()
else -> false
}
}
private fun same(target1: AssignTarget, target2: AssignTarget): Boolean { return super.process(label)
if(target1===target2)
return true
if(target1.register!=null && target2.register!=null)
return target1.register==target2.register
if(target1.identifier!=null && target2.identifier!=null)
return target1.identifier.nameInSource==target2.identifier.nameInSource
if(target1.memoryAddress!=null && target2.memoryAddress!=null) {
val addr1 = target1.memoryAddress!!.addressExpression.constValue(namespace, heap)
val addr2 = target2.memoryAddress!!.addressExpression.constValue(namespace, heap)
return addr1!=null && addr2!=null && addr1==addr2
}
if(target1.arrayindexed!=null && target2.arrayindexed!=null) {
if(target1.arrayindexed.identifier.nameInSource == target2.arrayindexed.identifier.nameInSource) {
val x1 = target1.arrayindexed.arrayspec.x.constValue(namespace, heap)
val x2 = target2.arrayindexed.arrayspec.x.constValue(namespace, heap)
return x1!=null && x2!=null && x1==x2
}
}
return false
} }
} }
fun same(left: IExpression, right: IExpression): Boolean {
if(left===right)
return true
when(left) {
is RegisterExpr ->
return (right is RegisterExpr && right.register==left.register)
is IdentifierReference ->
return (right is IdentifierReference && right.nameInSource==left.nameInSource)
is PrefixExpression ->
return (right is PrefixExpression && right.operator==left.operator && same(right.expression, left.expression))
is BinaryExpression ->
return (right is BinaryExpression && right.operator==left.operator
&& same(right.left, left.left)
&& same(right.right, left.right))
is ArrayIndexedExpression -> {
return (right is ArrayIndexedExpression && right.identifier.nameInSource == left.identifier.nameInSource
&& same(right.arrayspec.x, left.arrayspec.x))
}
is LiteralValue -> return (right is LiteralValue && right==left)
}
return false
}

View File

@ -4,7 +4,7 @@ import org.antlr.v4.runtime.CommonTokenStream
import org.antlr.v4.runtime.Lexer import org.antlr.v4.runtime.Lexer
class CommentHandlingTokenStream(lexer: Lexer) : CommonTokenStream(lexer) { internal class CommentHandlingTokenStream(lexer: Lexer) : CommonTokenStream(lexer) {
data class Comment(val type: String, val line: Int, val comment: String) data class Comment(val type: String, val line: Int, val comment: String)

View File

@ -2,21 +2,13 @@ package prog8.parser
import org.antlr.v4.runtime.* import org.antlr.v4.runtime.*
import prog8.ast.* import prog8.ast.*
import prog8.compiler.LauncherType
import prog8.compiler.OutputType
import prog8.determineCompilationOptions
import java.io.File
import java.io.InputStream import java.io.InputStream
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.util.*
class ParsingFailedError(override var message: String) : Exception(message) internal class ParsingFailedError(override var message: String) : Exception(message)
private val importedModules : HashMap<String, Module> = hashMapOf()
private class LexerErrorListener: BaseErrorListener() { private class LexerErrorListener: BaseErrorListener() {
@ -30,8 +22,36 @@ private class LexerErrorListener: BaseErrorListener() {
internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input) internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Module { internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
val moduleName = modulePath.fileName
internal fun importModule(program: Program, filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString()
if(importloc.startsWith(curdir))
importloc = "." + importloc.substring(curdir.length)
println(" (from '$importloc')")
}
else
println("")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
val input = CharStreams.fromPath(filePath)
return importModule(program, input, filePath, filePath.parent==null)
}
internal fun importLibraryModule(program: Program, name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0 ,0))
), Position("<<<implicit-import>>>", 0, 0 ,0))
return executeImportDirective(program, import, Paths.get(""))
}
internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream) val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener() val lexerErrors = LexerErrorListener()
lexer.addErrorListener(lexerErrors) lexer.addErrorListener(lexerErrors)
@ -46,68 +66,25 @@ fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Modu
// tokens.commentTokens().forEach { println(it) } // tokens.commentTokens().forEach { println(it) }
// convert to Ast // convert to Ast
val moduleAst = parseTree.toAst(moduleName.toString(), isLibrary, modulePath) val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
importedModules[moduleAst.name] = moduleAst moduleAst.program = program
moduleAst.linkParents(program.namespace)
program.modules.add(moduleAst)
// process imports // process additional imports
val lines = moduleAst.statements.toMutableList() val lines = moduleAst.statements.toMutableList()
if(!moduleAst.position.file.startsWith("c64utils.") && !moduleAst.isLibraryModule) { lines.asSequence()
// if the output is a PRG or BASIC program, include the c64utils library .mapIndexed { i, it -> Pair(i, it) }
val compilerOptions = determineCompilationOptions(moduleAst) .filter { (it.second as? Directive)?.directive == "%import" }
if(compilerOptions.launcher==LauncherType.BASIC || compilerOptions.output==OutputType.PRG) { .forEach { executeImportDirective(program, it.second as Directive, modulePath) }
lines.add(0, Directive("%import", listOf(DirectiveArg(null, "c64utils", null, moduleAst.position)), moduleAst.position))
}
}
// always import the prog8lib and math compiler libraries
if(!moduleAst.position.file.startsWith("math."))
lines.add(0, Directive("%import", listOf(DirectiveArg(null, "math", null, moduleAst.position)), moduleAst.position))
if(!moduleAst.position.file.startsWith("prog8lib."))
lines.add(0, Directive("%import", listOf(DirectiveArg(null, "prog8lib", null, moduleAst.position)), moduleAst.position))
val imports = lines
.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.map { Pair(it.first, executeImportDirective(it.second as Directive, modulePath)) }
.toList()
imports.reversed().forEach {
if(it.second==null) {
// this import was already satisfied. just remove this line.
lines.removeAt(it.first)
} else {
// merge imported lines at this spot
lines.addAll(it.first, it.second!!.statements)
}
}
moduleAst.statements = lines moduleAst.statements = lines
return moduleAst return moduleAst
} }
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
fun importModule(filePath: Path) : Module {
print("importing '${filePath.fileName}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString()
if(importloc.startsWith(curdir))
importloc = "." + importloc.substring(curdir.length)
println(" (from '$importloc')")
}
else
println("")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
val input = CharStreams.fromPath(filePath)
return importModule(input, filePath, filePath.parent==null)
}
private fun discoverImportedModuleFile(name: String, importedFrom: Path, position: Position?): Path {
val fileName = "$name.p8" val fileName = "$name.p8"
val locations = mutableListOf(Paths.get(importedFrom.parent.toString())) val locations = mutableListOf(Paths.get(source.parent.toString()))
val propPath = System.getProperty("prog8.libdir") val propPath = System.getProperty("prog8.libdir")
if(propPath!=null) if(propPath!=null)
@ -125,13 +102,15 @@ private fun discoverImportedModuleFile(name: String, importedFrom: Path, positio
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)") throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
} }
private fun executeImportDirective(import: Directive, importedFrom: Path): Module? { private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null) if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position) throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!! val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file) if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position) throw SyntaxError("cannot import self", import.position)
if(importedModules.containsKey(moduleName))
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null return null
val resource = tryGetEmbeddedResource(moduleName+".p8") val resource = tryGetEmbeddedResource(moduleName+".p8")
@ -139,18 +118,21 @@ private fun executeImportDirective(import: Directive, importedFrom: Path): Modul
if(resource!=null) { if(resource!=null) {
// load the module from the embedded resource // load the module from the embedded resource
resource.use { resource.use {
println("importing '$moduleName' (embedded library)") if(import.args[0].int==42)
importModule(CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true) println("importing '$moduleName' (library, auto)")
else
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
} }
} else { } else {
val modulePath = discoverImportedModuleFile(moduleName, importedFrom, import.position) val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
importModule(modulePath) importModule(program, modulePath)
} }
importedModule.checkImportedValid() importedModule.checkImportedValid()
return importedModule return importedModule
} }
fun tryGetEmbeddedResource(name: String): InputStream? { internal fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name") return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
} }

View File

@ -1,9 +1,9 @@
package prog8.stackvm package prog8.stackvm
import prog8.ast.DataType import prog8.ast.*
import prog8.ast.Position import prog8.compiler.RuntimeValue
import prog8.ast.unescape
import prog8.compiler.HeapValues import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.intermediate.* import prog8.compiler.intermediate.*
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -11,10 +11,10 @@ import java.util.regex.Pattern
class Program (val name: String, class Program (val name: String,
val program: MutableList<Instruction>, val program: MutableList<Instruction>,
val variables: Map<String, Value>, val variables: Map<String, RuntimeValue>,
val memoryPointers: Map<String, Pair<Int, DataType>>, val memoryPointers: Map<String, Pair<Int, DataType>>,
val labels: Map<String, Instruction>, val labels: Map<String, Int>,
val memory: Map<Int, List<Value>>, val memory: Map<Int, List<RuntimeValue>>,
val heap: HeapValues) val heap: HeapValues)
{ {
init { init {
@ -22,18 +22,17 @@ class Program (val name: String,
program.add(LabelInstr("____program_end", false)) program.add(LabelInstr("____program_end", false))
program.add(Instruction(Opcode.TERMINATE)) program.add(Instruction(Opcode.TERMINATE))
program.add(Instruction(Opcode.NOP)) program.add(Instruction(Opcode.NOP))
connect()
} }
companion object { companion object {
fun load(filename: String): Program { fun load(filename: String): Program {
val lines = File(filename).readLines().withIndex().iterator() val lines = File(filename).readLines().withIndex().iterator()
val memory = mutableMapOf<Int, List<Value>>() val memory = mutableMapOf<Int, List<RuntimeValue>>()
val heap = HeapValues() val heap = HeapValues()
val program = mutableListOf<Instruction>() val program = mutableListOf<Instruction>()
val variables = mutableMapOf<String, Value>() val variables = mutableMapOf<String, RuntimeValue>()
val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>() val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>()
val labels = mutableMapOf<String, Instruction>() val labels = mutableMapOf<String, Int>()
while(lines.hasNext()) { while(lines.hasNext()) {
val (lineNr, line) = lines.next() val (lineNr, line) = lines.next()
@ -53,9 +52,9 @@ class Program (val name: String,
private fun loadBlock(lines: Iterator<IndexedValue<String>>, private fun loadBlock(lines: Iterator<IndexedValue<String>>,
heap: HeapValues, heap: HeapValues,
program: MutableList<Instruction>, program: MutableList<Instruction>,
variables: MutableMap<String, Value>, variables: MutableMap<String, RuntimeValue>,
memoryPointers: MutableMap<String, Pair<Int, DataType>>, memoryPointers: MutableMap<String, Pair<Int, DataType>>,
labels: MutableMap<String, Instruction>) labels: MutableMap<String, Int>)
{ {
while(true) { while(true) {
val (_, line) = lines.next() val (_, line) = lines.next()
@ -69,8 +68,10 @@ class Program (val name: String,
loadMemoryPointers(lines, memoryPointers, heap) loadMemoryPointers(lines, memoryPointers, heap)
else if(line=="%instructions") { else if(line=="%instructions") {
val (blockInstructions, blockLabels) = loadInstructions(lines, heap) val (blockInstructions, blockLabels) = loadInstructions(lines, heap)
val baseIndex = program.size
program.addAll(blockInstructions) program.addAll(blockInstructions)
labels.putAll(blockLabels) val labelsWithIndex = blockLabels.mapValues { baseIndex+blockInstructions.indexOf(it.value) }
labels.putAll(labelsWithIndex)
} }
} }
} }
@ -88,22 +89,32 @@ class Program (val name: String,
} }
heapvalues.sortedBy { it.first }.forEach { heapvalues.sortedBy { it.first }.forEach {
when(it.second) { when(it.second) {
DataType.STR, DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length-1), Position("<stackvmsource>", 0, 0, 0)))
DataType.STR_P,
DataType.STR_S,
DataType.STR_PS -> heap.add(it.second, unescape(it.third.substring(1, it.third.length-1), Position("<stackvmsource>", 0, 0, 0)))
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numbers = it.third.substring(1, it.third.length-1).split(',') val numbers = it.third.substring(1, it.third.length-1).split(',')
val intarray = numbers.map{number->number.trim().toInt()}.toIntArray() val intarray = numbers.map{number->
heap.add(it.second, intarray) val num=number.trim()
if(num.startsWith("&")) {
// it's AddressOf
val scopedname = num.substring(1)
val iref = IdentifierReference(scopedname.split('.'), Position("<intermediate>", 0,0,0))
val addrOf = AddressOf(iref, Position("<intermediate>", 0,0,0))
addrOf.scopedname=scopedname
IntegerOrAddressOf(null, addrOf)
} else {
IntegerOrAddressOf(num.toInt(), null)
}
}.toTypedArray()
heap.addIntegerArray(it.second, intarray)
} }
DataType.ARRAY_F -> { DataType.ARRAY_F -> {
val numbers = it.third.substring(1, it.third.length-1).split(',') val numbers = it.third.substring(1, it.third.length-1).split(',')
val doublearray = numbers.map{number->number.trim().toDouble()}.toDoubleArray() val doublearray = numbers.map{number->number.trim().toDouble()}.toDoubleArray()
heap.add(it.second, doublearray) heap.addDoublesArray(doublearray)
} }
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> throw VmExecutionException("invalid heap value type ${it.second}") in NumericDatatypes -> throw VmExecutionException("invalid heap value type ${it.second}")
else -> throw VmExecutionException("weird datatype")
} }
} }
} }
@ -112,7 +123,7 @@ class Program (val name: String,
val instructions = mutableListOf<Instruction>() val instructions = mutableListOf<Instruction>()
val labels = mutableMapOf<String, Instruction>() val labels = mutableMapOf<String, Instruction>()
val splitpattern = Pattern.compile("\\s+") val splitpattern = Pattern.compile("\\s+")
val nextInstructionLabels = Stack<String>() // more than one label can occur on the same line val nextInstructionLabels = Stack<String>() // more than one label can occur on the isSameAs line
while(true) { while(true) {
val (lineNr, line) = lines.next() val (lineNr, line) = lines.next()
@ -133,7 +144,7 @@ class Program (val name: String,
Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC, Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC,
Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> { Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> {
if(args!!.startsWith('$')) { if(args!!.startsWith('$')) {
Instruction(opcode, Value(DataType.UWORD, args.substring(1).toInt(16))) Instruction(opcode, RuntimeValue(DataType.UWORD, args.substring(1).toInt(16)))
} else { } else {
Instruction(opcode, callLabel = args) Instruction(opcode, callLabel = args)
} }
@ -146,8 +157,26 @@ class Program (val name: String,
Instruction(opcode, callLabel = withoutQuotes) Instruction(opcode, callLabel = withoutQuotes)
} }
Opcode.SYSCALL -> { Opcode.SYSCALL -> {
val call = Syscall.valueOf(args!!) if(args!! in syscallNames) {
Instruction(opcode, Value(DataType.UBYTE, call.callNr)) val call = Syscall.valueOf(args)
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
} else {
val args2 = args.replace('.', '_')
if(args2 in syscallNames) {
val call = Syscall.valueOf(args2)
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
} else {
// the syscall is not yet implemented. emit a stub.
Instruction(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
}
}
}
Opcode.INCLUDE_FILE -> {
val argparts = args!!.split(' ')
val filename = argparts[0]
val offset = if(argparts.size>=2 && argparts[1]!="null") getArgValue(argparts[1], heap) else null
val length = if(argparts.size>=3 && argparts[2]!="null") getArgValue(argparts[2], heap) else null
Instruction(opcode, offset, length, filename)
} }
else -> { else -> {
Instruction(opcode, getArgValue(args, heap)) Instruction(opcode, getArgValue(args, heap))
@ -162,7 +191,7 @@ class Program (val name: String,
} }
} }
private fun getArgValue(args: String?, heap: HeapValues): Value? { private fun getArgValue(args: String?, heap: HeapValues): RuntimeValue? {
if(args==null) if(args==null)
return null return null
if(args[0]=='"' && args[args.length-1]=='"') { if(args[0]=='"' && args[args.length-1]=='"') {
@ -170,21 +199,21 @@ class Program (val name: String,
} }
val (type, valueStr) = args.split(':') val (type, valueStr) = args.split(':')
return when(type) { return when(type) {
"b" -> Value(DataType.BYTE, valueStr.toShort(16)) "b" -> RuntimeValue(DataType.BYTE, valueStr.toShort(16))
"ub" -> Value(DataType.UBYTE, valueStr.toShort(16)) "ub" -> RuntimeValue(DataType.UBYTE, valueStr.toShort(16))
"w" -> Value(DataType.WORD, valueStr.toInt(16)) "w" -> RuntimeValue(DataType.WORD, valueStr.toInt(16))
"uw" -> Value(DataType.UWORD, valueStr.toInt(16)) "uw" -> RuntimeValue(DataType.UWORD, valueStr.toInt(16))
"f" -> Value(DataType.FLOAT, valueStr.toDouble()) "f" -> RuntimeValue(DataType.FLOAT, valueStr.toDouble())
"heap" -> { "heap" -> {
val heapId = valueStr.toInt() val heapId = valueStr.toInt()
Value(heap.get(heapId).type, heapId) RuntimeValue(heap.get(heapId).type, heapId = heapId)
} }
else -> throw VmExecutionException("invalid datatype $type") else -> throw VmExecutionException("invalid datatype $type")
} }
} }
private fun loadVars(lines: Iterator<IndexedValue<String>>, private fun loadVars(lines: Iterator<IndexedValue<String>>,
vars: MutableMap<String, Value>) { vars: MutableMap<String, RuntimeValue>) {
val splitpattern = Pattern.compile("\\s+") val splitpattern = Pattern.compile("\\s+")
while(true) { while(true) {
val (_, line) = lines.next() val (_, line) = lines.next()
@ -193,35 +222,31 @@ class Program (val name: String,
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3) val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
if(valueStr[0] !='"' && ':' !in valueStr) if(valueStr[0] !='"' && ':' !in valueStr)
throw VmExecutionException("missing value type character") throw VmExecutionException("missing value type character")
val type = DataType.valueOf(typeStr.toUpperCase()) val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) {
val value = when(type) { DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).toShort(16))
DataType.UBYTE -> Value(DataType.UBYTE, valueStr.substring(3).toShort(16)) DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).toShort(16))
DataType.BYTE -> Value(DataType.BYTE, valueStr.substring(2).toShort(16)) DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).toInt(16))
DataType.UWORD -> Value(DataType.UWORD, valueStr.substring(3).toInt(16)) DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).toInt(16))
DataType.WORD -> Value(DataType.WORD, valueStr.substring(2).toInt(16)) DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).toDouble())
DataType.FLOAT -> Value(DataType.FLOAT, valueStr.substring(2).toDouble()) in StringDatatypes -> {
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
if(valueStr.startsWith('"') && valueStr.endsWith('"')) if(valueStr.startsWith('"') && valueStr.endsWith('"'))
throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap") throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap")
else if(!valueStr.startsWith("heap:")) else if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid string value, should be a heap reference") throw VmExecutionException("invalid string value, should be a heap reference")
else { else {
val heapId = valueStr.substring(5).toInt() val heapId = valueStr.substring(5).toInt()
Value(type, heapId) RuntimeValue(type, heapId = heapId)
} }
} }
DataType.ARRAY_UB, in ArrayDatatypes -> {
DataType.ARRAY_B,
DataType.ARRAY_UW,
DataType.ARRAY_W,
DataType.ARRAY_F -> {
if(!valueStr.startsWith("heap:")) if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid array value, should be a heap reference") throw VmExecutionException("invalid array value, should be a heap reference")
else { else {
val heapId = valueStr.substring(5).toInt() val heapId = valueStr.substring(5).toInt()
Value(type, heapId) RuntimeValue(type, heapId = heapId)
} }
} }
else -> throw VmExecutionException("weird datatype")
} }
vars[name] = value vars[name] = value
} }
@ -244,7 +269,7 @@ class Program (val name: String,
} }
} }
private fun loadMemory(lines: Iterator<IndexedValue<String>>, memory: MutableMap<Int, List<Value>>): Map<Int, List<Value>> { private fun loadMemory(lines: Iterator<IndexedValue<String>>, memory: MutableMap<Int, List<RuntimeValue>>): Map<Int, List<RuntimeValue>> {
while(true) { while(true) {
val (lineNr, line) = lines.next() val (lineNr, line) = lines.next()
if(line=="%end_memory") if(line=="%end_memory")
@ -255,11 +280,11 @@ class Program (val name: String,
TODO("memory init with char/string") TODO("memory init with char/string")
} else { } else {
val valueStrings = rest.split(' ') val valueStrings = rest.split(' ')
val values = mutableListOf<Value>() val values = mutableListOf<RuntimeValue>()
valueStrings.forEach { valueStrings.forEach {
when(it.length) { when(it.length) {
2 -> values.add(Value(DataType.UBYTE, it.toShort(16))) 2 -> values.add(RuntimeValue(DataType.UBYTE, it.toShort(16)))
4 -> values.add(Value(DataType.UWORD, it.toInt(16))) 4 -> values.add(RuntimeValue(DataType.UWORD, it.toInt(16)))
else -> throw VmExecutionException("invalid value at line $lineNr+1") else -> throw VmExecutionException("invalid value at line $lineNr+1")
} }
} }
@ -268,50 +293,4 @@ class Program (val name: String,
} }
} }
} }
private fun connect() {
val it1 = program.iterator()
val it2 = program.iterator()
it2.next()
while(it1.hasNext() && it2.hasNext()) {
val instr = it1.next()
val nextInstr = it2.next()
when(instr.opcode) {
Opcode.TERMINATE -> instr.next = instr // won't ever execute a next instruction
Opcode.RETURN -> instr.next = instr // kinda a special one, in actuality the return instruction is dynamic
Opcode.JUMP -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support JUMP to memory address")
} else {
// jump to label
val target = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = target
}
}
Opcode.BCC, Opcode.BCS, Opcode.BZ, Opcode.BNZ, Opcode.BNEG, Opcode.BPOS, Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support branch to memory address")
} else {
// branch to label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr
}
}
Opcode.CALL -> {
if(instr.callLabel==null) {
throw VmExecutionException("stackVm doesn't support CALL to memory address")
} else {
// call label
val jumpInstr = labels[instr.callLabel] ?: throw VmExecutionException("undefined label: ${instr.callLabel}")
instr.next = jumpInstr
instr.nextAlt = nextInstr // instruction to return to
}
}
else -> instr.next = nextInstr
}
}
}
} }

View File

@ -1,6 +1,7 @@
package prog8.stackvm package prog8.stackvm
import prog8.compiler.target.c64.Charset import prog8.compiler.target.c64.Charset
import prog8.compiler.target.c64.Colors
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import java.awt.* import java.awt.*
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
@ -15,6 +16,8 @@ class BitmapScreenPanel : KeyListener, JPanel() {
private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB) private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB)
private val g2d = image.graphics as Graphics2D private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0
private var cursorY: Int=0
init { init {
val size = Dimension(image.width * SCALING, image.height * SCALING) val size = Dimension(image.width * SCALING, image.height * SCALING)
@ -45,34 +48,81 @@ class BitmapScreenPanel : KeyListener, JPanel() {
g2d.drawImage(image, 0, 0, image.width * 3, image.height * 3, null) g2d.drawImage(image, 0, 0, image.width * 3, image.height * 3, null)
} }
fun clearScreen(color: Int) { fun clearScreen(color: Short) {
g2d.background = palette[color and 15] g2d.background = Colors.palette[color % Colors.palette.size]
g2d.clearRect(0, 0, BitmapScreenPanel.SCREENWIDTH, BitmapScreenPanel.SCREENHEIGHT) g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT)
cursorX = 0
cursorY = 0
} }
fun setPixel(x: Int, y: Int, color: Int) { fun setPixel(x: Int, y: Int, color: Short) {
image.setRGB(x, y, palette[color and 15].rgb) image.setRGB(x, y, Colors.palette[color % Colors.palette.size].rgb)
} }
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Int) { fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) {
g2d.color = palette[color and 15] g2d.color = Colors.palette[color % Colors.palette.size]
g2d.drawLine(x1, y1, x2, y2) g2d.drawLine(x1, y1, x2, y2)
} }
fun writeText(x: Int, y: Int, text: String, color: Int) { fun printText(text: String, color: Short, lowercase: Boolean) {
if(color!=1) { val lines = text.split('\n')
TODO("text can only be white for now") for(line in lines.withIndex()) {
} printTextSingleLine(line.value, color, lowercase)
var xx=x if(line.index<lines.size-1) {
var yy=y cursorX=0
for(sc in Petscii.encodeScreencode(text, true)) { cursorY++
setChar(xx, yy, sc)
xx++
if(xx>=(SCREENWIDTH/8)) {
yy++
xx=0
} }
} }
} }
fun setChar(x: Int, y: Int, screenCode: Short) { private fun printTextSingleLine(text: String, color: Short, lowercase: Boolean) {
g2d.drawImage(Charset.shiftedChars[screenCode.toInt()], 8*x, 8*y , null) for(clearx in cursorX until cursorX+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(cursorX, cursorY, sc, color)
cursorX++
if(cursorX>=(SCREENWIDTH/8)) {
cursorY++
cursorX=0
}
}
}
fun printChar(char: Short) {
if(char==13.toShort() || char==141.toShort()) {
cursorX=0
cursorY++
} else {
setChar(cursorX, cursorY, char, 1)
cursorX++
if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++
cursorX = 0
}
}
}
fun setChar(x: Int, y: Int, screenCode: Short, color: Short) {
g2d.clearRect(8*x, 8*y, 8, 8)
val colorIdx = (color % Colors.palette.size).toShort()
val coloredImage = Charset.getColoredChar(screenCode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null)
}
fun setCursorPos(x: Int, y: Int) {
cursorX = x
cursorY = y
}
fun getCursorPos(): Pair<Int, Int> {
return Pair(cursorX, cursorY)
}
fun writeText(x: Int, y: Int, text: String, color: Short, lowercase: Boolean) {
var xx=x
for(clearx in xx until xx+text.length) {
g2d.clearRect(8*clearx, 8*y, 8, 8)
}
for(sc in Petscii.encodeScreencode(text, lowercase)) {
setChar(xx++, y, sc, color)
}
} }
@ -80,24 +130,6 @@ class BitmapScreenPanel : KeyListener, JPanel() {
const val SCREENWIDTH = 320 const val SCREENWIDTH = 320
const val SCREENHEIGHT = 200 const val SCREENHEIGHT = 200
const val SCALING = 3 const val SCALING = 3
val palette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
Color(0x813338), // 2 = red
Color(0x75cec8), // 3 = cyan
Color(0x8e3c97), // 4 = purple
Color(0x56ac4d), // 5 = green
Color(0x2e2c9b), // 6 = blue
Color(0xedf171), // 7 = yellow
Color(0x8e5029), // 8 = orange
Color(0x553800), // 9 = brown
Color(0xc46c71), // 10 = light red
Color(0x4a4a4a), // 11 = dark grey
Color(0x7b7b7b), // 12 = medium grey
Color(0xa9ff9f), // 13 = light green
Color(0x706deb), // 14 = light blue
Color(0xb2b2b2) // 15 = light grey
)
} }
} }
@ -115,19 +147,19 @@ class ScreenDialog : JFrame() {
// the borders (top, left, right, bottom) // the borders (top, left, right, bottom)
val borderTop = JPanel().apply { val borderTop = JPanel().apply {
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth) preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = BitmapScreenPanel.palette[14] background = Colors.palette[14]
} }
val borderBottom = JPanel().apply { val borderBottom = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth) preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = BitmapScreenPanel.palette[14] background = Colors.palette[14]
} }
val borderLeft = JPanel().apply { val borderLeft = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT) preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = BitmapScreenPanel.palette[14] background = Colors.palette[14]
} }
val borderRight = JPanel().apply { val borderRight = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT) preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = BitmapScreenPanel.palette[14] background = Colors.palette[14]
} }
var c = GridBagConstraints() var c = GridBagConstraints()
c.gridx=0; c.gridy=1; c.gridwidth=3 c.gridx=0; c.gridy=1; c.gridwidth=3

File diff suppressed because it is too large Load Diff

View File

@ -5,182 +5,14 @@ import org.junit.jupiter.api.TestInstance
import prog8.ast.DataType import prog8.ast.DataType
import prog8.ast.LiteralValue import prog8.ast.LiteralValue
import prog8.ast.Position import prog8.ast.Position
import prog8.compiler.intermediate.Value
import prog8.compiler.intermediate.ValueException
import kotlin.test.* import kotlin.test.*
private fun sameValueAndType(v1: Value, v2: Value): Boolean {
return v1.type==v2.type && v1==v2
}
private fun sameValueAndType(lv1: LiteralValue, lv2: LiteralValue): Boolean { private fun sameValueAndType(lv1: LiteralValue, lv2: LiteralValue): Boolean {
return lv1.type==lv2.type && lv1==lv2 return lv1.type==lv2.type && lv1==lv2
} }
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestStackVmValue {
@Test
fun testIdentity() {
val v = Value(DataType.UWORD, 12345)
assertEquals(v, v)
assertFalse(v != v)
assertTrue(v<=v)
assertTrue(v>=v)
assertFalse(v<v)
assertFalse(v>v)
assertTrue(sameValueAndType(Value(DataType.UBYTE, 100), Value(DataType.UBYTE, 100)))
}
@Test
fun testEqualsAndNotEquals() {
assertEquals(Value(DataType.UBYTE, 100), Value(DataType.UBYTE, 100))
assertEquals(Value(DataType.UBYTE, 100), Value(DataType.UWORD, 100))
assertEquals(Value(DataType.UBYTE, 100), Value(DataType.FLOAT, 100))
assertEquals(Value(DataType.UWORD, 254), Value(DataType.UBYTE, 254))
assertEquals(Value(DataType.UWORD, 12345), Value(DataType.UWORD, 12345))
assertEquals(Value(DataType.UWORD, 12345), Value(DataType.FLOAT, 12345))
assertEquals(Value(DataType.FLOAT, 100.0), Value(DataType.UBYTE, 100))
assertEquals(Value(DataType.FLOAT, 22239.0), Value(DataType.UWORD, 22239))
assertEquals(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.99))
assertTrue(sameValueAndType(Value(DataType.UBYTE, 100), Value(DataType.UBYTE, 100)))
assertFalse(sameValueAndType(Value(DataType.UBYTE, 100), Value(DataType.UWORD, 100)))
assertFalse(sameValueAndType(Value(DataType.UBYTE, 100), Value(DataType.FLOAT, 100)))
assertFalse(sameValueAndType(Value(DataType.UWORD, 254), Value(DataType.UBYTE, 254)))
assertTrue(sameValueAndType(Value(DataType.UWORD, 12345), Value(DataType.UWORD, 12345)))
assertFalse(sameValueAndType(Value(DataType.UWORD, 12345), Value(DataType.FLOAT, 12345)))
assertFalse(sameValueAndType(Value(DataType.FLOAT, 100.0), Value(DataType.UBYTE, 100)))
assertFalse(sameValueAndType(Value(DataType.FLOAT, 22239.0), Value(DataType.UWORD, 22239)))
assertTrue(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.99)))
assertNotEquals(Value(DataType.UBYTE, 100), Value(DataType.UBYTE, 101))
assertNotEquals(Value(DataType.UBYTE, 100), Value(DataType.UWORD, 101))
assertNotEquals(Value(DataType.UBYTE, 100), Value(DataType.FLOAT, 101))
assertNotEquals(Value(DataType.UWORD, 245), Value(DataType.UBYTE, 246))
assertNotEquals(Value(DataType.UWORD, 12345), Value(DataType.UWORD, 12346))
assertNotEquals(Value(DataType.UWORD, 12345), Value(DataType.FLOAT, 12346))
assertNotEquals(Value(DataType.FLOAT, 9.99), Value(DataType.UBYTE, 9))
assertNotEquals(Value(DataType.FLOAT, 9.99), Value(DataType.UWORD, 9))
assertNotEquals(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.0))
assertFalse(sameValueAndType(Value(DataType.UBYTE, 100), Value(DataType.UBYTE, 101)))
assertFalse(sameValueAndType(Value(DataType.UBYTE, 100), Value(DataType.UWORD, 101)))
assertFalse(sameValueAndType(Value(DataType.UBYTE, 100), Value(DataType.FLOAT, 101)))
assertFalse(sameValueAndType(Value(DataType.UWORD, 245), Value(DataType.UBYTE, 246)))
assertFalse(sameValueAndType(Value(DataType.UWORD, 12345), Value(DataType.UWORD, 12346)))
assertFalse(sameValueAndType(Value(DataType.UWORD, 12345), Value(DataType.FLOAT, 12346)))
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.UBYTE, 9)))
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.UWORD, 9)))
assertFalse(sameValueAndType(Value(DataType.FLOAT, 9.99), Value(DataType.FLOAT, 9.0)))
}
@Test
fun testEqualsAndNotEqualsHeapTypes()
{
assertTrue(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 999)))
assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR_P, 999)))
assertFalse(sameValueAndType(Value(DataType.STR, 999), Value(DataType.STR, 222)))
assertTrue(sameValueAndType(Value(DataType.ARRAY_UB, 99), Value(DataType.ARRAY_UB, 99)))
assertFalse(sameValueAndType(Value(DataType.ARRAY_UB, 99), Value(DataType.ARRAY_UB, 22)))
assertTrue(sameValueAndType(Value(DataType.ARRAY_UW, 999), Value(DataType.ARRAY_UW, 999)))
assertFalse(sameValueAndType(Value(DataType.ARRAY_UW, 999), Value(DataType.ARRAY_UW, 222)))
assertTrue(sameValueAndType(Value(DataType.ARRAY_F, 999), Value(DataType.ARRAY_F, 999)))
assertFalse(sameValueAndType(Value(DataType.ARRAY_F, 999), Value(DataType.ARRAY_UW, 999)))
assertFalse(sameValueAndType(Value(DataType.ARRAY_F, 999), Value(DataType.ARRAY_F, 222)))
}
@Test
fun testGreaterThan(){
assertTrue(Value(DataType.UBYTE, 100) > Value(DataType.UBYTE, 99))
assertTrue(Value(DataType.UWORD, 254) > Value(DataType.UWORD, 253))
assertTrue(Value(DataType.FLOAT, 100.0) > Value(DataType.FLOAT, 99.9))
assertTrue(Value(DataType.UBYTE, 100) >= Value(DataType.UBYTE, 100))
assertTrue(Value(DataType.UWORD, 254) >= Value(DataType.UWORD, 254))
assertTrue(Value(DataType.FLOAT, 100.0) >= Value(DataType.FLOAT, 100.0))
assertFalse(Value(DataType.UBYTE, 100) > Value(DataType.UBYTE, 100))
assertFalse(Value(DataType.UWORD, 254) > Value(DataType.UWORD, 254))
assertFalse(Value(DataType.FLOAT, 100.0) > Value(DataType.FLOAT, 100.0))
assertFalse(Value(DataType.UBYTE, 100) >= Value(DataType.UBYTE, 101))
assertFalse(Value(DataType.UWORD, 254) >= Value(DataType.UWORD, 255))
assertFalse(Value(DataType.FLOAT, 100.0) >= Value(DataType.FLOAT, 100.1))
}
@Test
fun testLessThan() {
assertTrue(Value(DataType.UBYTE, 100) < Value(DataType.UBYTE, 101))
assertTrue(Value(DataType.UWORD, 254) < Value(DataType.UWORD, 255))
assertTrue(Value(DataType.FLOAT, 100.0) < Value(DataType.FLOAT, 100.1))
assertTrue(Value(DataType.UBYTE, 100) <= Value(DataType.UBYTE, 100))
assertTrue(Value(DataType.UWORD, 254) <= Value(DataType.UWORD, 254))
assertTrue(Value(DataType.FLOAT, 100.0) <= Value(DataType.FLOAT, 100.0))
assertFalse(Value(DataType.UBYTE, 100) < Value(DataType.UBYTE, 100))
assertFalse(Value(DataType.UWORD, 254) < Value(DataType.UWORD, 254))
assertFalse(Value(DataType.FLOAT, 100.0) < Value(DataType.FLOAT, 100.0))
assertFalse(Value(DataType.UBYTE, 100) <= Value(DataType.UBYTE, 99))
assertFalse(Value(DataType.UWORD, 254) <= Value(DataType.UWORD, 253))
assertFalse(Value(DataType.FLOAT, 100.0) <= Value(DataType.FLOAT, 99.9))
}
@Test
fun testNoDtConversion() {
assertFailsWith<ValueException> {
Value(DataType.UWORD, 100).add(Value(DataType.UBYTE, 120))
}
assertFailsWith<ValueException> {
Value(DataType.UBYTE, 100).add(Value(DataType.UWORD, 120))
}
assertFailsWith<ValueException> {
Value(DataType.FLOAT, 100.22).add(Value(DataType.UWORD, 120))
}
assertFailsWith<ValueException> {
Value(DataType.UWORD, 1002).add(Value(DataType.FLOAT, 120.22))
}
assertFailsWith<ValueException> {
Value(DataType.FLOAT, 100.22).add(Value(DataType.UBYTE, 120))
}
assertFailsWith<ValueException> {
Value(DataType.UBYTE, 12).add(Value(DataType.FLOAT, 120.22))
}
}
@Test
fun testNoAutoFloatConversion() {
assertFailsWith<ValueException> {
Value(DataType.UBYTE, 233).add(Value(DataType.FLOAT, 1.234))
}
assertFailsWith<ValueException> {
Value(DataType.UWORD, 233).add(Value(DataType.FLOAT, 1.234))
}
assertFailsWith<ValueException> {
Value(DataType.UBYTE, 233).mul(Value(DataType.FLOAT, 1.234))
}
assertFailsWith<ValueException> {
Value(DataType.UWORD, 233).mul(Value(DataType.FLOAT, 1.234))
}
assertFailsWith<ValueException> {
Value(DataType.UBYTE, 233).div(Value(DataType.FLOAT, 1.234))
}
assertFailsWith<ValueException> {
Value(DataType.UWORD, 233).div(Value(DataType.FLOAT, 1.234))
}
val result = Value(DataType.FLOAT, 233.333).add(Value(DataType.FLOAT, 1.234))
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestParserLiteralValue { class TestParserLiteralValue {

View File

@ -0,0 +1,248 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.DataType
import prog8.compiler.RuntimeValue
import kotlin.test.*
private fun sameValueAndType(v1: RuntimeValue, v2: RuntimeValue): Boolean {
return v1.type==v2.type && v1==v2
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestRuntimeValue {
@Test
fun testValueRanges() {
assertEquals(100, RuntimeValue(DataType.UBYTE, 100).integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 100).integerValue())
assertEquals(10000, RuntimeValue(DataType.UWORD, 10000).integerValue())
assertEquals(10000, RuntimeValue(DataType.WORD, 10000).integerValue())
assertEquals(100.11, RuntimeValue(DataType.FLOAT, 100.11).numericValue())
assertEquals(200, RuntimeValue(DataType.UBYTE, 200).integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 200).integerValue())
assertEquals(50000, RuntimeValue(DataType.UWORD, 50000).integerValue())
assertEquals(-15536, RuntimeValue(DataType.WORD, 50000).integerValue())
assertEquals(44, RuntimeValue(DataType.UBYTE, 300).integerValue())
assertEquals(44, RuntimeValue(DataType.BYTE, 300).integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 400).integerValue())
assertEquals(-112, RuntimeValue(DataType.BYTE, 400).integerValue())
assertEquals(34463, RuntimeValue(DataType.UWORD, 99999).integerValue())
assertEquals(-31073, RuntimeValue(DataType.WORD, 99999).integerValue())
assertEquals(156, RuntimeValue(DataType.UBYTE, -100).integerValue())
assertEquals(-100, RuntimeValue(DataType.BYTE, -100).integerValue())
assertEquals(55536, RuntimeValue(DataType.UWORD, -10000).integerValue())
assertEquals(-10000, RuntimeValue(DataType.WORD, -10000).integerValue())
assertEquals(-100.11, RuntimeValue(DataType.FLOAT, -100.11).numericValue())
assertEquals(56, RuntimeValue(DataType.UBYTE, -200).integerValue())
assertEquals(56, RuntimeValue(DataType.BYTE, -200).integerValue())
assertEquals(45536, RuntimeValue(DataType.UWORD, -20000).integerValue())
assertEquals(-20000, RuntimeValue(DataType.WORD, -20000).integerValue())
assertEquals(212, RuntimeValue(DataType.UBYTE, -300).integerValue())
assertEquals(-44, RuntimeValue(DataType.BYTE, -300).integerValue())
assertEquals(42184, RuntimeValue(DataType.UWORD, -88888).integerValue())
assertEquals(-23352, RuntimeValue(DataType.WORD, -88888).integerValue())
}
@Test
fun testTruthiness()
{
assertFalse(RuntimeValue(DataType.BYTE, 0).asBoolean)
assertFalse(RuntimeValue(DataType.UBYTE, 0).asBoolean)
assertFalse(RuntimeValue(DataType.WORD, 0).asBoolean)
assertFalse(RuntimeValue(DataType.UWORD, 0).asBoolean)
assertFalse(RuntimeValue(DataType.FLOAT, 0.0).asBoolean)
assertFalse(RuntimeValue(DataType.BYTE, 256).asBoolean)
assertFalse(RuntimeValue(DataType.UBYTE, 256).asBoolean)
assertFalse(RuntimeValue(DataType.WORD, 65536).asBoolean)
assertFalse(RuntimeValue(DataType.UWORD, 65536).asBoolean)
assertTrue(RuntimeValue(DataType.BYTE, 42).asBoolean)
assertTrue(RuntimeValue(DataType.UBYTE, 42).asBoolean)
assertTrue(RuntimeValue(DataType.WORD, 42).asBoolean)
assertTrue(RuntimeValue(DataType.UWORD, 42).asBoolean)
assertTrue(RuntimeValue(DataType.FLOAT, 42.0).asBoolean)
assertTrue(RuntimeValue(DataType.BYTE, -42).asBoolean)
assertTrue(RuntimeValue(DataType.UBYTE, -42).asBoolean)
assertTrue(RuntimeValue(DataType.WORD, -42).asBoolean)
assertTrue(RuntimeValue(DataType.UWORD, -42).asBoolean)
assertTrue(RuntimeValue(DataType.FLOAT, -42.0).asBoolean)
}
@Test
fun testIdentity() {
val v = RuntimeValue(DataType.UWORD, 12345)
assertEquals(v, v)
assertFalse(v != v)
assertTrue(v<=v)
assertTrue(v>=v)
assertFalse(v<v)
assertFalse(v>v)
assertTrue(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100)))
}
@Test
fun testEqualsAndNotEquals() {
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100))
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 100))
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 100))
assertEquals(RuntimeValue(DataType.UWORD, 254), RuntimeValue(DataType.UBYTE, 254))
assertEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12345))
assertEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12345))
assertEquals(RuntimeValue(DataType.FLOAT, 100.0), RuntimeValue(DataType.UBYTE, 100))
assertEquals(RuntimeValue(DataType.FLOAT, 22239.0), RuntimeValue(DataType.UWORD, 22239))
assertEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.99))
assertTrue(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 254), RuntimeValue(DataType.UBYTE, 254)))
assertTrue(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12345)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12345)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 100.0), RuntimeValue(DataType.UBYTE, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 22239.0), RuntimeValue(DataType.UWORD, 22239)))
assertTrue(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.99)))
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 101))
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 101))
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 101))
assertNotEquals(RuntimeValue(DataType.UWORD, 245), RuntimeValue(DataType.UBYTE, 246))
assertNotEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12346))
assertNotEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12346))
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UBYTE, 9))
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UWORD, 9))
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.0))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 101)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 101)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 101)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 245), RuntimeValue(DataType.UBYTE, 246)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12346)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12346)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UBYTE, 9)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UWORD, 9)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.0)))
}
@Test
fun testRequireHeap()
{
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.STR, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.STR_S, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_F, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_W, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_UW, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_B, num = 999) }
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.ARRAY_UB, num = 999) }
}
@Test
fun testEqualityHeapTypes()
{
assertTrue(sameValueAndType(RuntimeValue(DataType.STR, heapId = 999), RuntimeValue(DataType.STR, heapId = 999)))
assertFalse(sameValueAndType(RuntimeValue(DataType.STR, heapId = 999), RuntimeValue(DataType.STR, heapId = 222)))
assertTrue(sameValueAndType(RuntimeValue(DataType.ARRAY_UB, heapId = 99), RuntimeValue(DataType.ARRAY_UB, heapId = 99)))
assertFalse(sameValueAndType(RuntimeValue(DataType.ARRAY_UB, heapId = 99), RuntimeValue(DataType.ARRAY_UB, heapId = 22)))
assertTrue(sameValueAndType(RuntimeValue(DataType.ARRAY_UW, heapId = 999), RuntimeValue(DataType.ARRAY_UW, heapId = 999)))
assertFalse(sameValueAndType(RuntimeValue(DataType.ARRAY_UW, heapId = 999), RuntimeValue(DataType.ARRAY_UW, heapId = 222)))
assertTrue(sameValueAndType(RuntimeValue(DataType.ARRAY_F, heapId = 999), RuntimeValue(DataType.ARRAY_F, heapId = 999)))
assertFalse(sameValueAndType(RuntimeValue(DataType.ARRAY_F, heapId = 999), RuntimeValue(DataType.ARRAY_UW, heapId = 999)))
assertFalse(sameValueAndType(RuntimeValue(DataType.ARRAY_F, heapId = 999), RuntimeValue(DataType.ARRAY_F, heapId = 222)))
}
@Test
fun testGreaterThan(){
assertTrue(RuntimeValue(DataType.UBYTE, 100) > RuntimeValue(DataType.UBYTE, 99))
assertTrue(RuntimeValue(DataType.UWORD, 254) > RuntimeValue(DataType.UWORD, 253))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) > RuntimeValue(DataType.FLOAT, 99.9))
assertTrue(RuntimeValue(DataType.UBYTE, 100) >= RuntimeValue(DataType.UBYTE, 100))
assertTrue(RuntimeValue(DataType.UWORD, 254) >= RuntimeValue(DataType.UWORD, 254))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) >= RuntimeValue(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) > RuntimeValue(DataType.UBYTE, 100))
assertFalse(RuntimeValue(DataType.UWORD, 254) > RuntimeValue(DataType.UWORD, 254))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) > RuntimeValue(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) >= RuntimeValue(DataType.UBYTE, 101))
assertFalse(RuntimeValue(DataType.UWORD, 254) >= RuntimeValue(DataType.UWORD, 255))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) >= RuntimeValue(DataType.FLOAT, 100.1))
}
@Test
fun testLessThan() {
assertTrue(RuntimeValue(DataType.UBYTE, 100) < RuntimeValue(DataType.UBYTE, 101))
assertTrue(RuntimeValue(DataType.UWORD, 254) < RuntimeValue(DataType.UWORD, 255))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) < RuntimeValue(DataType.FLOAT, 100.1))
assertTrue(RuntimeValue(DataType.UBYTE, 100) <= RuntimeValue(DataType.UBYTE, 100))
assertTrue(RuntimeValue(DataType.UWORD, 254) <= RuntimeValue(DataType.UWORD, 254))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) <= RuntimeValue(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) < RuntimeValue(DataType.UBYTE, 100))
assertFalse(RuntimeValue(DataType.UWORD, 254) < RuntimeValue(DataType.UWORD, 254))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) < RuntimeValue(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) <= RuntimeValue(DataType.UBYTE, 99))
assertFalse(RuntimeValue(DataType.UWORD, 254) <= RuntimeValue(DataType.UWORD, 253))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) <= RuntimeValue(DataType.FLOAT, 99.9))
}
@Test
fun testNoDtConversion() {
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 100).add(RuntimeValue(DataType.UBYTE, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 100).add(RuntimeValue(DataType.UWORD, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.FLOAT, 100.22).add(RuntimeValue(DataType.UWORD, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 1002).add(RuntimeValue(DataType.FLOAT, 120.22))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.FLOAT, 100.22).add(RuntimeValue(DataType.UBYTE, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 12).add(RuntimeValue(DataType.FLOAT, 120.22))
}
}
@Test
fun testNoAutoFloatConversion() {
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 233).add(RuntimeValue(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 233).add(RuntimeValue(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 233).mul(RuntimeValue(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 233).mul(RuntimeValue(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 233).div(RuntimeValue(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 233).div(RuntimeValue(DataType.FLOAT, 1.234))
}
val result = RuntimeValue(DataType.FLOAT, 233.333).add(RuntimeValue(DataType.FLOAT, 1.234))
}
}

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,8 @@ import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.* import prog8.ast.*
import prog8.compiler.RuntimeValue
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.intermediate.Value
import prog8.compiler.target.c64.* import prog8.compiler.target.c64.*
import java.io.CharConversionException import java.io.CharConversionException
import kotlin.test.* import kotlin.test.*
@ -268,6 +268,14 @@ class TestZeropage {
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestPetscii { class TestPetscii {
@Test
fun testZero() {
assertThat(Petscii.encodePetscii("\u0000", true), equalTo(listOf<Short>(0)))
assertThat(Petscii.encodePetscii("\u0000", false), equalTo(listOf<Short>(0)))
assertThat(Petscii.decodePetscii(listOf(0), true), equalTo("\u0000"))
assertThat(Petscii.decodePetscii(listOf(0), false), equalTo("\u0000"))
}
@Test @Test
fun testLowercase() { fun testLowercase() {
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo( assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
@ -358,8 +366,8 @@ class TestPetscii {
@Test @Test
fun testStackvmValueComparisons() { fun testStackvmValueComparisons() {
val ten = Value(DataType.FLOAT, 10) val ten = RuntimeValue(DataType.FLOAT, 10)
val nine = Value(DataType.UWORD, 9) val nine = RuntimeValue(DataType.UWORD, 9)
assertEquals(ten, ten) assertEquals(ten, ten)
assertNotEquals(ten, nine) assertNotEquals(ten, nine)
assertFalse(ten != ten) assertFalse(ten != ten)

21
create_compiler_jar.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/sh
# this script uses the Gradle build to compile the code,
# and then adds the contents of several jar files into one output jar.
./gradlew jar
mkdir -p compiler_jar/extracted
mkdir -p compiler_jar/source
cp compiler/build/libs/compiler.jar parser/build/libs/parser.jar parser/antlr/lib/antlr-runtime-4.7.2.jar compiler_jar/source/
KOTLINLIBS=$(kotlinc -verbose -script 2>&1 | grep home | cut -d ' ' -f 6-)/lib
cp ${KOTLINLIBS}/kotlin-stdlib-jdk8.jar ${KOTLINLIBS}/kotlin-stdlib.jar compiler_jar/source/
pushd compiler_jar/extracted
for i in ../source/*.jar; do jar xf $i; done
cd ..
jar cfe ../prog8compiler.jar prog8.CompilerMainKt -C extracted .
popd
rm -r compiler_jar
ls -l prog8compiler.jar

View File

@ -2,10 +2,8 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <orderEntry type="jdk" jdkName="Python 3.7 (py3)" jdkType="Python SDK" />
</content>
<orderEntry type="jdk" jdkName="Python 3.7" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@ -25,58 +25,73 @@ The project is on github: https://github.com/irmen/prog8.git
This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html This software is licensed under the GNU GPL 3.0, see https://www.gnu.org/licenses/gpl.html
.. image:: _static/cube3d.png
:width: 33%
:alt: 3d rotating sprites
.. image:: _static/wizzine.png
:width: 33%
:alt: Simple wizzine sprite effect
.. image:: _static/tehtriz.png
:width: 33%
:alt: Fully playable tetris clone
Code example Code example
------------ ------------
When this code is compiled:: This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
%import c64lib
%import c64utils %import c64utils
%import c64flt %zeropage basicsafe
~ main { ~ main {
ubyte[256] sieve
ubyte candidate_prime = 2
sub start() { sub start() {
; set text color and activate lowercase charset memset(sieve, 256, false)
c64.COLOR = 13
c64.VMCSB |= 2
; use optimized routine to write text c64scr.print("prime numbers up to 255:\n\n")
c64scr.print("Hello!\n") ubyte amount=0
while true {
; use iteration to write text ubyte prime = find_next_prime()
str question = "How are you?\n" if prime==0
for ubyte char in question break
c64.CHROUT(char) c64scr.print_ub(prime)
c64scr.print(", ")
; use indexed loop to write characters amount++
str bye = "Goodbye!\n" }
for ubyte c in 0 to len(bye)
c64.CHROUT(bye[c])
float clock_seconds = ((mkword(c64.TIME_LO, c64.TIME_MID) as float)
+ (c64.TIME_HI as float)*65536.0)
/ 60
float hours = floor(clock_seconds / 3600)
clock_seconds -= hours*3600
float minutes = floor(clock_seconds / 60)
clock_seconds = floor(clock_seconds - minutes * 60.0)
c64scr.print("system time in ti$ is ")
c64flt.print_f(hours)
c64.CHROUT(':')
c64flt.print_f(minutes)
c64.CHROUT(':')
c64flt.print_f(clock_seconds)
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.print("number of primes (expected 54): ")
c64scr.print_ub(amount)
c64.CHROUT('\n')
}
sub find_next_prime() -> ubyte {
while sieve[candidate_prime] {
candidate_prime++
if candidate_prime==0
return 0
}
sieve[candidate_prime] = true
uword multiple = candidate_prime
while multiple < len(sieve) {
sieve[lsb(multiple)] = true
multiple += candidate_prime
}
return candidate_prime
} }
} }
when compiled an ran on a C-64 you'll get:
you get a program that outputs this when loaded on a C-64: .. image:: _static/primes_example.png
.. image:: _static/hello_screen.png
:align: center :align: center
:alt: result when run on C-64 :alt: result when run on C-64

View File

@ -187,9 +187,13 @@ Values will usually be part of an expression or assignment statement::
byte counter = 42 ; variable of size 8 bits, with initial value 42 byte counter = 42 ; variable of size 8 bits, with initial value 42
.. todo:: *zeropage tag:*
There must be a way to tell the compiler which variables you require to be in Zeropage: If you add the ``@zp`` tag to the variable declaration, the compiler will prioritize this variable
``zeropage`` modifier keyword on vardecl perhaps? when selecting variables to put into zero page. If there are enough free locations in the zeropage,
it will then try to fill it with as much other variables as possible (before they will be put in regular memory pages).
Example::
byte @zp zeropageCounter = 42
Variables that represent CPU hardware registers Variables that represent CPU hardware registers
@ -233,9 +237,9 @@ Arrays
^^^^^^ ^^^^^^
Array types are also supported. They can be made of bytes, words or floats:: Array types are also supported. They can be made of bytes, words or floats::
byte[4] array = [1, 2, 3, 4] ; initialize the array byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
byte[99] array = 255 ; initialize array with all 255's [255, 255, 255, 255, ...] byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199] byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
value = array[3] ; the fourth value in the array (index is 0-based) value = array[3] ; the fourth value in the array (index is 0-based)
char = string[4] ; the fifth character (=byte) in the string char = string[4] ; the fifth character (=byte) in the string
@ -277,13 +281,13 @@ You'll have to specify the initial value expression. This value is then used
by the compiler everywhere you refer to the constant (and no storage is allocated by the compiler everywhere you refer to the constant (and no storage is allocated
for the constant itself). This is only valid for the simple numeric types (byte, word, float). for the constant itself). This is only valid for the simple numeric types (byte, word, float).
When using ``memory``, the variable will point to specific location in memory, When using ``&`` (the address-of operator but now applied to a datatype), the variable will point to specific location in memory,
rather than being newly allocated. The initial value (mandatory) must be a valid rather than being newly allocated. The initial value (mandatory) must be a valid
memory address. Reading the variable will read the given data type from the memory address. Reading the variable will read the given data type from the
address you specified, and setting the varible will directly modify that memory location(s):: address you specified, and setting the varible will directly modify that memory location(s)::
const byte max_age = 2000 - 1974 ; max_age will be the constant value 26 const byte max_age = 2000 - 1974 ; max_age will be the constant value 26
memory word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021 &word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
.. note:: .. note::
@ -321,6 +325,11 @@ be set to zero only for the first run of the program. A second run will utilize
where it left off (but your code will be a bit smaller because no initialization instructions where it left off (but your code will be a bit smaller because no initialization instructions
are generated) are generated)
.. caution::
variables that get allocated in zero-page will *not* have a zero starting value when you omit
the variable's initialization. They'll be whatever the last value in that zero page
location was. So it's best to don't depend on the uninitialized starting value!
.. warning:: .. warning::
this behavior may change in a future version so that subsequent runs always this behavior may change in a future version so that subsequent runs always
use the same initial values use the same initial values
@ -462,11 +471,7 @@ There are various built-in functions such as sin(), cos(), min(), max() that can
You can also reference idendifiers defined elsewhere in your code. You can also reference idendifiers defined elsewhere in your code.
.. attention:: .. attention::
**Data type conversion (during calculations) and floating point handling:** **Floating points used in expressions:**
BYTE values used in arithmetic expressions (calculations) will be automatically converted into WORD values
if the calculation needs that to store the resulting value. Once a WORD value is used, all other results will be WORDs as well
(there's no automatic conversion of WORD into BYTE).
When a floating point value is used in a calculation, the result will be a floating point, and byte or word values When a floating point value is used in a calculation, the result will be a floating point, and byte or word values
will be automatically converted into floats in this case. The compiler will issue a warning though when this happens, because floating will be automatically converted into floats in this case. The compiler will issue a warning though when this happens, because floating
@ -494,6 +499,28 @@ Usually the normal precedence rules apply (``*`` goes before ``+`` etc.) but sub
within parentheses will be evaluated first. So ``(4 + 8) * 2`` is 24 and not 20, within parentheses will be evaluated first. So ``(4 + 8) * 2`` is 24 and not 20,
and ``(true or false) and false`` is false instead of true. and ``(true or false) and false`` is false instead of true.
.. attention::
**calculations keep their datatype:**
When you do calculations on a BYTE type, the result will remain a BYTE.
When you do calculations on a WORD type, the result will remain a WORD.
For instance::
byte b = 44
word w = b*55 ; the result will be 116! (even though the target variable is a word)
w *= 999 ; the result will be -15188 (the multiplication stays within a word)
The compiler will NOT give a warning about this! It's doing this for
performance reasons - so you won't get sudden 16 bit (or even float)
calculations where you needed only simple fast byte arithmetic.
If you do need the extended resulting value, cast at least one of the
operands of an operator to the larger datatype. For example::
byte b = 44
word w = b*55.w ; the result will be 2420
w = (b as word)*55 ; same result
Subroutines Subroutines
----------- -----------
@ -517,7 +544,9 @@ will issue a warning then telling you the result values of a subroutine call are
subroutines are *non-reentrant*. This means you cannot create recursive calls. subroutines are *non-reentrant*. This means you cannot create recursive calls.
If you do need a recursive algorithm, you'll have to hand code it in embedded assembly for now, If you do need a recursive algorithm, you'll have to hand code it in embedded assembly for now,
or rewrite it into an iterative algorithm. or rewrite it into an iterative algorithm.
Also, subroutines used in the main program should not be used from an IRQ handler. Also, subroutines used in the main program should not be used from an IRQ handler. This is because
the subroutine may be interrupted, and will then call itself from the IRQ handler. Results are
then undefined because the variables will get overwritten.
.. _builtinfunctions: .. _builtinfunctions:
@ -575,8 +604,11 @@ ln(x)
log2(x) log2(x)
Base 2 logarithm. Base 2 logarithm.
sqrt16(w)
16 bit unsigned integer Square root. Result is unsigned byte.
sqrt(x) sqrt(x)
Square root. Floating point Square root.
round(x) round(x)
Rounds the floating point to the closest integer. Rounds the floating point to the closest integer.
@ -610,6 +642,11 @@ len(x)
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte.
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
length of the string during execution, the value of len(string) may no longer be correct! length of the string during execution, the value of len(string) may no longer be correct!
(use strlen function if you want to dynamically determine the length)
strlen(str)
Number of bytes in the string. This value is determined during runtime and counts upto
the first terminating 0 byte in the string, regardless of the size of the string during compilation time.
lsb(x) lsb(x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte". Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
@ -708,3 +745,19 @@ rsave()
rrestore() rrestore()
Restores the CPU registers and the status flags from previously saved values. Restores the CPU registers and the status flags from previously saved values.
read_flags()
Returns the current value of the CPU status register.
Library routines
----------------
There are many routines available in the compiler libraries.
Some are used internally by the compiler as well.
There's too many to list here, just have a look through the source code
of the library modules to see what's there.
(They can be found in the compiler/res directory)
The example programs also use a small set of the library routines, you can study
their source code to see how they might be used.

View File

@ -123,6 +123,7 @@ Directives
This directive can only be used inside a block. This directive can only be used inside a block.
The assembler will include the file as binary bytes at this point, prog8 will not process this at all. The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
The optional offset and length can be used to select a particular piece of the file. The optional offset and length can be used to select a particular piece of the file.
The file is located relative to the current working directory!
.. data:: %asminclude "<filename>", "scopelabel" .. data:: %asminclude "<filename>", "scopelabel"
@ -133,6 +134,8 @@ Directives
The scopelabel argument will be used as a prefix to access the labels from the included source code, The scopelabel argument will be used as a prefix to access the labels from the included source code,
otherwise you would risk symbol redefinitions or duplications. otherwise you would risk symbol redefinitions or duplications.
If you know what you are doing you can leave it as an empty string to not have a scope prefix. If you know what you are doing you can leave it as an empty string to not have a scope prefix.
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
if the file can't be found there it is searched relative to the current directory.
.. data:: %breakpoint .. data:: %breakpoint
@ -213,9 +216,11 @@ Variable declarations
Variables should be declared with their exact type and size so the compiler can allocate storage Variables should be declared with their exact type and size so the compiler can allocate storage
for them. You must give them an initial value as well. That value can be a simple literal value, for them. You must give them an initial value as well. That value can be a simple literal value,
or an expression. The syntax is:: or an expression. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
when selecting variables to be put into zeropage.
The syntax is::
<datatype> <variable name> [ = <initial value> ] <datatype> [ @zp ] <variable name> [ = <initial value> ]
Various examples:: Various examples::
@ -224,10 +229,12 @@ Various examples::
byte age = 2018 - 1974 byte age = 2018 - 1974
float wallet = 55.25 float wallet = 55.25
str name = "my name is Irmen" str name = "my name is Irmen"
word address = #counter uword address = &counter
byte[5] values = [11, 22, 33, 44, 55] byte[] values = [11, 22, 33, 44, 55]
byte[5] values = 255 ; initialize with five 255 bytes byte[5] values = 255 ; initialize with five 255 bytes
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
Data types Data types
@ -247,22 +254,24 @@ type identifier type storage size example var declara
``uword`` unsigned word 2 bytes = 16 bits ``uword myvar = $8fee`` ``uword`` unsigned word 2 bytes = 16 bits ``uword myvar = $8fee``
``float`` floating-point 5 bytes = 40 bits ``float myvar = 1.2345`` ``float`` floating-point 5 bytes = 40 bits ``float myvar = 1.2345``
stored in 5-byte cbm MFLPT format stored in 5-byte cbm MFLPT format
``byte[x]`` signed byte array x bytes ``byte[4] myvar = [1, 2, 3, 4]`` ``byte[x]`` signed byte array x bytes ``byte[4] myvar``
``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar = [1, 2, 3, 4]`` ``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar``
``word[x]`` signed word array 2*x bytes ``word[4] myvar = [1, 2, 3, 4]`` ``word[x]`` signed word array 2*x bytes ``word[4] myvar``
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar = [1, 2, 3, 4]`` ``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar``
``float[x]`` floating-point array 5*x bytes ``float[4] myvar = [1.1, 2.2, 3.3, 4.4]`` ``float[x]`` floating-point array 5*x bytes ``float[4] myvar``
``byte[]`` signed byte array depends on value ``byte[] myvar = [1, 2, 3, 4]``
``ubyte[]`` unsigned byte array depends on value ``ubyte[] myvar = [1, 2, 3, 4]``
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
``uword[]`` unsigned word array depends on value ``uword[] myvar = [1, 2, 3, 4]``
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
``str`` string (petscii) varies ``str myvar = "hello."`` ``str`` string (petscii) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte implicitly terminated by a 0-byte
``str_p`` pascal-string (petscii) varies ``str_p myvar = "hello."``
implicit first byte = length, no 0-byte
``str_s`` string (screencodes) varies ``str_s myvar = "hello."`` ``str_s`` string (screencodes) varies ``str_s myvar = "hello."``
implicitly terminated by a 0-byte implicitly terminated by a 0-byte
``str_ps`` pascal-string varies ``str_ps myvar = "hello."``
(screencodes) implicit first byte = length, no 0-byte
=============== ======================= ================= ========================================= =============== ======================= ================= =========================================
**arrays:** you can split an array initializer list over several lines if you want. **arrays:** you can split an array initializer list over several lines if you want. When an initialization
value is given, the array size in the declaration can be omitted.
**hexadecimal numbers:** you can use a dollar prefix to write hexadecimal numbers: ``$20ac`` **hexadecimal numbers:** you can use a dollar prefix to write hexadecimal numbers: ``$20ac``
@ -282,13 +291,6 @@ of something with an operand starting with 1 or 0, you'll have to add a space in
- You can force a byte value into a word value by adding the ``.w`` datatype suffix to the number: ``$2a.w`` is equivalent to ``$002a``. - You can force a byte value into a word value by adding the ``.w`` datatype suffix to the number: ``$2a.w`` is equivalent to ``$002a``.
.. todo::
omit the array size in the var decl if an initialization array is given?
**@todo pointers/addresses? (as opposed to normal WORDs)**
Data type conversion Data type conversion
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
Many type conversions are possible by just writing ``as <type>`` at the end of an expression, Many type conversions are possible by just writing ``as <type>`` at the end of an expression,
@ -298,11 +300,11 @@ for example ``ubyte ub = floatvalue as ubyte`` will convert the floating point v
Memory mapped variables Memory mapped variables
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
The ``memory`` keyword is used in front of a data type keyword, to say that no storage The ``&`` (address-of operator) used in front of a data type keyword, indicates that no storage
should be allocated by the compiler. Instead, the (mandatory) value assigned to the variable should be allocated by the compiler. Instead, the (mandatory) value assigned to the variable
should be the *memory address* where the value is located:: should be the *memory address* where the value is located::
memory byte BORDER = $d020 &byte BORDERCOLOR = $d020
Direct access to memory locations Direct access to memory locations
@ -343,7 +345,7 @@ which represents a range of numbers or characters,
from the starting value to (and including) the ending value. from the starting value to (and including) the ending value.
If used in the place of a literal value, it expands into the actual array of values:: If used in the place of a literal value, it expands into the actual array of values::
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199] byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
Array indexing Array indexing
@ -360,19 +362,13 @@ Syntax is familiar with brackets: ``arrayvar[x]`` ::
Operators Operators
--------- ---------
.. todo::
address-of: ``#``
Takes the address of the symbol following it: ``word address = #somevar``
arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%`` arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%``
``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations. ``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations.
``/`` is division (will result in integer division when using on integer operands, and a floating point division when at least one of the operands is a float) ``/`` is division (will result in integer division when using on integer operands, and a floating point division when at least one of the operands is a float)
``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243. ``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243. (it only works on floating point variables)
``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space, %10 will be parsed as the binary number 2 ``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space, %10 will be parsed as the binary number 2
Remainder is only supported on integer operands (not floats). Remainder is only supported on integer operands (not floats).
bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>`` bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>``
``&`` is bitwise and, ``|`` is bitwise or, ``^`` is bitwise xor, ``~`` is bitwise invert (this one is an unary operator) ``&`` is bitwise and, ``|`` is bitwise or, ``^`` is bitwise xor, ``~`` is bitwise invert (this one is an unary operator)
``<<`` is bitwise left shift and ``>>`` is bitwise right shift (both will not change the datatype of the value) ``<<`` is bitwise left shift and ``>>`` is bitwise right shift (both will not change the datatype of the value)
@ -407,12 +403,19 @@ range creation: ``to``
X = 10 X = 10
A to X ; range of 5, 6, 7, 8, 9, 10 A to X ; range of 5, 6, 7, 8, 9, 10
byte[4] array = 10 to 13 ; sets the array to [1, 2, 3, 4] byte[] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
for i in 0 to 127 { for i in 0 to 127 {
; i loops 0, 1, 2, ... 127 ; i loops 0, 1, 2, ... 127
} }
address of: ``&``
This is a prefix operator that can be applied to a string or array variable or literal value.
It results in the memory address (UWORD) of that string or array in memory: ``uword a = &stringvar``
Sometimes the compiler silently inserts this operator to make it easier for instance
to pass strings or arrays as subroutine call arguments.
This operator can also be used as a prefix to a variable's data type keyword to indicate that
it is a memory mapped variable (for instance: ``&ubyte screencolor = $d021``)
precedence grouping in expressions, or subroutine parameter list: ``(`` *expression* ``)`` precedence grouping in expressions, or subroutine parameter list: ``(`` *expression* ``)``
Parentheses are used to group parts of an expression to change the order of evaluation. Parentheses are used to group parts of an expression to change the order of evaluation.
@ -431,10 +434,20 @@ You call a subroutine like this::
[ result = ] subroutinename_or_address ( [argument...] ) [ result = ] subroutinename_or_address ( [argument...] )
; example: ; example:
resultvariable = subroutine ( arg1, arg2, arg3 ) resultvariable = subroutine(arg1, arg2, arg3)
Arguments are separated by commas. The argument list can also be empty if the subroutine Arguments are separated by commas. The argument list can also be empty if the subroutine
takes no parameters. takes no parameters. If the subroutine returns a value, you can still omit the assignment to
a result variable (but the compiler will warn you about discarding the result of the call).
Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code or referencing
a routine in kernel ROM) can return more than one return values, for instance a status
in the carry bit and a number in A, or a 16-bit value in A/Y registers.
Only for these kind of subroutines it is possible to write a multi value assignment to
store the resulting values::
var1, var2, var3 = asmsubroutine()
@ -503,13 +516,21 @@ For example, this is a for loop using the existing byte variable ``i`` to loop o
And this is a loop over the values of the array ``fibonacci_numbers`` where the loop variable is declared in the loop itself:: And this is a loop over the values of the array ``fibonacci_numbers`` where the loop variable is declared in the loop itself::
word[20] fibonacci_numbers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181] word[] fibonacci_numbers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
for word fibnr in fibonacci_numbers { for word fibnr in fibonacci_numbers {
; do something ; do something
} }
You can inline the loop variable declaration in the for statement, including optional zp-tag. In this case
the variable is not visible outside of the for loop::
for ubyte @zp fastindex in 10 to 20 {
; do something
}
while loop while loop
^^^^^^^^^^ ^^^^^^^^^^

View File

@ -117,7 +117,8 @@ The following 6502 CPU hardware registers are directly usable in program code (a
- ``A``, ``X``, ``Y`` the three main cpu registers (8 bits) - ``A``, ``X``, ``Y`` the three main cpu registers (8 bits)
- the status register (P) carry flag and interrupt disable flag can be written via a couple of special - the status register (P) carry flag and interrupt disable flag can be written via a couple of special
builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``) builtin functions (``set_carry()``, ``clear_carry()``, ``set_irqd()``, ``clear_irqd()``),
and read via the ``read_flags()`` function.
However, you must assume that the 3 hardware registers ``A``, ``X`` and ``Y`` However, you must assume that the 3 hardware registers ``A``, ``X`` and ``Y``
are volatile. Their values cannot be depended upon, the compiler will use them as required. are volatile. Their values cannot be depended upon, the compiler will use them as required.
@ -144,3 +145,31 @@ Arguments and result values are passed via global variables stored in memory
*These are not allocated on a stack* so it is not possible to create recursive calls! *These are not allocated on a stack* so it is not possible to create recursive calls!
The result value(s) of a subroutine are returned on the evaluation stack, The result value(s) of a subroutine are returned on the evaluation stack,
to make it possible to use subroutines in expressions. to make it possible to use subroutines in expressions.
IRQ Handling
============
Normally, the system's default IRQ handling is not interfered with.
You can however install your own IRQ handler.
This is possible ofcourse by doing it all using customized inline assembly,
but there are a few library routines available to make setting up C-64 IRQs and raster IRQs a lot easier (no assembly code required).
These routines are::
c64utils.set_irqvec()
c64utils.set_irqvec_excl()
c64utils.set_rasterirq( <raster line> )
c64utils.set_rasterirq_excl( <raster line> )
c64utils.restore_irqvec() ; set it back to the systems default irq handler
If you activate an IRQ handler with one of these, it expects the handler to be defined
as a subroutine ``irq`` in the module ``irq`` so like this::
~ irq {
sub irq() {
; ... irq handling here ...
}
}

View File

@ -5,7 +5,7 @@ TODO
Memory Block Operations integrated in language? Memory Block Operations integrated in language?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@todo list,string memory block operations? list,string memory block operations?
- list operations (whole list, individual element) - list operations (whole list, individual element)
operations: set, get, copy (from another list with the same length), shift-N(left,right), rotate-N(left,right) operations: set, get, copy (from another list with the same length), shift-N(left,right), rotate-N(left,right)
@ -15,9 +15,9 @@ Memory Block Operations integrated in language?
- strings: identical operations as on lists. - strings: identical operations as on lists.
these should call (or emit inline) optimized pieces of assembly code, so they run as fast as possible these should call optimized pieces of assembly code, so they run as fast as possible
For now, we have the ``memcopy`` and ``memset`` builtin functions. For now, we have the ``memcopy``, ``memset`` and ``strlen`` builtin functions.
@ -52,17 +52,6 @@ Allocate a fixed word in ZP that is the TOS so we can operate on TOS directly
without having to to index into the stack? without having to to index into the stack?
More flexible (non-const) arrays?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Currently, array literals can only be constants
Allow for non-const arrays? Such as::
ubyte[16] block1
ubyte[16] block2
ubyte[16] block3
ubyte[3] blocks = [block1, block2, block3]
structs? structs?
^^^^^^^^ ^^^^^^^^
@ -71,7 +60,7 @@ of values together (and use it multiple times). Something like::
struct Point { struct Point {
ubyte color ubyte color
word[3] vec = [0,0,0] word[] vec = [0,0,0]
} }
Point p1 Point p1
@ -85,7 +74,4 @@ of values together (and use it multiple times). Something like::
Misc Misc
^^^^ ^^^^
- sqrt() should have integer implementation as well, instead of relying on float SQRT for all argument types
- code generation for POW instruction
- are there any other missing instructions in the code generator? - are there any other missing instructions in the code generator?
- implement %asmbinary

View File

@ -44,10 +44,10 @@ sub start() {
sub print_notes(ubyte n1, ubyte n2) { sub print_notes(ubyte n1, ubyte n2) {
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.PLOT(n1/2, 24) c64scr.plot(n1/2, 24)
c64.COLOR=7 c64.COLOR=7
c64.CHROUT('Q') c64.CHROUT('Q')
c64scr.PLOT(n2/2, 24) c64scr.plot(n2/2, 24)
c64.COLOR=4 c64.COLOR=4
c64.CHROUT('Q') c64.CHROUT('Q')
} }
@ -56,7 +56,7 @@ sub start() {
; details about the boulderdash music can be found here: ; details about the boulderdash music can be found here:
; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune ; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune
uword[128] notes = [ uword[] notes = [
$1622, $1d26, $2229, $252e, $1424, $1f27, $2029, $2730, $1622, $1d26, $2229, $252e, $1424, $1f27, $2029, $2730,
$122a, $122c, $1e2e, $1231, $202c, $3337, $212d, $3135, $122a, $122c, $1e2e, $1231, $202c, $3337, $212d, $3135,
$1622, $162e, $161d, $1624, $1420, $1430, $1424, $1420, $1622, $162e, $161d, $1624, $1420, $1430, $1424, $1420,
@ -76,7 +76,7 @@ sub start() {
] ]
uword[59] music_freq_table = [ uword[] music_freq_table = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
732, 778, 826, 876, 928, 978, 1042, 1100, 1170, 1238, 1312, 1390, 1464, 1556, 732, 778, 826, 876, 928, 978, 1042, 1100, 1170, 1238, 1312, 1390, 1464, 1556,
1652, 1752, 1856, 1956, 2084, 2200, 2340, 2476, 2624, 2780, 2928, 3112, 3304, 1652, 1752, 1856, 1956, 2084, 2200, 2340, 2476, 2624, 2780, 2928, 3112, 3304,

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -8,9 +8,9 @@
const uword height = 25 const uword height = 25
; vertices ; vertices
float[8] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ] float[] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
float[8] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ] float[] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
float[8] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ] float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
; storage for rotated coordinates ; storage for rotated coordinates
float[len(xcoor)] rotatedx=0.0 float[len(xcoor)] rotatedx=0.0
@ -24,7 +24,7 @@
c64scr.clear_screenchars(32) c64scr.clear_screenchars(32)
draw_edges() draw_edges()
time+=0.2 time+=0.2
c64scr.PLOT(0,0) c64scr.plot(0,0)
c64scr.print("3d cube! (float) ") c64scr.print("3d cube! (float) ")
c64scr.print_ub(c64.TIME_LO) c64scr.print_ub(c64.TIME_LO)
c64scr.print(" jiffies/frame") c64scr.print(" jiffies/frame")

View File

@ -6,7 +6,7 @@
; it must start on an address aligned to 64 bytes. ; it must start on an address aligned to 64 bytes.
%option force_output ; make sure the data in this block appears in the resulting program %option force_output ; make sure the data in this block appears in the resulting program
ubyte[128] sprites = [ ubyte[] sprites = [
%00000000,%00000000,%00000000, %00000000,%00000000,%00000000,
%00000000,%00111100,%00000000, %00000000,%00111100,%00000000,
%00000000,%11111111,%00000000, %00000000,%11111111,%00000000,
@ -64,9 +64,9 @@
const uword height = 200 const uword height = 200
; vertices ; vertices
byte[8] xcoor = [ -100, -100, -100, -100, 100, 100, 100, 100 ] byte[] xcoor = [ -100, -100, -100, -100, 100, 100, 100, 100 ]
byte[8] ycoor = [ -100, -100, 100, 100, -100, -100, 100, 100 ] byte[] ycoor = [ -100, -100, 100, 100, -100, -100, 100, 100 ]
byte[8] zcoor = [ -100, 100, -100, 100, -100, 100, -100, 100 ] byte[] zcoor = [ -100, 100, -100, 100, -100, 100, -100, 100 ]
; storage for rotated coordinates ; storage for rotated coordinates
word[len(xcoor)] rotatedx word[len(xcoor)] rotatedx
@ -89,7 +89,7 @@
anglex-=500 anglex-=500
angley+=217 angley+=217
anglez+=452 anglez+=452
c64scr.PLOT(0,0) c64scr.plot(0,0)
c64scr.print("3d cube! (sprites) ") c64scr.print("3d cube! (sprites) ")
c64scr.print_ub(c64.TIME_LO) c64scr.print_ub(c64.TIME_LO)
c64scr.print(" jiffies/frame ") c64scr.print(" jiffies/frame ")
@ -146,7 +146,7 @@
} }
} }
ubyte[8] spritecolors = [1,1,7,15,12,11,9,9] ubyte[] spritecolors = [1,1,7,15,12,11,9,9]
for ubyte i in 0 to 7 { for ubyte i in 0 to 7 {
word zc = rotatedz[i] word zc = rotatedz[i]

View File

@ -20,12 +20,12 @@
const uword height = 200 const uword height = 200
; vertices ; vertices
float[8] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ] float[] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
float[8] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ] float[] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
float[8] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ] float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
; edges (msb=from vertex, lsb=to vertex) ; edges (msb=from vertex, lsb=to vertex)
uword[12] edges = [$0001, $0103, $0302, $0200, $0405, $0507, $0706, $0604, $0004, $0105, $0206, $0307] uword[] edges = [$0001, $0103, $0302, $0200, $0405, $0507, $0706, $0604, $0004, $0105, $0206, $0307]
; storage for rotated coordinates ; storage for rotated coordinates
float[len(xcoor)] rotatedx float[len(xcoor)] rotatedx

View File

@ -7,9 +7,9 @@
const uword height = 25 const uword height = 25
; vertices ; vertices
byte[8] xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ] byte[] xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
byte[8] ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ] byte[] ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
byte[8] zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ] byte[] zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
; storage for rotated coordinates ; storage for rotated coordinates
word[len(xcoor)] rotatedx word[len(xcoor)] rotatedx
@ -29,7 +29,7 @@
anglex+=1000 anglex+=1000
angley+=433 angley+=433
anglez+=907 anglez+=907
c64scr.PLOT(0,0) c64scr.plot(0,0)
c64scr.print("3d cube! (integer) ") c64scr.print("3d cube! (integer) ")
c64scr.print_ub(c64.TIME_LO) c64scr.print_ub(c64.TIME_LO)
c64scr.print(" jiffies/frame") c64scr.print(" jiffies/frame")
@ -68,7 +68,7 @@
} }
} }
ubyte[6] vertexcolors = [1,7,7,12,11,6] ubyte[] vertexcolors = [1,7,7,12,11,6]
sub draw_edges() { sub draw_edges() {

View File

@ -2,8 +2,9 @@
<module type="WEB_MODULE" version="4"> <module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$">
<orderEntry type="inheritedJdk" /> <excludeFolder url="file://$MODULE_DIR$/compiled" />
</content>
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

View File

@ -32,7 +32,6 @@
float minutes = floor(clock_seconds / 60) float minutes = floor(clock_seconds / 60)
clock_seconds = floor(clock_seconds - minutes * 60.0) clock_seconds = floor(clock_seconds - minutes * 60.0)
; @todo implement strcpy/strcat/strlen?
c64scr.print("system time in ti$ is ") c64scr.print("system time in ti$ is ")
c64flt.print_f(hours) c64flt.print_f(hours)
c64.CHROUT(':') c64.CHROUT(':')

View File

@ -1,6 +1,8 @@
%import c64lib %import c64lib
%import c64utils %import c64utils
%import c64flt %import c64flt
%zeropage basicsafe
~ main { ~ main {
const uword width = 30 const uword width = 30
@ -8,7 +10,7 @@
const ubyte max_iter = 16 const ubyte max_iter = 16
sub start() { sub start() {
c64scr.print("calculating mandelbrot fractal...\n") c64scr.print("calculating mandelbrot fractal...")
c64.TIME_HI=0 c64.TIME_HI=0
c64.TIME_MID=0 c64.TIME_MID=0
@ -40,7 +42,7 @@
float duration = floor(((c64.TIME_LO as float) float duration = floor(((c64.TIME_LO as float)
+ 256.0*(c64.TIME_MID as float) + 256.0*(c64.TIME_MID as float)
+ 65536.0*(c64.TIME_HI as float))/60.0) + 65536.0*(c64.TIME_HI as float))/60.0)
c64scr.PLOT(0, 21) c64scr.plot(0, 21)
c64scr.print("finished in ") c64scr.print("finished in ")
c64flt.print_f(duration) c64flt.print_f(duration)
c64scr.print(" seconds!\n") c64scr.print(" seconds!\n")

View File

@ -29,7 +29,7 @@
c64scr.print("es") c64scr.print("es")
c64scr.print(" left.\nWhat is your next guess? ") c64scr.print(" left.\nWhat is your next guess? ")
c64scr.input_chars(input) c64scr.input_chars(input)
ubyte guess = c64utils.str2ubyte(input) ubyte guess = lsb(c64utils.str2uword(input))
if guess==secretnumber { if guess==secretnumber {
return ending(true) return ending(true)

View File

@ -4,25 +4,31 @@
~ main { ~ main {
ubyte[256] sieve ubyte[256] sieve
ubyte candidate_prime = 2 ubyte candidate_prime = 2 ; is increased in the loop
sub start() { sub start() {
memset(sieve, 256, false) ; clear the sieve memset(sieve, 256, false) ; clear the sieve, to reset starting situation on subsequent runs
; calculate primes ; calculate primes
c64scr.print("prime numbers up to 255:\n\n") c64scr.print("prime numbers up to 255:\n\n")
ubyte amount=0
while true { while true {
ubyte prime = find_next_prime() ubyte prime = find_next_prime()
if prime==0 if prime==0
break break
c64scr.print_ub(prime) c64scr.print_ub(prime)
c64scr.print(", ") c64scr.print(", ")
amount++
} }
c64.CHROUT('\n') c64.CHROUT('\n')
c64scr.print("number of primes (expected 54): ")
c64scr.print_ub(amount)
c64.CHROUT('\n')
} }
sub find_next_prime() -> ubyte { sub find_next_prime() -> ubyte {
while sieve[candidate_prime] { while sieve[candidate_prime] {
candidate_prime++ candidate_prime++
if candidate_prime==0 if candidate_prime==0
@ -31,10 +37,14 @@
; found next one, mark the multiples and return it. ; found next one, mark the multiples and return it.
sieve[candidate_prime] = true sieve[candidate_prime] = true
uword multiple = candidate_prime**2 uword multiple = candidate_prime
while multiple < len(sieve) { while multiple < len(sieve) {
sieve[lsb(multiple)] = true sieve[lsb(multiple)] = true
multiple += candidate_prime multiple += candidate_prime
; c64scr.print_uw(multiple) ; TODO
; c4.CHROUT('\n') ; TODO
} }
return candidate_prime return candidate_prime
} }

View File

@ -18,28 +18,24 @@
~ irq { ~ irq {
const ubyte barheight = 4 const ubyte barheight = 4
ubyte[13] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9] ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]
ubyte color = 0 ubyte color = 0
ubyte ypos = 0 ubyte ypos = 0
sub irq() { sub irq() {
Y++ ; delay for alignment Y++ ; slight timing delay to avoid rasterline transition issues
Y++ ; delay for alignment
Y++ ; delay for alignment
Y++ ; delay for alignment
ubyte rasterpos = c64.RASTER ubyte rasterpos = c64.RASTER
if color!=len(colors) { if color!=len(colors) {
c64.EXTCOL = colors[color] c64.EXTCOL = colors[color]
c64.RASTER = rasterpos+barheight
color++ color++
c64.RASTER = rasterpos+barheight
} }
else { else {
Y++ ; delay for alignment
Y++ ; delay for alignment
ypos += 2 ypos += 2
c64.EXTCOL = 0 c64.EXTCOL = 0
c64.RASTER = sin8u(ypos)/2+40
color = 0 color = 0
c64.RASTER = sin8u(ypos)/2+40
} }
} }
} }

View File

@ -8,27 +8,27 @@
; it must start on an address aligned to 64 bytes. ; it must start on an address aligned to 64 bytes.
%option force_output ; make sure the data in this block appears in the resulting program %option force_output ; make sure the data in this block appears in the resulting program
ubyte[63] balloonsprite = [ %00000000,%01111111,%00000000, ubyte[] balloonsprite = [ %00000000,%01111111,%00000000,
%00000001,%11111111,%11000000, %00000001,%11111111,%11000000,
%00000011,%11111111,%11100000, %00000011,%11111111,%11100000,
%00000011,%11100011,%11100000, %00000011,%11100011,%11100000,
%00000111,%11011100,%11110000, %00000111,%11011100,%11110000,
%00000111,%11011101,%11110000, %00000111,%11011101,%11110000,
%00000111,%11011100,%11110000, %00000111,%11011100,%11110000,
%00000011,%11100011,%11100000, %00000011,%11100011,%11100000,
%00000011,%11111111,%11100000, %00000011,%11111111,%11100000,
%00000011,%11111111,%11100000, %00000011,%11111111,%11100000,
%00000010,%11111111,%10100000, %00000010,%11111111,%10100000,
%00000001,%01111111,%01000000, %00000001,%01111111,%01000000,
%00000001,%00111110,%01000000, %00000001,%00111110,%01000000,
%00000000,%10011100,%10000000, %00000000,%10011100,%10000000,
%00000000,%10011100,%10000000, %00000000,%10011100,%10000000,
%00000000,%01001001,%00000000, %00000000,%01001001,%00000000,
%00000000,%01001001,%00000000, %00000000,%01001001,%00000000,
%00000000,%00111110,%00000000, %00000000,%00111110,%00000000,
%00000000,%00111110,%00000000, %00000000,%00111110,%00000000,
%00000000,%00111110,%00000000, %00000000,%00111110,%00000000,
%00000000,%00011100,%00000000 ] %00000000,%00011100,%00000000 ]
} }
~ main { ~ main {

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