mirror of
https://github.com/irmen/prog8.git
synced 2025-06-15 18:23:35 +00:00
Compare commits
189 Commits
Author | SHA1 | Date | |
---|---|---|---|
ac70ae6a76 | |||
d83f49d84f | |||
ff1294207e | |||
a56956797a | |||
3242495b0b | |||
49eb7e7803 | |||
1d7f0d3537 | |||
31137743f0 | |||
2c69e10489 | |||
3a1fa9e069 | |||
2c08d2f9c6 | |||
4743cacb73 | |||
5f5a1447e0 | |||
a3004555a8 | |||
267c678292 | |||
6c50043a4a | |||
3ee1b2efdd | |||
75d8c832ad | |||
53a4379c45 | |||
29b3a7e94e | |||
0782f6ecf1 | |||
595e58ec46 | |||
060e05c868 | |||
f49eefad6f | |||
d68360461b | |||
343978d164 | |||
b11d10e2ff | |||
268856823a | |||
4bac5043b6 | |||
eb25b4c800 | |||
a079e44b02 | |||
e53c860f1a | |||
99121004bf | |||
6dd3371781 | |||
f473be8951 | |||
ebd38f27e6 | |||
a6c3251668 | |||
560047adee | |||
a86852874f | |||
6d44d6a901 | |||
968f02823f | |||
5d321d759e | |||
7de7d5234f | |||
b374af3526 | |||
b35430214b | |||
e96d3d4455 | |||
6a17f7a0ad | |||
c559682c0b | |||
6ce1277438 | |||
262e0bd6b9 | |||
755af6010e | |||
0298cf8b90 | |||
a6d0aecd66 | |||
ef6e364339 | |||
3b37e0f99d | |||
78fbbf7119 | |||
0ee43294c4 | |||
a81b82495c | |||
390043e9e8 | |||
e384822b2c | |||
730e08698d | |||
5497de4234 | |||
c71b78dee6 | |||
dfcb57a0b0 | |||
f219ae43f7 | |||
a9bbe0bc40 | |||
35aa954be8 | |||
78ddcf9db7 | |||
cd0fa9405a | |||
4462def8ea | |||
3f93b87745 | |||
9f302cc640 | |||
0a73125606 | |||
7780441524 | |||
8bec4eaa87 | |||
4434d31a3b | |||
51454c71c7 | |||
fb2796ac06 | |||
742b15357b | |||
ac6ed27052 | |||
f3c1783bf2 | |||
ce8853ab50 | |||
5e3e00fbad | |||
1dde49d644 | |||
fd19298a05 | |||
ede2b83ce4 | |||
fc47d3feb8 | |||
87446028e0 | |||
b200f9945f | |||
eebd4e5f18 | |||
1069b5f5d5 | |||
3e7e44acfe | |||
518c3bfd76 | |||
905d8a0c06 | |||
b57c02b0ba | |||
03d0411679 | |||
83ace753b2 | |||
ec2e7db23e | |||
c4615591c9 | |||
25e3b599e7 | |||
5502a3e3ee | |||
62ceace941 | |||
7114d3193c | |||
f6bc69139d | |||
f3fc2fe523 | |||
1e045b6a62 | |||
747c9604dd | |||
1e5b2e0be3 | |||
0820716e7b | |||
191707cd37 | |||
223bab21aa | |||
563122ac92 | |||
bc9d00922e | |||
d9d83248fe | |||
f2397527f1 | |||
bf3caaefe1 | |||
1aaf854ef7 | |||
ce40f6f862 | |||
a349599943 | |||
ac7faa8d25 | |||
747ee32e81 | |||
75fadaa24f | |||
e4ea1f1014 | |||
cd2c4e13da | |||
f5ba072294 | |||
87d6312a37 | |||
3af7d4c930 | |||
0fc3071a21 | |||
7f36d08acc | |||
b040e5ddad | |||
f36ce5e0ee | |||
ffbdac7e9a | |||
f2b03342ac | |||
52ff61470b | |||
28277469b6 | |||
aa98104d54 | |||
9be70bcbe7 | |||
3a6fae4447 | |||
06f0984fa1 | |||
77dc35dc6a | |||
ed43f7cd9b | |||
32405a1637 | |||
43cab3f247 | |||
5ea2f2d4db | |||
b8ae808b65 | |||
96ecbc9fe4 | |||
588133d418 | |||
2f1249489b | |||
95f7c9bad0 | |||
8811d2f7c5 | |||
d6ca1e6a12 | |||
b0ad66bd04 | |||
c1d2b4601b | |||
c265625ed1 | |||
52352d9d04 | |||
cc5898d010 | |||
8684f0c8f5 | |||
d05d8de447 | |||
29b7d91293 | |||
bcdf3f2b83 | |||
ee497d2ffb | |||
9f8ae485c3 | |||
3b32fb74f7 | |||
7ff1af3934 | |||
ae21e03e1d | |||
f0a504baec | |||
f83b9732ee | |||
86ff08e854 | |||
b911a95fc2 | |||
73b0cc4056 | |||
8e36a64d49 | |||
255c808b16 | |||
b311b5ef4a | |||
9a8ada7e75 | |||
32a9ab30fb | |||
5c8d7c1255 | |||
53045b9e36 | |||
d35d28ac5a | |||
993dcde985 | |||
0ecef00b13 | |||
13a5eabc09 | |||
b8624c72d9 | |||
49c5b2b107 | |||
1e89bea288 | |||
2c64f95b06 | |||
037c78cfaf | |||
de777b1308 | |||
b3df3c999d | |||
4790c3a793 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,11 +1,10 @@
|
||||
**/.idea/
|
||||
.idea/workspace.xml
|
||||
/build/
|
||||
/dist/
|
||||
/output/
|
||||
.*cache/
|
||||
*.directory
|
||||
*.prg
|
||||
*.asm
|
||||
*.bin
|
||||
*.labels.txt
|
||||
*.vm.txt
|
||||
@ -29,3 +28,6 @@ compiler/src/prog8_kotlin.jar
|
||||
compiler/src/compiled_java
|
||||
.attach_pid*
|
||||
|
||||
.gradle
|
||||
build/
|
||||
/prog8compiler.jar
|
||||
|
6
.idea/kotlinc.xml
generated
Normal file
6
.idea/kotlinc.xml
generated
Normal 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
19
.idea/libraries/KotlinJavaRuntime.xml
generated
Normal 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>
|
9
.idea/libraries/antlr_4_7_2_complete.xml
generated
Normal file
9
.idea/libraries/antlr_4_7_2_complete.xml
generated
Normal 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
9
.idea/libraries/antlr_runtime_4_7_2.xml
generated
Normal 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
10
.idea/libraries/unittest_libs.xml
generated
Normal 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
6
.idea/misc.xml
generated
Normal 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
11
.idea/modules.xml
generated
Normal 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
124
.idea/uiDesigner.xml
generated
Normal 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
6
.idea/vcs.xml
generated
Normal 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
11
.travis.yml
Normal file
@ -0,0 +1,11 @@
|
||||
language: java
|
||||
# jdk: openjdk8
|
||||
# dist: xenial
|
||||
# sudo: false
|
||||
|
||||
before_install:
|
||||
- chmod +x gradlew
|
||||
|
||||
script:
|
||||
- ./gradlew test
|
||||
|
103
README.md
103
README.md
@ -1,3 +1,6 @@
|
||||
[](https://saythanks.io/to/irmen)
|
||||
[](https://travis-ci.org/irmen/prog8)
|
||||
|
||||
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):
|
||||
|
||||
- reduction of source code length
|
||||
- easier program understanding (because it's higher level, and more terse)
|
||||
- option to automatically run the compiled program in the Vice emulator
|
||||
- easier program understanding (because it's higher level, and way more compact)
|
||||
- modularity, symbol scoping, subroutines
|
||||
- subroutines have enforced input- and output parameter definitions
|
||||
- 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
|
||||
- constant folding in expressions (compile-time evaluation)
|
||||
- conditional branches
|
||||
- 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
|
||||
- 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
|
||||
- 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.
|
||||
Contributions to add support for other 8-bit (or other?!) machines are welcome.
|
||||
|
||||
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.
|
||||
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.
|
||||
If you want to build it from source, you'll need a Kotlin 1.3 SDK as well (or for instance,
|
||||
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 Java SDK + Kotlin 1.3.x SDK (or for instance,
|
||||
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
|
||||
@ -50,55 +59,58 @@ of the [Vice emulator](http://vice-emu.sourceforge.net/)
|
||||
Example code
|
||||
------------
|
||||
|
||||
When this code is compiled::
|
||||
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
|
||||
ubyte[256] sieve
|
||||
ubyte candidate_prime = 2
|
||||
|
||||
sub start() {
|
||||
; set text color and activate lowercase charset
|
||||
c64.COLOR = 13
|
||||
c64.VMCSB |= 2
|
||||
memset(sieve, 256, false)
|
||||
|
||||
; use optimized routine to write text
|
||||
c64scr.print("Hello!\n")
|
||||
|
||||
; use iteration to write text
|
||||
str question = "How are you?\n"
|
||||
for ubyte char in question
|
||||
c64.CHROUT(char)
|
||||
|
||||
; use indexed loop to write characters
|
||||
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)
|
||||
c64scr.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
while true {
|
||||
ubyte prime = find_next_prime()
|
||||
if prime==0
|
||||
break
|
||||
c64scr.print_ub(prime)
|
||||
c64scr.print(", ")
|
||||
amount++
|
||||
}
|
||||
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:
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
If you want to play a video game, a fully working Tetris clone is included in the examples:
|
||||
|
||||

|
||||
|
5
clean.sh
Executable file
5
clean.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
rm *.jar *.asm *.prg *.vm.txt *.vice-mon-list
|
||||
rm -r build
|
||||
|
105
compiler/build.gradle
Normal file
105
compiler/build.gradle
Normal file
@ -0,0 +1,105 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm" version "1.3.40"
|
||||
id 'application'
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
def kotlinVersion = '1.3.40'
|
||||
|
||||
dependencies {
|
||||
implementation project(':parser')
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
|
||||
implementation "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.junit.jupiter:junit-jupiter-api:5.3.2'
|
||||
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 {
|
||||
main {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/src"]
|
||||
}
|
||||
resources {
|
||||
srcDirs = ["${project.projectDir}/res"]
|
||||
}
|
||||
}
|
||||
test {
|
||||
java {
|
||||
srcDirs = ["${project.projectDir}/test"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
application {
|
||||
mainClassName = 'prog8.CompilerMainKt'
|
||||
applicationName = 'p8compile'
|
||||
}
|
||||
|
||||
task p8vmScript(type: CreateStartScripts) {
|
||||
mainClassName = "prog8.StackVmMainKt"
|
||||
applicationName = "p8vm"
|
||||
outputDir = new File(project.buildDir, 'scripts')
|
||||
classpath = jar.outputs.files + project.configurations.runtime
|
||||
}
|
||||
|
||||
applicationDistribution.into("bin") {
|
||||
from(p8vmScript)
|
||||
fileMode = 0755
|
||||
}
|
||||
|
||||
task fatJar(type: Jar) {
|
||||
manifest {
|
||||
attributes 'Main-Class': 'prog8.CompilerMainKt'
|
||||
}
|
||||
archiveBaseName = 'prog8compiler'
|
||||
destinationDirectory = rootProject.projectDir
|
||||
from {
|
||||
project.configurations.runtime.collect { it.isDirectory() ? it : zipTree(it) }
|
||||
}
|
||||
with jar
|
||||
}
|
||||
// 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"
|
||||
}
|
@ -3,15 +3,17 @@
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<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$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<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="testlibs" level="project" />
|
||||
<orderEntry type="module" module-name="parser" />
|
||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -9,31 +9,32 @@
|
||||
|
||||
~ c64flt {
|
||||
; ---- this block contains C-64 floating point related functions ----
|
||||
|
||||
|
||||
const float PI = 3.141592653589793
|
||||
const float TWOPI = 6.283185307179586
|
||||
|
||||
|
||||
|
||||
; ---- C64 basic and kernal ROM float constants and functions ----
|
||||
|
||||
; note: the fac1 and fac2 are working registers and take 6 bytes each,
|
||||
; floats in memory (and rom) are stored in 5-byte MFLPT packed format.
|
||||
|
||||
; constants in five-byte "mflpt" format in the BASIC ROM
|
||||
memory float FL_PIVAL = $aea8 ; 3.1415926...
|
||||
memory float FL_N32768 = $b1a5 ; -32768
|
||||
memory float FL_FONE = $b9bc ; 1
|
||||
memory float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
||||
memory float FL_SQRTWO = $b9db ; SQR(2)
|
||||
memory float FL_NEGHLF = $b9e0 ; -.5
|
||||
memory float FL_LOG2 = $b9e5 ; LOG(2)
|
||||
memory float FL_TENC = $baf9 ; 10
|
||||
memory float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
||||
memory float FL_FHALF = $bf11 ; .5
|
||||
memory float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
||||
memory float FL_PIHALF = $e2e0 ; PI / 2
|
||||
memory float FL_TWOPI = $e2e5 ; 2 * PI
|
||||
memory float FL_FR4 = $e2ea ; .25
|
||||
&float FL_PIVAL = $aea8 ; 3.1415926...
|
||||
&float FL_N32768 = $b1a5 ; -32768
|
||||
&float FL_FONE = $b9bc ; 1
|
||||
&float FL_SQRHLF = $b9d6 ; SQR(2) / 2
|
||||
&float FL_SQRTWO = $b9db ; SQR(2)
|
||||
&float FL_NEGHLF = $b9e0 ; -.5
|
||||
&float FL_LOG2 = $b9e5 ; LOG(2)
|
||||
&float FL_TENC = $baf9 ; 10
|
||||
&float FL_NZMIL = $bdbd ; 1e9 (1 billion)
|
||||
&float FL_FHALF = $bf11 ; .5
|
||||
&float FL_LOGEB2 = $bfbf ; 1 / LOG(2)
|
||||
&float FL_PIHALF = $e2e0 ; PI / 2
|
||||
&float FL_TWOPI = $e2e5 ; 2 * PI
|
||||
&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.
|
||||
@ -165,14 +166,14 @@ asmsub GIVAYFAY (uword value @ AY) -> clobbers(A,X,Y) -> () {
|
||||
sta c64.SCRATCH_ZPREG
|
||||
tya
|
||||
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) {
|
||||
; ---- fac1 to signed word in A/Y
|
||||
%asm {{
|
||||
jsr c64flt.FTOSWORDYA ; note the inverse Y/A order
|
||||
jsr FTOSWORDYA ; note the inverse Y/A order
|
||||
sta c64.SCRATCH_ZPREG
|
||||
tya
|
||||
ldy c64.SCRATCH_ZPREG
|
||||
@ -183,7 +184,7 @@ asmsub FTOSWRDAY () -> clobbers(X) -> (uword @ AY) {
|
||||
asmsub GETADRAY () -> clobbers(X) -> (uword @ AY) {
|
||||
; ---- fac1 to unsigned word in A/Y
|
||||
%asm {{
|
||||
jsr c64flt.GETADR ; this uses the inverse order, Y/A
|
||||
jsr GETADR ; this uses the inverse order, Y/A
|
||||
sta c64.SCRATCH_ZPB1
|
||||
tya
|
||||
ldy c64.SCRATCH_ZPB1
|
||||
@ -192,13 +193,13 @@ asmsub GETADRAY () -> clobbers(X) -> (uword @ AY) {
|
||||
}
|
||||
|
||||
sub print_f (float value) {
|
||||
; ---- prints the floating point value (without a newline) using basic rom routines.
|
||||
; ---- prints the floating point value (without a newline) using basic rom routines.
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<print_f_value
|
||||
ldy #>print_f_value
|
||||
jsr c64flt.MOVFM ; load float into fac1
|
||||
jsr c64flt.FOUT ; fac1 to string in A/Y
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FOUT ; fac1 to string in A/Y
|
||||
jsr c64.STROUT ; print string in A/Y
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
@ -211,12 +212,12 @@ sub print_fln (float value) {
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<print_fln_value
|
||||
ldy #>print_fln_value
|
||||
jsr c64flt.MOVFM ; load float into fac1
|
||||
jsr c64flt.FPRINTLN ; print fac1 with newline
|
||||
jsr MOVFM ; load float into fac1
|
||||
jsr FPRINTLN ; print fac1 with newline
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
}}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -229,10 +230,10 @@ ub2float .proc
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
ldy c64.SCRATCH_ZPB1
|
||||
jsr c64flt.FREADUY
|
||||
jsr FREADUY
|
||||
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
|
||||
ldy c64.SCRATCH_ZPWORD2+1
|
||||
jsr c64flt.MOVMF
|
||||
jsr MOVMF
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
.pend
|
||||
@ -244,7 +245,7 @@ b2float .proc
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
lda c64.SCRATCH_ZPB1
|
||||
jsr c64flt.FREADSA
|
||||
jsr FREADSA
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
@ -255,7 +256,7 @@ uw2float .proc
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr c64flt.GIVUAYFAY
|
||||
jsr GIVUAYFAY
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
@ -266,26 +267,26 @@ w2float .proc
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
ldy c64.SCRATCH_ZPWORD1
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
jsr c64flt.GIVAYF
|
||||
jsr GIVAYF
|
||||
jmp ub2float._fac_to_mem
|
||||
.pend
|
||||
|
||||
|
||||
stack_b2float .proc
|
||||
; -- b2float operating on the stack
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.FREADSA
|
||||
jsr FREADSA
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
|
||||
stack_w2float .proc
|
||||
; -- w2float operating on the stack
|
||||
inx
|
||||
ldy c64.ESTACK_LO,x
|
||||
lda c64.ESTACK_HI,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.GIVAYF
|
||||
jsr GIVAYF
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
@ -295,7 +296,7 @@ stack_ub2float .proc
|
||||
lda c64.ESTACK_LO,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
tay
|
||||
jsr c64flt.FREADUY
|
||||
jsr FREADUY
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
@ -305,14 +306,14 @@ stack_uw2float .proc
|
||||
lda c64.ESTACK_LO,x
|
||||
ldy c64.ESTACK_HI,x
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.GIVUAYFAY
|
||||
jsr GIVUAYFAY
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
stack_float2w .proc
|
||||
|
||||
stack_float2w .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.AYINT
|
||||
jsr AYINT
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
lda $64
|
||||
sta c64.ESTACK_HI,x
|
||||
@ -321,11 +322,11 @@ stack_float2w .proc
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
stack_float2uw .proc
|
||||
|
||||
stack_float2uw .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.GETADR
|
||||
jsr GETADR
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
sta c64.ESTACK_HI,x
|
||||
tya
|
||||
@ -335,7 +336,7 @@ stack_float2uw .proc
|
||||
.pend
|
||||
|
||||
push_float .proc
|
||||
; ---- push mflpt5 in A/Y onto stack
|
||||
; ---- push mflpt5 in A/Y onto stack
|
||||
; (taking 3 stack positions = 6 bytes of which 1 is padding)
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
@ -359,23 +360,23 @@ push_float .proc
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
func_rndf .proc
|
||||
; -- put a random floating point value on the stack
|
||||
stx c64.SCRATCH_ZPREG
|
||||
lda #1
|
||||
jsr c64flt.FREADSA
|
||||
jsr c64flt.RND ; rng into fac1
|
||||
jsr FREADSA
|
||||
jsr RND ; rng into fac1
|
||||
ldx #<_rndf_rnum5
|
||||
ldy #>_rndf_rnum5
|
||||
jsr c64flt.MOVMF ; fac1 to mem X/Y
|
||||
jsr MOVMF ; fac1 to mem X/Y
|
||||
ldx c64.SCRATCH_ZPREG
|
||||
lda #<_rndf_rnum5
|
||||
ldy #>_rndf_rnum5
|
||||
jmp push_float
|
||||
_rndf_rnum5 .byte 0,0,0,0,0
|
||||
.pend
|
||||
|
||||
|
||||
push_float_from_indexed_var .proc
|
||||
; -- push the float from the array at A/Y with index on stack, onto the stack.
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
@ -412,7 +413,7 @@ pop_float .proc
|
||||
sta (c64.SCRATCH_ZPWORD1),y
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
pop_float_fac1 .proc
|
||||
; -- pops float from stack into FAC1
|
||||
lda #<fmath_float1
|
||||
@ -420,9 +421,9 @@ pop_float_fac1 .proc
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jmp c64flt.MOVFM
|
||||
jmp MOVFM
|
||||
.pend
|
||||
|
||||
|
||||
pop_float_to_indexed_var .proc
|
||||
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
@ -435,7 +436,7 @@ pop_float_to_indexed_var .proc
|
||||
.pend
|
||||
|
||||
copy_float .proc
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
|
||||
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
sty c64.SCRATCH_ZPWORD2+1
|
||||
@ -462,17 +463,17 @@ inc_var_f .proc
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.MOVFM
|
||||
jsr MOVFM
|
||||
lda #<FL_FONE
|
||||
ldy #>FL_FONE
|
||||
jsr c64flt.FADD
|
||||
jsr FADD
|
||||
ldx c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr c64flt.MOVMF
|
||||
jsr MOVMF
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
dec_var_f .proc
|
||||
; -- subtract 1 from float pointed to by A/Y
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
@ -480,17 +481,17 @@ dec_var_f .proc
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<FL_FONE
|
||||
ldy #>FL_FONE
|
||||
jsr c64flt.MOVFM
|
||||
jsr MOVFM
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr c64flt.FSUB
|
||||
jsr FSUB
|
||||
ldx c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr c64flt.MOVMF
|
||||
jsr MOVMF
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
inc_indexed_var_f .proc
|
||||
; -- add 1 to float in array pointed to by A/Y, at index X
|
||||
pha
|
||||
@ -508,7 +509,7 @@ inc_indexed_var_f .proc
|
||||
iny
|
||||
+ jmp inc_var_f
|
||||
.pend
|
||||
|
||||
|
||||
dec_indexed_var_f .proc
|
||||
; -- subtract 1 to float in array pointed to by A/Y, at index X
|
||||
pha
|
||||
@ -526,7 +527,7 @@ dec_indexed_var_f .proc
|
||||
iny
|
||||
+ jmp dec_var_f
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
pop_2_floats_f2_in_fac1 .proc
|
||||
; -- pop 2 floats from stack, load the second one in FAC1 as well
|
||||
@ -538,9 +539,10 @@ pop_2_floats_f2_in_fac1 .proc
|
||||
jsr pop_float
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
jmp c64flt.MOVFM
|
||||
jmp MOVFM
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
||||
@ -548,13 +550,31 @@ push_fac1_as_result .proc
|
||||
; -- push the float in FAC1 onto the stack, and return from calculation
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr c64flt.MOVMF
|
||||
jsr MOVMF
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
jmp push_float
|
||||
.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
|
||||
; -- push f1/f2 on stack
|
||||
@ -562,7 +582,7 @@ div_f .proc
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr c64flt.FDIV
|
||||
jsr FDIV
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
@ -572,7 +592,7 @@ add_f .proc
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr c64flt.FADD
|
||||
jsr FADD
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
@ -582,7 +602,7 @@ sub_f .proc
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr c64flt.FSUB
|
||||
jsr FSUB
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
@ -592,15 +612,15 @@ mul_f .proc
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr c64flt.FMULT
|
||||
jsr FMULT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
|
||||
neg_f .proc
|
||||
; -- push -flt back on stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.NEGOP
|
||||
jsr NEGOP
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
@ -608,7 +628,7 @@ abs_f .proc
|
||||
; -- push abs(float) on stack (as float)
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.ABS
|
||||
jsr ABS
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
@ -638,7 +658,7 @@ _equals_store inx
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
_equals_false lda #0
|
||||
beq _equals_store
|
||||
beq _equals_store
|
||||
.pend
|
||||
|
||||
notequal_f .proc
|
||||
@ -656,7 +676,7 @@ less_f .proc
|
||||
beq compare_floats._return_true
|
||||
bne compare_floats._return_false
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
lesseq_f .proc
|
||||
; -- is f1 <= f2?
|
||||
@ -695,11 +715,11 @@ compare_floats .proc
|
||||
jsr pop_float
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr c64flt.MOVFM ; fac1 = flt1
|
||||
jsr MOVFM ; fac1 = flt1
|
||||
lda #<fmath_float2
|
||||
ldy #>fmath_float2
|
||||
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
|
||||
rts
|
||||
_return_false lda #0
|
||||
@ -708,13 +728,13 @@ _return_result sta c64.ESTACK_LO,x
|
||||
rts
|
||||
_return_true lda #1
|
||||
bne _return_result
|
||||
.pend
|
||||
.pend
|
||||
|
||||
func_sin .proc
|
||||
; -- push sin(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.SIN
|
||||
jsr SIN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
@ -722,7 +742,7 @@ func_cos .proc
|
||||
; -- push cos(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.COS
|
||||
jsr COS
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
@ -730,99 +750,99 @@ func_tan .proc
|
||||
; -- push tan(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.TAN
|
||||
jsr TAN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
|
||||
func_atan .proc
|
||||
; -- push atan(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.ATN
|
||||
jsr ATN
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
|
||||
func_ln .proc
|
||||
; -- push ln(f) back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.LOG
|
||||
jsr LOG
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
|
||||
func_log2 .proc
|
||||
; -- push log base 2, ln(f)/ln(2), back onto stack
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.LOG
|
||||
jsr c64flt.MOVEF
|
||||
jsr LOG
|
||||
jsr MOVEF
|
||||
lda #<c64.FL_LOG2
|
||||
ldy #>c64.FL_LOG2
|
||||
jsr c64flt.MOVFM
|
||||
jsr c64flt.FDIVT
|
||||
jsr MOVFM
|
||||
jsr FDIVT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
|
||||
func_sqrt .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.SQR
|
||||
jsr SQR
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
|
||||
func_rad .proc
|
||||
; -- convert degrees to radians (d * pi / 180)
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<_pi_div_180
|
||||
ldy #>_pi_div_180
|
||||
jsr c64flt.FMULT
|
||||
jsr FMULT
|
||||
jmp push_fac1_as_result
|
||||
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
|
||||
.pend
|
||||
|
||||
|
||||
func_deg .proc
|
||||
; -- convert radians to degrees (d * (1/ pi * 180))
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
lda #<_one_over_pi_div_180
|
||||
ldy #>_one_over_pi_div_180
|
||||
jsr c64flt.FMULT
|
||||
jsr FMULT
|
||||
jmp push_fac1_as_result
|
||||
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
|
||||
.pend
|
||||
|
||||
|
||||
func_round .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.FADDH
|
||||
jsr c64flt.INT
|
||||
jsr FADDH
|
||||
jsr INT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
|
||||
func_floor .proc
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr c64flt.INT
|
||||
jsr INT
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
|
||||
func_ceil .proc
|
||||
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
|
||||
jsr pop_float_fac1
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
ldx #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr MOVMF
|
||||
jsr INT
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr c64flt.MOVMF
|
||||
jsr c64flt.INT
|
||||
lda #<fmath_float1
|
||||
ldy #>fmath_float1
|
||||
jsr c64flt.FCOMP
|
||||
jsr FCOMP
|
||||
cmp #0
|
||||
beq +
|
||||
lda #<FL_FONE
|
||||
ldy #>FL_FONE
|
||||
jsr c64flt.FADD
|
||||
jsr FADD
|
||||
+ jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
@ -834,98 +854,93 @@ func_any_f .proc
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
||||
jmp func_any_b._entry
|
||||
jmp prog8_lib.func_any_b._entry
|
||||
.pend
|
||||
|
||||
func_all_f .proc
|
||||
inx
|
||||
jsr prog8_lib.peek_address
|
||||
lda c64.ESTACK_LO,x ; array size
|
||||
sta c64.SCRATCH_ZPB1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1 ; times 5 because of float
|
||||
sta _cmp_mod+1 ; self-modifying code
|
||||
jsr peek_address
|
||||
ldy #0
|
||||
tay
|
||||
dey
|
||||
- lda (c64.SCRATCH_ZPWORD1),y
|
||||
bne +
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
bne +
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
bne +
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
bne +
|
||||
iny
|
||||
lda (c64.SCRATCH_ZPWORD1),y
|
||||
bne +
|
||||
lda #0
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
+ iny
|
||||
_cmp_mod cpy #255 ; modified
|
||||
bne -
|
||||
clc
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
dey
|
||||
cmp #0
|
||||
beq +
|
||||
cpy #255
|
||||
bne -
|
||||
lda #1
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
+ sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_max_f .proc
|
||||
lda #<_min_float
|
||||
ldy #>_min_float
|
||||
jsr c64flt.MOVFM ; fac1=min(float)
|
||||
lda #255
|
||||
sta _cmp_mod+1 ; compare using 255 so we keep larger values
|
||||
_minmax_entry jsr pop_array_and_lengthmin1Y
|
||||
sta _minmax_cmp+1
|
||||
lda #<_largest_neg_float
|
||||
ldy #>_largest_neg_float
|
||||
_minmax_entry jsr MOVFM
|
||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
- sty c64.SCRATCH_ZPREG
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr c64flt.FCOMP
|
||||
_cmp_mod cmp #255 ; will be modified
|
||||
jsr FCOMP
|
||||
_minmax_cmp cmp #255 ; modified
|
||||
bne +
|
||||
; fac1 is smaller/larger, so store the new value instead
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr c64flt.MOVFM
|
||||
ldy c64.SCRATCH_ZPREG
|
||||
dey
|
||||
cmp #255
|
||||
beq +
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
jsr MOVFM
|
||||
+ lda c64.SCRATCH_ZPWORD1
|
||||
clc
|
||||
adc #5
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
bcc -
|
||||
bcc +
|
||||
inc c64.SCRATCH_ZPWORD1+1
|
||||
+ ldy c64.SCRATCH_ZPREG
|
||||
dey
|
||||
cpy #255
|
||||
bne -
|
||||
+ jmp push_fac1_as_result
|
||||
_min_float .byte 255,255,255,255,255 ; -1.7014118345e+38
|
||||
jmp push_fac1_as_result
|
||||
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
|
||||
.pend
|
||||
|
||||
func_min_f .proc
|
||||
lda #<_max_float
|
||||
ldy #>_max_float
|
||||
jsr c64flt.MOVFM ; fac1=max(float)
|
||||
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
|
||||
_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
|
||||
|
||||
func_sum_f .proc
|
||||
lda #<c64.FL_NEGHLF
|
||||
ldy #>c64.FL_NEGHLF
|
||||
jsr c64flt.MOVFM
|
||||
jsr pop_array_and_lengthmin1Y
|
||||
lda #<FL_ZERO
|
||||
ldy #>FL_ZERO
|
||||
jsr MOVFM
|
||||
jsr prog8_lib.pop_array_and_lengthmin1Y
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
- sty c64.SCRATCH_ZPREG
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
ldy c64.SCRATCH_ZPWORD1+1
|
||||
jsr c64flt.FADD
|
||||
jsr FADD
|
||||
ldy c64.SCRATCH_ZPREG
|
||||
dey
|
||||
cpy #255
|
||||
@ -937,8 +952,7 @@ func_sum_f .proc
|
||||
bcc -
|
||||
inc c64.SCRATCH_ZPWORD1+1
|
||||
bne -
|
||||
+ jsr c64flt.FADDH
|
||||
jmp push_fac1_as_result
|
||||
+ jmp push_fac1_as_result
|
||||
.pend
|
||||
}}
|
||||
|
||||
|
@ -7,178 +7,178 @@
|
||||
|
||||
|
||||
~ 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_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
|
||||
memory ubyte TIME_MID = $a1 ; .. mid byte
|
||||
memory ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||
memory ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||
memory ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||
|
||||
memory ubyte COLOR = $0286 ; cursor color
|
||||
memory ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||
memory uword CINV = $0314 ; IRQ vector
|
||||
memory 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
|
||||
memory uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
|
||||
&ubyte TIME_MID = $a1 ; .. mid byte
|
||||
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
|
||||
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
|
||||
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
|
||||
|
||||
&ubyte COLOR = $0286 ; cursor color
|
||||
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
|
||||
&uword CINV = $0314 ; IRQ vector
|
||||
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
|
||||
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
|
||||
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
|
||||
|
||||
; 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 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)
|
||||
memory ubyte SPRPTR0 = 2040
|
||||
memory ubyte SPRPTR1 = 2041
|
||||
memory ubyte SPRPTR2 = 2042
|
||||
memory ubyte SPRPTR3 = 2043
|
||||
memory ubyte SPRPTR4 = 2044
|
||||
memory ubyte SPRPTR5 = 2045
|
||||
memory ubyte SPRPTR6 = 2046
|
||||
memory ubyte SPRPTR7 = 2047
|
||||
memory ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
|
||||
|
||||
&ubyte SPRPTR0 = 2040
|
||||
&ubyte SPRPTR1 = 2041
|
||||
&ubyte SPRPTR2 = 2042
|
||||
&ubyte SPRPTR3 = 2043
|
||||
&ubyte SPRPTR4 = 2044
|
||||
&ubyte SPRPTR5 = 2045
|
||||
&ubyte SPRPTR6 = 2046
|
||||
&ubyte SPRPTR7 = 2047
|
||||
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
|
||||
|
||||
|
||||
; ---- VIC-II 6567/6569/856x registers ----
|
||||
|
||||
memory ubyte SP0X = $d000
|
||||
memory ubyte SP0Y = $d001
|
||||
memory ubyte SP1X = $d002
|
||||
memory ubyte SP1Y = $d003
|
||||
memory ubyte SP2X = $d004
|
||||
memory ubyte SP2Y = $d005
|
||||
memory ubyte SP3X = $d006
|
||||
memory ubyte SP3Y = $d007
|
||||
memory ubyte SP4X = $d008
|
||||
memory ubyte SP4Y = $d009
|
||||
memory ubyte SP5X = $d00a
|
||||
memory ubyte SP5Y = $d00b
|
||||
memory ubyte SP6X = $d00c
|
||||
memory ubyte SP6Y = $d00d
|
||||
memory ubyte SP7X = $d00e
|
||||
memory ubyte SP7Y = $d00f
|
||||
memory 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.
|
||||
&ubyte SP0X = $d000
|
||||
&ubyte SP0Y = $d001
|
||||
&ubyte SP1X = $d002
|
||||
&ubyte SP1Y = $d003
|
||||
&ubyte SP2X = $d004
|
||||
&ubyte SP2Y = $d005
|
||||
&ubyte SP3X = $d006
|
||||
&ubyte SP3Y = $d007
|
||||
&ubyte SP4X = $d008
|
||||
&ubyte SP4Y = $d009
|
||||
&ubyte SP5X = $d00a
|
||||
&ubyte SP5Y = $d00b
|
||||
&ubyte SP6X = $d00c
|
||||
&ubyte SP6Y = $d00d
|
||||
&ubyte SP7X = $d00e
|
||||
&ubyte SP7Y = $d00f
|
||||
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
|
||||
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
|
||||
|
||||
memory ubyte MSIGX = $d010
|
||||
memory ubyte SCROLY = $d011
|
||||
memory ubyte RASTER = $d012
|
||||
memory ubyte LPENX = $d013
|
||||
memory ubyte LPENY = $d014
|
||||
memory ubyte SPENA = $d015
|
||||
memory ubyte SCROLX = $d016
|
||||
memory ubyte YXPAND = $d017
|
||||
memory ubyte VMCSB = $d018
|
||||
memory ubyte VICIRQ = $d019
|
||||
memory ubyte IREQMASK = $d01a
|
||||
memory ubyte SPBGPR = $d01b
|
||||
memory ubyte SPMC = $d01c
|
||||
memory ubyte XXPAND = $d01d
|
||||
memory ubyte SPSPCL = $d01e
|
||||
memory ubyte SPBGCL = $d01f
|
||||
&ubyte MSIGX = $d010
|
||||
&ubyte SCROLY = $d011
|
||||
&ubyte RASTER = $d012
|
||||
&ubyte LPENX = $d013
|
||||
&ubyte LPENY = $d014
|
||||
&ubyte SPENA = $d015
|
||||
&ubyte SCROLX = $d016
|
||||
&ubyte YXPAND = $d017
|
||||
&ubyte VMCSB = $d018
|
||||
&ubyte VICIRQ = $d019
|
||||
&ubyte IREQMASK = $d01a
|
||||
&ubyte SPBGPR = $d01b
|
||||
&ubyte SPMC = $d01c
|
||||
&ubyte XXPAND = $d01d
|
||||
&ubyte SPSPCL = $d01e
|
||||
&ubyte SPBGCL = $d01f
|
||||
|
||||
&ubyte EXTCOL = $d020 ; border color
|
||||
&ubyte BGCOL0 = $d021 ; screen color
|
||||
&ubyte BGCOL1 = $d022
|
||||
&ubyte BGCOL2 = $d023
|
||||
&ubyte BGCOL4 = $d024
|
||||
&ubyte SPMC0 = $d025
|
||||
&ubyte SPMC1 = $d026
|
||||
&ubyte SP0COL = $d027
|
||||
&ubyte SP1COL = $d028
|
||||
&ubyte SP2COL = $d029
|
||||
&ubyte SP3COL = $d02a
|
||||
&ubyte SP4COL = $d02b
|
||||
&ubyte SP5COL = $d02c
|
||||
&ubyte SP6COL = $d02d
|
||||
&ubyte SP7COL = $d02e
|
||||
&ubyte[8] SPCOL = $d027
|
||||
|
||||
memory ubyte EXTCOL = $d020 ; border color
|
||||
memory ubyte BGCOL0 = $d021 ; screen color
|
||||
memory ubyte BGCOL1 = $d022
|
||||
memory ubyte BGCOL2 = $d023
|
||||
memory ubyte BGCOL4 = $d024
|
||||
memory ubyte SPMC0 = $d025
|
||||
memory ubyte SPMC1 = $d026
|
||||
memory ubyte SP0COL = $d027
|
||||
memory ubyte SP1COL = $d028
|
||||
memory ubyte SP2COL = $d029
|
||||
memory ubyte SP3COL = $d02a
|
||||
memory ubyte SP4COL = $d02b
|
||||
memory ubyte SP5COL = $d02c
|
||||
memory ubyte SP6COL = $d02d
|
||||
memory ubyte SP7COL = $d02e
|
||||
memory ubyte[8] SPCOL = $d027
|
||||
|
||||
|
||||
; ---- end of VIC-II registers ----
|
||||
|
||||
; ---- CIA 6526 1 & 2 registers ----
|
||||
|
||||
memory ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive
|
||||
memory ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port
|
||||
memory ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
|
||||
memory ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
|
||||
memory ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
|
||||
memory ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
|
||||
memory ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
|
||||
memory ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
|
||||
memory ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
|
||||
memory ubyte CIA1TODSEC = $DC09 ; time of day, seconds
|
||||
memory ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
|
||||
memory ubyte CIA1TODHR = $DC0B ; time of day, hours
|
||||
memory ubyte CIA1SDR = $DC0C ; Serial Data Register
|
||||
memory ubyte CIA1ICR = $DC0D
|
||||
memory ubyte CIA1CRA = $DC0E
|
||||
memory ubyte CIA1CRB = $DC0F
|
||||
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
|
||||
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
|
||||
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
|
||||
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
|
||||
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
|
||||
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
|
||||
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
|
||||
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
|
||||
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
|
||||
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
|
||||
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
|
||||
&ubyte CIA1TODHR = $DC0B ; time of day, hours
|
||||
&ubyte CIA1SDR = $DC0C ; Serial Data Register
|
||||
&ubyte CIA1ICR = $DC0D
|
||||
&ubyte CIA1CRA = $DC0E
|
||||
&ubyte CIA1CRB = $DC0F
|
||||
|
||||
memory ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
|
||||
memory ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
|
||||
memory ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
|
||||
memory ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
|
||||
memory ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
|
||||
memory ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
|
||||
memory ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
|
||||
memory ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
|
||||
memory ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
|
||||
memory ubyte CIA2TODSEC = $DD09 ; time of day, seconds
|
||||
memory ubyte CIA2TODMIN = $DD0A ; time of day, minutes
|
||||
memory ubyte CIA2TODHR = $DD0B ; time of day, hours
|
||||
memory ubyte CIA2SDR = $DD0C ; Serial Data Register
|
||||
memory ubyte CIA2ICR = $DD0D
|
||||
memory ubyte CIA2CRA = $DD0E
|
||||
memory ubyte CIA2CRB = $DD0F
|
||||
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
|
||||
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
|
||||
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
|
||||
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
|
||||
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
|
||||
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
|
||||
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
|
||||
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
|
||||
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
|
||||
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
|
||||
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
|
||||
&ubyte CIA2TODHR = $DD0B ; time of day, hours
|
||||
&ubyte CIA2SDR = $DD0C ; Serial Data Register
|
||||
&ubyte CIA2ICR = $DD0D
|
||||
&ubyte CIA2CRA = $DD0E
|
||||
&ubyte CIA2CRB = $DD0F
|
||||
|
||||
; ---- end of CIA registers ----
|
||||
|
||||
; ---- SID 6581/8580 registers ----
|
||||
|
||||
memory ubyte FREQLO1 = $D400 ; channel 1 freq lo
|
||||
memory ubyte FREQHI1 = $D401 ; channel 1 freq hi
|
||||
memory uword FREQ1 = $D400 ; channel 1 freq (word)
|
||||
memory ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
|
||||
memory ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
|
||||
memory uword PW1 = $D402 ; channel 1 pulse width (word)
|
||||
memory ubyte CR1 = $D404 ; channel 1 voice control register
|
||||
memory ubyte AD1 = $D405 ; channel 1 attack & decay
|
||||
memory ubyte SR1 = $D406 ; channel 1 sustain & release
|
||||
memory ubyte FREQLO2 = $D407 ; channel 2 freq lo
|
||||
memory ubyte FREQHI2 = $D408 ; channel 2 freq hi
|
||||
memory uword FREQ2 = $D407 ; channel 2 freq (word)
|
||||
memory ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
|
||||
memory ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
|
||||
memory uword PW2 = $D409 ; channel 2 pulse width (word)
|
||||
memory ubyte CR2 = $D40B ; channel 2 voice control register
|
||||
memory ubyte AD2 = $D40C ; channel 2 attack & decay
|
||||
memory ubyte SR2 = $D40D ; channel 2 sustain & release
|
||||
memory ubyte FREQLO3 = $D40E ; channel 3 freq lo
|
||||
memory ubyte FREQHI3 = $D40F ; channel 3 freq hi
|
||||
memory uword FREQ3 = $D40E ; channel 3 freq (word)
|
||||
memory ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
|
||||
memory ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
|
||||
memory uword PW3 = $D410 ; channel 3 pulse width (word)
|
||||
memory ubyte CR3 = $D412 ; channel 3 voice control register
|
||||
memory ubyte AD3 = $D413 ; channel 3 attack & decay
|
||||
memory ubyte SR3 = $D414 ; channel 3 sustain & release
|
||||
memory ubyte FCLO = $D415 ; filter cutoff lo (2-0)
|
||||
memory ubyte FCHI = $D416 ; filter cutoff hi (10-3)
|
||||
memory uword FC = $D415 ; filter cutoff (word)
|
||||
memory ubyte RESFILT = $D417 ; filter resonance and routing
|
||||
memory ubyte MVOL = $D418 ; filter mode and main volume control
|
||||
memory ubyte POTX = $D419 ; potentiometer X
|
||||
memory ubyte POTY = $D41A ; potentiometer Y
|
||||
memory ubyte OSC3 = $D41B ; channel 3 oscillator value read
|
||||
memory ubyte ENV3 = $D41C ; channel 3 envelope value read
|
||||
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
|
||||
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
|
||||
&uword FREQ1 = $D400 ; channel 1 freq (word)
|
||||
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
|
||||
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
|
||||
&uword PW1 = $D402 ; channel 1 pulse width (word)
|
||||
&ubyte CR1 = $D404 ; channel 1 voice control register
|
||||
&ubyte AD1 = $D405 ; channel 1 attack & decay
|
||||
&ubyte SR1 = $D406 ; channel 1 sustain & release
|
||||
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
|
||||
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
|
||||
&uword FREQ2 = $D407 ; channel 2 freq (word)
|
||||
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
|
||||
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
|
||||
&uword PW2 = $D409 ; channel 2 pulse width (word)
|
||||
&ubyte CR2 = $D40B ; channel 2 voice control register
|
||||
&ubyte AD2 = $D40C ; channel 2 attack & decay
|
||||
&ubyte SR2 = $D40D ; channel 2 sustain & release
|
||||
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
|
||||
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
|
||||
&uword FREQ3 = $D40E ; channel 3 freq (word)
|
||||
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
|
||||
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
|
||||
&uword PW3 = $D410 ; channel 3 pulse width (word)
|
||||
&ubyte CR3 = $D412 ; channel 3 voice control register
|
||||
&ubyte AD3 = $D413 ; channel 3 attack & decay
|
||||
&ubyte SR3 = $D414 ; channel 3 sustain & release
|
||||
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
|
||||
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
|
||||
&uword FC = $D415 ; filter cutoff (word)
|
||||
&ubyte RESFILT = $D417 ; filter resonance and routing
|
||||
&ubyte MVOL = $D418 ; filter mode and main volume control
|
||||
&ubyte POTX = $D419 ; potentiometer X
|
||||
&ubyte POTY = $D41A ; potentiometer Y
|
||||
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
|
||||
&ubyte ENV3 = $D41C ; channel 3 envelope value read
|
||||
|
||||
; ---- 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 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 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
|
||||
|
||||
; ---- end of C64 kernal routines ----
|
||||
|
@ -54,40 +54,39 @@ asmsub ubyte2hex (ubyte value @ A) -> clobbers() -> (ubyte @ A, ubyte @ Y) {
|
||||
pha
|
||||
and #$0f
|
||||
tax
|
||||
ldy hex_digits,x
|
||||
ldy _hex_digits,x
|
||||
pla
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
lsr a
|
||||
tax
|
||||
lda hex_digits,x
|
||||
lda _hex_digits,x
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
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) -> () {
|
||||
; ---- 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 {{
|
||||
sta c64.SCRATCH_ZPREG
|
||||
tya
|
||||
jsr ubyte2hex
|
||||
sta word2hex_output
|
||||
sty word2hex_output+1
|
||||
sta output
|
||||
sty output+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
jsr ubyte2hex
|
||||
sta word2hex_output+2
|
||||
sty word2hex_output+3
|
||||
sta output+2
|
||||
sty output+3
|
||||
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) -> () {
|
||||
; Convert an 16 bit binary value to BCD
|
||||
;
|
||||
@ -99,47 +98,55 @@ asmsub uword2bcd (uword value @ AY) -> clobbers(A,Y) -> () {
|
||||
%asm {{
|
||||
sta c64.SCRATCH_ZPB1
|
||||
sty c64.SCRATCH_ZPREG
|
||||
php
|
||||
pla ; read status register
|
||||
and #%00000100
|
||||
sta _had_irqd
|
||||
sei ; disable interrupts because of bcd math
|
||||
sed ; switch to decimal mode
|
||||
lda #0 ; ensure the result is clear
|
||||
sta word2bcd_bcdbuff+0
|
||||
sta word2bcd_bcdbuff+1
|
||||
sta word2bcd_bcdbuff+2
|
||||
sta bcdbuff+0
|
||||
sta bcdbuff+1
|
||||
sta bcdbuff+2
|
||||
ldy #16 ; the number of source bits
|
||||
|
||||
- asl c64.SCRATCH_ZPB1 ; shift out one bit
|
||||
rol c64.SCRATCH_ZPREG
|
||||
lda word2bcd_bcdbuff+0 ; and add into result
|
||||
adc word2bcd_bcdbuff+0
|
||||
sta word2bcd_bcdbuff+0
|
||||
lda word2bcd_bcdbuff+1 ; propagating any carry
|
||||
adc word2bcd_bcdbuff+1
|
||||
sta word2bcd_bcdbuff+1
|
||||
lda word2bcd_bcdbuff+2 ; ... thru whole result
|
||||
adc word2bcd_bcdbuff+2
|
||||
sta word2bcd_bcdbuff+2
|
||||
lda bcdbuff+0 ; and add into result
|
||||
adc bcdbuff+0
|
||||
sta bcdbuff+0
|
||||
lda bcdbuff+1 ; propagating any carry
|
||||
adc bcdbuff+1
|
||||
sta bcdbuff+1
|
||||
lda bcdbuff+2 ; ... thru whole result
|
||||
adc bcdbuff+2
|
||||
sta bcdbuff+2
|
||||
dey ; and repeat for next bit
|
||||
bne -
|
||||
cld ; back to binary
|
||||
cli ; enable interrupts again @todo don't re-enable if it wasn't enabled before
|
||||
rts
|
||||
lda _had_irqd
|
||||
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,Y) -> () {
|
||||
; ---- convert 16 bit uword in A/Y into decimal string into memory 'word2decimal_output'
|
||||
asmsub uword2decimal (uword value @ AY) -> clobbers(A) -> (ubyte @ Y) {
|
||||
; ---- convert 16 bit uword in A/Y into 0-terminated decimal string into memory 'uword2decimal.output'
|
||||
; returns length of resulting string in Y
|
||||
%asm {{
|
||||
jsr uword2bcd
|
||||
lda word2bcd_bcdbuff+2
|
||||
lda uword2bcd.bcdbuff+2
|
||||
clc
|
||||
adc #'0'
|
||||
sta word2decimal_output
|
||||
sta output
|
||||
ldy #1
|
||||
lda word2bcd_bcdbuff+1
|
||||
lda uword2bcd.bcdbuff+1
|
||||
jsr +
|
||||
lda word2bcd_bcdbuff+0
|
||||
lda uword2bcd.bcdbuff+0
|
||||
|
||||
+ pha
|
||||
lsr a
|
||||
@ -148,153 +155,135 @@ asmsub uword2decimal (uword value @ AY) -> clobbers(A,Y) -> () {
|
||||
lsr a
|
||||
clc
|
||||
adc #'0'
|
||||
sta word2decimal_output,y
|
||||
sta output,y
|
||||
iny
|
||||
pla
|
||||
and #$0f
|
||||
adc #'0'
|
||||
sta word2decimal_output,y
|
||||
sta output,y
|
||||
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
|
||||
dey
|
||||
bpl +
|
||||
sta output,y
|
||||
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
|
||||
;-- return the length of the numeric string at ZPWORD1, in Y
|
||||
output .text "00000", $00 ; 0 terminated
|
||||
|
||||
}}
|
||||
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
- lda (c64.SCRATCH_ZPWORD1),y
|
||||
cmp #'0'
|
||||
bmi +
|
||||
cmp #':' ; one after '9'
|
||||
sty _result
|
||||
sty _result+1
|
||||
_mod lda $ffff,y ; modified
|
||||
sec
|
||||
sbc #48
|
||||
bpl +
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
_done ; return result
|
||||
lda _result
|
||||
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 c64flt_GETADR () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b7f7 ; @todo needed for (slow) str conversion below
|
||||
asmsub c64flt_FTOSWORDYA () -> clobbers(X) -> (ubyte @ Y, ubyte @ A) = $b1aa ; @todo needed for (slow) str conversion below
|
||||
|
||||
asmsub str2uword(str string @ AY) -> clobbers() -> (uword @ AY) {
|
||||
asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) {
|
||||
; -- returns the signed word value of the string number argument in AY
|
||||
; the number may be preceded by a + or - sign but may NOT contain spaces
|
||||
; (any non-digit character will terminate the number string that is parsed)
|
||||
%asm {{
|
||||
;-- convert string (address in A/Y) to uword number in A/Y
|
||||
; @todo don't use the (slow) kernel floating point conversion
|
||||
sta $22
|
||||
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
|
||||
_result = c64.SCRATCH_ZPWORD2
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
sty c64.SCRATCH_ZPWORD1+1
|
||||
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 +
|
||||
iny
|
||||
bne -
|
||||
+ rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub str2word(str string @ AY) -> clobbers() -> (word @ AY) {
|
||||
%asm {{
|
||||
;-- convert string (address in A/Y) to signed word number in A/Y
|
||||
; @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
|
||||
sec
|
||||
lda #0
|
||||
sbc _result
|
||||
sta _result
|
||||
lda #0
|
||||
sbc _result+1
|
||||
sta _result+1
|
||||
+ lda _result
|
||||
ldy _result+1
|
||||
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) -> () {
|
||||
%asm {{
|
||||
sei
|
||||
@ -304,7 +293,9 @@ asmsub set_irqvec_excl() -> clobbers(A) -> () {
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
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
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
lda c64.CIA1ICR ; acknowledge CIA1 interrupt
|
||||
@ -321,10 +312,64 @@ asmsub set_irqvec() -> clobbers(A) -> () {
|
||||
sta c64.CINV+1
|
||||
cli
|
||||
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
|
||||
|
||||
}}
|
||||
_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
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
jmp c64.IRQDFRT
|
||||
@ -396,7 +443,9 @@ asmsub set_rasterirq_excl(uword rasterpos @ AY) -> clobbers(A) -> () {
|
||||
rts
|
||||
|
||||
_raster_irq_handler
|
||||
jsr set_irqvec._irq_handler_init
|
||||
jsr irq.irq
|
||||
jsr set_irqvec._irq_handler_end
|
||||
lda #$ff
|
||||
sta c64.VICIRQ ; acknowledge raster irq
|
||||
jmp c64.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
|
||||
; contents of the rightmost column are unchanged, you should clear/refill this yourself
|
||||
; 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
|
||||
; contents of the leftmost column are unchanged, you should clear/refill this yourself
|
||||
; 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
|
||||
; contents of the bottom row are unchanged, you should refill/clear this yourself
|
||||
; 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
|
||||
; contents of the top row are unchanged, you should refill/clear this yourself
|
||||
; 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) -> () {
|
||||
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
|
||||
%asm {{
|
||||
@ -763,13 +791,12 @@ _print_byte_digits
|
||||
bne _print_hundreds
|
||||
cpx #'0'
|
||||
bne _print_tens
|
||||
pla
|
||||
jmp c64.CHROUT
|
||||
jmp _end
|
||||
_print_hundreds tya
|
||||
jsr c64.CHROUT
|
||||
_print_tens txa
|
||||
jsr c64.CHROUT
|
||||
pla
|
||||
_end pla
|
||||
jsr c64.CHROUT
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
@ -867,7 +894,7 @@ asmsub print_uw0 (uword value @ AY) -> clobbers(A,Y) -> () {
|
||||
%asm {{
|
||||
jsr c64utils.uword2decimal
|
||||
ldy #0
|
||||
- lda c64utils.word2decimal_output,y
|
||||
- lda c64utils.uword2decimal.output,y
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
cpy #5
|
||||
@ -882,25 +909,25 @@ asmsub print_uw (uword value @ AY) -> clobbers(A,Y) -> () {
|
||||
%asm {{
|
||||
jsr c64utils.uword2decimal
|
||||
ldy #0
|
||||
lda c64utils.word2decimal_output
|
||||
lda c64utils.uword2decimal.output
|
||||
cmp #'0'
|
||||
bne _pr_decimal
|
||||
iny
|
||||
lda c64utils.word2decimal_output+1
|
||||
lda c64utils.uword2decimal.output+1
|
||||
cmp #'0'
|
||||
bne _pr_decimal
|
||||
iny
|
||||
lda c64utils.word2decimal_output+2
|
||||
lda c64utils.uword2decimal.output+2
|
||||
cmp #'0'
|
||||
bne _pr_decimal
|
||||
iny
|
||||
lda c64utils.word2decimal_output+3
|
||||
lda c64utils.uword2decimal.output+3
|
||||
cmp #'0'
|
||||
bne _pr_decimal
|
||||
iny
|
||||
|
||||
_pr_decimal
|
||||
lda c64utils.word2decimal_output,y
|
||||
lda c64utils.uword2decimal.output,y
|
||||
jsr c64.CHROUT
|
||||
iny
|
||||
cpy #5
|
||||
@ -910,7 +937,7 @@ _pr_decimal
|
||||
}
|
||||
|
||||
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 {{
|
||||
cpy #0
|
||||
bpl +
|
||||
@ -931,7 +958,7 @@ asmsub print_w (word value @ AY) -> clobbers(A,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!
|
||||
|
||||
%asm {{
|
||||
@ -973,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) -> () {
|
||||
; ---- set the color in SCRATCH_ZPB1 on the screen matrix at the given position
|
||||
%asm {{
|
||||
@ -995,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) {
|
||||
; ---- set char+color at the given position on the screen
|
||||
@ -1022,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.
|
||||
%asm {{
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
|
@ -58,29 +58,29 @@ multiply_words .proc
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
|
||||
mult16 lda #$00
|
||||
sta multiply_words_result+2 ; clear upper bits of product
|
||||
sta multiply_words_result+3
|
||||
sta result+2 ; clear upper bits of product
|
||||
sta result+3
|
||||
ldx #16 ; for all 16 bits...
|
||||
- lsr c64.SCRATCH_ZPWORD1+1 ; divide multiplier by 2
|
||||
ror c64.SCRATCH_ZPWORD1
|
||||
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
|
||||
adc c64.SCRATCH_ZPWORD2
|
||||
sta multiply_words_result+2
|
||||
lda multiply_words_result+3
|
||||
sta result+2
|
||||
lda result+3
|
||||
adc c64.SCRATCH_ZPWORD2+1
|
||||
+ ror a ; rotate partial product
|
||||
sta multiply_words_result+3
|
||||
ror multiply_words_result+2
|
||||
ror multiply_words_result+1
|
||||
ror multiply_words_result
|
||||
sta result+3
|
||||
ror result+2
|
||||
ror result+1
|
||||
ror result
|
||||
dex
|
||||
bne -
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
rts
|
||||
|
||||
multiply_words_result .byte 0,0,0,0
|
||||
result .byte 0,0,0,0
|
||||
.pend
|
||||
|
||||
|
||||
@ -241,26 +241,24 @@ mul_byte_3 .proc
|
||||
.pend
|
||||
|
||||
mul_word_3 .proc
|
||||
; W + W*2
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
; W*2 + W
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.SCRATCH_ZPREG
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
clc
|
||||
lda c64.ESTACK_LO+1,x
|
||||
adc c64.SCRATCH_ZPWORD1
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.ESTACK_HI+1,x
|
||||
adc c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_HI+1,x
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
mul_byte_5 .proc
|
||||
; X + X*4
|
||||
; X*4 + X
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
@ -271,58 +269,49 @@ mul_byte_5 .proc
|
||||
.pend
|
||||
|
||||
mul_word_5 .proc
|
||||
; W + W*4
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
; W*4 + W
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.SCRATCH_ZPREG
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
clc
|
||||
lda c64.ESTACK_LO+1,x
|
||||
adc c64.SCRATCH_ZPWORD1
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.ESTACK_HI+1,x
|
||||
adc c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_HI+1,x
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
mul_byte_6 .proc
|
||||
; X*2 + X*4
|
||||
; (X*2 + X)*2
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
sta c64.SCRATCH_ZPREG
|
||||
clc
|
||||
adc c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPREG
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_word_6 .proc
|
||||
; W*2 + W*4
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
; (W*2 + W)*2
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.SCRATCH_ZPWORD2+1
|
||||
asl c64.SCRATCH_ZPWORD2
|
||||
rol c64.SCRATCH_ZPWORD2+1
|
||||
sta c64.SCRATCH_ZPREG
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
clc
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
adc c64.SCRATCH_ZPWORD2
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
adc c64.SCRATCH_ZPWORD2+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_HI+1,x
|
||||
asl c64.ESTACK_LO+1,x
|
||||
rol a
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
@ -341,29 +330,26 @@ mul_byte_7 .proc
|
||||
|
||||
mul_word_7 .proc
|
||||
; W*8 - W
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
sta c64.SCRATCH_ZPREG
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
sec
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
sbc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
sbc c64.ESTACK_HI+1,x
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_byte_9 .proc
|
||||
; X + X*8
|
||||
; X*8 + X
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
@ -375,80 +361,67 @@ mul_byte_9 .proc
|
||||
.pend
|
||||
|
||||
mul_word_9 .proc
|
||||
; W + W*8
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
; W*8 + W
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.SCRATCH_ZPREG
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
clc
|
||||
lda c64.ESTACK_LO+1,x
|
||||
adc c64.SCRATCH_ZPWORD1
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.ESTACK_HI+1,x
|
||||
adc c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_HI+1,x
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_byte_10 .proc
|
||||
; X + X + X*8
|
||||
; (X*4 + X)*2
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
clc
|
||||
adc c64.ESTACK_LO+1,x
|
||||
clc
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_word_10 .proc
|
||||
; W*2 + W*8
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
; (W*4 + W)*2
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.SCRATCH_ZPWORD2+1
|
||||
asl c64.SCRATCH_ZPWORD2
|
||||
rol c64.SCRATCH_ZPWORD2+1
|
||||
asl c64.SCRATCH_ZPWORD2
|
||||
rol c64.SCRATCH_ZPWORD2+1
|
||||
sta c64.SCRATCH_ZPREG
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
clc
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
adc c64.SCRATCH_ZPWORD2
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
adc c64.SCRATCH_ZPWORD2+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_HI+1,x
|
||||
asl c64.ESTACK_LO+1,x
|
||||
rol a
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_byte_11 .proc
|
||||
; X + X + X + X*8
|
||||
; (X*2 + X)*4 - X
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPREG
|
||||
clc
|
||||
adc c64.SCRATCH_ZPREG
|
||||
clc
|
||||
adc c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
sec
|
||||
sbc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
@ -456,58 +429,47 @@ mul_byte_11 .proc
|
||||
; mul_word_11 is skipped (too much code)
|
||||
|
||||
mul_byte_12 .proc
|
||||
; X*4 + X*8
|
||||
; (X*2 + X)*4
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
sta c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_word_12 .proc
|
||||
; W*4 + W*8
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
; (W*2 + W)*4
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.SCRATCH_ZPWORD2+1
|
||||
asl c64.SCRATCH_ZPWORD2
|
||||
rol c64.SCRATCH_ZPWORD2+1
|
||||
sta c64.SCRATCH_ZPREG
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
clc
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
adc c64.SCRATCH_ZPWORD2
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
adc c64.SCRATCH_ZPWORD2+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_HI+1,x
|
||||
asl c64.ESTACK_LO+1,x
|
||||
rol a
|
||||
asl c64.ESTACK_LO+1,x
|
||||
rol a
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_byte_13 .proc
|
||||
; X*16 - X -X -X
|
||||
; (X*2 + X)*4 + X
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
clc
|
||||
adc c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
sec
|
||||
sbc c64.SCRATCH_ZPREG
|
||||
sec
|
||||
sbc c64.SCRATCH_ZPREG
|
||||
sec
|
||||
sbc c64.SCRATCH_ZPREG
|
||||
clc
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
@ -515,16 +477,14 @@ mul_byte_13 .proc
|
||||
; mul_word_13 is skipped (too much code)
|
||||
|
||||
mul_byte_14 .proc
|
||||
; X*16 - X -X
|
||||
; (X*8 - X)*2
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
sec
|
||||
sbc c64.ESTACK_LO+1,x
|
||||
sec
|
||||
sec
|
||||
sbc c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
@ -534,7 +494,6 @@ mul_byte_14 .proc
|
||||
mul_byte_15 .proc
|
||||
; X*16 - X
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
@ -547,80 +506,70 @@ mul_byte_15 .proc
|
||||
|
||||
mul_word_15 .proc
|
||||
; W*16 - W
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.SCRATCH_ZPREG
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
sec
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
sbc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
sbc c64.ESTACK_HI+1,x
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_byte_20 .proc
|
||||
; X*4 + X*16
|
||||
; (X*4 + X)*4
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
sta c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_word_20 .proc
|
||||
; W*4 + W*16
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
; (W*4 + W)*4
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
sta c64.SCRATCH_ZPWORD2
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.SCRATCH_ZPWORD2+1
|
||||
asl c64.SCRATCH_ZPWORD2
|
||||
rol c64.SCRATCH_ZPWORD2+1
|
||||
asl c64.SCRATCH_ZPWORD2
|
||||
rol c64.SCRATCH_ZPWORD2+1
|
||||
sta c64.SCRATCH_ZPREG
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
clc
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
adc c64.SCRATCH_ZPWORD2
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
adc c64.SCRATCH_ZPWORD2+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_HI+1,x
|
||||
asl c64.ESTACK_LO+1,x
|
||||
rol a
|
||||
asl c64.ESTACK_LO+1,x
|
||||
rol a
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_byte_25 .proc
|
||||
; X + X*8 + X*16
|
||||
; (X*2 + X)*8 + X
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
sta c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
@ -629,76 +578,68 @@ mul_byte_25 .proc
|
||||
|
||||
mul_word_25 .proc
|
||||
; W + W*8 + W*16
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
clc
|
||||
lda c64.ESTACK_LO+1,x
|
||||
adc c64.SCRATCH_ZPWORD1
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
clc
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.ESTACK_HI+1,x
|
||||
adc c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
adc c64.ESTACK_HI+1,x
|
||||
sta c64.ESTACK_HI+1,x
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
clc
|
||||
lda c64.ESTACK_LO+1,x
|
||||
adc c64.SCRATCH_ZPWORD1
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.ESTACK_HI+1,x
|
||||
adc c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
adc c64.ESTACK_HI+1,x
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_byte_40 .proc
|
||||
; X*8 + X*32
|
||||
; (X*4 + X)*8
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
sta c64.SCRATCH_ZPREG
|
||||
clc
|
||||
adc c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPREG
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
mul_word_40 .proc
|
||||
; W*8 + W*32
|
||||
lda c64.ESTACK_LO+1,x
|
||||
sta c64.SCRATCH_ZPWORD1
|
||||
; (W*4 + W)*8
|
||||
lda c64.ESTACK_HI+1,x
|
||||
sta c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPWORD1
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.ESTACK_HI+1,x
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
asl c64.SCRATCH_ZPWORD1
|
||||
rol c64.SCRATCH_ZPWORD1+1
|
||||
sta c64.SCRATCH_ZPREG
|
||||
lda c64.ESTACK_LO+1,x
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
asl a
|
||||
rol c64.SCRATCH_ZPREG
|
||||
clc
|
||||
lda c64.ESTACK_LO+1,x
|
||||
adc c64.SCRATCH_ZPWORD1
|
||||
adc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
lda c64.ESTACK_HI+1,x
|
||||
adc c64.SCRATCH_ZPWORD1+1
|
||||
lda c64.SCRATCH_ZPREG
|
||||
adc c64.ESTACK_HI+1,x
|
||||
asl c64.ESTACK_LO+1,x
|
||||
rol a
|
||||
asl c64.ESTACK_LO+1,x
|
||||
rol a
|
||||
asl c64.ESTACK_LO+1,x
|
||||
rol a
|
||||
sta c64.ESTACK_HI+1,x
|
||||
rts
|
||||
.pend
|
||||
|
@ -49,19 +49,17 @@ add_a_to_zpword .proc
|
||||
pop_index_times_5 .proc
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
sta c64.SCRATCH_ZPB1
|
||||
asl a
|
||||
asl a
|
||||
clc
|
||||
adc c64.SCRATCH_ZPB1 ; A*=5
|
||||
adc c64.ESTACK_LO,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
neg_b .proc
|
||||
lda c64.ESTACK_LO+1,x
|
||||
eor #255
|
||||
clc
|
||||
adc #1
|
||||
lda #0
|
||||
sec
|
||||
sbc c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
@ -90,10 +88,9 @@ inv_word .proc
|
||||
not_byte .proc
|
||||
lda c64.ESTACK_LO+1,x
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta c64.ESTACK_LO+1,x
|
||||
lda #1
|
||||
+ eor #1
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
.pend
|
||||
|
||||
@ -101,14 +98,169 @@ not_word .proc
|
||||
lda c64.ESTACK_LO + 1,x
|
||||
ora c64.ESTACK_HI + 1,x
|
||||
beq +
|
||||
lda #0
|
||||
beq ++
|
||||
+ lda #1
|
||||
+ sta c64.ESTACK_LO + 1,x
|
||||
lda #1
|
||||
+ eor #1
|
||||
sta c64.ESTACK_LO + 1,x
|
||||
lsr a
|
||||
sta c64.ESTACK_HI + 1,x
|
||||
rts
|
||||
.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
|
||||
; -- push abs(byte) on stack (as byte)
|
||||
lda c64.ESTACK_LO+1,x
|
||||
@ -170,9 +322,9 @@ mul_word .proc
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
jsr math.multiply_words
|
||||
ldx c64.SCRATCH_ZPREGX
|
||||
lda math.multiply_words.multiply_words_result
|
||||
lda math.multiply_words.result
|
||||
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
|
||||
rts
|
||||
.pend
|
||||
@ -300,25 +452,18 @@ equal_w .proc
|
||||
|
||||
notequal_b .proc
|
||||
; -- are the two bytes on the stack different?
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
eor c64.ESTACK_LO+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
lda c64.ESTACK_LO+1,x
|
||||
cmp c64.ESTACK_LO+2,x
|
||||
beq equal_b._equal_b_false
|
||||
bne equal_b._equal_b_true
|
||||
.pend
|
||||
|
||||
notequal_w .proc
|
||||
; -- are the two words on the stack different?
|
||||
inx
|
||||
lda c64.ESTACK_LO,x
|
||||
eor c64.ESTACK_LO+1,x
|
||||
beq +
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
+ lda c64.ESTACK_HI,x
|
||||
eor c64.ESTACK_HI+1,x
|
||||
sta c64.ESTACK_LO+1,x
|
||||
rts
|
||||
lda c64.ESTACK_HI+1,x
|
||||
cmp c64.ESTACK_HI+2,x
|
||||
beq notequal_b
|
||||
bne equal_b._equal_b_true
|
||||
.pend
|
||||
|
||||
less_ub .proc
|
||||
@ -375,11 +520,10 @@ _equal_b_false lda #0
|
||||
.pend
|
||||
|
||||
lesseq_ub .proc
|
||||
lda c64.ESTACK_LO+2,x
|
||||
cmp c64.ESTACK_LO+1,x
|
||||
bcc equal_b._equal_b_true
|
||||
beq equal_b._equal_b_true
|
||||
bcs equal_b._equal_b_false
|
||||
lda c64.ESTACK_LO+1,x
|
||||
cmp c64.ESTACK_LO+2,x
|
||||
bcs equal_b._equal_b_true
|
||||
bcc equal_b._equal_b_false
|
||||
.pend
|
||||
|
||||
lesseq_b .proc
|
||||
@ -496,6 +640,65 @@ greatereq_w .proc
|
||||
bmi equal_b._equal_b_false
|
||||
.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
|
||||
ldy c64.ESTACK_LO+1,x
|
||||
@ -790,17 +993,15 @@ _loop lda (c64.SCRATCH_ZPWORD1),y
|
||||
func_sum_ub .proc
|
||||
jsr pop_array_and_lengthmin1Y
|
||||
lda #0
|
||||
sta c64.ESTACK_LO,x
|
||||
sta c64.ESTACK_HI,x
|
||||
- lda (c64.SCRATCH_ZPWORD1),y
|
||||
clc
|
||||
adc c64.ESTACK_LO,x
|
||||
sta c64.ESTACK_LO,x
|
||||
- clc
|
||||
adc (c64.SCRATCH_ZPWORD1),y
|
||||
bcc +
|
||||
inc c64.ESTACK_HI,x
|
||||
+ dey
|
||||
cpy #255
|
||||
bne -
|
||||
sta c64.ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
@ -961,8 +1162,7 @@ _gtequ dey
|
||||
_result_minw .word 0
|
||||
.pend
|
||||
|
||||
|
||||
func_len_str .proc
|
||||
func_strlen .proc
|
||||
; -- push length of 0-terminated string on stack
|
||||
jsr peek_address
|
||||
ldy #0
|
||||
@ -975,15 +1175,6 @@ func_len_str .proc
|
||||
rts
|
||||
.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
|
||||
; -- put a random ubyte on the estack
|
||||
jsr math.randbyte
|
||||
@ -1003,7 +1194,7 @@ func_rndw .proc
|
||||
.pend
|
||||
|
||||
|
||||
func_memcopy .proc
|
||||
func_memcopy .proc
|
||||
; note: clobbers A,Y
|
||||
inx
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
@ -1029,7 +1220,7 @@ func_memcopy .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_memset .proc
|
||||
func_memset .proc
|
||||
; note: clobbers A,Y
|
||||
inx
|
||||
stx c64.SCRATCH_ZPREGX
|
||||
@ -1049,7 +1240,7 @@ func_memset .proc
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_memsetw .proc
|
||||
func_memsetw .proc
|
||||
; note: clobbers A,Y
|
||||
; -- fill memory from (SCRATCH_ZPWORD1) number of words in SCRATCH_ZPWORD2, with word value in AY.
|
||||
|
||||
|
@ -1 +1 @@
|
||||
1.1 (beta)
|
||||
1.8
|
||||
|
@ -1,6 +1,7 @@
|
||||
package prog8
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.astvm.AstVm
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.target.c64.AsmGen
|
||||
import prog8.compiler.target.c64.C64Zeropage
|
||||
@ -8,7 +9,9 @@ import prog8.optimizing.constantFold
|
||||
import prog8.optimizing.optimizeStatements
|
||||
import prog8.optimizing.simplifyExpressions
|
||||
import prog8.parser.ParsingFailedError
|
||||
import prog8.parser.importLibraryModule
|
||||
import prog8.parser.importModule
|
||||
import prog8.parser.moduleName
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import java.lang.Exception
|
||||
@ -33,10 +36,9 @@ fun main(args: Array<String>) {
|
||||
compileMain(args)
|
||||
}
|
||||
|
||||
fun printSoftwareHeader(what: String) {
|
||||
internal fun printSoftwareHeader(what: String) {
|
||||
val buildVersion = object {}.javaClass.getResource("/version.txt").readText().trim()
|
||||
println("\nProg8 $what by Irmen de Jong (irmen@razorvine.net)")
|
||||
println("Version: $buildVersion")
|
||||
println("\nProg8 $what v$buildVersion by Irmen de Jong (irmen@razorvine.net)")
|
||||
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 writeVmCode = false
|
||||
var writeAssembly = true
|
||||
var optimize = true
|
||||
var optimizeInlining = true
|
||||
var launchAstVm = false
|
||||
for (arg in args) {
|
||||
if(arg=="-emu")
|
||||
emulatorToStart = "x64"
|
||||
@ -55,6 +60,12 @@ private fun compileMain(args: Array<String>) {
|
||||
writeVmCode = true
|
||||
else if(arg=="-noasm")
|
||||
writeAssembly = false
|
||||
else if(arg=="-noopt")
|
||||
optimize = false
|
||||
else if(arg=="-nooptinline")
|
||||
optimizeInlining = false
|
||||
else if(arg=="-avm")
|
||||
launchAstVm = true
|
||||
else if(!arg.startsWith("-"))
|
||||
moduleFile = arg
|
||||
else
|
||||
@ -65,63 +76,72 @@ private fun compileMain(args: Array<String>) {
|
||||
|
||||
val filepath = Paths.get(moduleFile).normalize()
|
||||
var programname = "?"
|
||||
lateinit var programAst: Program
|
||||
|
||||
try {
|
||||
val totalTime = measureTimeMillis {
|
||||
// import main module and process additional imports
|
||||
// import main module and everything it needs
|
||||
println("Parsing...")
|
||||
val moduleAst = importModule(filepath)
|
||||
moduleAst.linkParents()
|
||||
var namespace = moduleAst.definingScope()
|
||||
|
||||
// determine special compiler options
|
||||
|
||||
val compilerOptions = determineCompilationOptions(moduleAst)
|
||||
programAst = Program(moduleName(filepath.fileName), mutableListOf())
|
||||
importModule(programAst, filepath)
|
||||
|
||||
val compilerOptions = determineCompilationOptions(programAst)
|
||||
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
|
||||
println("Syntax check...")
|
||||
val heap = HeapValues()
|
||||
val time1= measureTimeMillis {
|
||||
moduleAst.checkIdentifiers(heap)
|
||||
programAst.checkIdentifiers()
|
||||
}
|
||||
//println(" time1: $time1")
|
||||
val time2 = measureTimeMillis {
|
||||
moduleAst.constantFold(namespace, heap)
|
||||
programAst.constantFold()
|
||||
}
|
||||
//println(" time2: $time2")
|
||||
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")
|
||||
val time4 = measureTimeMillis {
|
||||
moduleAst.checkValid(namespace, compilerOptions, heap) // check if tree is valid
|
||||
programAst.checkValid(compilerOptions) // check if tree is valid
|
||||
}
|
||||
//println(" time4: $time4")
|
||||
|
||||
// optimize the parse tree
|
||||
println("Optimizing...")
|
||||
val allScopedSymbolDefinitions = moduleAst.checkIdentifiers(heap) // useful for checking symbol usage later?
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = moduleAst.simplifyExpressions(namespace, heap)
|
||||
val optsDone2 = moduleAst.optimizeStatements(namespace, heap)
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
break
|
||||
programAst.checkIdentifiers()
|
||||
if(optimize) {
|
||||
// optimize the parse tree
|
||||
println("Optimizing...")
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = programAst.simplifyExpressions()
|
||||
val optsDone2 = programAst.optimizeStatements(optimizeInlining)
|
||||
if (optsDone1 + optsDone2 == 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
namespace = moduleAst.definingScope() // create it again, it could have changed in the meantime
|
||||
moduleAst.checkValid(namespace, compilerOptions, heap) // check if final tree is valid
|
||||
moduleAst.checkRecursion(namespace) // check if there are recursive subroutine calls
|
||||
programAst.checkValid(compilerOptions) // check if final tree is valid
|
||||
programAst.checkRecursion() // check if there are recursive subroutine calls
|
||||
|
||||
// namespace.debugPrint()
|
||||
|
||||
// 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)
|
||||
intermediate.optimize()
|
||||
if(optimize)
|
||||
intermediate.optimize()
|
||||
|
||||
if(writeVmCode) {
|
||||
val stackVmFilename = intermediate.name + ".vm.txt"
|
||||
@ -134,7 +154,7 @@ private fun compileMain(args: Array<String>) {
|
||||
if(writeAssembly) {
|
||||
val zeropage = C64Zeropage(compilerOptions)
|
||||
intermediate.allocateZeropage(zeropage)
|
||||
val assembly = AsmGen(compilerOptions, intermediate, heap, zeropage).compileToAssembly()
|
||||
val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize)
|
||||
assembly.assemble(compilerOptions)
|
||||
programname = assembly.name
|
||||
}
|
||||
@ -146,6 +166,11 @@ private fun compileMain(args: Array<String>) {
|
||||
System.err.println(px.message)
|
||||
System.err.print("\u001b[0m") // reset
|
||||
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) {
|
||||
print("\u001b[91m") // bright red
|
||||
println("\n* internal error *")
|
||||
@ -160,6 +185,12 @@ private fun compileMain(args: Array<String>) {
|
||||
throw x
|
||||
}
|
||||
|
||||
if(launchAstVm) {
|
||||
println("\nLaunching AST-based vm...")
|
||||
val vm = AstVm(programAst)
|
||||
vm.run()
|
||||
}
|
||||
|
||||
if(emulatorToStart.isNotEmpty()) {
|
||||
println("\nStarting C-64 emulator $emulatorToStart...")
|
||||
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "$programname.vice-mon-list",
|
||||
@ -169,20 +200,22 @@ 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()
|
||||
val outputType = (moduleAst.statements.singleOrNull { it is Directive && it.directive == "%output" }
|
||||
|
||||
private fun determineCompilationOptions(program: Program): CompilationOptions {
|
||||
val mainModule = program.modules.first()
|
||||
val outputType = (mainModule.statements.singleOrNull { it is Directive && it.directive == "%output" }
|
||||
as? Directive)?.args?.single()?.name?.toUpperCase()
|
||||
val launcherType = (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()
|
||||
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
|
||||
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()
|
||||
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 =
|
||||
if (zpoption == null)
|
||||
if(floatsEnabled) ZeropageType.BASICSAFE else ZeropageType.KERNALSAFE
|
||||
if(floatsEnabled) ZeropageType.FLOATSAFE else ZeropageType.KERNALSAFE
|
||||
else
|
||||
try {
|
||||
ZeropageType.valueOf(zpoption)
|
||||
@ -190,7 +223,7 @@ fun determineCompilationOptions(moduleAst: Module): CompilationOptions {
|
||||
ZeropageType.KERNALSAFE
|
||||
// error will be printed by the astchecker
|
||||
}
|
||||
val zpReserved = moduleAst.statements
|
||||
val zpReserved = mainModule.statements
|
||||
.asSequence()
|
||||
.filter { it is Directive && it.directive == "%zpreserved" }
|
||||
.map { (it as Directive).args }
|
||||
@ -206,11 +239,14 @@ fun determineCompilationOptions(moduleAst: Module): CompilationOptions {
|
||||
|
||||
private fun usage() {
|
||||
System.err.println("Missing argument(s):")
|
||||
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(" [-writevm] write intermediate vm code to a file as well")
|
||||
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(" modulefile main module file to compile")
|
||||
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(" [-writevm] write intermediate vm code to a file as well")
|
||||
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(" [-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)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,16 +5,16 @@ import prog8.compiler.HeapValues
|
||||
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.optimizing.same
|
||||
import prog8.parser.ParsingFailedError
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* General checks on the Ast
|
||||
*/
|
||||
|
||||
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions, heap: HeapValues) {
|
||||
val checker = AstChecker(globalNamespace, compilerOptions, heap)
|
||||
this.process(checker)
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
|
||||
val checker = AstChecker(this, compilerOptions)
|
||||
checker.process(this)
|
||||
printErrors(checker.result(), name)
|
||||
}
|
||||
|
||||
@ -51,20 +51,71 @@ fun printWarning(msg: String) {
|
||||
print("\u001b[0m\n") // normal
|
||||
}
|
||||
|
||||
private class AstChecker(private val namespace: INameScope,
|
||||
private val compilerOptions: CompilationOptions,
|
||||
private val heap: HeapValues) : IAstProcessor {
|
||||
private class AstChecker(private val program: Program,
|
||||
private val compilerOptions: CompilationOptions) : IAstProcessor {
|
||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||
private val heapStringSentinel: Int
|
||||
init {
|
||||
val stringSentinel = heap.allEntries().firstOrNull {it.value.str==""}
|
||||
heapStringSentinel = stringSentinel?.key ?: heap.add(DataType.STR, "")
|
||||
val stringSentinel = program.heap.allEntries().firstOrNull {it.value.str==""}
|
||||
heapStringSentinel = stringSentinel?.key ?: program.heap.addString(DataType.STR, "")
|
||||
}
|
||||
|
||||
fun result(): List<AstException> {
|
||||
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) {
|
||||
super.process(module)
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@ -120,7 +132,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(expectedReturnValues.size != returnStmt.values.size) {
|
||||
// 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) {
|
||||
val dt = (returnStmt.values[0] as FunctionCall).resultingDatatype(namespace, heap)
|
||||
val dt = (returnStmt.values[0] as FunctionCall).inferType(program)
|
||||
if(dt!=null && expectedReturnValues.isEmpty())
|
||||
checkResult.add(SyntaxError("invalid number of return values", returnStmt.position))
|
||||
} else
|
||||
@ -128,7 +140,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
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)
|
||||
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 {
|
||||
if(forLoop.body.isEmpty())
|
||||
if(forLoop.body.containsNoCodeNorVars())
|
||||
printWarning("for loop body is empty", forLoop.position)
|
||||
|
||||
if(forLoop.iterable is LiteralValue)
|
||||
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(!forLoop.iterable.isIterable(namespace, heap)) {
|
||||
val iterableDt = forLoop.iterable.inferType(program)
|
||||
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
|
||||
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
|
||||
} else {
|
||||
val iterableDt = forLoop.iterable.resultingDatatype(namespace, heap)
|
||||
if (forLoop.loopRegister != null) {
|
||||
printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position)
|
||||
// loop register
|
||||
@ -153,7 +162,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position))
|
||||
} else {
|
||||
// loop variable
|
||||
val loopvar = forLoop.loopVar!!.targetStatement(namespace) as? VarDecl
|
||||
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
|
||||
if(loopvar==null || loopvar.type==VarDeclType.CONST) {
|
||||
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.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)
|
||||
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)) {
|
||||
if(param.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
|
||||
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())
|
||||
err("a return register is also in the clobber list")
|
||||
} 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 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)
|
||||
@ -357,24 +366,18 @@ private class AstChecker(private val namespace: INameScope,
|
||||
|
||||
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
|
||||
if(assignment.value is FunctionCall) {
|
||||
val stmt = (assignment.value as FunctionCall).target.targetStatement(namespace)
|
||||
if (stmt is Subroutine && stmt.returntypes.size > 1) {
|
||||
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
|
||||
if (stmt is Subroutine) {
|
||||
if (stmt.isAsmSubroutine) {
|
||||
if (stmt.returntypes.size != assignment.targets.size)
|
||||
checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position))
|
||||
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)) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
} else
|
||||
} else if(assignment.targets.size>1)
|
||||
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 {
|
||||
val memAddr = target.memoryAddress?.addressExpression?.constValue(namespace, heap)?.asIntegerValue
|
||||
val memAddr = target.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue
|
||||
if(memAddr!=null) {
|
||||
if(memAddr<0 || memAddr>=65536)
|
||||
checkResult.add(ExpressionError("address out of range", target.position))
|
||||
@ -395,7 +398,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
|
||||
if(target.identifier!=null) {
|
||||
val targetName = target.identifier.nameInSource
|
||||
val targetSymbol = namespace.lookup(targetName, assignment)
|
||||
val targetSymbol = program.namespace.lookup(targetName, assignment)
|
||||
when (targetSymbol) {
|
||||
null -> {
|
||||
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))
|
||||
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) {
|
||||
// check augmented assignment (and convert it into a normal assignment!)
|
||||
// A /= 3 -> check as if it was A = A / 3
|
||||
@ -446,23 +436,23 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return assignment2
|
||||
}
|
||||
|
||||
val targetDatatype = target.determineDatatype(namespace, heap, assignment)
|
||||
val targetDatatype = target.inferType(program, assignment)
|
||||
if(targetDatatype!=null) {
|
||||
val constVal = assignment.value.constValue(namespace, heap)
|
||||
val constVal = assignment.value.constValue(program)
|
||||
if(constVal!=null) {
|
||||
val arrayspec = if(target.identifier!=null) {
|
||||
val targetVar = namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
|
||||
targetVar?.arrayspec
|
||||
val targetVar = program.namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
|
||||
targetVar?.arraysize
|
||||
} else null
|
||||
checkValueTypeAndRange(targetDatatype,
|
||||
arrayspec ?: ArraySpec(LiteralValue.optimalInteger(-1, assignment.position), assignment.position),
|
||||
constVal, heap)
|
||||
arrayspec ?: ArrayIndex(LiteralValue.optimalInteger(-1, assignment.position), assignment.position),
|
||||
constVal, program.heap)
|
||||
} else {
|
||||
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap)
|
||||
val sourceDatatype: DataType? = assignment.value.inferType(program)
|
||||
if(sourceDatatype==null) {
|
||||
if(assignment.targets.size<=1) {
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
@ -487,7 +489,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
|
||||
@ -498,22 +500,43 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// 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) {
|
||||
VarDeclType.VAR, VarDeclType.CONST -> {
|
||||
if (decl.value == null) {
|
||||
when {
|
||||
decl.datatype in NumericDatatypes -> {
|
||||
// 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
|
||||
decl.value = litVal
|
||||
}
|
||||
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
|
||||
decl.value = litVal
|
||||
}
|
||||
@ -523,15 +546,18 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return super.process(decl)
|
||||
}
|
||||
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 -> {
|
||||
val arraySpec = decl.arrayspec ?: (
|
||||
val arraySpec = decl.arraysize ?: (
|
||||
if((decl.value as LiteralValue).isArray)
|
||||
ArraySpec.forArray(decl.value as LiteralValue, heap)
|
||||
ArrayIndex.forArray(decl.value as LiteralValue, program.heap)
|
||||
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 -> {
|
||||
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 -> {
|
||||
if(decl.arrayspec!=null) {
|
||||
val arraySize = decl.arrayspec.size() ?: 1
|
||||
if(decl.arraysize!=null) {
|
||||
val arraySize = decl.arraysize!!.size() ?: 1
|
||||
when(decl.datatype) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB ->
|
||||
if(arraySize > 256)
|
||||
@ -592,9 +618,10 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
if(directive.args.size!=1 ||
|
||||
directive.args[0].name != "basicsafe" &&
|
||||
directive.args[0].name != "floatsafe" &&
|
||||
directive.args[0].name != "kernalsafe" &&
|
||||
directive.args[0].name != "full")
|
||||
err("invalid zp type, expected basicsafe, kernalsafe, or full")
|
||||
err("invalid zp type, expected basicsafe, floatsafe, kernalsafe, or full")
|
||||
}
|
||||
"%zpreserved" -> {
|
||||
if(directive.parent !is Module) err("this directive may only occur at module level")
|
||||
@ -622,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.args.size!=2 || directive.args[0].str==null || directive.args[1].str==null)
|
||||
err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"")
|
||||
checkFileExists(directive, directive.args[0].str!!)
|
||||
}
|
||||
"%asmbinary" -> {
|
||||
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
|
||||
@ -631,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==3 && directive.args[2].int==null) err(errormsg)
|
||||
if(directive.args.size>3) err(errormsg)
|
||||
checkFileExists(directive, directive.args[0].str!!)
|
||||
}
|
||||
"%option" -> {
|
||||
if(directive.parent !is Block && directive.parent !is Module) err("this directive may only occur in a block or at module level")
|
||||
@ -644,16 +673,24 @@ private class AstChecker(private val namespace: INameScope,
|
||||
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 {
|
||||
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))
|
||||
}
|
||||
val arrayspec =
|
||||
if(literalValue.isArray)
|
||||
ArraySpec.forArray(literalValue, heap)
|
||||
ArrayIndex.forArray(literalValue, program.heap)
|
||||
else
|
||||
ArraySpec(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position)
|
||||
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, heap)
|
||||
ArrayIndex(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position)
|
||||
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, program.heap)
|
||||
|
||||
val lv = super.process(literalValue)
|
||||
when(lv.type) {
|
||||
@ -672,7 +709,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
|
||||
override fun process(expr: PrefixExpression): IExpression {
|
||||
if(expr.operator=="-") {
|
||||
val dt = expr.resultingDatatype(namespace, heap)
|
||||
val dt = expr.inferType(program)
|
||||
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
|
||||
checkResult.add(ExpressionError("can only take negative of a signed number type", expr.position))
|
||||
}
|
||||
@ -681,23 +718,40 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
override fun process(expr: BinaryExpression): IExpression {
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
|
||||
when(expr.operator){
|
||||
"/", "%" -> {
|
||||
val constvalRight = expr.right.constValue(namespace, heap)
|
||||
val constvalRight = expr.right.constValue(program)
|
||||
val divisor = constvalRight?.asNumericValue?.toDouble()
|
||||
if(divisor==0.0)
|
||||
checkResult.add(ExpressionError("division by zero", expr.right.position))
|
||||
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))
|
||||
checkResult.add(ExpressionError("remainder can only be used on unsigned integer operands", expr.right.position))
|
||||
}
|
||||
}
|
||||
"**" -> {
|
||||
if(leftDt in IntegerDatatypes)
|
||||
checkResult.add(ExpressionError("power operator requires floating point", expr.position))
|
||||
}
|
||||
"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)
|
||||
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)
|
||||
checkResult.add(ExpressionError("left operand is not numeric", expr.left.position))
|
||||
if(rightDt!in NumericDatatypes)
|
||||
@ -708,12 +762,6 @@ private class AstChecker(private val namespace: INameScope,
|
||||
override fun process(typecast: TypecastExpression): IExpression {
|
||||
if(typecast.type in IterableDatatypes)
|
||||
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)
|
||||
}
|
||||
|
||||
@ -722,9 +770,9 @@ private class AstChecker(private val namespace: INameScope,
|
||||
checkResult.add(SyntaxError(msg, range.position))
|
||||
}
|
||||
super.process(range)
|
||||
val from = range.from.constValue(namespace, heap)
|
||||
val to = range.to.constValue(namespace, heap)
|
||||
val stepLv = range.step.constValue(namespace, heap) ?: LiteralValue(DataType.UBYTE, 1, position = range.position)
|
||||
val from = range.from.constValue(program)
|
||||
val to = range.to.constValue(program)
|
||||
val stepLv = range.step.constValue(program) ?: LiteralValue(DataType.UBYTE, 1, position = range.position)
|
||||
if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) {
|
||||
err("range step must be an integer != 0")
|
||||
return range
|
||||
@ -741,8 +789,8 @@ private class AstChecker(private val namespace: INameScope,
|
||||
err("descending range requires step < 0")
|
||||
}
|
||||
from.isString && to.isString -> {
|
||||
val fromString = from.strvalue(heap)
|
||||
val toString = to.strvalue(heap)
|
||||
val fromString = from.strvalue!!
|
||||
val toString = to.strvalue!!
|
||||
if(fromString.length!=1 || toString.length!=1)
|
||||
err("range from and to must be a single character")
|
||||
if(fromString[0] == toString[0])
|
||||
@ -769,13 +817,13 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return super.process(functionCall)
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCallStatement): IStatement {
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCall.target, functionCall)
|
||||
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
|
||||
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
|
||||
if(targetStatement!=null)
|
||||
checkFunctionCall(targetStatement, functionCall.arglist, functionCall.position)
|
||||
checkFunctionCall(targetStatement, functionCallStatement.arglist, functionCallStatement.position)
|
||||
if(targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty())
|
||||
printWarning("result value of subroutine call is discarded", functionCall.position)
|
||||
return super.process(functionCall)
|
||||
printWarning("result value of subroutine call is discarded", functionCallStatement.position)
|
||||
return super.process(functionCallStatement)
|
||||
}
|
||||
|
||||
private fun checkFunctionCall(target: IStatement, args: List<IExpression>, position: Position) {
|
||||
@ -789,20 +837,20 @@ private class AstChecker(private val namespace: INameScope,
|
||||
checkResult.add(SyntaxError("invalid number of arguments", position))
|
||||
else {
|
||||
for (arg in args.withIndex().zip(func.parameters)) {
|
||||
val argDt=arg.first.value.resultingDatatype(namespace, heap)
|
||||
if(argDt!=null && !argDt.assignableTo(arg.second.possibleDatatypes)) {
|
||||
checkResult.add(ExpressionError("builtin function argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
|
||||
val argDt=arg.first.value.inferType(program)
|
||||
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))
|
||||
}
|
||||
}
|
||||
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
|
||||
val dt1 = args[0].resultingDatatype(namespace, heap)!!
|
||||
val dt2 = args[1].resultingDatatype(namespace, heap)!!
|
||||
val dt1 = args[0].inferType(program)!!
|
||||
val dt2 = args[1].inferType(program)!!
|
||||
if (dt1 != dt2)
|
||||
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))
|
||||
else if(same(args[0], args[1]))
|
||||
else if(args[0] isSameAs args[1])
|
||||
checkResult.add(ExpressionError("swap should have 2 different args", position))
|
||||
else if(dt1 !in NumericDatatypes)
|
||||
checkResult.add(ExpressionError("swap requires args of numerical type", position))
|
||||
@ -813,15 +861,31 @@ private class AstChecker(private val namespace: INameScope,
|
||||
checkResult.add(SyntaxError("invalid number of arguments", position))
|
||||
else {
|
||||
for (arg in args.withIndex().zip(target.parameters)) {
|
||||
val argDt = arg.first.value.resultingDatatype(namespace, heap)
|
||||
if(argDt!=null && !argDt.assignableTo(arg.second.type))
|
||||
checkResult.add(ExpressionError("subroutine argument ${arg.first.index+1} has invalid type $argDt, expected ${arg.second.type}", position))
|
||||
val argDt = arg.first.value.inferType(program)
|
||||
if(argDt!=null && !(argDt isAssignableTo arg.second.type)) {
|
||||
// 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.asmParameterRegisters[arg.first.index].registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.XY, RegisterOrPair.X)) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -831,7 +895,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
override fun process(postIncrDecr: PostIncrDecr): IStatement {
|
||||
if(postIncrDecr.target.identifier != null) {
|
||||
val targetName = postIncrDecr.target.identifier!!.nameInSource
|
||||
val target = namespace.lookup(targetName, postIncrDecr)
|
||||
val target = program.namespace.lookup(targetName, postIncrDecr)
|
||||
if(target==null) {
|
||||
checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position))
|
||||
} else {
|
||||
@ -842,7 +906,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
}
|
||||
} 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) {
|
||||
checkResult.add(SyntaxError("undefined symbol", postIncrDecr.position))
|
||||
}
|
||||
@ -858,24 +922,21 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
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.datatype !in IterableDatatypes)
|
||||
checkResult.add(SyntaxError("indexing requires an iterable variable", arrayIndexedExpression.position))
|
||||
val arraysize = target.arrayspec?.size()
|
||||
val arraysize = target.arraysize?.size()
|
||||
if(arraysize!=null) {
|
||||
// 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))
|
||||
checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position))
|
||||
} 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
|
||||
val heapId = (target.value as LiteralValue).heapId!!
|
||||
val stringLen = heap.get(heapId).str!!.length
|
||||
val index = (arrayIndexedExpression.arrayspec.x as? LiteralValue)?.asIntegerValue
|
||||
val stringLen = program.heap.get(heapId).str!!.length
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? LiteralValue)?.asIntegerValue
|
||||
if(index!=null && (index<0 || index>=stringLen))
|
||||
checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position))
|
||||
}
|
||||
@ -883,7 +944,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position))
|
||||
|
||||
// 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)
|
||||
checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position))
|
||||
|
||||
@ -891,16 +952,16 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, range: RangeExpr) : Boolean {
|
||||
val from = range.from.constValue(namespace, heap)
|
||||
val to = range.to.constValue(namespace, heap)
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArrayIndex, range: RangeExpr) : Boolean {
|
||||
val from = range.from.constValue(program)
|
||||
val to = range.to.constValue(program)
|
||||
if(from==null || to==null) {
|
||||
checkResult.add(SyntaxError("range from and to values must be constants", range.position))
|
||||
return false
|
||||
@ -917,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))
|
||||
return false
|
||||
}
|
||||
val rangeSize=range.size(heap)
|
||||
val rangeSize=range.size()
|
||||
if(rangeSize!=null && (rangeSize<0 || rangeSize>255)) {
|
||||
checkResult.add(ExpressionError("size of range for string must be 0..255, instead of $rangeSize", range.position))
|
||||
return false
|
||||
@ -926,8 +987,8 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
// range and length check bytes
|
||||
val expectedSize = arrayspec!!.size()
|
||||
val rangeSize=range.size(heap)
|
||||
val expectedSize = arrayspec.size()
|
||||
val rangeSize=range.size()
|
||||
if(rangeSize!=null && rangeSize != expectedSize) {
|
||||
checkResult.add(ExpressionError("range size doesn't match array size, expected $expectedSize found $rangeSize", range.position))
|
||||
return false
|
||||
@ -938,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 {
|
||||
checkResult.add(ExpressionError(msg, value.position))
|
||||
return false
|
||||
@ -946,8 +1007,8 @@ private class AstChecker(private val namespace: INameScope,
|
||||
when (targetDt) {
|
||||
DataType.FLOAT -> {
|
||||
val number = when(value.type) {
|
||||
DataType.UBYTE, DataType.BYTE -> value.bytevalue!!.toDouble()
|
||||
DataType.UWORD, DataType.WORD -> value.wordvalue!!.toDouble()
|
||||
in ByteDatatypes -> value.bytevalue!!.toDouble()
|
||||
in WordDatatypes -> value.wordvalue!!.toDouble()
|
||||
DataType.FLOAT -> value.floatvalue!!
|
||||
else -> return err("numeric value expected")
|
||||
}
|
||||
@ -971,8 +1032,6 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return err("value '$number' out of range for byte")
|
||||
}
|
||||
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)
|
||||
err("unsigned word value expected instead of float; possible loss of precision")
|
||||
else
|
||||
@ -988,15 +1047,14 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if (number < -32768 || number > 32767)
|
||||
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)
|
||||
return err("string value expected")
|
||||
val str = value.strvalue(heap)
|
||||
if (str.length > 255)
|
||||
if (value.strvalue!!.length > 255)
|
||||
return err("string length must be 0-255")
|
||||
}
|
||||
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(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
@ -1005,7 +1063,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>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)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX.asIntegerValue
|
||||
@ -1018,7 +1076,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return err("invalid byte array initialization value ${value.type}, expected $targetDt")
|
||||
}
|
||||
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(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
@ -1027,7 +1085,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>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)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX.asIntegerValue
|
||||
@ -1040,7 +1098,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return err("invalid word array initialization value ${value.type}, expected $targetDt")
|
||||
}
|
||||
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(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
@ -1049,7 +1107,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize < 1 || arraySpecSize>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)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX.asIntegerValue
|
||||
@ -1060,7 +1118,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
|
||||
// check if the floating point values are all within range
|
||||
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
|
||||
heap.get(value.heapId!!).doubleArray!!
|
||||
if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE})
|
||||
@ -1074,20 +1132,45 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
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
|
||||
when(type) {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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 -> {
|
||||
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
|
||||
else -> throw AstException("invalid array type $type")
|
||||
@ -1110,12 +1193,10 @@ private class AstChecker(private val namespace: INameScope,
|
||||
DataType.BYTE -> sourceDatatype==DataType.BYTE
|
||||
DataType.UBYTE -> sourceDatatype==DataType.UBYTE
|
||||
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.STR -> sourceDatatype==DataType.STR
|
||||
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))
|
||||
}
|
||||
|
||||
@ -1129,9 +1210,9 @@ private class AstChecker(private val namespace: INameScope,
|
||||
checkResult.add(ExpressionError("cannot assign word to byte, use msb() or lsb()?", position))
|
||||
}
|
||||
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
|
||||
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
|
||||
}
|
||||
|
@ -1,74 +1,85 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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> {
|
||||
val checker = AstIdentifiersChecker(heap)
|
||||
this.process(checker)
|
||||
internal fun Program.checkIdentifiers() {
|
||||
val checker = AstIdentifiersChecker(namespace)
|
||||
checker.process(this)
|
||||
|
||||
// add any anonymous variables for heap values that are used, and replace literalvalue by identifierref
|
||||
for (variable in checker.anonymousVariablesFromHeap) {
|
||||
if(modules.map {it.name}.toSet().size != modules.size) {
|
||||
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()
|
||||
scope.statements.add(variable.second)
|
||||
val parent = variable.first.parent
|
||||
when {
|
||||
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)
|
||||
parent.value = idref
|
||||
}
|
||||
parent is IFunctionCall -> {
|
||||
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)
|
||||
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)")
|
||||
}
|
||||
variable.second.linkParents(scope as Node)
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
var symbols: MutableMap<String, IStatement> = mutableMapOf()
|
||||
private set
|
||||
private var blocks: MutableMap<String, Block> = mutableMapOf()
|
||||
|
||||
fun result(): List<AstException> {
|
||||
internal fun result(): List<AstException> {
|
||||
return checkResult
|
||||
}
|
||||
|
||||
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 {
|
||||
val scopedName = block.scopedname
|
||||
val existing = symbols[scopedName]
|
||||
if(existing!=null) {
|
||||
val existing = blocks[block.name]
|
||||
if(existing!=null)
|
||||
nameError(block.name, block.position, existing)
|
||||
} else {
|
||||
symbols[scopedName] = block
|
||||
}
|
||||
else
|
||||
blocks[block.name] = block
|
||||
|
||||
return super.process(block)
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
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"
|
||||
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)
|
||||
return super.process(typecast)
|
||||
}
|
||||
@ -84,13 +95,10 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
// the builtin functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
|
||||
|
||||
val scopedName = decl.scopedname
|
||||
val existing = symbols[scopedName]
|
||||
if(existing!=null) {
|
||||
val existing = namespace.lookup(listOf(decl.name), decl)
|
||||
if (existing != null && existing !== decl)
|
||||
nameError(decl.name, decl.position, existing)
|
||||
} else {
|
||||
symbols[scopedName] = decl
|
||||
}
|
||||
|
||||
return super.process(decl)
|
||||
}
|
||||
|
||||
@ -102,34 +110,36 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
if (subroutine.parameters.any { it.name in BuiltinFunctions })
|
||||
checkResult.add(NameError("builtin function name cannot be used as parameter", subroutine.position))
|
||||
|
||||
val scopedName = subroutine.scopedname
|
||||
val existing = symbols[scopedName]
|
||||
if (existing != null) {
|
||||
val existing = namespace.lookup(listOf(subroutine.name), subroutine)
|
||||
if (existing != null && existing !== subroutine)
|
||||
nameError(subroutine.name, subroutine.position, existing)
|
||||
} else {
|
||||
symbols[scopedName] = subroutine
|
||||
}
|
||||
|
||||
// check that there are no local variables that redefine the subroutine's parameters
|
||||
val allDefinedNames = subroutine.allLabelsAndVariables()
|
||||
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
|
||||
val symbolsInSub = subroutine.allDefinedSymbols()
|
||||
val namesInSub = symbolsInSub.map{ it.first }.toSet()
|
||||
val paramNames = subroutine.parameters.map { it.name }.toSet()
|
||||
val paramsToCheck = paramNames.intersect(allDefinedNames)
|
||||
val paramsToCheck = paramNames.intersect(namesInSub)
|
||||
for(name in paramsToCheck) {
|
||||
val thing = subroutine.getLabelOrVariable(name)!!
|
||||
if(thing.position != subroutine.position)
|
||||
nameError(name, thing.position, subroutine)
|
||||
val labelOrVar = subroutine.getLabelOrVariable(name)
|
||||
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
|
||||
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)
|
||||
// NOTE:
|
||||
// - numeric types BYTE and WORD and FLOAT are passed by value;
|
||||
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
|
||||
if(subroutine.asmAddress==null) {
|
||||
// - do NOT do this is the statement can be transformed into an asm subroutine later!
|
||||
if(subroutine.asmAddress==null && !subroutine.canBeAsmSubroutine) {
|
||||
if(subroutine.asmParameterRegisters.isEmpty()) {
|
||||
subroutine.parameters
|
||||
.filter { it.name !in allDefinedNames }
|
||||
.filter { it.name !in namesInSub }
|
||||
.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)
|
||||
subroutine.statements.add(0, vardecl)
|
||||
}
|
||||
@ -144,13 +154,9 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
// the builtin functions can't be redefined
|
||||
checkResult.add(NameError("builtin function cannot be redefined", label.position))
|
||||
} else {
|
||||
val scopedName = label.scopedname
|
||||
val existing = symbols[scopedName]
|
||||
if (existing != null) {
|
||||
val existing = namespace.lookup(listOf(label.name), label)
|
||||
if (existing != null && existing !== label)
|
||||
nameError(label.name, label.position, existing)
|
||||
} else {
|
||||
symbols[scopedName] = label
|
||||
}
|
||||
}
|
||||
return super.process(label)
|
||||
}
|
||||
@ -162,16 +168,17 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
// additional interation count variable in their scope.
|
||||
if(forLoop.loopRegister!=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)
|
||||
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
|
||||
} else if(forLoop.loopVar!=null) {
|
||||
val varName = forLoop.loopVar.nameInSource.last()
|
||||
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) {
|
||||
// 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)
|
||||
forLoop.body.statements.add(0, vardecl)
|
||||
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
||||
@ -180,10 +187,11 @@ private class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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)
|
||||
forLoop.body.statements.add(0, vardecl)
|
||||
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
|
||||
@ -224,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 {
|
||||
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.
|
||||
// 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)
|
||||
anonymousVariablesFromHeap.add(Pair(literalValue, variable))
|
||||
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue,
|
||||
isArray = false, autoGenerated = false, position = literalValue.position)
|
||||
anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable)
|
||||
}
|
||||
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_"
|
||||
|
@ -4,9 +4,9 @@ package prog8.ast
|
||||
* Checks for the occurrence of recursive subroutine calls
|
||||
*/
|
||||
|
||||
fun Module.checkRecursion(namespace: INameScope) {
|
||||
internal fun Program.checkRecursion() {
|
||||
val checker = AstRecursionChecker(namespace)
|
||||
this.process(checker)
|
||||
checker.process(this)
|
||||
printErrors(checker.result(), name)
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ private class DirectedGraph<VT> {
|
||||
|
||||
fun print() {
|
||||
println("#vertices: $numVertices")
|
||||
graph.forEach { from, to ->
|
||||
graph.forEach { (from, to) ->
|
||||
println("$from CALLS:")
|
||||
to.forEach { println(" $it") }
|
||||
}
|
||||
@ -41,8 +41,8 @@ private class DirectedGraph<VT> {
|
||||
}
|
||||
|
||||
fun checkForCycle(): MutableList<VT> {
|
||||
val visited = uniqueVertices.associate { it to false }.toMutableMap()
|
||||
val recStack = uniqueVertices.associate { it to false }.toMutableMap()
|
||||
val visited = uniqueVertices.associateWith { false }.toMutableMap()
|
||||
val recStack = uniqueVertices.associateWith { false }.toMutableMap()
|
||||
val cycle = mutableListOf<VT>()
|
||||
for(node in uniqueVertices) {
|
||||
if(isCyclicUntil(node, visited, recStack, cycle))
|
||||
@ -84,7 +84,7 @@ private class DirectedGraph<VT> {
|
||||
private class AstRecursionChecker(private val namespace: INameScope) : IAstProcessor {
|
||||
private val callGraph = DirectedGraph<INameScope>()
|
||||
|
||||
fun result(): List<AstException> {
|
||||
internal fun result(): List<AstException> {
|
||||
val cycle = callGraph.checkForCycle()
|
||||
if(cycle.isEmpty())
|
||||
return emptyList()
|
||||
@ -92,9 +92,9 @@ private class AstRecursionChecker(private val namespace: INameScope) : IAstProce
|
||||
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain"))
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCallStatement): IStatement {
|
||||
val scope = functionCall.definingScope()
|
||||
val targetStatement = functionCall.target.targetStatement(namespace)
|
||||
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
|
||||
val scope = functionCallStatement.definingScope()
|
||||
val targetStatement = functionCallStatement.target.targetStatement(namespace)
|
||||
if(targetStatement!=null) {
|
||||
val targetScope = when (targetStatement) {
|
||||
is Subroutine -> targetStatement
|
||||
@ -102,7 +102,7 @@ private class AstRecursionChecker(private val namespace: INameScope) : IAstProce
|
||||
}
|
||||
callGraph.add(scope, targetScope)
|
||||
}
|
||||
return super.process(functionCall)
|
||||
return super.process(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
|
@ -5,10 +5,9 @@ package prog8.ast
|
||||
* Checks that are specific for imported modules.
|
||||
*/
|
||||
|
||||
fun Module.checkImportedValid() {
|
||||
internal fun Module.checkImportedValid() {
|
||||
val checker = ImportedAstChecker()
|
||||
this.linkParents()
|
||||
this.process(checker)
|
||||
checker.process(this)
|
||||
printErrors(checker.result(), name)
|
||||
}
|
||||
|
||||
@ -16,7 +15,7 @@ fun Module.checkImportedValid() {
|
||||
private class ImportedAstChecker : IAstProcessor {
|
||||
private val checkResult: MutableList<SyntaxError> = mutableListOf()
|
||||
|
||||
fun result(): List<SyntaxError> {
|
||||
internal fun result(): List<SyntaxError> {
|
||||
return checkResult
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,19 @@
|
||||
package prog8.ast
|
||||
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.functions.BuiltinFunctions
|
||||
|
||||
fun Module.reorderStatements(namespace: INameScope, heap: HeapValues) {
|
||||
val initvalueCreator = VarInitValueCreator()
|
||||
this.process(initvalueCreator)
|
||||
internal fun Program.reorderStatements() {
|
||||
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
|
||||
initvalueCreator.process(this)
|
||||
|
||||
val checker = StatementReorderer(namespace, heap)
|
||||
this.process(checker)
|
||||
val checker = StatementReorderer(this)
|
||||
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.
|
||||
// - '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.
|
||||
@ -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.
|
||||
// - 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")
|
||||
|
||||
@ -33,11 +36,21 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
val (blocks, other) = module.statements.partition { it is Block }
|
||||
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
|
||||
|
||||
val mainBlock = module.statements.single { it is Block && it.name=="main" }
|
||||
if((mainBlock as Block).address==null) {
|
||||
module.statements.remove(mainBlock)
|
||||
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything
|
||||
val nonLibraryBlocks = module.statements.withIndex()
|
||||
.filter { it.value is Block && !(it.value as Block).isInLibrary }
|
||||
.map { it.index to it.value }
|
||||
.reversed()
|
||||
for(nonLibBlock in nonLibraryBlocks)
|
||||
module.statements.removeAt(nonLibBlock.first)
|
||||
for(nonLibBlock in nonLibraryBlocks)
|
||||
module.statements.add(0, nonLibBlock.second)
|
||||
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
|
||||
if(mainBlock!=null && (mainBlock as Block).address==null) {
|
||||
module.remove(mainBlock)
|
||||
module.statements.add(0, mainBlock)
|
||||
}
|
||||
|
||||
val varDecls = module.statements.filterIsInstance<VarDecl>()
|
||||
module.statements.removeAll(varDecls)
|
||||
module.statements.addAll(0, varDecls)
|
||||
@ -46,8 +59,6 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
module.statements.removeAll(directives)
|
||||
module.statements.addAll(0, directives)
|
||||
|
||||
// TODO make sure user-defined blocks come BEFORE library blocks
|
||||
|
||||
sortConstantAssignments(module.statements)
|
||||
}
|
||||
|
||||
@ -58,7 +69,7 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
// move all subroutines to the end of the block
|
||||
for (subroutine in subroutines) {
|
||||
if(subroutine.name!="start" || block.name!="main") {
|
||||
block.statements.remove(subroutine)
|
||||
block.remove(subroutine)
|
||||
block.statements.add(subroutine)
|
||||
}
|
||||
numSubroutinesAtEnd++
|
||||
@ -66,7 +77,7 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
// move the "start" subroutine to the top
|
||||
if(block.name=="main") {
|
||||
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
|
||||
block.statements.remove(it)
|
||||
block.remove(it)
|
||||
block.statements.add(0, it)
|
||||
numSubroutinesAtEnd--
|
||||
}
|
||||
@ -89,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.addAll(0, varDecls)
|
||||
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
|
||||
block.statements.removeAll(directives)
|
||||
block.statements.addAll(0, directives)
|
||||
block.linkParents(block.parent)
|
||||
|
||||
sortConstantAssignments(block.statements)
|
||||
|
||||
// create subroutine that initializes the block's variables (if any)
|
||||
val varInits = block.statements.withIndex().filter { it.value is VariableInitializationAssignment }
|
||||
if(varInits.isNotEmpty()) {
|
||||
val statements = varInits.map{it.value}.toMutableList()
|
||||
val varInitSub = Subroutine(initvarsSubName, emptyList(), emptyList(), emptyList(), emptyList(),
|
||||
emptySet(), null, false, statements, block.position)
|
||||
varInitSub.keepAlways = true
|
||||
varInitSub.linkParents(block)
|
||||
block.statements.add(varInitSub)
|
||||
|
||||
@ -147,13 +161,36 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
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>) {
|
||||
// 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 stmtIter = statements.iterator()
|
||||
for(stmt in stmtIter) {
|
||||
if(stmt is Assignment) {
|
||||
val constval = stmt.value.constValue(namespace, heap)
|
||||
if(stmt is Assignment && !stmt.targets.any { it.isMemoryMapped(program.namespace) }) {
|
||||
val constval = stmt.value.constValue(program)
|
||||
if(constval!=null) {
|
||||
val (sorted, trailing) = sortConstantAssignmentSequence(stmt, stmtIter)
|
||||
result.addAll(sorted)
|
||||
@ -170,13 +207,88 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
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?> {
|
||||
val sequence= mutableListOf(first)
|
||||
var trailing: IStatement? = null
|
||||
while(stmtIter.hasNext()) {
|
||||
val next = stmtIter.next()
|
||||
if(next is Assignment) {
|
||||
val constValue = next.value.constValue(namespace, heap)
|
||||
val constValue = next.value.constValue(program)
|
||||
if(constValue==null) {
|
||||
trailing = next
|
||||
break
|
||||
@ -188,31 +300,43 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
// Replace the var decl with an assignment and add a new vardecl with the default constant value.
|
||||
private class VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstProcessor {
|
||||
// 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.
|
||||
// Variable decls without a value don't get this treatment, which means they retain the last
|
||||
// value they had when restarting the program.
|
||||
// This is done in a separate step because it interferes with the namespace lookup of symbols
|
||||
// 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) {
|
||||
vardeclsToAdd.clear()
|
||||
super.process(module)
|
||||
|
||||
// add any new vardecls to the various scopes
|
||||
for(decl in vardeclsToAdd)
|
||||
for(d in decl.value) {
|
||||
d.linkParents(decl.key as Node)
|
||||
decl.key.statements.add(0, d)
|
||||
d.value.linkParents(decl.key as Node)
|
||||
decl.key.statements.add(0, d.value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,9 +347,7 @@ private class VarInitValueCreator: IAstProcessor {
|
||||
|
||||
if(decl.datatype in NumericDatatypes) {
|
||||
val scope = decl.definingScope()
|
||||
if(scope !in vardeclsToAdd)
|
||||
vardeclsToAdd[scope] = mutableListOf()
|
||||
vardeclsToAdd[scope]!!.add(decl.asDefaultValueDecl(null))
|
||||
addVarDecl(scope, decl.asDefaultValueDecl(null))
|
||||
val declvalue = decl.value!!
|
||||
val value =
|
||||
if(declvalue is LiteralValue) {
|
||||
@ -234,8 +356,9 @@ private class VarInitValueCreator: IAstProcessor {
|
||||
}
|
||||
else
|
||||
declvalue
|
||||
val identifierName = listOf(decl.name) // // TODO this was: (scoped name) decl.scopedname.split(".")
|
||||
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,
|
||||
value,
|
||||
decl.position
|
||||
@ -244,4 +367,64 @@ private class VarInitValueCreator: IAstProcessor {
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
|
563
compiler/src/prog8/astvm/AstVm.kt
Normal file
563
compiler/src/prog8/astvm/AstVm.kt
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
204
compiler/src/prog8/astvm/BuiltinFunctions.kt
Normal file
204
compiler/src/prog8/astvm/BuiltinFunctions.kt
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
18
compiler/src/prog8/astvm/CallStack.kt
Normal file
18
compiler/src/prog8/astvm/CallStack.kt
Normal 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))
|
||||
}
|
||||
|
||||
}
|
154
compiler/src/prog8/astvm/Expressions.kt
Normal file
154
compiler/src/prog8/astvm/Expressions.kt
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
122
compiler/src/prog8/astvm/Memory.kt
Normal file
122
compiler/src/prog8/astvm/Memory.kt
Normal 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
|
||||
}
|
||||
}
|
192
compiler/src/prog8/astvm/ScreenDialog.kt
Normal file
192
compiler/src/prog8/astvm/ScreenDialog.kt
Normal 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()
|
||||
}
|
||||
}
|
73
compiler/src/prog8/astvm/VariablesCreator.kt
Normal file
73
compiler/src/prog8/astvm/VariablesCreator.kt
Normal 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
528
compiler/src/prog8/compiler/RuntimeValue.kt
Normal file
528
compiler/src/prog8/compiler/RuntimeValue.kt
Normal 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()
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import prog8.ast.*
|
||||
class ZeropageDepletedError(message: String) : Exception(message)
|
||||
|
||||
|
||||
abstract class Zeropage(private val options: CompilationOptions) {
|
||||
abstract class Zeropage(protected val options: CompilationOptions) {
|
||||
|
||||
private val allocations = mutableMapOf<Int, Pair<String, DataType>>()
|
||||
val free = mutableListOf<Int>() // subclasses must set this to the appropriate free locations.
|
||||
@ -16,12 +16,12 @@ abstract class Zeropage(private val options: CompilationOptions) {
|
||||
fun available() = free.size
|
||||
|
||||
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 =
|
||||
when (datatype) {
|
||||
DataType.UBYTE, DataType.BYTE -> 1
|
||||
DataType.UWORD, DataType.WORD -> 2
|
||||
in ByteDatatypes -> 1
|
||||
in WordDatatypes -> 2
|
||||
DataType.FLOAT -> {
|
||||
if (options.floats) {
|
||||
if(position!=null)
|
||||
@ -61,4 +61,12 @@ abstract class Zeropage(private val options: CompilationOptions) {
|
||||
|
||||
private fun loneByte(address: Int) = address in free && address-1 !in free && address+1 !in free
|
||||
private fun sequentialFree(address: Int, size: Int) = free.containsAll((address until address+size).toList())
|
||||
|
||||
enum class ExitProgramStrategy {
|
||||
CLEAN_EXIT,
|
||||
SYSTEM_RESET
|
||||
}
|
||||
|
||||
abstract val exitProgramStrategy: ExitProgramStrategy
|
||||
|
||||
}
|
||||
|
@ -1,32 +1,42 @@
|
||||
package prog8.compiler.intermediate
|
||||
|
||||
import prog8.compiler.RuntimeValue
|
||||
import prog8.stackvm.Syscall
|
||||
|
||||
open class Instruction(val opcode: Opcode,
|
||||
val arg: Value? = null,
|
||||
val arg2: Value? = null,
|
||||
val arg: RuntimeValue? = null,
|
||||
val arg2: RuntimeValue? = null,
|
||||
val callLabel: String? = null,
|
||||
val callLabel2: String? = null)
|
||||
{
|
||||
lateinit var next: Instruction
|
||||
var nextAlt: Instruction? = null
|
||||
var branchAddress: Int? = null
|
||||
|
||||
override fun toString(): String {
|
||||
val argStr = arg?.toString() ?: ""
|
||||
val result =
|
||||
when {
|
||||
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 -> {
|
||||
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
|
||||
"syscall $syscall"
|
||||
}
|
||||
opcode in opcodesWithVarArgument -> {
|
||||
// opcodes that manipulate a variable
|
||||
"${opcode.toString().toLowerCase()} ${callLabel?:""} ${callLabel2?:""}".trimEnd()
|
||||
"${opcode.name.toLowerCase()} ${callLabel?:""} ${callLabel2?:""}".trimEnd()
|
||||
}
|
||||
callLabel==null -> "${opcode.toString().toLowerCase()} $argStr"
|
||||
else -> "${opcode.toString().toLowerCase()} $callLabel $argStr"
|
||||
callLabel==null -> "${opcode.name.toLowerCase()} $argStr"
|
||||
else -> "${opcode.name.toLowerCase()} $callLabel $argStr"
|
||||
}
|
||||
.trimEnd()
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package prog8.compiler.intermediate
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.RuntimeValue
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.Zeropage
|
||||
@ -9,15 +10,14 @@ import java.io.PrintStream
|
||||
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,
|
||||
val shortname: String,
|
||||
class ProgramBlock(val name: String,
|
||||
var address: Int?,
|
||||
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 labels: MutableMap<String, Instruction> = mutableMapOf(),
|
||||
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
|
||||
val force_output: Boolean)
|
||||
{
|
||||
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 blocks = mutableListOf<ProgramBlock>()
|
||||
val memory = mutableMapOf<Int, List<Value>>()
|
||||
val memory = mutableMapOf<Int, List<RuntimeValue>>()
|
||||
private lateinit var currentBlock: ProgramBlock
|
||||
|
||||
val numVariables: Int
|
||||
@ -70,7 +70,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
optimizeMultipleSequentialLineInstrs()
|
||||
optimizeCallReturnIntoJump()
|
||||
optimizeConditionalBranches()
|
||||
// todo: add more optimizations to stackvm code
|
||||
// todo: add more optimizations to intermediate code!
|
||||
|
||||
optimizeRemoveNops() // must be done as the last step
|
||||
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 {
|
||||
if (it[1].value.opcode in branchOpcodes) {
|
||||
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)
|
||||
val replacement: Instruction =
|
||||
if (value) {
|
||||
@ -257,17 +257,17 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
||||
}
|
||||
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[index1] = Instruction(Opcode.NOP)
|
||||
}
|
||||
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[index1] = Instruction(Opcode.NOP)
|
||||
}
|
||||
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[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.MSB -> throw CompilerException("msb of a byte")
|
||||
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[index1] = Instruction(Opcode.NOP)
|
||||
}
|
||||
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[index1] = Instruction(Opcode.NOP)
|
||||
}
|
||||
@ -317,7 +317,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
||||
}
|
||||
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[index1] = Instruction(Opcode.NOP)
|
||||
}
|
||||
@ -327,6 +327,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
instructionsToReplace[index1] = Instruction(Opcode.NOP)
|
||||
}
|
||||
Opcode.DISCARD_WORD, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
|
||||
Opcode.MKWORD -> {}
|
||||
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) {
|
||||
VarDeclType.VAR -> {
|
||||
val value = when(decl.datatype) {
|
||||
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
||||
in NumericDatatypes -> RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
|
||||
in StringDatatypes -> {
|
||||
val litval = (decl.value as LiteralValue)
|
||||
if(litval.heapId==null)
|
||||
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,
|
||||
DataType.ARRAY_UB, DataType.ARRAY_UW, DataType.ARRAY_F -> {
|
||||
in ArrayDatatypes -> {
|
||||
val litval = (decl.value as LiteralValue)
|
||||
if(litval.heapId==null)
|
||||
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
|
||||
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))
|
||||
}
|
||||
|
||||
@ -447,8 +448,8 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
currentBlock.memoryPointers[name] = Pair(address, datatype)
|
||||
}
|
||||
|
||||
fun newBlock(scopedname: String, shortname: String, address: Int?, options: Set<String>) {
|
||||
currentBlock = ProgramBlock(scopedname, shortname, address, force_output="force_output" in options)
|
||||
fun newBlock(name: String, address: Int?, options: Set<String>) {
|
||||
currentBlock = ProgramBlock(name, address, force_output="force_output" in options)
|
||||
blocks.add(currentBlock)
|
||||
}
|
||||
|
||||
@ -460,29 +461,44 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
out.println("%end_memory")
|
||||
out.println("%heap")
|
||||
heap.allEntries().forEach {
|
||||
out.print("${it.key} ${it.value.type.name.toLowerCase()} ")
|
||||
when {
|
||||
it.value.str!=null ->
|
||||
out.println("${it.key} ${it.value.type.toString().toLowerCase()} \"${escape(it.value.str!!)}\"")
|
||||
it.value.array!=null ->
|
||||
out.println("${it.key} ${it.value.type.toString().toLowerCase()} ${it.value.array!!.toList()}")
|
||||
out.println("\"${escape(it.value.str!!)}\"")
|
||||
it.value.array!=null -> {
|
||||
// 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 ->
|
||||
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")
|
||||
}
|
||||
}
|
||||
out.println("%end_heap")
|
||||
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")
|
||||
for(variable in blk.variables) {
|
||||
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("%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("%instructions")
|
||||
|
@ -58,10 +58,6 @@ enum class Opcode {
|
||||
DIV_F,
|
||||
REMAINDER_UB, // signed remainder is undefined/unimplemented
|
||||
REMAINDER_UW, // signed remainder is undefined/unimplemented
|
||||
POW_UB,
|
||||
POW_B,
|
||||
POW_UW,
|
||||
POW_W,
|
||||
POW_F,
|
||||
NEG_B,
|
||||
NEG_W,
|
||||
@ -244,7 +240,6 @@ enum class Opcode {
|
||||
JZW, // branch if value is zero (word)
|
||||
JNZW, // branch if value is not zero (word)
|
||||
|
||||
|
||||
// subroutines
|
||||
CALL,
|
||||
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
|
||||
SEI, // set 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
|
||||
RSAVEX, // save just X (the evaluation stack pointer)
|
||||
RRESTORE, // restore all internal registers and status flags
|
||||
@ -266,7 +262,8 @@ enum class Opcode {
|
||||
BREAKPOINT, // breakpoint
|
||||
TERMINATE, // end the program
|
||||
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(
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.RuntimeValue
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.intermediate.*
|
||||
import prog8.stackvm.Syscall
|
||||
@ -16,20 +17,15 @@ import kotlin.math.abs
|
||||
class AssemblyError(msg: String) : RuntimeException(msg)
|
||||
|
||||
|
||||
// TODO: code generation for POW instruction
|
||||
|
||||
|
||||
class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, val heap: HeapValues, val zeropage: Zeropage) {
|
||||
class AsmGen(private val options: CompilationOptions, private val program: IntermediateProgram,
|
||||
private val heap: HeapValues, private val zeropage: Zeropage) {
|
||||
private val globalFloatConsts = mutableMapOf<Double, String>()
|
||||
private val assemblyLines = mutableListOf<String>()
|
||||
private lateinit var block: IntermediateProgram.ProgramBlock
|
||||
private var breakpointCounter = 0
|
||||
|
||||
init {
|
||||
// Because 64tass understands scoped names via .proc / .block,
|
||||
// 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!
|
||||
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
|
||||
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
|
||||
for(block in program.blocks) {
|
||||
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)
|
||||
}
|
||||
}.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(
|
||||
block.scopedname,
|
||||
block.shortname,
|
||||
block.name,
|
||||
block.address,
|
||||
newinstructions,
|
||||
newvars,
|
||||
newConstants,
|
||||
newMempointers,
|
||||
newlabels,
|
||||
force_output = block.force_output)
|
||||
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... ")
|
||||
|
||||
assemblyLines.clear()
|
||||
@ -84,9 +79,11 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
for(b in program.blocks)
|
||||
block2asm(b)
|
||||
|
||||
var optimizationsDone=1
|
||||
while(optimizationsDone>0) {
|
||||
optimizationsDone = optimizeAssembly(assemblyLines)
|
||||
if(optimize) {
|
||||
var optimizationsDone = 1
|
||||
while (optimizationsDone > 0) {
|
||||
optimizationsDone = optimizeAssembly(assemblyLines)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if(' ' in scoped)
|
||||
return scoped
|
||||
val blockLocal: Boolean
|
||||
var name = when {
|
||||
block==null -> {
|
||||
blockLocal=true
|
||||
scoped
|
||||
}
|
||||
scoped.startsWith("${block.shortname}.") -> {
|
||||
blockLocal = true
|
||||
scoped.substring(block.shortname.length+1)
|
||||
}
|
||||
scoped.startsWith("block.") -> {
|
||||
blockLocal = false
|
||||
scoped
|
||||
}
|
||||
else -> {
|
||||
blockLocal = false
|
||||
scoped
|
||||
}
|
||||
var name = if (block!=null && scoped.startsWith("${block.name}.")) {
|
||||
blockLocal = true
|
||||
scoped.substring(block.name.length+1)
|
||||
}
|
||||
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=="-")
|
||||
return "-"
|
||||
if(blockLocal)
|
||||
@ -186,15 +174,32 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
|
||||
if(zeropage.exitProgramStrategy!=Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
|
||||
// disable shift-commodore charset switching and run/stop key
|
||||
out(" lda #$80")
|
||||
out(" lda #$80")
|
||||
out(" sta 657\t; disable charset switching")
|
||||
out(" lda #239")
|
||||
out(" sta 808\t; disable run/stop key")
|
||||
}
|
||||
|
||||
out(" ldx #\$ff\t; init estack pointer")
|
||||
out(" ; initialize the variables in each block")
|
||||
for(block in program.blocks) {
|
||||
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name==initvarsSubName } as? LabelInstr
|
||||
if(initVarsLabel!=null)
|
||||
out(" jsr ${block.scopedname}.${initVarsLabel.name}")
|
||||
out(" jsr ${block.name}.${initVarsLabel.name}")
|
||||
}
|
||||
out(" clc")
|
||||
out(" jmp main.start\t; jump to program entrypoint")
|
||||
when(zeropage.exitProgramStrategy) {
|
||||
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
|
||||
out(" jmp main.start\t; jump to program entrypoint")
|
||||
}
|
||||
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
|
||||
out(" jsr main.start\t; call program entrypoint")
|
||||
out(" jmp (c64.RESET_VEC)\t; cold reset")
|
||||
}
|
||||
}
|
||||
out("")
|
||||
|
||||
// the global list of all floating point constants for the whole program
|
||||
@ -206,9 +211,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
|
||||
private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
|
||||
block = blk
|
||||
out("\n; ---- block: '${block.shortname}' ----")
|
||||
out("\n; ---- block: '${block.name}' ----")
|
||||
if(!blk.force_output)
|
||||
out("${block.shortname}\t.proc\n")
|
||||
out("${block.name}\t.proc\n")
|
||||
if(block.address!=null) {
|
||||
out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'")
|
||||
out("* = ${block.address?.toHex()}")
|
||||
@ -216,14 +221,14 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
|
||||
// deal with zeropage 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]
|
||||
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)
|
||||
if(variable.value.type in zeropage.allowedDatatypes && variable.value.type != DataType.FLOAT) {
|
||||
try {
|
||||
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
|
||||
blk.variablesMarkedForZeropage.add(variable.key)
|
||||
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
|
||||
@ -277,18 +282,15 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
DataType.UWORD -> out("${v.first}\t.word 0")
|
||||
DataType.WORD -> out("${v.first}\t.sint 0")
|
||||
DataType.FLOAT -> out("${v.first}\t.byte 0,0,0,0,0 ; float")
|
||||
DataType.STR,
|
||||
DataType.STR_P,
|
||||
DataType.STR_S,
|
||||
DataType.STR_PS -> {
|
||||
val rawStr = heap.get(v.second.heapId).str!!
|
||||
DataType.STR, DataType.STR_S -> {
|
||||
val rawStr = heap.get(v.second.heapId!!).str!!
|
||||
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))
|
||||
out(" .byte " + chunk.joinToString())
|
||||
}
|
||||
DataType.ARRAY_UB -> {
|
||||
// unsigned integer byte arrayspec
|
||||
// unsigned integer byte arraysize
|
||||
val data = makeArrayFillDataUnsigned(v.second)
|
||||
if (data.size <= 16)
|
||||
out("${v.first}\t.byte ${data.joinToString()}")
|
||||
@ -299,7 +301,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
// signed integer byte arrayspec
|
||||
// signed integer byte arraysize
|
||||
val data = makeArrayFillDataSigned(v.second)
|
||||
if (data.size <= 16)
|
||||
out("${v.first}\t.char ${data.joinToString()}")
|
||||
@ -310,7 +312,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
// unsigned word arrayspec
|
||||
// unsigned word arraysize
|
||||
val data = makeArrayFillDataUnsigned(v.second)
|
||||
if (data.size <= 16)
|
||||
out("${v.first}\t.word ${data.joinToString()}")
|
||||
@ -321,7 +323,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
// signed word arrayspec
|
||||
// signed word arraysize
|
||||
val data = makeArrayFillDataSigned(v.second)
|
||||
if (data.size <= 16)
|
||||
out("${v.first}\t.sint ${data.joinToString()}")
|
||||
@ -332,8 +334,8 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
// float arrayspec
|
||||
val array = heap.get(v.second.heapId).doubleArray!!
|
||||
// float arraysize
|
||||
val array = heap.get(v.second.heapId!!).doubleArray!!
|
||||
val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) }
|
||||
out(v.first)
|
||||
for(f in array.zip(floatFills))
|
||||
@ -344,48 +346,48 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
|
||||
private fun encodeStr(str: String, dt: DataType): List<Short> {
|
||||
when(dt) {
|
||||
return when(dt) {
|
||||
DataType.STR -> {
|
||||
val bytes = Petscii.encodePetscii(str, true)
|
||||
return bytes.plus(0)
|
||||
}
|
||||
DataType.STR_P -> {
|
||||
val result = listOf(str.length.toShort())
|
||||
val bytes = Petscii.encodePetscii(str, true)
|
||||
return result.plus(bytes)
|
||||
bytes.plus(0)
|
||||
}
|
||||
DataType.STR_S -> {
|
||||
val bytes = Petscii.encodeScreencode(str, true)
|
||||
return bytes.plus(0)
|
||||
}
|
||||
DataType.STR_PS -> {
|
||||
val result = listOf(str.length.toShort())
|
||||
val bytes = Petscii.encodeScreencode(str, true)
|
||||
return result.plus(bytes)
|
||||
bytes.plus(0)
|
||||
}
|
||||
else -> throw AssemblyError("invalid str type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeArrayFillDataUnsigned(value: Value): List<String> {
|
||||
val array = heap.get(value.heapId).array!!
|
||||
return if (value.type == DataType.ARRAY_UB || value.type == DataType.ARRAY_UW)
|
||||
array.map { "$"+it.toString(16).padStart(2, '0') }
|
||||
else
|
||||
throw AssemblyError("invalid arrayspec type")
|
||||
private fun makeArrayFillDataUnsigned(value: RuntimeValue): List<String> {
|
||||
val array = heap.get(value.heapId!!).array!!
|
||||
return when {
|
||||
value.type==DataType.ARRAY_UB ->
|
||||
// byte array can never contain pointer-to types, so treat values as all integers
|
||||
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> {
|
||||
val array = heap.get(value.heapId).array!!
|
||||
private fun makeArrayFillDataSigned(value: RuntimeValue): List<String> {
|
||||
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) {
|
||||
array.map {
|
||||
if(it>=0)
|
||||
"$"+it.toString(16).padStart(2, '0')
|
||||
if(it.integer!!>=0)
|
||||
"$"+it.integer.toString(16).padStart(2, '0')
|
||||
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 {
|
||||
@ -417,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()]
|
||||
?: throw AssemblyError("should have a global float const for number $value")
|
||||
|
||||
private fun simpleInstr2Asm(ins: Instruction): String? {
|
||||
// a label 'instruction' is simply translated into a asm label
|
||||
if(ins is LabelInstr) {
|
||||
if(ins.name.startsWith("block."))
|
||||
return ""
|
||||
|
||||
val labelresult =
|
||||
if(ins.name.startsWith("${block.shortname}."))
|
||||
ins.name.substring(block.shortname.length+1)
|
||||
if(ins.name.startsWith("${block.name}."))
|
||||
ins.name.substring(block.name.length+1)
|
||||
else
|
||||
ins.name
|
||||
return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult
|
||||
@ -446,6 +445,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
Opcode.CLC -> " clc"
|
||||
Opcode.SEI -> " sei"
|
||||
Opcode.CLI -> " cli"
|
||||
Opcode.CARRY_TO_A -> " lda #0 | adc #0"
|
||||
Opcode.JUMP -> {
|
||||
if(ins.callLabel!=null)
|
||||
" jmp ${ins.callLabel}"
|
||||
@ -473,7 +473,12 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
Opcode.DISCARD_BYTE -> " inx"
|
||||
Opcode.DISCARD_WORD -> " 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 -> {
|
||||
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()}")
|
||||
@ -497,9 +502,9 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
Syscall.FUNC_ALL_F,
|
||||
Syscall.FUNC_MAX_F,
|
||||
Syscall.FUNC_MIN_F,
|
||||
Syscall.FUNC_AVG_F,
|
||||
Syscall.FUNC_SUM_F -> " jsr c64flt.${call.toString().toLowerCase()}"
|
||||
else -> " jsr prog8_lib.${call.toString().toLowerCase()}"
|
||||
Syscall.FUNC_SUM_F -> " jsr c64flt.${call.name.toLowerCase()}"
|
||||
null -> ""
|
||||
else -> " jsr prog8_lib.${call.name.toLowerCase()}"
|
||||
}
|
||||
}
|
||||
Opcode.BREAKPOINT -> {
|
||||
@ -740,6 +745,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
Opcode.ABS_B -> " jsr prog8_lib.abs_b"
|
||||
Opcode.ABS_W -> " jsr prog8_lib.abs_w"
|
||||
Opcode.ABS_F -> " jsr c64flt.abs_f"
|
||||
Opcode.POW_F -> " jsr c64flt.pow_f"
|
||||
Opcode.INV_BYTE -> {
|
||||
"""
|
||||
lda ${(ESTACK_LO + 1).toHex()},x
|
||||
@ -870,30 +876,20 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
Opcode.IDIV_W -> " jsr prog8_lib.idiv_w"
|
||||
Opcode.IDIV_UW -> " jsr prog8_lib.idiv_uw"
|
||||
|
||||
Opcode.AND_BYTE -> {
|
||||
"""
|
||||
lda ${(ESTACK_LO + 2).toHex()},x
|
||||
and ${(ESTACK_LO + 1).toHex()},x
|
||||
inx
|
||||
sta ${(ESTACK_LO + 1).toHex()},x
|
||||
"""
|
||||
}
|
||||
Opcode.OR_BYTE -> {
|
||||
"""
|
||||
lda ${(ESTACK_LO + 2).toHex()},x
|
||||
ora ${(ESTACK_LO + 1).toHex()},x
|
||||
inx
|
||||
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.AND_BYTE -> " jsr prog8_lib.and_b"
|
||||
Opcode.OR_BYTE -> " jsr prog8_lib.or_b"
|
||||
Opcode.XOR_BYTE -> " jsr prog8_lib.xor_b"
|
||||
Opcode.AND_WORD -> " jsr prog8_lib.and_w"
|
||||
Opcode.OR_WORD -> " jsr prog8_lib.or_w"
|
||||
Opcode.XOR_WORD -> " jsr prog8_lib.xor_w"
|
||||
|
||||
Opcode.BITAND_BYTE -> " jsr prog8_lib.bitand_b"
|
||||
Opcode.BITOR_BYTE -> " jsr prog8_lib.bitor_b"
|
||||
Opcode.BITXOR_BYTE -> " jsr prog8_lib.bitxor_b"
|
||||
Opcode.BITAND_WORD -> " jsr prog8_lib.bitand_w"
|
||||
Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
|
||||
Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
|
||||
|
||||
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
|
||||
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
|
||||
|
||||
@ -943,23 +939,21 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
|
||||
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))
|
||||
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))
|
||||
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))
|
||||
return " jsr prog8_lib.neg_b | jsr math.mul_byte_${-amount}"
|
||||
}
|
||||
else if(mulIns.opcode == Opcode.MUL_UW) {
|
||||
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))
|
||||
return " jsr math.mul_word_$amount"
|
||||
}
|
||||
else if(mulIns.opcode == Opcode.MUL_W) {
|
||||
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))
|
||||
return " jsr math.mul_word_$amount"
|
||||
if(amount in setOf(-3,-5,-6,-7,-9,-10,-12,-15,-20,-25,-40))
|
||||
@ -1261,9 +1255,6 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
Opcode.ROR2_WORD -> {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -3310,44 +3301,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
null
|
||||
},
|
||||
|
||||
// 16 bit addition avoiding excessive stack usage
|
||||
// @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
|
||||
"""
|
||||
},
|
||||
// @todo optimize 8 and 16 bit adds and subs (avoid stack use altogether on most common operations)
|
||||
|
||||
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)
|
||||
|
@ -64,7 +64,7 @@ fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>
|
||||
|
||||
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...
|
||||
// @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 fourthvalue = sixth.substring(4)
|
||||
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
|
||||
// lda/ldy sta/sty twice the same word --> remove second lda/ldy pair (fifth and sixth lines)
|
||||
// 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[5].index)
|
||||
}
|
||||
@ -96,7 +96,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
|
||||
val firstvalue = first.substring(4)
|
||||
val secondvalue = third.substring(4)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -29,27 +29,56 @@ class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
|
||||
const val SCRATCH_B1 = 0x02
|
||||
const val SCRATCH_REG = 0x03 // temp storage for a register
|
||||
const val SCRATCH_REG_X = 0xfa // temp storage for register X (the evaluation stack pointer)
|
||||
const val SCRATCH_W1 = 0xfb // $fb/$fc
|
||||
const val SCRATCH_W2 = 0xfd // $fd/$fe
|
||||
const val SCRATCH_W1 = 0xfb // $fb+$fc
|
||||
const val SCRATCH_W2 = 0xfd // $fd+$fe
|
||||
}
|
||||
|
||||
override val exitProgramStrategy: ExitProgramStrategy = when(options.zeropage) {
|
||||
ZeropageType.BASICSAFE -> ExitProgramStrategy.CLEAN_EXIT
|
||||
ZeropageType.FLOATSAFE, ZeropageType.KERNALSAFE, ZeropageType.FULL -> ExitProgramStrategy.SYSTEM_RESET
|
||||
}
|
||||
|
||||
|
||||
init {
|
||||
if(options.zeropage== ZeropageType.FULL) {
|
||||
if(options.floats && options.zeropage!=ZeropageType.FLOATSAFE && options.zeropage!=ZeropageType.BASICSAFE)
|
||||
throw CompilerException("when floats are enabled, zero page type should be 'floatsafe' or 'basicsafe'")
|
||||
|
||||
if(options.zeropage == ZeropageType.FULL) {
|
||||
free.addAll(0x04 .. 0xf9)
|
||||
free.add(0xff)
|
||||
free.removeAll(listOf(SCRATCH_B1, SCRATCH_REG, SCRATCH_REG_X, SCRATCH_W1, SCRATCH_W1+1, SCRATCH_W2, SCRATCH_W2+1))
|
||||
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
|
||||
} else {
|
||||
if(options.zeropage== ZeropageType.KERNALSAFE) {
|
||||
// add the Zp addresses that are just used by BASIC routines to the free list
|
||||
free.addAll(listOf(0x09, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
if(options.zeropage == ZeropageType.KERNALSAFE || options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
free.addAll(listOf(0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
|
||||
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
|
||||
0x22, 0x23, 0x24, 0x25,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53, 0x6f, 0x70))
|
||||
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x51, 0x52, 0x53,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c,
|
||||
0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xff
|
||||
// 0x90-0xfa is 'kernel work storage area'
|
||||
))
|
||||
}
|
||||
// add the other free Zp addresses
|
||||
// these are valid for the C-64 (when no RS232 I/O is performed):
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0d, 0x0e,
|
||||
|
||||
if(options.zeropage == ZeropageType.FLOATSAFE) {
|
||||
// remove the zero page locations used for floating point operations from the free list
|
||||
free.removeAll(listOf(
|
||||
0x12, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60,
|
||||
0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
||||
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72,
|
||||
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0xf
|
||||
))
|
||||
}
|
||||
|
||||
// add the other free Zp addresses,
|
||||
// these are valid for the C-64 (when no RS232 I/O is performed) but to keep BASIC running fully:
|
||||
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0d, 0x0e,
|
||||
0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
|
||||
0xb5, 0xb6, 0xf7, 0xf8, 0xf9))
|
||||
}
|
||||
@ -154,4 +183,60 @@ object Charset {
|
||||
|
||||
val normalChars = scanChars(normalImg)
|
||||
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
|
||||
)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class Petscii {
|
||||
// character tables used from https://github.com/dj51d/cbmcodecs
|
||||
|
||||
private val decodingPetsciiLowercase = arrayOf(
|
||||
'\ufffe', // 0x00 -> UNDEFINED
|
||||
'\u0000', // 0x00 -> \u0000
|
||||
'\ufffe', // 0x01 -> UNDEFINED
|
||||
'\ufffe', // 0x02 -> UNDEFINED
|
||||
'\ufffe', // 0x03 -> UNDEFINED
|
||||
@ -268,7 +268,7 @@ class Petscii {
|
||||
)
|
||||
|
||||
private val decodingPetsciiUppercase = arrayOf(
|
||||
'\ufffe', // 0x00 -> UNDEFINED
|
||||
'\u0000', // 0x00 -> \u0000
|
||||
'\ufffe', // 0x01 -> UNDEFINED
|
||||
'\ufffe', // 0x02 -> UNDEFINED
|
||||
'\ufffe', // 0x03 -> UNDEFINED
|
||||
@ -1055,11 +1055,12 @@ class Petscii {
|
||||
val lookup = if(lowercase) encodingPetsciiLowercase else encodingPetsciiUppercase
|
||||
return text.map {
|
||||
val petscii = lookup[it]
|
||||
if(petscii==null) {
|
||||
val case = if(lowercase) "lower" else "upper"
|
||||
petscii?.toShort() ?: if(it=='\u0000')
|
||||
0.toShort()
|
||||
else {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}case Petscii character for '$it'")
|
||||
}
|
||||
petscii.toShort()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1072,11 +1073,12 @@ class Petscii {
|
||||
val lookup = if(lowercase) encodingScreencodeLowercase else encodingScreencodeUppercase
|
||||
return text.map{
|
||||
val screencode = lookup[it]
|
||||
if(screencode==null) {
|
||||
val case = if(lowercase) "lower" else "upper"
|
||||
screencode?.toShort() ?: if(it=='\u0000')
|
||||
0.toShort()
|
||||
else {
|
||||
val case = if (lowercase) "lower" else "upper"
|
||||
throw CharConversionException("no ${case}Screencode character for '$it'")
|
||||
}
|
||||
screencode.toShort()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,7 @@ package prog8.functions
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.HeapValues
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.log2
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.*
|
||||
|
||||
|
||||
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?
|
||||
val parameters: List<BuiltinFunctionParam>,
|
||||
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(
|
||||
@ -26,38 +22,38 @@ val BuiltinFunctions = mapOf(
|
||||
"lsl" 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):
|
||||
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { 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
|
||||
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.sum() }}, // 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, prg -> collectionArgOutputNumber(a, p, prg) { it.min()!! }}, // 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
|
||||
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
|
||||
// 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 ),
|
||||
"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 ),
|
||||
"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 ),
|
||||
"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 ),
|
||||
"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) },
|
||||
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, 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) },
|
||||
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, ::log2) },
|
||||
// TODO: sqrt() should have integer versions too
|
||||
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, 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) },
|
||||
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toDegrees) },
|
||||
"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, prg -> oneDoubleArg(a, p, prg, Math::atan) },
|
||||
"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, prg -> oneDoubleArg(a, p, prg, ::log2) },
|
||||
"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, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
|
||||
"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, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
|
||||
"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) },
|
||||
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, 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) },
|
||||
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), DataType.UWORD, ::builtinLen),
|
||||
"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, 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, 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, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x ushr 8 and 255}},
|
||||
"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, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
|
||||
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
|
||||
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { 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} }},
|
||||
"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 }},
|
||||
"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}},
|
||||
"mkword" to FunctionSignature(true, listOf(
|
||||
BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)),
|
||||
BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
|
||||
@ -70,19 +66,21 @@ val BuiltinFunctions = mapOf(
|
||||
"clear_carry" to FunctionSignature(false, emptyList(), null),
|
||||
"set_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),
|
||||
"memcopy" to FunctionSignature(false, listOf(
|
||||
BuiltinFunctionParam("from", 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(
|
||||
BuiltinFunctionParam("address", IterableDatatypes + 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(
|
||||
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
|
||||
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
|
||||
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_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), 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 datatypeFromListArg(arglist: IExpression): DataType {
|
||||
fun datatypeFromIterableArg(arglist: IExpression): DataType {
|
||||
if(arglist is LiteralValue) {
|
||||
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}) {
|
||||
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.UWORD }) return DataType.UWORD
|
||||
@ -124,19 +122,15 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
|
||||
}
|
||||
}
|
||||
if(arglist is IdentifierReference) {
|
||||
val dt = arglist.resultingDatatype(namespace, heap)
|
||||
val dt = arglist.inferType(program)
|
||||
return when(dt) {
|
||||
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT,
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> dt
|
||||
DataType.ARRAY_UB -> DataType.UBYTE
|
||||
DataType.ARRAY_B -> DataType.BYTE
|
||||
DataType.ARRAY_UW -> DataType.UWORD
|
||||
DataType.ARRAY_W -> DataType.WORD
|
||||
DataType.ARRAY_F -> DataType.FLOAT
|
||||
null -> throw FatalAstException("function requires one argument which is an arrayspec $function")
|
||||
in NumericDatatypes -> dt!!
|
||||
in StringDatatypes -> dt!!
|
||||
in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!)
|
||||
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||
}
|
||||
}
|
||||
throw FatalAstException("function requires one argument which is an arrayspec $function")
|
||||
throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||
}
|
||||
|
||||
val func = BuiltinFunctions.getValue(function)
|
||||
@ -145,30 +139,41 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
|
||||
// function has return values, but the return type depends on the arguments
|
||||
|
||||
return when (function) {
|
||||
"max", "min", "abs" -> {
|
||||
val dt = datatypeFromListArg(args.single())
|
||||
"abs" -> {
|
||||
val dt = args.single().inferType(program)
|
||||
when(dt) {
|
||||
DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT -> dt
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> DataType.UBYTE
|
||||
DataType.ARRAY_UB -> DataType.UBYTE
|
||||
DataType.ARRAY_B -> DataType.BYTE
|
||||
DataType.ARRAY_UW -> DataType.UWORD
|
||||
DataType.ARRAY_W -> DataType.WORD
|
||||
DataType.ARRAY_F -> DataType.FLOAT
|
||||
in ByteDatatypes -> DataType.UBYTE
|
||||
in WordDatatypes -> DataType.UWORD
|
||||
DataType.FLOAT -> DataType.FLOAT
|
||||
else -> throw FatalAstException("weird datatype passed to abs $dt")
|
||||
}
|
||||
}
|
||||
"max", "min" -> {
|
||||
val dt = datatypeFromIterableArg(args.single())
|
||||
when(dt) {
|
||||
in NumericDatatypes -> dt
|
||||
in StringDatatypes -> DataType.UBYTE
|
||||
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"sum" -> {
|
||||
val dt=datatypeFromListArg(args.single())
|
||||
when(dt) {
|
||||
when(datatypeFromIterableArg(args.single())) {
|
||||
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
|
||||
DataType.BYTE, DataType.WORD -> DataType.WORD
|
||||
DataType.FLOAT -> DataType.FLOAT
|
||||
DataType.ARRAY_UB, DataType.ARRAY_UW -> DataType.UWORD
|
||||
DataType.ARRAY_B, DataType.ARRAY_W -> DataType.WORD
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -177,10 +182,10 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
|
||||
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)
|
||||
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)
|
||||
throw SyntaxError("built-in function requires one floating point argument", position)
|
||||
|
||||
@ -188,19 +193,19 @@ private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
throw SyntaxError("built-in function requires one integer argument", position)
|
||||
|
||||
@ -209,14 +214,14 @@ private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, name
|
||||
}
|
||||
|
||||
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position,
|
||||
namespace:INameScope, heap: HeapValues,
|
||||
program: Program,
|
||||
function: (arg: Collection<Double>)->Number): LiteralValue {
|
||||
if(args.size!=1)
|
||||
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 constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue }
|
||||
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
|
||||
if(null in constants)
|
||||
throw NotConstArgumentException()
|
||||
function(constants.map { it!!.toDouble() }).toDouble()
|
||||
@ -224,10 +229,14 @@ private fun collectionArgOutputNumber(args: List<IExpression>, position: Positio
|
||||
when(iterable.type) {
|
||||
DataType.UBYTE, DataType.UWORD, DataType.FLOAT -> throw SyntaxError("function expects an iterable type", position)
|
||||
else -> {
|
||||
if(iterable.heapId==null)
|
||||
throw FatalAstException("iterable value should be on the heap")
|
||||
val array = heap.get(iterable.heapId).array ?: throw SyntaxError("function expects an iterable type", position)
|
||||
function(array.map { it.toDouble() })
|
||||
val heapId = iterable.heapId ?: throw FatalAstException("iterable value should be on the heap")
|
||||
val array = program.heap.get(heapId).array ?: throw SyntaxError("function expects an iterable type", position)
|
||||
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")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -235,164 +244,195 @@ private fun collectionArgOutputNumber(args: List<IExpression>, position: Positio
|
||||
}
|
||||
|
||||
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position,
|
||||
namespace:INameScope, heap: HeapValues,
|
||||
program: Program,
|
||||
function: (arg: Collection<Double>)->Boolean): LiteralValue {
|
||||
if(args.size!=1)
|
||||
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 constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue }
|
||||
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
|
||||
if(null in constants)
|
||||
throw NotConstArgumentException()
|
||||
function(constants.map { it!!.toDouble() })
|
||||
} else {
|
||||
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array argument", position)
|
||||
function(array.map { it.toDouble() })
|
||||
val array = program.heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array argument", position)
|
||||
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)
|
||||
}
|
||||
|
||||
private fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
||||
// 1 arg, type = float or int, result type= same as argument type
|
||||
private fun builtinAbs(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
||||
// 1 arg, type = float or int, result type= isSameAs as argument type
|
||||
if(args.size!=1)
|
||||
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
|
||||
return when (number) {
|
||||
is Int, is Byte, is Short -> numericLiteral(Math.abs(number.toInt()), args[0].position)
|
||||
is Double -> numericLiteral(Math.abs(number.toDouble()), args[0].position)
|
||||
is Int, is Byte, is Short -> numericLiteral(abs(number.toInt()), args[0].position)
|
||||
is Double -> numericLiteral(abs(number.toDouble()), args[0].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)
|
||||
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 constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue }
|
||||
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
|
||||
if (null in constants)
|
||||
throw NotConstArgumentException()
|
||||
(constants.map { it!!.toDouble() }).average()
|
||||
}
|
||||
else {
|
||||
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("avg requires array argument", position)
|
||||
array.average()
|
||||
val heapId = iterable.heapId!!
|
||||
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)
|
||||
}
|
||||
|
||||
private fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
||||
// note: in some cases the length is > 255 and so we have to return a UWORD type instead of a byte.
|
||||
private fun builtinStrlen(args: List<IExpression>, position: Position, program: Program): LiteralValue {
|
||||
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)
|
||||
throw SyntaxError("len requires one argument", position)
|
||||
var argument = args[0].constValue(namespace, heap)
|
||||
var argument = args[0].constValue(program)
|
||||
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)
|
||||
throw SyntaxError("len over weird argument ${args[0]}", position)
|
||||
val target = (args[0] as IdentifierReference).targetStatement(namespace)
|
||||
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
||||
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
|
||||
val argValue = (target as? VarDecl)?.value
|
||||
argument = argValue?.constValue(namespace, heap)
|
||||
argument = argValue?.constValue(program)
|
||||
?: throw NotConstArgumentException()
|
||||
}
|
||||
return when(argument.type) {
|
||||
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)
|
||||
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 -> {
|
||||
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize
|
||||
val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize
|
||||
if(arraySize>256)
|
||||
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 -> {
|
||||
val str = argument.strvalue(heap)
|
||||
in StringDatatypes -> {
|
||||
val str = argument.strvalue!!
|
||||
if(str.length>255)
|
||||
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,
|
||||
DataType.UWORD, DataType.WORD,
|
||||
DataType.FLOAT -> throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
|
||||
else -> throw CompilerException("weird datatype")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
throw SyntaxError("mkword requires lsb and msb arguments", position)
|
||||
val constLsb = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
||||
val constMsb = args[1].constValue(namespace, heap) ?: throw NotConstArgumentException()
|
||||
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
|
||||
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
|
||||
val result = (constMsb.asIntegerValue!! shl 8) or constLsb.asIntegerValue!!
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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
|
||||
return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5* cos(rad)).toInt(), position = position)
|
||||
}
|
||||
@ -400,7 +440,7 @@ private fun builtinCos16u(args: List<IExpression>, position: Position, namespace
|
||||
private fun numericLiteral(value: Number, position: Position): LiteralValue {
|
||||
val floatNum=value.toDouble()
|
||||
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.
|
||||
else
|
||||
floatNum
|
||||
|
192
compiler/src/prog8/optimizing/CallGraph.kt
Normal file
192
compiler/src/prog8/optimizing/CallGraph.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -10,11 +10,11 @@ val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "=
|
||||
|
||||
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) {
|
||||
"+" -> plus(left, right, heap)
|
||||
"+" -> plus(left, right)
|
||||
"-" -> minus(left, right)
|
||||
"*" -> multiply(left, right, heap)
|
||||
"*" -> multiply(left, right)
|
||||
"/" -> divide(left, right)
|
||||
"%" -> remainder(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"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
@ -176,7 +176,7 @@ class ConstExprEvaluator {
|
||||
}
|
||||
left.isString -> when {
|
||||
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)
|
||||
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}"
|
||||
return when {
|
||||
left.asIntegerValue!=null -> when {
|
||||
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.isString -> {
|
||||
if(right.strvalue(heap).length * left.asIntegerValue > 255) throw ExpressionError("string too long", left.position)
|
||||
LiteralValue(DataType.STR, strvalue = right.strvalue(heap).repeat(left.asIntegerValue), position = left.position)
|
||||
if(right.strvalue!!.length * left.asIntegerValue > 255) throw ExpressionError("string too long", left.position)
|
||||
LiteralValue(DataType.STR, strvalue = right.strvalue.repeat(left.asIntegerValue), position = left.position)
|
||||
}
|
||||
else -> throw ExpressionError(error, left.position)
|
||||
}
|
||||
|
@ -3,19 +3,20 @@ package prog8.optimizing
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.CompilerException
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.IntegerOrAddressOf
|
||||
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
|
||||
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 errors : MutableList<AstException> = mutableListOf()
|
||||
|
||||
private val reportedErrorMessages = mutableSetOf<String>()
|
||||
|
||||
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) {
|
||||
reportedErrorMessages.add(x.toString())
|
||||
errors.add(x)
|
||||
@ -24,31 +25,73 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
|
||||
override fun process(decl: VarDecl): IStatement {
|
||||
// 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))
|
||||
return decl
|
||||
}
|
||||
|
||||
val result = super.process(decl)
|
||||
|
||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||
val litval = decl.value as? LiteralValue
|
||||
if(litval!=null && litval.isArray)
|
||||
if(litval!=null && litval.isArray && litval.heapId!=null)
|
||||
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) {
|
||||
DataType.FLOAT -> {
|
||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||
if (litval != null && litval.type in IntegerDatatypes) {
|
||||
val newValue = LiteralValue(DataType.FLOAT, floatvalue = litval.asNumericValue!!.toDouble(), position = litval.position)
|
||||
decl.value = newValue
|
||||
optimizationsDone++
|
||||
return decl
|
||||
}
|
||||
}
|
||||
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)
|
||||
errors.add(ExpressionError("arrayspec requires only integers here", litval.position))
|
||||
val size = decl.arrayspec!!.size()
|
||||
if ((litval==null || !litval.isArray) && size != null) {
|
||||
// arrayspec initializer is empty or a single int, and we know the size; create the arrayspec.
|
||||
errors.add(ExpressionError("arraysize requires only integers here", litval.position))
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
if ((litval==null || !litval.isArray) && rangeExpr==null) {
|
||||
// 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
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
@ -69,54 +112,60 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
val fillArray = IntArray(size) { fillvalue }
|
||||
val heapId = heap.add(decl.datatype, fillArray)
|
||||
decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval?.position ?: decl.position)
|
||||
val heapId = program.heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) })
|
||||
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval?.position ?: decl.position)
|
||||
optimizationsDone++
|
||||
return decl
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
val size = decl.arrayspec!!.size()
|
||||
if ((litval==null || !litval.isArray) && size != null) {
|
||||
// arrayspec initializer is empty or a single int, and we know the size; create the arrayspec.
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
if (litval==null || !litval.isArray) {
|
||||
// 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
|
||||
if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE)
|
||||
errors.add(ExpressionError("float value overflow", litval?.position ?: decl.position))
|
||||
else {
|
||||
val fillArray = DoubleArray(size) { fillvalue }
|
||||
val heapId = heap.add(decl.datatype, fillArray)
|
||||
decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval?.position ?: decl.position)
|
||||
val heapId = program.heap.addDoublesArray(DoubleArray(size) { fillvalue })
|
||||
decl.value = LiteralValue(DataType.ARRAY_F, initHeapId = 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) {
|
||||
// fix the type of the array value that's on the heap, to match the vardecl.
|
||||
// notice that checking the bounds of the actual values is not done here, but in the AstChecker later.
|
||||
|
||||
if(decl.datatype==litval.type)
|
||||
return // already correct datatype
|
||||
val heapId = litval.heapId!!
|
||||
val array=heap.get(heapId)
|
||||
val heapId = litval.heapId ?: throw FatalAstException("expected array to be on heap $litval")
|
||||
val array = program.heap.get(heapId)
|
||||
when(decl.datatype) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
if(array.array!=null) {
|
||||
heap.update(heapId, HeapValues.HeapValue(decl.datatype, null, array.array, null))
|
||||
decl.value = LiteralValue(decl.datatype, heapId=heapId, position = litval.position)
|
||||
program.heap.update(heapId, HeapValues.HeapValue(decl.datatype, null, array.array, null))
|
||||
decl.value = LiteralValue(decl.datatype, initHeapId=heapId, position = litval.position)
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
if(array.array!=null) {
|
||||
// convert a non-float array to floats
|
||||
val doubleArray = array.array.map { it.toDouble() }.toDoubleArray()
|
||||
heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray))
|
||||
decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval.position)
|
||||
val doubleArray = array.array.map { it.integer!!.toDouble() }.toDoubleArray()
|
||||
program.heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray))
|
||||
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}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +174,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
*/
|
||||
override fun process(identifier: IdentifierReference): IExpression {
|
||||
return try {
|
||||
val cval = identifier.constValue(namespace, heap) ?: return identifier
|
||||
val cval = identifier.constValue(program) ?: return identifier
|
||||
return if(cval.isNumeric) {
|
||||
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, null, cval.arrayvalue, position = identifier.position)
|
||||
copy.parent = identifier.parent
|
||||
@ -141,13 +190,54 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
return try {
|
||||
super.process(functionCall)
|
||||
functionCall.constValue(namespace, heap) ?: functionCall
|
||||
typeCastConstArguments(functionCall)
|
||||
functionCall.constValue(program) ?: functionCall
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
functionCall
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
|
||||
super.process(functionCallStatement)
|
||||
typeCastConstArguments(functionCallStatement)
|
||||
return functionCallStatement
|
||||
}
|
||||
|
||||
private fun typeCastConstArguments(functionCall: IFunctionCall) {
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
// 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)) {
|
||||
val expectedDt = arg.second.type
|
||||
val argConst = arg.first.value.constValue(program)
|
||||
if(argConst!=null && argConst.type!=expectedDt) {
|
||||
val convertedValue = argConst.intoDatatype(expectedDt)
|
||||
if(convertedValue!=null) {
|
||||
functionCall.arglist[arg.first.index] = convertedValue
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
* Compile-time constant sub expressions will be evaluated on the spot.
|
||||
@ -221,8 +311,8 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
override fun process(expr: BinaryExpression): IExpression {
|
||||
return try {
|
||||
super.process(expr)
|
||||
val leftconst = expr.left.constValue(namespace, heap)
|
||||
val rightconst = expr.right.constValue(namespace, heap)
|
||||
val leftconst = expr.left.constValue(program)
|
||||
val rightconst = expr.right.constValue(program)
|
||||
|
||||
val subExpr: BinaryExpression? = when {
|
||||
leftconst!=null -> expr.right as? BinaryExpression
|
||||
@ -230,8 +320,8 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
else -> null
|
||||
}
|
||||
if(subExpr!=null) {
|
||||
val subleftconst = subExpr.left.constValue(namespace, heap)
|
||||
val subrightconst = subExpr.right.constValue(namespace, heap)
|
||||
val subleftconst = subExpr.left.constValue(program)
|
||||
val subrightconst = subExpr.right.constValue(program)
|
||||
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
|
||||
// try reordering.
|
||||
return groupTwoConstsTogether(expr, subExpr,
|
||||
@ -245,7 +335,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
return when {
|
||||
leftconst != null && rightconst != null -> {
|
||||
optimizationsDone++
|
||||
evaluator.evaluate(leftconst, expr.operator, rightconst, heap)
|
||||
evaluator.evaluate(leftconst, expr.operator, rightconst)
|
||||
}
|
||||
else -> expr
|
||||
}
|
||||
@ -264,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
|
||||
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(expr.operator=="+" || expr.operator=="*") {
|
||||
if(leftIsConst) {
|
||||
@ -467,7 +557,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
val rangeTo = iterableRange.to as? LiteralValue
|
||||
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) {
|
||||
val stepLiteral = iterableRange.step as? LiteralValue
|
||||
when(loopvar.datatype) {
|
||||
@ -502,79 +592,69 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
}
|
||||
|
||||
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
|
||||
if(literalValue.strvalue(heap).length !in 1..255)
|
||||
addError(ExpressionError("string literal length must be between 1 and 255", literalValue.position))
|
||||
if(litval.strvalue!!.length !in 1..255)
|
||||
addError(ExpressionError("string literal length must be between 1 and 255", litval.position))
|
||||
else {
|
||||
val heapId = heap.add(literalValue.type, literalValue.strvalue(heap)) // TODO: we don't know the actual string type yet, STR != STR_P etc...
|
||||
val newValue = LiteralValue(literalValue.type, heapId = heapId, position = literalValue.position)
|
||||
return super.process(newValue)
|
||||
litval.addToHeap(program.heap) // TODO: we don't know the actual string type yet, STR != STR_S etc...
|
||||
}
|
||||
} else if(literalValue.arrayvalue!=null) {
|
||||
return moveArrayToHeap(literalValue)
|
||||
} else if(litval.arrayvalue!=null) {
|
||||
// first, adjust the array datatype
|
||||
val litval2 = adjustArrayValDatatype(litval)
|
||||
litval2.addToHeap(program.heap)
|
||||
return litval2
|
||||
}
|
||||
|
||||
return super.process(literalValue)
|
||||
return litval
|
||||
}
|
||||
|
||||
private fun moveArrayToHeap(arraylit: LiteralValue): LiteralValue {
|
||||
val array: Array<IExpression> = arraylit.arrayvalue!!.map { it.process(this) }.toTypedArray()
|
||||
val allElementsAreConstant = array.fold(true) { c, expr-> c and (expr is LiteralValue)}
|
||||
if(!allElementsAreConstant) {
|
||||
addError(ExpressionError("array literal can contain only constant values", arraylit.position))
|
||||
return arraylit
|
||||
} else {
|
||||
val valuesInArray = array.map { it.constValue(namespace, heap)!!.asNumericValue!! }
|
||||
val integerArray = valuesInArray.map{it.toInt()}.toIntArray()
|
||||
val doubleArray = valuesInArray.map{it.toDouble()}.toDoubleArray()
|
||||
val typesInArray: Set<DataType> = array.mapNotNull { it.resultingDatatype(namespace, heap) }.toSet()
|
||||
|
||||
// Take an educated guess about the array type.
|
||||
// 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
|
||||
private fun adjustArrayValDatatype(litval: LiteralValue): LiteralValue {
|
||||
val array = litval.arrayvalue!!
|
||||
val typesInArray = array.mapNotNull { it.inferType(program) }.toSet()
|
||||
val arrayDt =
|
||||
when {
|
||||
array.any { it is AddressOf} -> DataType.ARRAY_UW
|
||||
DataType.FLOAT in typesInArray -> DataType.ARRAY_F
|
||||
DataType.WORD in typesInArray -> DataType.ARRAY_W
|
||||
else -> {
|
||||
val allElementsAreConstantOrAddressOf = array.fold(true) { c, expr-> c and (expr is LiteralValue || expr is AddressOf)}
|
||||
if(!allElementsAreConstantOrAddressOf) {
|
||||
addError(ExpressionError("array literal can only consist of constant primitive numerical values or memory pointers", litval.position))
|
||||
return litval
|
||||
} else {
|
||||
// signed
|
||||
if (maxValue <= 127)
|
||||
DataType.ARRAY_B
|
||||
else
|
||||
DataType.ARRAY_W
|
||||
val integerArray = array.map { it.constValue(program)!!.asIntegerValue!! }
|
||||
val maxValue = integerArray.max()!!
|
||||
val minValue = integerArray.min()!!
|
||||
if (minValue >= 0) {
|
||||
// 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) {
|
||||
DataType.ARRAY_UB,
|
||||
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)
|
||||
if(arrayDt!=litval.type) {
|
||||
return LiteralValue(arrayDt, arrayvalue = litval.arrayvalue, position = litval.position)
|
||||
}
|
||||
return litval
|
||||
}
|
||||
|
||||
override fun process(assignment: Assignment): IStatement {
|
||||
super.process(assignment)
|
||||
val lv = assignment.value as? LiteralValue
|
||||
if(lv!=null) {
|
||||
val targetDt = assignment.singleTarget?.determineDatatype(namespace, heap, assignment)
|
||||
// see if we can promote/convert a literal value to the required datatype
|
||||
when(targetDt) {
|
||||
when(assignment.singleTarget?.inferType(program, assignment)) {
|
||||
DataType.UWORD -> {
|
||||
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
|
||||
if(lv.type==DataType.UBYTE)
|
||||
@ -635,5 +715,3 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
return assignment
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,48 +1,49 @@
|
||||
package prog8.optimizing
|
||||
|
||||
import prog8.ast.AstException
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.ast.*
|
||||
import prog8.parser.ParsingFailedError
|
||||
|
||||
|
||||
fun Module.constantFold(globalNamespace: INameScope, heap: HeapValues) {
|
||||
val optimizer = ConstantFolding(globalNamespace, heap)
|
||||
internal fun Program.constantFold() {
|
||||
val optimizer = ConstantFolding(this)
|
||||
try {
|
||||
this.process(optimizer)
|
||||
optimizer.process(this)
|
||||
} catch (ax: AstException) {
|
||||
optimizer.addError(ax)
|
||||
}
|
||||
|
||||
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
|
||||
optimizer.optimizationsDone = 0
|
||||
this.process(optimizer)
|
||||
optimizer.process(this)
|
||||
}
|
||||
|
||||
if(optimizer.errors.isNotEmpty()) {
|
||||
optimizer.errors.forEach { System.err.println(it) }
|
||||
throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
|
||||
} 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 {
|
||||
val optimizer = StatementOptimizer(globalNamespace, heap)
|
||||
this.process(optimizer)
|
||||
for(stmt in optimizer.statementsToRemove) {
|
||||
val scope=stmt.definingScope()
|
||||
scope.remove(stmt)
|
||||
internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int {
|
||||
val optimizer = StatementOptimizer(this, optimizeInlining)
|
||||
optimizer.process(this)
|
||||
for(scope in optimizer.scopesToFlatten.reversed()) {
|
||||
val namescope = scope.parent as INameScope
|
||||
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
|
||||
}
|
||||
|
||||
fun Module.simplifyExpressions(namespace: INameScope, heap: HeapValues) : Int {
|
||||
val optimizer = SimplifyExpressions(namespace, heap)
|
||||
this.process(optimizer)
|
||||
internal fun Program.simplifyExpressions() : Int {
|
||||
val optimizer = SimplifyExpressions(this)
|
||||
optimizer.process(this)
|
||||
return optimizer.optimizationsDone
|
||||
}
|
||||
|
@ -1,15 +1,17 @@
|
||||
package prog8.optimizing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.HeapValues
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
|
||||
/*
|
||||
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
|
||||
|
||||
override fun process(assignment: Assignment): IStatement {
|
||||
@ -18,6 +20,43 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
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 {
|
||||
if (expr.operator == "+") {
|
||||
// +X --> X
|
||||
@ -67,15 +106,15 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
|
||||
override fun process(expr: BinaryExpression): IExpression {
|
||||
super.process(expr)
|
||||
val leftVal = expr.left.constValue(namespace, heap)
|
||||
val rightVal = expr.right.constValue(namespace, heap)
|
||||
val leftVal = expr.left.constValue(program)
|
||||
val rightVal = expr.right.constValue(program)
|
||||
val constTrue = LiteralValue.fromBoolean(true, expr.position)
|
||||
val constFalse = LiteralValue.fromBoolean(false, expr.position)
|
||||
|
||||
val leftDt = expr.left.resultingDatatype(namespace, heap)
|
||||
val rightDt = expr.right.resultingDatatype(namespace, heap)
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
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)) {
|
||||
optimizationsDone++
|
||||
return expr
|
||||
@ -280,8 +319,8 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
|
||||
private fun determineY(x: IExpression, subBinExpr: BinaryExpression): IExpression? {
|
||||
return when {
|
||||
same(subBinExpr.left, x) -> subBinExpr.right
|
||||
same(subBinExpr.right, x) -> subBinExpr.left
|
||||
subBinExpr.left isSameAs x -> subBinExpr.right
|
||||
subBinExpr.right isSameAs x -> subBinExpr.left
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -347,27 +386,30 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
|
||||
if(leftConstVal==null && rightConstVal!=null) {
|
||||
val (adjusted, newValue) = adjust(rightConstVal, leftDt)
|
||||
if(adjusted) {
|
||||
expr.right = newValue
|
||||
optimizationsDone++
|
||||
return true
|
||||
if(leftDt biggerThan rightDt) {
|
||||
val (adjusted, newValue) = adjust(rightConstVal, leftDt)
|
||||
if (adjusted) {
|
||||
expr.right = newValue
|
||||
optimizationsDone++
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
} else if(leftConstVal!=null && rightConstVal==null) {
|
||||
val (adjusted, newValue) = adjust(leftConstVal, rightDt)
|
||||
if(adjusted) {
|
||||
expr.left = newValue
|
||||
optimizationsDone++
|
||||
return true
|
||||
if(rightDt biggerThan leftDt) {
|
||||
val (adjusted, newValue) = adjust(leftConstVal, rightDt)
|
||||
if (adjusted) {
|
||||
expr.left = newValue
|
||||
optimizationsDone++
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
} 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 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.right = tmp
|
||||
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 {
|
||||
@ -523,7 +565,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
"%" -> {
|
||||
if (cv == 1.0) {
|
||||
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) {
|
||||
optimizationsDone++
|
||||
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
|
||||
val rightConst: LiteralValue = rightVal
|
||||
val cv = rightConst.asNumericValue?.toDouble()
|
||||
val leftDt = expr.left.resultingDatatype(namespace, heap)
|
||||
val leftDt = expr.left.inferType(program)
|
||||
when(cv) {
|
||||
-1.0 -> {
|
||||
// '/' -> -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
|
||||
val leftValue: IExpression = expr.left
|
||||
val rightConst: LiteralValue = rightVal
|
||||
val cv = rightConst.asNumericValue?.toDouble()
|
||||
when(cv) {
|
||||
when(val cv = rightConst.asNumericValue?.toDouble()) {
|
||||
-1.0 -> {
|
||||
// -left
|
||||
optimizationsDone++
|
||||
@ -635,7 +676,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
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 -> {
|
||||
if(leftValue.resultingDatatype(namespace, heap) in IntegerDatatypes) {
|
||||
if(leftValue.inferType(program) in IntegerDatatypes) {
|
||||
// times a power of two => shift left
|
||||
optimizationsDone++
|
||||
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 -> {
|
||||
if(leftValue.resultingDatatype(namespace, heap) in IntegerDatatypes) {
|
||||
if(leftValue.inferType(program) in IntegerDatatypes) {
|
||||
// times a negative power of two => negate, then shift left
|
||||
optimizationsDone++
|
||||
val numshifts = log2(-cv).toInt()
|
||||
|
@ -1,49 +1,158 @@
|
||||
package prog8.optimizing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
/*
|
||||
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)
|
||||
|
||||
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
|
||||
*/
|
||||
|
||||
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
|
||||
private set
|
||||
var statementsToRemove = mutableListOf<IStatement>()
|
||||
private set
|
||||
var scopesToFlatten = mutableListOf<INameScope>()
|
||||
|
||||
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 {
|
||||
if(block.statements.isEmpty()) {
|
||||
// remove empty block
|
||||
optimizationsDone++
|
||||
statementsToRemove.add(block)
|
||||
if("force_output" !in block.options()) {
|
||||
if (block.containsNoCodeNorVars()) {
|
||||
optimizationsDone++
|
||||
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)
|
||||
}
|
||||
|
||||
override fun process(subroutine: Subroutine): IStatement {
|
||||
super.process(subroutine)
|
||||
|
||||
if(subroutine.asmAddress==null) {
|
||||
if(subroutine.statements.isEmpty()) {
|
||||
// remove empty subroutine
|
||||
val forceOutput = "force_output" in subroutine.definingBlock().options()
|
||||
if(subroutine.asmAddress==null && !forceOutput) {
|
||||
if(subroutine.containsNoCodeNorVars()) {
|
||||
printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
statementsToRemove.add(subroutine)
|
||||
return NopStatement(subroutine.position)
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,24 +161,54 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
|
||||
}
|
||||
|
||||
if(subroutine.canBeAsmSubroutine) {
|
||||
optimizationsDone++
|
||||
return subroutine.intoAsmSubroutine() // TODO this doesn't work yet due to parameter vardecl issue
|
||||
|
||||
// TODO fix parameter passing so this also works:
|
||||
// asmsub aa(byte arg @ Y) -> clobbers() -> () {
|
||||
// byte local = arg ; @todo fix 'undefined symbol arg' by some sort of alias name for the parameter
|
||||
// A=44
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
return NopStatement(subroutine.position) // remove unused 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> {
|
||||
// removes 'duplicate' assignments that assign the same target
|
||||
// removes 'duplicate' assignments that assign the isSameAs target
|
||||
val linesToRemove = mutableListOf<Int>()
|
||||
var previousAssignmentLine: Int? = null
|
||||
for (i in 0 until statements.size) {
|
||||
val stmt = statements[i] as? Assignment
|
||||
if (stmt != null) {
|
||||
if (stmt != null && stmt.value is LiteralValue) {
|
||||
if (previousAssignmentLine == null) {
|
||||
previousAssignmentLine = i
|
||||
continue
|
||||
} else {
|
||||
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
|
||||
if (isNotMemory(prev.targets[0]))
|
||||
if (prev.targets[0].isNotMemory(program.namespace))
|
||||
linesToRemove.add(previousAssignmentLine)
|
||||
}
|
||||
previousAssignmentLine = i
|
||||
@ -80,68 +219,39 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
return linesToRemove
|
||||
}
|
||||
|
||||
private fun returnregisters(subroutine: Subroutine): List<RegisterOrStatusflag> {
|
||||
return when {
|
||||
subroutine.returntypes.isEmpty() -> listOf()
|
||||
subroutine.returntypes.size==1 && subroutine.returntypes[0] in setOf(DataType.BYTE, DataType.UBYTE) -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null))
|
||||
subroutine.returntypes.size==1 && subroutine.returntypes[0] in setOf(DataType.WORD, DataType.UWORD) -> listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null))
|
||||
subroutine.returntypes.size==2 && subroutine.returntypes.all { it in setOf(DataType.BYTE, DataType.UBYTE)} -> listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null))
|
||||
else -> throw FatalAstException("can't convert return values to registers")
|
||||
}
|
||||
}
|
||||
|
||||
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(functionCall: FunctionCallStatement): IStatement {
|
||||
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0] in BuiltinFunctions) {
|
||||
val functionName = functionCall.target.nameInSource[0]
|
||||
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
|
||||
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
|
||||
val functionName = functionCallStatement.target.nameInSource[0]
|
||||
if (functionName in pureBuiltinFunctions) {
|
||||
printWarning("statement has no effect (function return value is discarded)", functionCall.position)
|
||||
statementsToRemove.add(functionCall)
|
||||
return functionCall
|
||||
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
|
||||
optimizationsDone++
|
||||
return NopStatement(functionCallStatement.position)
|
||||
}
|
||||
}
|
||||
|
||||
if(functionCall.target.nameInSource==listOf("c64scr", "print") ||
|
||||
functionCall.target.nameInSource==listOf("c64scr", "print_p")) {
|
||||
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
|
||||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
|
||||
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
|
||||
if(functionCall.arglist.single() is LiteralValue)
|
||||
if(functionCallStatement.arglist.single() is LiteralValue)
|
||||
throw AstException("string argument should be on heap already")
|
||||
val stringVar = functionCall.arglist.single() as? IdentifierReference
|
||||
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference
|
||||
if(stringVar!=null) {
|
||||
val heapId = stringVar.heapId(namespace)
|
||||
val string = heap.get(heapId).str!!
|
||||
val heapId = stringVar.heapId(program.namespace)
|
||||
val string = program.heap.get(heapId).str!!
|
||||
if(string.length==1) {
|
||||
val petscii = Petscii.encodePetscii(string, true)[0]
|
||||
functionCall.arglist.clear()
|
||||
functionCall.arglist.add(LiteralValue.optimalInteger(petscii, functionCall.position))
|
||||
functionCall.target = IdentifierReference(listOf("c64", "CHROUT"), functionCall.target.position)
|
||||
functionCallStatement.arglist.clear()
|
||||
functionCallStatement.arglist.add(LiteralValue.optimalInteger(petscii, functionCallStatement.position))
|
||||
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
|
||||
optimizationsDone++
|
||||
return functionCall
|
||||
return functionCallStatement
|
||||
} else if(string.length==2) {
|
||||
val petscii = Petscii.encodePetscii(string, true)
|
||||
val scope = AnonymousScope(mutableListOf(), functionCall.position)
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCall.target.position),
|
||||
mutableListOf(LiteralValue.optimalInteger(petscii[0], functionCall.position)), functionCall.position))
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCall.target.position),
|
||||
mutableListOf(LiteralValue.optimalInteger(petscii[1], functionCall.position)), functionCall.position))
|
||||
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||
mutableListOf(LiteralValue.optimalInteger(petscii[0], functionCallStatement.position)), functionCallStatement.position))
|
||||
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
|
||||
mutableListOf(LiteralValue.optimalInteger(petscii[1], functionCallStatement.position)), functionCallStatement.position))
|
||||
optimizationsDone++
|
||||
return scope
|
||||
}
|
||||
@ -151,27 +261,27 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
// if it calls a subroutine,
|
||||
// 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
|
||||
val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine
|
||||
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump && first.identifier!=null) {
|
||||
optimizationsDone++
|
||||
return FunctionCallStatement(first.identifier, functionCall.arglist, functionCall.position)
|
||||
return FunctionCallStatement(first.identifier, functionCallStatement.arglist, functionCallStatement.position)
|
||||
}
|
||||
if(first is ReturnFromIrq || first is Return) {
|
||||
optimizationsDone++
|
||||
return NopStatement(functionCall.position)
|
||||
return NopStatement(functionCallStatement.position)
|
||||
}
|
||||
}
|
||||
|
||||
return super.process(functionCall)
|
||||
return super.process(functionCallStatement)
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
// if it calls a subroutine,
|
||||
// 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
|
||||
val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump && first.identifier!=null) {
|
||||
@ -179,7 +289,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
return FunctionCall(first.identifier, functionCall.arglist, functionCall.position)
|
||||
}
|
||||
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)
|
||||
return constval
|
||||
}
|
||||
@ -190,13 +300,12 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
override fun process(ifStatement: IfStatement): IStatement {
|
||||
super.process(ifStatement)
|
||||
|
||||
if(ifStatement.truepart.isEmpty() && ifStatement.elsepart.isEmpty()) {
|
||||
statementsToRemove.add(ifStatement)
|
||||
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
|
||||
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
|
||||
ifStatement.truepart = ifStatement.elsepart
|
||||
ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
|
||||
@ -205,7 +314,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
return ifStatement
|
||||
}
|
||||
|
||||
val constvalue = ifStatement.condition.constValue(namespace, heap)
|
||||
val constvalue = ifStatement.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only if-part
|
||||
@ -224,25 +333,23 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
|
||||
override fun process(forLoop: ForLoop): IStatement {
|
||||
super.process(forLoop)
|
||||
if(forLoop.body.isEmpty()) {
|
||||
if(forLoop.body.containsNoCodeNorVars()) {
|
||||
// remove empty for loop
|
||||
statementsToRemove.add(forLoop)
|
||||
optimizationsDone++
|
||||
return forLoop
|
||||
return NopStatement(forLoop.position)
|
||||
} else if(forLoop.body.statements.size==1) {
|
||||
val loopvar = forLoop.body.statements[0] as? VarDecl
|
||||
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
|
||||
// remove empty for loop
|
||||
statementsToRemove.add(forLoop)
|
||||
optimizationsDone++
|
||||
return forLoop
|
||||
return NopStatement(forLoop.position)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val range = forLoop.iterable as? RangeExpr
|
||||
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
|
||||
// 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)
|
||||
@ -256,7 +363,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
|
||||
override fun process(whileLoop: WhileLoop): IStatement {
|
||||
super.process(whileLoop)
|
||||
val constvalue = whileLoop.condition.constValue(namespace, heap)
|
||||
val constvalue = whileLoop.condition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> print a warning, and optimize into body + jump (if there are no continue and break statements)
|
||||
@ -282,7 +389,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
|
||||
override fun process(repeatLoop: RepeatLoop): IStatement {
|
||||
super.process(repeatLoop)
|
||||
val constvalue = repeatLoop.untilCondition.constValue(namespace, heap)
|
||||
val constvalue = repeatLoop.untilCondition.constValue(program)
|
||||
if(constvalue!=null) {
|
||||
return if(constvalue.asBooleanValue){
|
||||
// always true -> keep only the statement block (if there are no continue and break statements)
|
||||
@ -336,7 +443,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
}
|
||||
|
||||
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 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()
|
||||
@ -345,6 +452,17 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
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
|
||||
}
|
||||
|
||||
@ -354,17 +472,17 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
|
||||
if(assignment.targets.size==1) {
|
||||
val target=assignment.targets[0]
|
||||
if(same(target, assignment.value)) {
|
||||
if(target isSameAs assignment.value) {
|
||||
optimizationsDone++
|
||||
return NopStatement(assignment.position)
|
||||
}
|
||||
val targetDt = target.determineDatatype(namespace, heap, assignment)!!
|
||||
val targetDt = target.inferType(program, assignment)
|
||||
val bexpr=assignment.value as? BinaryExpression
|
||||
if(bexpr!=null) {
|
||||
val cv = bexpr.right.constValue(namespace, heap)?.asNumericValue?.toDouble()
|
||||
val cv = bexpr.right.constValue(program)?.asNumericValue?.toDouble()
|
||||
if(cv==null) {
|
||||
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.right = LiteralValue.optimalInteger(2, assignment.value.position)
|
||||
optimizationsDone++
|
||||
@ -372,10 +490,10 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
}
|
||||
}
|
||||
} 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
|
||||
// A = A <operator> B
|
||||
val vardeclDt = (target.identifier?.targetStatement(namespace) as? VarDecl)?.type
|
||||
val vardeclDt = (target.identifier?.targetVarDecl(program.namespace))?.type
|
||||
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
@ -481,71 +599,29 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
return super.process(assignment)
|
||||
}
|
||||
|
||||
override fun process(scope: AnonymousScope): AnonymousScope {
|
||||
override fun process(scope: AnonymousScope): IStatement {
|
||||
val linesToRemove = deduplicateAssignments(scope.statements)
|
||||
if(linesToRemove.isNotEmpty()) {
|
||||
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)
|
||||
}
|
||||
|
||||
private fun same(target: AssignTarget, value: IExpression): Boolean {
|
||||
return when {
|
||||
target.memoryAddress!=null -> false
|
||||
target.register!=null -> value is RegisterExpr && value.register==target.register
|
||||
target.identifier!=null -> value is IdentifierReference && value.nameInSource==target.identifier.nameInSource
|
||||
target.arrayindexed!=null -> value is ArrayIndexedExpression &&
|
||||
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
|
||||
}
|
||||
}
|
||||
override fun process(label: Label): IStatement {
|
||||
// remove duplicate labels
|
||||
val stmts = label.definingScope().statements
|
||||
val startIdx = stmts.indexOf(label)
|
||||
if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label)
|
||||
return NopStatement(label.position)
|
||||
|
||||
private fun same(target1: AssignTarget, target2: AssignTarget): Boolean {
|
||||
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
|
||||
return super.process(label)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ import org.antlr.v4.runtime.CommonTokenStream
|
||||
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)
|
||||
|
||||
|
@ -2,20 +2,13 @@ package prog8.parser
|
||||
|
||||
import org.antlr.v4.runtime.*
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.LauncherType
|
||||
import prog8.compiler.OutputType
|
||||
import prog8.determineCompilationOptions
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
|
||||
|
||||
class ParsingFailedError(override var message: String) : Exception(message)
|
||||
|
||||
|
||||
private val importedModules : HashMap<String, Module> = hashMapOf()
|
||||
internal class ParsingFailedError(override var message: String) : Exception(message)
|
||||
|
||||
|
||||
private class LexerErrorListener: BaseErrorListener() {
|
||||
@ -26,63 +19,14 @@ private class LexerErrorListener: BaseErrorListener() {
|
||||
}
|
||||
|
||||
|
||||
fun importModule(stream: CharStream, moduleName: String, isLibrary: Boolean): Module {
|
||||
val lexer = prog8Lexer(stream)
|
||||
val lexerErrors = LexerErrorListener()
|
||||
lexer.addErrorListener(lexerErrors)
|
||||
val tokens = CommentHandlingTokenStream(lexer)
|
||||
val parser = prog8Parser(tokens)
|
||||
val parseTree = parser.module()
|
||||
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
|
||||
if(numberOfErrors > 0)
|
||||
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName.p8'.")
|
||||
|
||||
// You can do something with the parsed comments:
|
||||
// tokens.commentTokens().forEach { println(it) }
|
||||
|
||||
// convert to Ast
|
||||
val moduleAst = parseTree.toAst(moduleName, isLibrary, Paths.get(stream.sourceName))
|
||||
importedModules[moduleAst.name] = moduleAst
|
||||
|
||||
// process imports
|
||||
val lines = moduleAst.statements.toMutableList()
|
||||
if(!moduleAst.position.file.startsWith("c64utils.") && !moduleAst.isLibraryModule) {
|
||||
// if the output is a PRG or BASIC program, include the c64utils library
|
||||
val compilerOptions = determineCompilationOptions(moduleAst)
|
||||
if(compilerOptions.launcher==LauncherType.BASIC || compilerOptions.output==OutputType.PRG) {
|
||||
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, Paths.get("$moduleName.p8"))) }
|
||||
.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
|
||||
return moduleAst
|
||||
}
|
||||
internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
|
||||
|
||||
|
||||
fun importModule(filePath: Path) : Module {
|
||||
print("importing '${filePath.fileName}'")
|
||||
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
|
||||
|
||||
|
||||
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()
|
||||
@ -95,15 +39,52 @@ fun importModule(filePath: Path) : Module {
|
||||
if(!Files.isReadable(filePath))
|
||||
throw ParsingFailedError("No such file: $filePath")
|
||||
|
||||
val moduleName = filePath.fileName.toString().substringBeforeLast('.')
|
||||
val input = CharStreams.fromPath(filePath)
|
||||
return importModule(input, moduleName, filePath.parent==null)
|
||||
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(""))
|
||||
}
|
||||
|
||||
private fun discoverImportedModuleFile(name: String, importedFrom: Path, position: Position?): Path {
|
||||
internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
|
||||
val moduleName = moduleName(modulePath.fileName)
|
||||
val lexer = CustomLexer(modulePath, stream)
|
||||
val lexerErrors = LexerErrorListener()
|
||||
lexer.addErrorListener(lexerErrors)
|
||||
val tokens = CommentHandlingTokenStream(lexer)
|
||||
val parser = prog8Parser(tokens)
|
||||
val parseTree = parser.module()
|
||||
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
|
||||
if(numberOfErrors > 0)
|
||||
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
|
||||
|
||||
// You can do something with the parsed comments:
|
||||
// tokens.commentTokens().forEach { println(it) }
|
||||
|
||||
// convert to Ast
|
||||
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
|
||||
moduleAst.program = program
|
||||
moduleAst.linkParents(program.namespace)
|
||||
program.modules.add(moduleAst)
|
||||
|
||||
// process additional imports
|
||||
val lines = moduleAst.statements.toMutableList()
|
||||
lines.asSequence()
|
||||
.mapIndexed { i, it -> Pair(i, it) }
|
||||
.filter { (it.second as? Directive)?.directive == "%import" }
|
||||
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
|
||||
|
||||
moduleAst.statements = lines
|
||||
return moduleAst
|
||||
}
|
||||
|
||||
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
|
||||
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")
|
||||
if(propPath!=null)
|
||||
@ -121,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)")
|
||||
}
|
||||
|
||||
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)
|
||||
throw SyntaxError("invalid import directive", import.position)
|
||||
val moduleName = import.args[0].name!!
|
||||
if("$moduleName.p8" == import.position.file)
|
||||
throw SyntaxError("cannot import self", import.position)
|
||||
if(importedModules.containsKey(moduleName))
|
||||
|
||||
val existing = program.modules.singleOrNull { it.name == moduleName }
|
||||
if(existing!=null)
|
||||
return null
|
||||
|
||||
val resource = tryGetEmbeddedResource(moduleName+".p8")
|
||||
@ -135,18 +118,21 @@ private fun executeImportDirective(import: Directive, importedFrom: Path): Modul
|
||||
if(resource!=null) {
|
||||
// load the module from the embedded resource
|
||||
resource.use {
|
||||
println("importing '$moduleName' (embedded library)")
|
||||
importModule(CharStreams.fromStream(it), moduleName, true)
|
||||
if(import.args[0].int==42)
|
||||
println("importing '$moduleName' (library, auto)")
|
||||
else
|
||||
println("importing '$moduleName' (library)")
|
||||
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
|
||||
}
|
||||
} else {
|
||||
val modulePath = discoverImportedModuleFile(moduleName, importedFrom, import.position)
|
||||
importModule(modulePath)
|
||||
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
|
||||
importModule(program, modulePath)
|
||||
}
|
||||
|
||||
importedModule.checkImportedValid()
|
||||
return importedModule
|
||||
}
|
||||
|
||||
fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
internal fun tryGetEmbeddedResource(name: String): InputStream? {
|
||||
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package prog8.stackvm
|
||||
|
||||
import prog8.ast.DataType
|
||||
import prog8.ast.Position
|
||||
import prog8.ast.unescape
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.RuntimeValue
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.IntegerOrAddressOf
|
||||
import prog8.compiler.intermediate.*
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
@ -11,10 +11,10 @@ import java.util.regex.Pattern
|
||||
|
||||
class Program (val name: String,
|
||||
val program: MutableList<Instruction>,
|
||||
val variables: Map<String, Value>,
|
||||
val variables: Map<String, RuntimeValue>,
|
||||
val memoryPointers: Map<String, Pair<Int, DataType>>,
|
||||
val labels: Map<String, Instruction>,
|
||||
val memory: Map<Int, List<Value>>,
|
||||
val labels: Map<String, Int>,
|
||||
val memory: Map<Int, List<RuntimeValue>>,
|
||||
val heap: HeapValues)
|
||||
{
|
||||
init {
|
||||
@ -22,18 +22,17 @@ class Program (val name: String,
|
||||
program.add(LabelInstr("____program_end", false))
|
||||
program.add(Instruction(Opcode.TERMINATE))
|
||||
program.add(Instruction(Opcode.NOP))
|
||||
connect()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun load(filename: String): Program {
|
||||
val lines = File(filename).readLines().withIndex().iterator()
|
||||
val memory = mutableMapOf<Int, List<Value>>()
|
||||
val memory = mutableMapOf<Int, List<RuntimeValue>>()
|
||||
val heap = HeapValues()
|
||||
val program = mutableListOf<Instruction>()
|
||||
val variables = mutableMapOf<String, Value>()
|
||||
val variables = mutableMapOf<String, RuntimeValue>()
|
||||
val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>()
|
||||
val labels = mutableMapOf<String, Instruction>()
|
||||
val labels = mutableMapOf<String, Int>()
|
||||
|
||||
while(lines.hasNext()) {
|
||||
val (lineNr, line) = lines.next()
|
||||
@ -53,9 +52,9 @@ class Program (val name: String,
|
||||
private fun loadBlock(lines: Iterator<IndexedValue<String>>,
|
||||
heap: HeapValues,
|
||||
program: MutableList<Instruction>,
|
||||
variables: MutableMap<String, Value>,
|
||||
variables: MutableMap<String, RuntimeValue>,
|
||||
memoryPointers: MutableMap<String, Pair<Int, DataType>>,
|
||||
labels: MutableMap<String, Instruction>)
|
||||
labels: MutableMap<String, Int>)
|
||||
{
|
||||
while(true) {
|
||||
val (_, line) = lines.next()
|
||||
@ -69,8 +68,10 @@ class Program (val name: String,
|
||||
loadMemoryPointers(lines, memoryPointers, heap)
|
||||
else if(line=="%instructions") {
|
||||
val (blockInstructions, blockLabels) = loadInstructions(lines, heap)
|
||||
val baseIndex = program.size
|
||||
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 {
|
||||
when(it.second) {
|
||||
DataType.STR,
|
||||
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.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length-1), Position("<stackvmsource>", 0, 0, 0)))
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val numbers = it.third.substring(1, it.third.length-1).split(',')
|
||||
val intarray = numbers.map{number->number.trim().toInt()}.toIntArray()
|
||||
heap.add(it.second, intarray)
|
||||
val intarray = numbers.map{number->
|
||||
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 -> {
|
||||
val numbers = it.third.substring(1, it.third.length-1).split(',')
|
||||
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 labels = mutableMapOf<String, Instruction>()
|
||||
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) {
|
||||
val (lineNr, line) = lines.next()
|
||||
@ -133,7 +144,7 @@ class Program (val name: String,
|
||||
Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC,
|
||||
Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> {
|
||||
if(args!!.startsWith('$')) {
|
||||
Instruction(opcode, Value(DataType.UWORD, args.substring(1).toInt(16)))
|
||||
Instruction(opcode, RuntimeValue(DataType.UWORD, args.substring(1).toInt(16)))
|
||||
} else {
|
||||
Instruction(opcode, callLabel = args)
|
||||
}
|
||||
@ -146,8 +157,26 @@ class Program (val name: String,
|
||||
Instruction(opcode, callLabel = withoutQuotes)
|
||||
}
|
||||
Opcode.SYSCALL -> {
|
||||
val call = Syscall.valueOf(args!!)
|
||||
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
|
||||
if(args!! in syscallNames) {
|
||||
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 -> {
|
||||
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)
|
||||
return null
|
||||
if(args[0]=='"' && args[args.length-1]=='"') {
|
||||
@ -170,21 +199,21 @@ class Program (val name: String,
|
||||
}
|
||||
val (type, valueStr) = args.split(':')
|
||||
return when(type) {
|
||||
"b" -> Value(DataType.BYTE, valueStr.toShort(16))
|
||||
"ub" -> Value(DataType.UBYTE, valueStr.toShort(16))
|
||||
"w" -> Value(DataType.WORD, valueStr.toInt(16))
|
||||
"uw" -> Value(DataType.UWORD, valueStr.toInt(16))
|
||||
"f" -> Value(DataType.FLOAT, valueStr.toDouble())
|
||||
"b" -> RuntimeValue(DataType.BYTE, valueStr.toShort(16))
|
||||
"ub" -> RuntimeValue(DataType.UBYTE, valueStr.toShort(16))
|
||||
"w" -> RuntimeValue(DataType.WORD, valueStr.toInt(16))
|
||||
"uw" -> RuntimeValue(DataType.UWORD, valueStr.toInt(16))
|
||||
"f" -> RuntimeValue(DataType.FLOAT, valueStr.toDouble())
|
||||
"heap" -> {
|
||||
val heapId = valueStr.toInt()
|
||||
Value(heap.get(heapId).type, heapId)
|
||||
RuntimeValue(heap.get(heapId).type, heapId = heapId)
|
||||
}
|
||||
else -> throw VmExecutionException("invalid datatype $type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadVars(lines: Iterator<IndexedValue<String>>,
|
||||
vars: MutableMap<String, Value>) {
|
||||
vars: MutableMap<String, RuntimeValue>) {
|
||||
val splitpattern = Pattern.compile("\\s+")
|
||||
while(true) {
|
||||
val (_, line) = lines.next()
|
||||
@ -193,35 +222,31 @@ class Program (val name: String,
|
||||
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
|
||||
if(valueStr[0] !='"' && ':' !in valueStr)
|
||||
throw VmExecutionException("missing value type character")
|
||||
val type = DataType.valueOf(typeStr.toUpperCase())
|
||||
val value = when(type) {
|
||||
DataType.UBYTE -> Value(DataType.UBYTE, valueStr.substring(3).toShort(16))
|
||||
DataType.BYTE -> Value(DataType.BYTE, valueStr.substring(2).toShort(16))
|
||||
DataType.UWORD -> Value(DataType.UWORD, valueStr.substring(3).toInt(16))
|
||||
DataType.WORD -> Value(DataType.WORD, valueStr.substring(2).toInt(16))
|
||||
DataType.FLOAT -> Value(DataType.FLOAT, valueStr.substring(2).toDouble())
|
||||
DataType.STR, DataType.STR_P, DataType.STR_S, DataType.STR_PS -> {
|
||||
val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) {
|
||||
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).toShort(16))
|
||||
DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).toShort(16))
|
||||
DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).toInt(16))
|
||||
DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).toInt(16))
|
||||
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).toDouble())
|
||||
in StringDatatypes -> {
|
||||
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")
|
||||
else if(!valueStr.startsWith("heap:"))
|
||||
throw VmExecutionException("invalid string value, should be a heap reference")
|
||||
else {
|
||||
val heapId = valueStr.substring(5).toInt()
|
||||
Value(type, heapId)
|
||||
RuntimeValue(type, heapId = heapId)
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB,
|
||||
DataType.ARRAY_B,
|
||||
DataType.ARRAY_UW,
|
||||
DataType.ARRAY_W,
|
||||
DataType.ARRAY_F -> {
|
||||
in ArrayDatatypes -> {
|
||||
if(!valueStr.startsWith("heap:"))
|
||||
throw VmExecutionException("invalid array value, should be a heap reference")
|
||||
else {
|
||||
val heapId = valueStr.substring(5).toInt()
|
||||
Value(type, heapId)
|
||||
RuntimeValue(type, heapId = heapId)
|
||||
}
|
||||
}
|
||||
else -> throw VmExecutionException("weird datatype")
|
||||
}
|
||||
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) {
|
||||
val (lineNr, line) = lines.next()
|
||||
if(line=="%end_memory")
|
||||
@ -255,11 +280,11 @@ class Program (val name: String,
|
||||
TODO("memory init with char/string")
|
||||
} else {
|
||||
val valueStrings = rest.split(' ')
|
||||
val values = mutableListOf<Value>()
|
||||
val values = mutableListOf<RuntimeValue>()
|
||||
valueStrings.forEach {
|
||||
when(it.length) {
|
||||
2 -> values.add(Value(DataType.UBYTE, it.toShort(16)))
|
||||
4 -> values.add(Value(DataType.UWORD, it.toInt(16)))
|
||||
2 -> values.add(RuntimeValue(DataType.UBYTE, it.toShort(16)))
|
||||
4 -> values.add(RuntimeValue(DataType.UWORD, it.toInt(16)))
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package prog8.stackvm
|
||||
|
||||
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
|
||||
@ -15,6 +16,8 @@ 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)
|
||||
@ -45,34 +48,81 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
g2d.drawImage(image, 0, 0, image.width * 3, image.height * 3, null)
|
||||
}
|
||||
|
||||
fun clearScreen(color: Int) {
|
||||
g2d.background = palette[color and 15]
|
||||
g2d.clearRect(0, 0, BitmapScreenPanel.SCREENWIDTH, BitmapScreenPanel.SCREENHEIGHT)
|
||||
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: Int) {
|
||||
image.setRGB(x, y, palette[color and 15].rgb)
|
||||
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: Int) {
|
||||
g2d.color = palette[color and 15]
|
||||
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 writeText(x: Int, y: Int, text: String, color: Int) {
|
||||
if(color!=1) {
|
||||
TODO("text can only be white for now")
|
||||
}
|
||||
var xx=x
|
||||
var yy=y
|
||||
for(sc in Petscii.encodeScreencode(text, true)) {
|
||||
setChar(xx, yy, sc)
|
||||
xx++
|
||||
if(xx>=(SCREENWIDTH/8)) {
|
||||
yy++
|
||||
xx=0
|
||||
fun printText(text: String, color: Short, lowercase: Boolean) {
|
||||
val lines = text.split('\n')
|
||||
for(line in lines.withIndex()) {
|
||||
printTextSingleLine(line.value, color, lowercase)
|
||||
if(line.index<lines.size-1) {
|
||||
cursorX=0
|
||||
cursorY++
|
||||
}
|
||||
}
|
||||
}
|
||||
fun setChar(x: Int, y: Int, screenCode: Short) {
|
||||
g2d.drawImage(Charset.shiftedChars[screenCode.toInt()], 8*x, 8*y , null)
|
||||
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) {
|
||||
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 SCREENHEIGHT = 200
|
||||
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)
|
||||
val borderTop = JPanel().apply {
|
||||
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
|
||||
background = BitmapScreenPanel.palette[14]
|
||||
background = Colors.palette[14]
|
||||
}
|
||||
val borderBottom = JPanel().apply {
|
||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH+2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
|
||||
background = BitmapScreenPanel.palette[14]
|
||||
background = Colors.palette[14]
|
||||
}
|
||||
val borderLeft = JPanel().apply {
|
||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
|
||||
background = BitmapScreenPanel.palette[14]
|
||||
background = Colors.palette[14]
|
||||
}
|
||||
val borderRight = JPanel().apply {
|
||||
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
|
||||
background = BitmapScreenPanel.palette[14]
|
||||
background = Colors.palette[14]
|
||||
}
|
||||
var c = GridBagConstraints()
|
||||
c.gridx=0; c.gridy=1; c.gridwidth=3
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,182 +5,14 @@ import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.DataType
|
||||
import prog8.ast.LiteralValue
|
||||
import prog8.ast.Position
|
||||
import prog8.compiler.intermediate.Value
|
||||
import prog8.compiler.intermediate.ValueException
|
||||
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 {
|
||||
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)
|
||||
class TestParserLiteralValue {
|
||||
|
248
compiler/test/RuntimeValueTests.kt
Normal file
248
compiler/test/RuntimeValueTests.kt
Normal 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
@ -6,8 +6,8 @@ import org.hamcrest.Matchers.equalTo
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.RuntimeValue
|
||||
import prog8.compiler.*
|
||||
import prog8.compiler.intermediate.Value
|
||||
import prog8.compiler.target.c64.*
|
||||
import java.io.CharConversionException
|
||||
import kotlin.test.*
|
||||
@ -138,23 +138,41 @@ class TestZeropage {
|
||||
assertFailsWith<CompilerException> {
|
||||
zp.allocate("", DataType.FLOAT, null)
|
||||
}
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
||||
zp2.allocate("", DataType.FLOAT, null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testZpModesWithFloats() {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
|
||||
assertFailsWith<CompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
|
||||
}
|
||||
assertFailsWith<CompilerException> {
|
||||
C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFreeSpaces() {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(19, zp1.available())
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), true))
|
||||
assertEquals(67, zp2.available())
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
|
||||
assertEquals(238, zp3.available())
|
||||
assertEquals(20, zp1.available())
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), false))
|
||||
assertEquals(95, zp2.available())
|
||||
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, emptyList(), false))
|
||||
assertEquals(129, zp3.available())
|
||||
val zp4 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
assertEquals(238, zp4.available())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReservedSpace() {
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
|
||||
val zp1 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
assertEquals(238, zp1.available())
|
||||
assertTrue(50 in zp1.free)
|
||||
assertTrue(100 in zp1.free)
|
||||
@ -163,8 +181,7 @@ class TestZeropage {
|
||||
assertTrue(200 in zp1.free)
|
||||
assertTrue(255 in zp1.free)
|
||||
assertTrue(199 in zp1.free)
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL,
|
||||
listOf(50 .. 100, 200..255), true))
|
||||
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, listOf(50 .. 100, 200..255), false))
|
||||
assertEquals(139, zp2.available())
|
||||
assertFalse(50 in zp2.free)
|
||||
assertFalse(100 in zp2.free)
|
||||
@ -178,7 +195,7 @@ class TestZeropage {
|
||||
@Test
|
||||
fun testBasicsafeAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(19, zp.available())
|
||||
assertEquals(20, zp.available())
|
||||
|
||||
zp.allocate("", DataType.FLOAT, null)
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
@ -201,32 +218,28 @@ class TestZeropage {
|
||||
|
||||
@Test
|
||||
fun testFullAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), true))
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
|
||||
assertEquals(238, zp.available())
|
||||
val loc = zp.allocate("", DataType.FLOAT, null)
|
||||
val loc = zp.allocate("", DataType.UWORD, null)
|
||||
assertTrue(loc > 3)
|
||||
assertFalse(loc in zp.free)
|
||||
val num = zp.available() / 5
|
||||
val rest = zp.available() % 5
|
||||
val num = zp.available() / 2
|
||||
|
||||
for(i in 0..num-4) {
|
||||
zp.allocate("", DataType.FLOAT, null)
|
||||
zp.allocate("", DataType.UWORD, null)
|
||||
}
|
||||
assertEquals(18,zp.available())
|
||||
assertEquals(6,zp.available())
|
||||
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
// can't allocate because no more sequential bytes, only fragmented
|
||||
zp.allocate("", DataType.FLOAT, null)
|
||||
zp.allocate("", DataType.UWORD, null)
|
||||
}
|
||||
|
||||
for(i in 0..13) {
|
||||
for(i in 0..5) {
|
||||
zp.allocate("", DataType.UBYTE, null)
|
||||
}
|
||||
zp.allocate("", DataType.UWORD, null)
|
||||
|
||||
assertEquals(2, zp.available())
|
||||
zp.allocate("", DataType.UBYTE, null)
|
||||
zp.allocate("", DataType.UBYTE, null)
|
||||
assertEquals(0, zp.available())
|
||||
assertFailsWith<ZeropageDepletedError> {
|
||||
// no more space
|
||||
zp.allocate("", DataType.UBYTE, null)
|
||||
@ -236,7 +249,7 @@ class TestZeropage {
|
||||
@Test
|
||||
fun testEfficientAllocation() {
|
||||
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
|
||||
assertEquals(19, zp.available())
|
||||
assertEquals(20, zp.available())
|
||||
assertEquals(0x04, zp.allocate("", DataType.FLOAT, null))
|
||||
assertEquals(0x09, zp.allocate("", DataType.UBYTE, null))
|
||||
assertEquals(0x0d, zp.allocate("", DataType.UWORD, null))
|
||||
@ -245,6 +258,7 @@ class TestZeropage {
|
||||
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null))
|
||||
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
|
||||
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null))
|
||||
assertEquals(0, zp.available())
|
||||
}
|
||||
@ -254,6 +268,14 @@ class TestZeropage {
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
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
|
||||
fun testLowercase() {
|
||||
assertThat(Petscii.encodePetscii("hello WORLD 123 @!£", true), equalTo(
|
||||
@ -344,8 +366,8 @@ class TestPetscii {
|
||||
|
||||
@Test
|
||||
fun testStackvmValueComparisons() {
|
||||
val ten = Value(DataType.FLOAT, 10)
|
||||
val nine = Value(DataType.UWORD, 9)
|
||||
val ten = RuntimeValue(DataType.FLOAT, 10)
|
||||
val nine = RuntimeValue(DataType.UWORD, 9)
|
||||
assertEquals(ten, ten)
|
||||
assertNotEquals(ten, nine)
|
||||
assertFalse(ten != ten)
|
||||
|
21
create_compiler_jar.sh
Executable file
21
create_compiler_jar.sh
Executable 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
|
@ -2,10 +2,8 @@
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.7" jdkType="Python SDK" />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.7 (py3)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
Binary file not shown.
Before Width: | Height: | Size: 28 KiB |
BIN
docs/source/_static/primes_example.png
Normal file
BIN
docs/source/_static/primes_example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 93 KiB |
BIN
docs/source/_static/tehtriz.png
Normal file
BIN
docs/source/_static/tehtriz.png
Normal file
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 |
@ -2,8 +2,33 @@
|
||||
Writing and building a program
|
||||
==============================
|
||||
|
||||
What is a "Program" anyway?
|
||||
---------------------------
|
||||
First, getting a working compiler
|
||||
---------------------------------
|
||||
|
||||
Before you can compile Prog8 programs, you'll have to build the compiler itself.
|
||||
First make sure you have installed the :ref:`requirements`.
|
||||
Then you can choose a few ways to create the compiler:
|
||||
|
||||
**Using the shell script:**
|
||||
|
||||
#. run the "build_the_compiler.sh" shell script
|
||||
#. it will create a "prog8compiler.jar" file which contains everything.
|
||||
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it.
|
||||
|
||||
**using the Gradle build system:**
|
||||
|
||||
#. run the command "./gradlew installDist"
|
||||
#. it will create the commands and required libraries in the "./compiler/build/install/p8compile/" directory
|
||||
#. run the compiler with the "./compiler/build/install/p8compile/bin/p8compile" command to see how you can use it.
|
||||
|
||||
**download a precompiled version from github:**
|
||||
|
||||
#. download a recent "prog8compiler.jar" from `the releases on Github <https://github.com/irmen/prog8/releases>`_
|
||||
#. run the compiler with "java -jar prog8compiler.jar" to see how you can use it.
|
||||
|
||||
|
||||
What is a Prog8 "Program" anyway?
|
||||
---------------------------------
|
||||
|
||||
A "complete runnable program" is a compiled, assembled, and linked together single unit.
|
||||
It contains all of the program's code and data and has a certain file format that
|
||||
|
@ -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
|
||||
|
||||
|
||||
.. 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
|
||||
------------
|
||||
|
||||
When this code is compiled::
|
||||
This code calculates prime numbers using the Sieve of Eratosthenes algorithm::
|
||||
|
||||
%import c64lib
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
|
||||
ubyte[256] sieve
|
||||
ubyte candidate_prime = 2
|
||||
|
||||
sub start() {
|
||||
; set text color and activate lowercase charset
|
||||
c64.COLOR = 13
|
||||
c64.VMCSB |= 2
|
||||
memset(sieve, 256, false)
|
||||
|
||||
; use optimized routine to write text
|
||||
c64scr.print("Hello!\n")
|
||||
|
||||
; use iteration to write text
|
||||
str question = "How are you?\n"
|
||||
for ubyte char in question
|
||||
c64.CHROUT(char)
|
||||
|
||||
; use indexed loop to write characters
|
||||
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)
|
||||
c64scr.print("prime numbers up to 255:\n\n")
|
||||
ubyte amount=0
|
||||
while true {
|
||||
ubyte prime = find_next_prime()
|
||||
if prime==0
|
||||
break
|
||||
c64scr.print_ub(prime)
|
||||
c64scr.print(", ")
|
||||
amount++
|
||||
}
|
||||
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/hello_screen.png
|
||||
.. image:: _static/primes_example.png
|
||||
:align: center
|
||||
:alt: result when run on C-64
|
||||
|
||||
@ -120,8 +135,12 @@ A recent .exe version of this tool for Windows can be obtained from my `clone <h
|
||||
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.
|
||||
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead
|
||||
and for Windows it's possible to get that as well: for instance,
|
||||
`Azul's Zulu <https://www.azul.com/downloads/zulu/>`_ is a certified OpenJDK
|
||||
implementation available for various platforms.
|
||||
|
||||
A C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence
|
||||
Finally: a **C-64 emulator** (or a real C-64 ofcourse) to run the programs on. The compiler assumes the presence
|
||||
of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
|
||||
.. hint::
|
||||
@ -130,11 +149,16 @@ of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
|
||||
packaged jar file.
|
||||
|
||||
.. note::
|
||||
To build the compiler from source, Kotlin SDK version 1.3 or newer is needed.
|
||||
Building the compiler itself:
|
||||
|
||||
(re)building the compiler itself requires a Kotlin SDK version 1.3.
|
||||
The compiler is developed using the `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_
|
||||
IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available).
|
||||
But a bare Kotlin SDK installation should work just as well.
|
||||
A shell script is provided to build and package the compiler from the command line.
|
||||
A shell script (``build_the_compiler.sh``) is provided to build and package the compiler from the command line.
|
||||
You can also use the Gradle build system to build the compiler (it will take care of
|
||||
downloading all required libraries for you) by typing ``gradle installDist`` for instance.
|
||||
The output of this gradle build will appear in the "./compiler/build/install/p8compile/" directory.
|
||||
|
||||
.. note::
|
||||
Development and testing is done on Linux, but the compiler should run on most
|
||||
|
@ -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
|
||||
|
||||
|
||||
.. todo::
|
||||
There must be a way to tell the compiler which variables you require to be in Zeropage:
|
||||
``zeropage`` modifier keyword on vardecl perhaps?
|
||||
*zeropage tag:*
|
||||
If you add the ``@zp`` tag to the variable declaration, the compiler will prioritize this variable
|
||||
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
|
||||
@ -233,9 +237,9 @@ Arrays
|
||||
^^^^^^
|
||||
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[99] array = 255 ; initialize array with all 255's [255, 255, 255, 255, ...]
|
||||
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
|
||||
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
||||
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)
|
||||
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
|
||||
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
|
||||
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)::
|
||||
|
||||
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::
|
||||
@ -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
|
||||
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::
|
||||
this behavior may change in a future version so that subsequent runs always
|
||||
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.
|
||||
|
||||
.. attention::
|
||||
**Data type conversion (during calculations) and floating point handling:**
|
||||
|
||||
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).
|
||||
**Floating points used in expressions:**
|
||||
|
||||
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
|
||||
@ -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,
|
||||
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
|
||||
-----------
|
||||
@ -505,7 +532,6 @@ Subroutines are parts of the code that can be repeatedly invoked using a subrout
|
||||
Their definition, using the ``sub`` statement, includes the specification of the required parameters and return value.
|
||||
Subroutines can be defined in a Block, but also nested inside another subroutine. Everything is scoped accordingly.
|
||||
|
||||
|
||||
Calling a subroutine
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -514,9 +540,13 @@ It is possible to not store the return value but the compiler
|
||||
will issue a warning then telling you the result values of a subroutine call are discarded.
|
||||
|
||||
.. caution::
|
||||
Note that *recursive* subroutine calls are not supported at this time.
|
||||
Note that due to the way parameters are processed by the compiler,
|
||||
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,
|
||||
or rewrite it into an iterative algorithm.
|
||||
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:
|
||||
@ -574,8 +604,11 @@ ln(x)
|
||||
log2(x)
|
||||
Base 2 logarithm.
|
||||
|
||||
sqrt16(w)
|
||||
16 bit unsigned integer Square root. Result is unsigned byte.
|
||||
|
||||
sqrt(x)
|
||||
Square root.
|
||||
Floating point Square root.
|
||||
|
||||
round(x)
|
||||
Rounds the floating point to the closest integer.
|
||||
@ -609,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: 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!
|
||||
(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)
|
||||
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
|
||||
@ -707,3 +745,19 @@ rsave()
|
||||
|
||||
rrestore()
|
||||
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.
|
||||
|
@ -60,16 +60,20 @@ Directives
|
||||
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
|
||||
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
|
||||
including default IRQs during normal system operation.
|
||||
When the program exits, a system reset is performed (because BASIC will be in a corrupt state).
|
||||
- style ``floatsafe`` -- like the previous one but also reserves the addresses that
|
||||
are required to perform floating point operations (from the BASIC kernel). No clean exit is possible.
|
||||
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
|
||||
touch change anything else. This allows full use of BASIC and KERNAL ROM routines including default IRQs
|
||||
during normal system operation.
|
||||
When the program exits, it simply returns to the BASIC ready prompt.
|
||||
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
|
||||
except the few addresses mentioned above that are used by the system's IRQ routine.
|
||||
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
|
||||
It is also not possible to cleanly exit the program, other than resetting the machine.
|
||||
This option makes programs smaller and faster because many more variables can
|
||||
be stored in the ZP, which is more efficient.
|
||||
As with ``kernalsafe``, it is not possible to cleanly exit the program, other than to reset the machine.
|
||||
This option makes programs smaller and faster because even more variables can
|
||||
be stored in the ZP (which allows for more efficient assembly code).
|
||||
|
||||
Also read :ref:`zeropage`.
|
||||
|
||||
@ -119,6 +123,7 @@ Directives
|
||||
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 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"
|
||||
|
||||
@ -129,6 +134,8 @@ Directives
|
||||
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.
|
||||
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
|
||||
|
||||
@ -209,9 +216,11 @@ Variable declarations
|
||||
|
||||
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,
|
||||
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::
|
||||
|
||||
@ -220,10 +229,12 @@ Various examples::
|
||||
byte age = 2018 - 1974
|
||||
float wallet = 55.25
|
||||
str name = "my name is Irmen"
|
||||
word address = #counter
|
||||
byte[5] values = [11, 22, 33, 44, 55]
|
||||
uword address = &counter
|
||||
byte[] values = [11, 22, 33, 44, 55]
|
||||
byte[5] values = 255 ; initialize with five 255 bytes
|
||||
|
||||
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
|
||||
|
||||
|
||||
|
||||
Data types
|
||||
@ -243,22 +254,24 @@ type identifier type storage size example var declara
|
||||
``uword`` unsigned word 2 bytes = 16 bits ``uword myvar = $8fee``
|
||||
``float`` floating-point 5 bytes = 40 bits ``float myvar = 1.2345``
|
||||
stored in 5-byte cbm MFLPT format
|
||||
``byte[x]`` signed byte array x bytes ``byte[4] myvar = [1, 2, 3, 4]``
|
||||
``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar = [1, 2, 3, 4]``
|
||||
``word[x]`` signed word array 2*x bytes ``word[4] myvar = [1, 2, 3, 4]``
|
||||
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar = [1, 2, 3, 4]``
|
||||
``float[x]`` floating-point array 5*x bytes ``float[4] myvar = [1.1, 2.2, 3.3, 4.4]``
|
||||
``byte[x]`` signed byte array x bytes ``byte[4] myvar``
|
||||
``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar``
|
||||
``word[x]`` signed word array 2*x bytes ``word[4] myvar``
|
||||
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar``
|
||||
``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."``
|
||||
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."``
|
||||
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``
|
||||
|
||||
@ -278,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``.
|
||||
|
||||
|
||||
.. 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
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
Many type conversions are possible by just writing ``as <type>`` at the end of an expression,
|
||||
@ -294,11 +300,11 @@ for example ``ubyte ub = floatvalue as ubyte`` will convert the floating point v
|
||||
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 the *memory address* where the value is located::
|
||||
|
||||
memory byte BORDER = $d020
|
||||
&byte BORDERCOLOR = $d020
|
||||
|
||||
|
||||
Direct access to memory locations
|
||||
@ -339,7 +345,7 @@ which represents a range of numbers or characters,
|
||||
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::
|
||||
|
||||
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
|
||||
@ -356,19 +362,13 @@ Syntax is familiar with brackets: ``arrayvar[x]`` ::
|
||||
Operators
|
||||
---------
|
||||
|
||||
.. todo::
|
||||
address-of: ``#``
|
||||
Takes the address of the symbol following it: ``word address = #somevar``
|
||||
|
||||
|
||||
arithmetic: ``+`` ``-`` ``*`` ``/`` ``**`` ``%``
|
||||
``+``, ``-``, ``*``, ``/`` 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 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
|
||||
Remainder is only supported on integer operands (not floats).
|
||||
|
||||
|
||||
bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>``
|
||||
``&`` 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)
|
||||
@ -403,12 +403,19 @@ range creation: ``to``
|
||||
X = 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 {
|
||||
; 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* ``)``
|
||||
Parentheses are used to group parts of an expression to change the order of evaluation.
|
||||
@ -427,10 +434,20 @@ You call a subroutine like this::
|
||||
[ result = ] subroutinename_or_address ( [argument...] )
|
||||
|
||||
; 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
|
||||
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()
|
||||
|
||||
|
||||
|
||||
@ -499,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::
|
||||
|
||||
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 {
|
||||
; 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
|
||||
^^^^^^^^^^
|
||||
|
||||
|
@ -77,11 +77,6 @@ Theoretically they can all be used in a program, with the follwoing limitations:
|
||||
- it's more convenient and safe to let the compiler allocate these addresses for you and just
|
||||
use symbolic names in the program code.
|
||||
|
||||
.. todo::
|
||||
There must be a way to tell the compiler which variables you require to be in Zeropage:
|
||||
``zeropage`` modifier keyword on vardecl perhaps?
|
||||
|
||||
|
||||
Prog8 knows what addresses are safe to use in the various ZP handling configurations.
|
||||
It will use the free ZP addresses to place its ZP variables in,
|
||||
until they're all used up. If instructed to output a program that takes over the entire
|
||||
@ -93,6 +88,7 @@ treats the ZP for the program. The default is to be reasonably restrictive to us
|
||||
part of the ZP that is not used by the C64's kernal routines.
|
||||
It's possible to claim the whole ZP as well (by disabling the operating system or kernal).
|
||||
If you want, it's also possible to be more restricive and stay clear of the addresses used by BASIC routines too.
|
||||
This allows the program to exit cleanly back to a BASIC ready prompt - something that is not possible in the other modes.
|
||||
|
||||
|
||||
IRQs and the ZeroPage
|
||||
@ -121,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)
|
||||
- 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``
|
||||
are volatile. Their values cannot be depended upon, the compiler will use them as required.
|
||||
@ -148,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!
|
||||
The result value(s) of a subroutine are returned on the evaluation stack,
|
||||
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 ...
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ TODO
|
||||
Memory Block Operations integrated in language?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@todo list,string memory block operations?
|
||||
list,string memory block operations?
|
||||
|
||||
- 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)
|
||||
@ -15,9 +15,9 @@ Memory Block Operations integrated in language?
|
||||
|
||||
- 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.
|
||||
|
||||
|
||||
|
||||
@ -29,14 +29,49 @@ Add more compiler optimizations to the existing ones.
|
||||
- on the language AST level
|
||||
- on the StackVM intermediate code level
|
||||
- on the final assembly source level
|
||||
- can the parameter passing to subroutines be optimized to avoid copying?
|
||||
|
||||
- subroutines with 1 or 2 byte args (or 1 word arg) should be converted to asm calling convention with the args in A/Y register
|
||||
this requires rethinking the way parameters are represented, simply injecting vardecls to
|
||||
declare local variables for them is not always correct anymore
|
||||
|
||||
|
||||
Also some library routines and code patterns could perhaps be optimized further
|
||||
|
||||
|
||||
Eval stack redesign? (lot of work)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The eval stack is now a split lsb/msb stack using X as the stackpointer.
|
||||
Is it easier/faster to just use a single page unsplit stack?
|
||||
It could then even be moved into the zeropage to greatly reduce code size and slowness.
|
||||
|
||||
Or just move the LSB portion into a slab of the zeropage.
|
||||
|
||||
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?
|
||||
|
||||
|
||||
structs?
|
||||
^^^^^^^^
|
||||
|
||||
A user defined struct type would be nice to group a bunch
|
||||
of values together (and use it multiple times). Something like::
|
||||
|
||||
struct Point {
|
||||
ubyte color
|
||||
word[] vec = [0,0,0]
|
||||
}
|
||||
|
||||
Point p1
|
||||
Point p2
|
||||
Point p3
|
||||
|
||||
p1.color = 3
|
||||
p1.vec[2] = 2
|
||||
|
||||
|
||||
Misc
|
||||
^^^^
|
||||
|
||||
- sqrt() should have integer implementation as well, instead of relying on float SQRT for all argument types
|
||||
- code generation for POW instruction
|
||||
- make sure user-defined blocks come BEFORE library blocks (this helps zeropage variable allocations)
|
||||
- are there any other missing instructions in the code generator?
|
||||
|
@ -44,10 +44,10 @@ sub start() {
|
||||
|
||||
sub print_notes(ubyte n1, ubyte n2) {
|
||||
c64.CHROUT('\n')
|
||||
c64scr.PLOT(n1/2, 24)
|
||||
c64scr.plot(n1/2, 24)
|
||||
c64.COLOR=7
|
||||
c64.CHROUT('Q')
|
||||
c64scr.PLOT(n2/2, 24)
|
||||
c64scr.plot(n2/2, 24)
|
||||
c64.COLOR=4
|
||||
c64.CHROUT('Q')
|
||||
}
|
||||
@ -56,7 +56,7 @@ sub start() {
|
||||
; details about the boulderdash music can be found here:
|
||||
; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune
|
||||
|
||||
uword[128] notes = [
|
||||
uword[] notes = [
|
||||
$1622, $1d26, $2229, $252e, $1424, $1f27, $2029, $2730,
|
||||
$122a, $122c, $1e2e, $1231, $202c, $3337, $212d, $3135,
|
||||
$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,
|
||||
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,
|
||||
|
@ -1,4 +1,6 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
~ main {
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
~ main {
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
~ main {
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
|
||||
~ main {
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
%import c64utils
|
||||
%import c64flt
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
|
||||
~ main {
|
||||
|
||||
|
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.
BIN
examples/compiled/tehtriz.prg
Normal file
BIN
examples/compiled/tehtriz.prg
Normal file
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user