mirror of
https://github.com/irmen/prog8.git
synced 2025-06-15 18:23:35 +00:00
Compare commits
49 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 |
20
.idea/gradle.xml
generated
20
.idea/gradle.xml
generated
@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="/usr/share/java/gradle" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/compiler" />
|
||||
<option value="$PROJECT_DIR$/parser" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="useQualifiedModuleNames" value="true" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
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>
|
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<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>
|
3
.idea/modules.xml
generated
3
.idea/modules.xml
generated
@ -2,7 +2,10 @@
|
||||
<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>
|
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
<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
|
||||
|
23
README.md
23
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
|
||||
|
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
|
||||
|
@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id "org.jetbrains.kotlin.jvm" version "1.3.30"
|
||||
id "org.jetbrains.kotlin.jvm" version "1.3.40"
|
||||
id 'application'
|
||||
id 'org.jetbrains.dokka' version "0.9.18"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@ -8,7 +9,7 @@ repositories {
|
||||
jcenter()
|
||||
}
|
||||
|
||||
def kotlinVersion = '1.3.30'
|
||||
def kotlinVersion = '1.3.40'
|
||||
|
||||
dependencies {
|
||||
implementation project(':parser')
|
||||
@ -28,6 +29,7 @@ dependencies {
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,3 +97,9 @@ test {
|
||||
events "passed", "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dokka {
|
||||
outputFormat = 'html'
|
||||
outputDirectory = "$buildDir/kdoc"
|
||||
}
|
||||
|
19
compiler/compiler.iml
Normal file
19
compiler/compiler.iml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/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="module" module-name="parser" />
|
||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
||||
</component>
|
||||
</module>
|
@ -1 +1 @@
|
||||
1.7
|
||||
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")
|
||||
}
|
||||
|
||||
@ -47,6 +49,8 @@ private fun compileMain(args: Array<String>) {
|
||||
var writeVmCode = false
|
||||
var writeAssembly = true
|
||||
var optimize = true
|
||||
var optimizeInlining = true
|
||||
var launchAstVm = false
|
||||
for (arg in args) {
|
||||
if(arg=="-emu")
|
||||
emulatorToStart = "x64"
|
||||
@ -58,6 +62,10 @@ private fun compileMain(args: Array<String>) {
|
||||
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
|
||||
@ -68,63 +76,69 @@ 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(namespace)
|
||||
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")
|
||||
|
||||
moduleAst.checkIdentifiers(namespace)
|
||||
programAst.checkIdentifiers()
|
||||
if(optimize) {
|
||||
// optimize the parse tree
|
||||
println("Optimizing...")
|
||||
while (true) {
|
||||
// keep optimizing expressions and statements until no more steps remain
|
||||
val optsDone1 = moduleAst.simplifyExpressions(namespace, heap)
|
||||
val optsDone2 = moduleAst.optimizeStatements(namespace, heap)
|
||||
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)
|
||||
if(optimize)
|
||||
intermediate.optimize()
|
||||
@ -140,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(optimize)
|
||||
val assembly = AsmGen(compilerOptions, intermediate, programAst.heap, zeropage).compileToAssembly(optimize)
|
||||
assembly.assemble(compilerOptions)
|
||||
programname = assembly.name
|
||||
}
|
||||
@ -171,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",
|
||||
@ -180,17 +200,19 @@ private fun compileMain(args: Array<String>) {
|
||||
}
|
||||
}
|
||||
|
||||
fun determineCompilationOptions(moduleAst: Module): CompilationOptions {
|
||||
val options = moduleAst.statements.filter { it is Directive && it.directive == "%option" }.flatMap { (it as Directive).args }.toSet()
|
||||
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.FLOATSAFE else ZeropageType.KERNALSAFE
|
||||
@ -201,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 }
|
||||
@ -217,12 +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(" [-noopt] don't perform optimizations")
|
||||
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,7 +5,6 @@ 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
|
||||
|
||||
@ -13,9 +12,9 @@ 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)
|
||||
}
|
||||
|
||||
@ -52,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.addString(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 }
|
||||
@ -75,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 {
|
||||
@ -121,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
|
||||
@ -129,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))
|
||||
}
|
||||
@ -137,13 +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.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
|
||||
@ -151,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!!.targetVarDecl(namespace)
|
||||
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))
|
||||
|
||||
@ -258,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)
|
||||
@ -355,14 +366,14 @@ 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)
|
||||
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 {
|
||||
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))
|
||||
}
|
||||
}
|
||||
@ -379,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))
|
||||
@ -387,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))
|
||||
@ -425,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
|
||||
val targetVar = program.namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
|
||||
targetVar?.arraysize
|
||||
} else null
|
||||
checkValueTypeAndRange(targetDatatype,
|
||||
arrayspec ?: ArrayIndex(LiteralValue.optimalInteger(-1, assignment.position), assignment.position),
|
||||
constVal, heap)
|
||||
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))
|
||||
}
|
||||
@ -457,7 +468,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
override fun process(addressOf: AddressOf): IExpression {
|
||||
val variable=addressOf.identifier.targetVarDecl(namespace)
|
||||
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 {
|
||||
@ -489,12 +500,12 @@ 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.isUnsizedArray) {
|
||||
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) {
|
||||
@ -515,12 +526,17 @@ private class AstChecker(private val namespace: INameScope,
|
||||
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
|
||||
}
|
||||
@ -531,17 +547,17 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
when {
|
||||
decl.value is RangeExpr -> {
|
||||
if(!decl.isUnsizedArray)
|
||||
if(decl.arraysize!=null)
|
||||
checkValueTypeAndRange(decl.datatype, decl.arraysize!!, decl.value as RangeExpr)
|
||||
}
|
||||
decl.value is LiteralValue -> {
|
||||
val arraySpec = decl.arraysize ?: (
|
||||
if((decl.value as LiteralValue).isArray)
|
||||
ArrayIndex.forArray(decl.value as LiteralValue, heap)
|
||||
ArrayIndex.forArray(decl.value as LiteralValue, program.heap)
|
||||
else
|
||||
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}")
|
||||
@ -661,20 +677,20 @@ private class AstChecker(private val namespace: INameScope,
|
||||
var definingModule = directive.parent
|
||||
while (definingModule !is Module)
|
||||
definingModule = definingModule.parent
|
||||
if (!(filename.startsWith("library:") || definingModule.importedFrom.resolveSibling(filename).toFile().isFile || File(filename).isFile))
|
||||
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)
|
||||
ArrayIndex.forArray(literalValue, heap)
|
||||
ArrayIndex.forArray(literalValue, program.heap)
|
||||
else
|
||||
ArrayIndex(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position)
|
||||
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, heap)
|
||||
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, program.heap)
|
||||
|
||||
val lv = super.process(literalValue)
|
||||
when(lv.type) {
|
||||
@ -693,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))
|
||||
}
|
||||
@ -702,12 +718,12 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
override fun process(expr: BinaryExpression): IExpression {
|
||||
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)
|
||||
|
||||
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))
|
||||
@ -724,8 +740,8 @@ private class AstChecker(private val namespace: INameScope,
|
||||
// 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(namespace, heap)
|
||||
val constRight = expr.right.constValue(namespace, heap)
|
||||
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))
|
||||
}
|
||||
@ -754,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
|
||||
@ -773,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])
|
||||
@ -821,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)) {
|
||||
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))
|
||||
@ -845,8 +861,8 @@ 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)) {
|
||||
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))
|
||||
@ -879,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 {
|
||||
@ -890,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))
|
||||
}
|
||||
@ -906,7 +922,7 @@ 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))
|
||||
@ -919,7 +935,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
} else if(target.datatype in StringDatatypes) {
|
||||
// check string lengths
|
||||
val heapId = (target.value as LiteralValue).heapId!!
|
||||
val stringLen = heap.get(heapId).str!!.length
|
||||
val stringLen = program.heap.get(heapId).str!!.length
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? LiteralValue)?.asIntegerValue
|
||||
if(index!=null && (index<0 || index>=stringLen))
|
||||
checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position))
|
||||
@ -928,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.index.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))
|
||||
|
||||
@ -936,7 +952,7 @@ 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(NameError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
|
||||
@ -944,8 +960,8 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArrayIndex, range: RangeExpr) : Boolean {
|
||||
val from = range.from.constValue(namespace, heap)
|
||||
val to = range.to.constValue(namespace, heap)
|
||||
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
|
||||
@ -962,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
|
||||
@ -972,7 +988,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
in ArrayDatatypes -> {
|
||||
// range and length check bytes
|
||||
val expectedSize = arrayspec.size()
|
||||
val rangeSize=range.size(heap)
|
||||
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
|
||||
@ -1034,8 +1050,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
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 -> {
|
||||
@ -1048,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.index.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
|
||||
@ -1070,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.index.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
|
||||
@ -1092,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.index.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
|
||||
@ -1103,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})
|
||||
@ -1117,7 +1132,32 @@ 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 -> {
|
||||
|
@ -8,9 +8,13 @@ import prog8.functions.BuiltinFunctions
|
||||
* Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly.
|
||||
*/
|
||||
|
||||
fun Module.checkIdentifiers(namespace: INameScope) {
|
||||
internal fun Program.checkIdentifiers() {
|
||||
val checker = AstIdentifiersChecker(namespace)
|
||||
this.process(checker)
|
||||
checker.process(this)
|
||||
|
||||
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
|
||||
@ -47,10 +51,9 @@ fun Module.checkIdentifiers(namespace: INameScope) {
|
||||
private class AstIdentifiersChecker(private val namespace: INameScope) : IAstProcessor {
|
||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||
|
||||
var blocks: MutableMap<String, Block> = mutableMapOf()
|
||||
private set
|
||||
private var blocks: MutableMap<String, Block> = mutableMapOf()
|
||||
|
||||
fun result(): List<AstException> {
|
||||
internal fun result(): List<AstException> {
|
||||
return checkResult
|
||||
}
|
||||
|
||||
@ -58,6 +61,11 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
|
||||
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 existing = blocks[block.name]
|
||||
if(existing!=null)
|
||||
@ -71,7 +79,7 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
|
||||
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)
|
||||
}
|
||||
@ -106,14 +114,18 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
|
||||
if (existing != null && existing !== subroutine)
|
||||
nameError(subroutine.name, subroutine.position, existing)
|
||||
|
||||
// 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)
|
||||
@ -124,9 +136,10 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
|
||||
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, false, 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)
|
||||
}
|
||||
@ -155,16 +168,17 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
|
||||
// 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, false, 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'
|
||||
@ -173,10 +187,11 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
|
||||
}
|
||||
|
||||
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, false, 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,7 +239,8 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
|
||||
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, false, "$autoHeapValuePrefix${literalValue.heapId}", literalValue, literalValue.position)
|
||||
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)
|
||||
|
@ -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()
|
||||
|
@ -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) {
|
||||
internal fun Program.reorderStatements() {
|
||||
val initvalueCreator = VarInitValueAndAddressOfCreator(namespace)
|
||||
this.process(initvalueCreator)
|
||||
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")
|
||||
|
||||
@ -42,9 +45,9 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
module.statements.removeAt(nonLibBlock.first)
|
||||
for(nonLibBlock in nonLibraryBlocks)
|
||||
module.statements.add(0, nonLibBlock.second)
|
||||
val mainBlock = module.statements.single { it is Block && it.name=="main" }
|
||||
if((mainBlock as Block).address==null) {
|
||||
module.statements.remove(mainBlock)
|
||||
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)
|
||||
}
|
||||
|
||||
@ -66,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++
|
||||
@ -74,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--
|
||||
}
|
||||
@ -97,20 +100,23 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
}
|
||||
}
|
||||
|
||||
val varDecls = block.statements.filter { it is VarDecl }
|
||||
val varDecls = block.statements.filterIsInstance<VarDecl>()
|
||||
block.statements.removeAll(varDecls)
|
||||
block.statements.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)
|
||||
|
||||
@ -155,24 +161,36 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
return scope
|
||||
}
|
||||
|
||||
override fun process(decl: VarDecl): IStatement {
|
||||
if(decl.arraysize==null) {
|
||||
val array = decl.value as? LiteralValue
|
||||
if(array!=null && array.isArray) {
|
||||
val size = heap.get(array.heapId!!).arraysize
|
||||
decl.arraysize = ArrayIndex(LiteralValue.optimalInteger(size, decl.position), decl.position)
|
||||
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(decl)
|
||||
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 && !stmt.targets.any { it.isMemoryMapped(namespace) }) {
|
||||
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)
|
||||
@ -189,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
|
||||
@ -207,15 +300,24 @@ 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 VarInitValueAndAddressOfCreator(private val namespace: INameScope): IAstProcessor {
|
||||
// Replace the var decl with an assignment and add a new vardecl with the default constant value.
|
||||
// For VarDecls that declare an initialization value:
|
||||
// Replace the vardecl with an assignment (to set the initial value),
|
||||
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
|
||||
// This makes sure the variables get reset to the intended value on a next run of the program.
|
||||
// Variable decls without a value don't get this treatment, which means they retain the last
|
||||
// value they had when restarting the program.
|
||||
@ -224,10 +326,10 @@ private class VarInitValueAndAddressOfCreator(private val namespace: INameScope)
|
||||
|
||||
// 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
|
||||
@ -254,8 +356,9 @@ private class VarInitValueAndAddressOfCreator(private val namespace: INameScope)
|
||||
}
|
||||
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
|
||||
@ -309,7 +412,8 @@ private class VarInitValueAndAddressOfCreator(private val namespace: INameScope)
|
||||
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, false, autoVarName, strvalue, strvalue.position)
|
||||
val variable = VarDecl(VarDeclType.VAR, strvalue.type, false, null, autoVarName, strvalue,
|
||||
isArray = false, autoGenerated = false, position=strvalue.position)
|
||||
addVarDecl(strvalue.definingScope(), 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)
|
||||
// }
|
||||
|
||||
}
|
@ -4,10 +4,8 @@ import prog8.ast.*
|
||||
import prog8.ast.RegisterOrPair.*
|
||||
import prog8.compiler.intermediate.IntermediateProgram
|
||||
import prog8.compiler.intermediate.Opcode
|
||||
import prog8.compiler.intermediate.Value
|
||||
import prog8.compiler.intermediate.branchOpcodes
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.optimizing.same
|
||||
import prog8.parser.tryGetEmbeddedResource
|
||||
import prog8.stackvm.Syscall
|
||||
import java.io.File
|
||||
@ -68,7 +66,7 @@ class HeapValues {
|
||||
if (str.length > 255)
|
||||
throw IllegalArgumentException("string length must be 0-255")
|
||||
|
||||
// strings are 'interned' and shared if they're the same
|
||||
// strings are 'interned' and shared if they're the isSameAs
|
||||
val value = HeapValue(type, str, null, null)
|
||||
|
||||
val existing = heap.filter { it.value==value }.map { it.key }.firstOrNull()
|
||||
@ -145,19 +143,18 @@ data class CompilationOptions(val output: OutputType,
|
||||
val floats: Boolean)
|
||||
|
||||
|
||||
internal class Compiler(private val rootModule: Module,
|
||||
private val namespace: INameScope,
|
||||
private val heap: HeapValues): IAstProcessor {
|
||||
|
||||
val prog: IntermediateProgram = IntermediateProgram(rootModule.name, rootModule.loadAddress, heap, rootModule.importedFrom)
|
||||
internal class Compiler(private val program: Program): IAstProcessor {
|
||||
|
||||
private val prog: IntermediateProgram = IntermediateProgram(program.name, program.loadAddress, program.heap, program.modules.first().source)
|
||||
private var generatedLabelSequenceNumber = 0
|
||||
private val breakStmtLabelStack : Stack<String> = Stack()
|
||||
private val continueStmtLabelStack : Stack<String> = Stack()
|
||||
|
||||
fun compile(options: CompilationOptions) : IntermediateProgram {
|
||||
println("Creating stackVM code...")
|
||||
process(rootModule)
|
||||
program.modules.forEach {
|
||||
process(it)
|
||||
}
|
||||
return prog
|
||||
}
|
||||
|
||||
@ -189,7 +186,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
return r
|
||||
} else {
|
||||
// asmsub
|
||||
if(subroutine.isNotEmpty())
|
||||
if(subroutine.containsCodeOrVars())
|
||||
throw CompilerException("kernel subroutines (with memory address) can't have a body: $subroutine")
|
||||
|
||||
prog.memoryPointer(subroutine.scopedname, subroutine.asmAddress, DataType.UBYTE) // the datatype is a bit of a dummy in this case
|
||||
@ -218,7 +215,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
is Return -> translate(stmt)
|
||||
is Directive -> {
|
||||
when(stmt.directive) {
|
||||
"%asminclude" -> translateAsmInclude(stmt.args, prog.importedFrom)
|
||||
"%asminclude" -> translateAsmInclude(stmt.args, prog.source)
|
||||
"%asmbinary" -> translateAsmBinary(stmt.args)
|
||||
"%breakpoint" -> {
|
||||
prog.line(stmt.position)
|
||||
@ -424,7 +421,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
* if the branch statement just contains jumps, more efficient code is generated.
|
||||
* (just the appropriate branching instruction is outputted!)
|
||||
*/
|
||||
if(branch.elsepart.isEmpty() && branch.truepart.isEmpty())
|
||||
if(branch.elsepart.containsNoCodeNorVars() && branch.truepart.containsNoCodeNorVars())
|
||||
return
|
||||
|
||||
fun branchOpcode(branch: BranchStatement, complement: Boolean) =
|
||||
@ -468,7 +465,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
val labelElse = makeLabel(branch, "else")
|
||||
val labelEnd = makeLabel(branch, "end")
|
||||
val opcode = branchOpcode(branch, true)
|
||||
if (branch.elsepart.isEmpty()) {
|
||||
if (branch.elsepart.containsNoCodeNorVars()) {
|
||||
prog.instr(opcode, callLabel = labelEnd)
|
||||
translate(branch.truepart)
|
||||
prog.label(labelEnd)
|
||||
@ -517,7 +514,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
val trueGoto = stmt.truepart.statements.singleOrNull() as? Jump
|
||||
if(trueGoto!=null) {
|
||||
// optimization for if (condition) goto ....
|
||||
val conditionJumpOpcode = when(stmt.condition.resultingDatatype(namespace, heap)) {
|
||||
val conditionJumpOpcode = when(stmt.condition.inferType(program)) {
|
||||
in ByteDatatypes -> Opcode.JNZ
|
||||
in WordDatatypes -> Opcode.JNZW
|
||||
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
|
||||
@ -527,13 +524,13 @@ internal class Compiler(private val rootModule: Module,
|
||||
return
|
||||
}
|
||||
|
||||
val conditionJumpOpcode = when(stmt.condition.resultingDatatype(namespace, heap)) {
|
||||
val conditionJumpOpcode = when(stmt.condition.inferType(program)) {
|
||||
in ByteDatatypes -> Opcode.JZ
|
||||
in WordDatatypes -> Opcode.JZW
|
||||
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
|
||||
}
|
||||
val labelEnd = makeLabel(stmt, "end")
|
||||
if(stmt.elsepart.isEmpty()) {
|
||||
if(stmt.elsepart.containsNoCodeNorVars()) {
|
||||
prog.instr(conditionJumpOpcode, callLabel = labelEnd)
|
||||
translate(stmt.truepart)
|
||||
prog.label(labelEnd)
|
||||
@ -549,70 +546,6 @@ internal class Compiler(private val rootModule: Module,
|
||||
prog.instr(Opcode.NOP)
|
||||
}
|
||||
|
||||
private fun commonDatatype(leftDt: DataType, rightDt: DataType, leftpos: Position, rightpos: Position): DataType {
|
||||
// byte + byte -> byte
|
||||
// byte + word -> word
|
||||
// word + byte -> word
|
||||
// word + word -> word
|
||||
// a combination with a float will be float (but give a warning about this!)
|
||||
|
||||
val floatWarning = "byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic"
|
||||
|
||||
return when(leftDt) {
|
||||
DataType.UBYTE -> {
|
||||
when(rightDt) {
|
||||
DataType.UBYTE -> DataType.UBYTE
|
||||
DataType.BYTE -> DataType.BYTE
|
||||
DataType.UWORD -> DataType.UWORD
|
||||
DataType.WORD -> DataType.WORD
|
||||
DataType.FLOAT -> {
|
||||
printWarning(floatWarning, leftpos)
|
||||
DataType.FLOAT
|
||||
}
|
||||
else -> throw CompilerException("non-numeric datatype $rightDt")
|
||||
}
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
when(rightDt) {
|
||||
in ByteDatatypes -> DataType.BYTE
|
||||
in WordDatatypes -> DataType.WORD
|
||||
DataType.FLOAT -> {
|
||||
printWarning(floatWarning, leftpos)
|
||||
DataType.FLOAT
|
||||
}
|
||||
else -> throw CompilerException("non-numeric datatype $rightDt")
|
||||
}
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
when(rightDt) {
|
||||
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
|
||||
DataType.BYTE, DataType.WORD -> DataType.WORD
|
||||
DataType.FLOAT -> {
|
||||
printWarning(floatWarning, leftpos)
|
||||
DataType.FLOAT
|
||||
}
|
||||
else -> throw CompilerException("non-numeric datatype $rightDt")
|
||||
}
|
||||
}
|
||||
DataType.WORD -> {
|
||||
when(rightDt) {
|
||||
DataType.UBYTE, DataType.UWORD, DataType.BYTE, DataType.WORD -> DataType.WORD
|
||||
DataType.FLOAT -> {
|
||||
printWarning(floatWarning, leftpos)
|
||||
DataType.FLOAT
|
||||
}
|
||||
else -> throw CompilerException("non-numeric datatype $rightDt")
|
||||
}
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
if(rightDt!=DataType.FLOAT)
|
||||
printWarning(floatWarning, rightpos)
|
||||
DataType.FLOAT
|
||||
}
|
||||
else -> throw CompilerException("non-numeric datatype $leftDt")
|
||||
}
|
||||
}
|
||||
|
||||
private fun translate(expr: IExpression) {
|
||||
when(expr) {
|
||||
is RegisterExpr -> {
|
||||
@ -620,16 +553,12 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
is PrefixExpression -> {
|
||||
translate(expr.expression)
|
||||
translatePrefixOperator(expr.operator, expr.expression.resultingDatatype(namespace, heap))
|
||||
translatePrefixOperator(expr.operator, expr.expression.inferType(program))
|
||||
}
|
||||
is BinaryExpression -> {
|
||||
val leftDt = expr.left.resultingDatatype(namespace, heap)!!
|
||||
val rightDt = expr.right.resultingDatatype(namespace, heap)!!
|
||||
val commonDt =
|
||||
if(expr.operator=="/")
|
||||
BinaryExpression.divisionOpDt(leftDt, rightDt)
|
||||
else
|
||||
commonDatatype(leftDt, rightDt, expr.left.position, expr.right.position)
|
||||
val leftDt = expr.left.inferType(program)!!
|
||||
val rightDt = expr.right.inferType(program)!!
|
||||
val (commonDt, _) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right)
|
||||
translate(expr.left)
|
||||
if(leftDt!=commonDt)
|
||||
convertType(leftDt, commonDt)
|
||||
@ -637,12 +566,12 @@ internal class Compiler(private val rootModule: Module,
|
||||
if(rightDt!=commonDt)
|
||||
convertType(rightDt, commonDt)
|
||||
if(expr.operator=="<<" || expr.operator==">>")
|
||||
translateBitshiftedOperator(expr.operator, leftDt, expr.right.constValue(namespace, heap))
|
||||
translateBitshiftedOperator(expr.operator, leftDt, expr.right.constValue(program))
|
||||
else
|
||||
translateBinaryOperator(expr.operator, commonDt)
|
||||
}
|
||||
is FunctionCall -> {
|
||||
val target = expr.target.targetStatement(namespace)
|
||||
val target = expr.target.targetStatement(program.namespace)
|
||||
if(target is BuiltinFunctionStatementPlaceholder) {
|
||||
// call to a builtin function (some will just be an opcode!)
|
||||
val funcname = expr.target.nameInSource[0]
|
||||
@ -660,11 +589,11 @@ internal class Compiler(private val rootModule: Module,
|
||||
is DirectMemoryWrite -> translate(expr)
|
||||
is AddressOf -> translate(expr)
|
||||
else -> {
|
||||
val lv = expr.constValue(namespace, heap) ?: throw CompilerException("constant expression required, not $expr")
|
||||
val lv = expr.constValue(program) ?: throw CompilerException("constant expression required, not $expr")
|
||||
when(lv.type) {
|
||||
in ByteDatatypes -> prog.instr(Opcode.PUSH_BYTE, Value(lv.type, lv.bytevalue!!))
|
||||
in WordDatatypes -> prog.instr(Opcode.PUSH_WORD, Value(lv.type, lv.wordvalue!!))
|
||||
DataType.FLOAT -> prog.instr(Opcode.PUSH_FLOAT, Value(lv.type, lv.floatvalue!!))
|
||||
in ByteDatatypes -> prog.instr(Opcode.PUSH_BYTE, RuntimeValue(lv.type, lv.bytevalue!!))
|
||||
in WordDatatypes -> prog.instr(Opcode.PUSH_WORD, RuntimeValue(lv.type, lv.wordvalue!!))
|
||||
DataType.FLOAT -> prog.instr(Opcode.PUSH_FLOAT, RuntimeValue(lv.type, lv.floatvalue!!))
|
||||
in StringDatatypes -> {
|
||||
if(lv.heapId==null)
|
||||
throw CompilerException("string should have been moved into heap ${lv.position}")
|
||||
@ -729,7 +658,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
|
||||
private fun translate(identifierRef: IdentifierReference) {
|
||||
val target = identifierRef.targetStatement(namespace)
|
||||
val target = identifierRef.targetStatement(program.namespace)
|
||||
when (target) {
|
||||
is VarDecl -> {
|
||||
when (target.type) {
|
||||
@ -741,11 +670,11 @@ internal class Compiler(private val rootModule: Module,
|
||||
throw CompilerException("const ref should have been const-folded away")
|
||||
VarDeclType.MEMORY -> {
|
||||
when (target.datatype) {
|
||||
DataType.UBYTE -> prog.instr(Opcode.PUSH_MEM_UB, Value(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.BYTE-> prog.instr(Opcode.PUSH_MEM_B, Value(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.UWORD -> prog.instr(Opcode.PUSH_MEM_UW, Value(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.WORD -> prog.instr(Opcode.PUSH_MEM_W, Value(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.FLOAT -> prog.instr(Opcode.PUSH_MEM_FLOAT, Value(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.UBYTE -> prog.instr(Opcode.PUSH_MEM_UB, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.BYTE-> prog.instr(Opcode.PUSH_MEM_B, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.UWORD -> prog.instr(Opcode.PUSH_MEM_UW, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.WORD -> prog.instr(Opcode.PUSH_MEM_W, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
DataType.FLOAT -> prog.instr(Opcode.PUSH_MEM_FLOAT, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
|
||||
else -> throw CompilerException("invalid datatype for memory variable expression: $target")
|
||||
}
|
||||
}
|
||||
@ -758,7 +687,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
|
||||
private fun translate(stmt: FunctionCallStatement) {
|
||||
prog.line(stmt.position)
|
||||
val targetStmt = stmt.target.targetStatement(namespace)!!
|
||||
val targetStmt = stmt.target.targetStatement(program.namespace)!!
|
||||
if(targetStmt is BuiltinFunctionStatementPlaceholder) {
|
||||
val funcname = stmt.target.nameInSource[0]
|
||||
translateBuiltinFunctionCall(funcname, stmt.arglist)
|
||||
@ -796,7 +725,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
// cast type if needed
|
||||
if(builtinFuncParams!=null) {
|
||||
val paramDts = builtinFuncParams[index].possibleDatatypes
|
||||
val argDt = arg.resultingDatatype(namespace, heap)!!
|
||||
val argDt = arg.inferType(program)!!
|
||||
if(argDt !in paramDts) {
|
||||
for(paramDt in paramDts.sorted())
|
||||
if(tryConvertType(argDt, paramDt))
|
||||
@ -809,7 +738,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
"len" -> {
|
||||
// 1 argument, type determines the exact syscall to use
|
||||
val arg=args.single()
|
||||
when (arg.resultingDatatype(namespace, heap)) {
|
||||
when (arg.inferType(program)) {
|
||||
DataType.STR, DataType.STR_S -> createSyscall("${funcname}_str")
|
||||
else -> throw CompilerException("wrong datatype for len()")
|
||||
}
|
||||
@ -817,10 +746,10 @@ internal class Compiler(private val rootModule: Module,
|
||||
"any", "all" -> {
|
||||
// 1 array argument, type determines the exact syscall to use
|
||||
val arg=args.single() as IdentifierReference
|
||||
val target=arg.targetVarDecl(namespace)!!
|
||||
val length=Value(DataType.UBYTE, target.arraysize!!.size()!!)
|
||||
val target=arg.targetVarDecl(program.namespace)!!
|
||||
val length= RuntimeValue(DataType.UBYTE, target.arraysize!!.size()!!)
|
||||
prog.instr(Opcode.PUSH_BYTE, length)
|
||||
when (arg.resultingDatatype(namespace, heap)) {
|
||||
when (arg.inferType(program)) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB -> createSyscall("${funcname}_b")
|
||||
DataType.ARRAY_W, DataType.ARRAY_UW -> createSyscall("${funcname}_w")
|
||||
DataType.ARRAY_F -> createSyscall("${funcname}_f")
|
||||
@ -830,9 +759,9 @@ internal class Compiler(private val rootModule: Module,
|
||||
"avg" -> {
|
||||
// 1 array argument, type determines the exact syscall to use
|
||||
val arg=args.single() as IdentifierReference
|
||||
val target=arg.targetVarDecl(namespace)!!
|
||||
val length=Value(DataType.UBYTE, target.arraysize!!.size()!!)
|
||||
val arrayDt=arg.resultingDatatype(namespace, heap)
|
||||
val target=arg.targetVarDecl(program.namespace)!!
|
||||
val length= RuntimeValue(DataType.UBYTE, target.arraysize!!.size()!!)
|
||||
val arrayDt=arg.inferType(program)
|
||||
prog.instr(Opcode.PUSH_BYTE, length)
|
||||
when (arrayDt) {
|
||||
DataType.ARRAY_UB -> {
|
||||
@ -855,16 +784,16 @@ internal class Compiler(private val rootModule: Module,
|
||||
else -> throw CompilerException("wrong datatype for avg")
|
||||
}
|
||||
// divide by the number of elements
|
||||
prog.instr(opcodePush(DataType.FLOAT), Value(DataType.FLOAT, length.numericValue()))
|
||||
prog.instr(opcodePush(DataType.FLOAT), RuntimeValue(DataType.FLOAT, length.numericValue()))
|
||||
prog.instr(Opcode.DIV_F)
|
||||
}
|
||||
"min", "max", "sum" -> {
|
||||
// 1 array argument, type determines the exact syscall to use
|
||||
val arg=args.single() as IdentifierReference
|
||||
val target=arg.targetVarDecl(namespace)!!
|
||||
val length=Value(DataType.UBYTE, target.arraysize!!.size()!!)
|
||||
val target=arg.targetVarDecl(program.namespace)!!
|
||||
val length= RuntimeValue(DataType.UBYTE, target.arraysize!!.size()!!)
|
||||
prog.instr(Opcode.PUSH_BYTE, length)
|
||||
when (arg.resultingDatatype(namespace, heap)) {
|
||||
when (arg.inferType(program)) {
|
||||
DataType.ARRAY_UB -> createSyscall("${funcname}_ub")
|
||||
DataType.ARRAY_B -> createSyscall("${funcname}_b")
|
||||
DataType.ARRAY_UW -> createSyscall("${funcname}_uw")
|
||||
@ -876,7 +805,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
"abs" -> {
|
||||
// 1 argument, type determines the exact opcode to use
|
||||
val arg = args.single()
|
||||
when (arg.resultingDatatype(namespace, heap)) {
|
||||
when (arg.inferType(program)) {
|
||||
DataType.UBYTE, DataType.UWORD -> {}
|
||||
DataType.BYTE -> prog.instr(Opcode.ABS_B)
|
||||
DataType.WORD -> prog.instr(Opcode.ABS_W)
|
||||
@ -888,7 +817,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
"mkword" -> prog.instr(Opcode.MKWORD)
|
||||
"lsl" -> {
|
||||
val arg = args.single()
|
||||
val dt = arg.resultingDatatype(namespace, heap)
|
||||
val dt = arg.inferType(program)
|
||||
when (dt) {
|
||||
in ByteDatatypes -> prog.instr(Opcode.SHL_BYTE)
|
||||
in WordDatatypes -> prog.instr(Opcode.SHL_WORD)
|
||||
@ -899,7 +828,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
"lsr" -> {
|
||||
val arg = args.single()
|
||||
val dt = arg.resultingDatatype(namespace, heap)
|
||||
val dt = arg.inferType(program)
|
||||
when (dt) {
|
||||
DataType.UBYTE -> prog.instr(Opcode.SHR_UBYTE)
|
||||
DataType.BYTE -> prog.instr(Opcode.SHR_SBYTE)
|
||||
@ -912,18 +841,18 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
"rol" -> {
|
||||
val arg = args.single()
|
||||
val dt = arg.resultingDatatype(namespace, heap)
|
||||
val dt = arg.inferType(program)
|
||||
when (dt) {
|
||||
DataType.UBYTE -> prog.instr(Opcode.ROL_BYTE)
|
||||
DataType.UWORD -> prog.instr(Opcode.ROL_WORD)
|
||||
in ByteDatatypes -> prog.instr(Opcode.ROL_BYTE)
|
||||
in WordDatatypes -> prog.instr(Opcode.ROL_WORD)
|
||||
else -> throw CompilerException("wrong datatype")
|
||||
}
|
||||
// this function doesn't return a value on the stack so we pop it directly into the argument register/variable again
|
||||
popValueIntoTarget(AssignTarget.fromExpr(arg), dt)
|
||||
popValueIntoTarget(AssignTarget.fromExpr(arg), dt!!)
|
||||
}
|
||||
"ror" -> {
|
||||
val arg = args.single()
|
||||
val dt = arg.resultingDatatype(namespace, heap)
|
||||
val dt = arg.inferType(program)
|
||||
when (dt) {
|
||||
in ByteDatatypes -> prog.instr(Opcode.ROR_BYTE)
|
||||
in WordDatatypes -> prog.instr(Opcode.ROR_WORD)
|
||||
@ -934,7 +863,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
"rol2" -> {
|
||||
val arg = args.single()
|
||||
val dt = arg.resultingDatatype(namespace, heap)
|
||||
val dt = arg.inferType(program)
|
||||
when (dt) {
|
||||
in ByteDatatypes -> prog.instr(Opcode.ROL2_BYTE)
|
||||
in WordDatatypes -> prog.instr(Opcode.ROL2_WORD)
|
||||
@ -945,7 +874,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
"ror2" -> {
|
||||
val arg = args.single()
|
||||
val dt = arg.resultingDatatype(namespace, heap)
|
||||
val dt = arg.inferType(program)
|
||||
when (dt) {
|
||||
in ByteDatatypes -> prog.instr(Opcode.ROR2_BYTE)
|
||||
in WordDatatypes -> prog.instr(Opcode.ROR2_WORD)
|
||||
@ -968,13 +897,13 @@ internal class Compiler(private val rootModule: Module,
|
||||
// swap(x,y) is treated differently, it's not a normal function call
|
||||
if (args.size != 2)
|
||||
throw AstException("swap requires 2 arguments")
|
||||
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)
|
||||
throw AstException("swap requires 2 args of identical type")
|
||||
if (args[0].constValue(namespace, heap) != null || args[1].constValue(namespace, heap) != null)
|
||||
if (args[0].constValue(program) != null || args[1].constValue(program) != null)
|
||||
throw AstException("swap requires 2 variables, not constant value(s)")
|
||||
if(same(args[0], args[1]))
|
||||
if(args[0] isSameAs args[1])
|
||||
throw AstException("swap should have 2 different args")
|
||||
if(dt1 !in NumericDatatypes)
|
||||
throw AstException("swap requires args of numerical type")
|
||||
@ -1002,7 +931,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
// (subroutine arguments are not passed via the stack!)
|
||||
for (arg in arguments.zip(subroutine.parameters)) {
|
||||
translate(arg.first)
|
||||
convertType(arg.first.resultingDatatype(namespace, heap)!!, arg.second.type) // convert types of arguments to required parameter type
|
||||
convertType(arg.first.inferType(program)!!, arg.second.type) // convert types of arguments to required parameter type
|
||||
val opcode = opcodePopvar(arg.second.type)
|
||||
prog.instr(opcode, callLabel = subroutine.scopedname + "." + arg.second.name)
|
||||
}
|
||||
@ -1044,7 +973,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
for (arg in arguments.zip(subroutine.asmParameterRegisters)) {
|
||||
if (arg.second.statusflag != null) {
|
||||
if (arg.second.statusflag == Statusflag.Pc)
|
||||
carryParam = arg.first.constValue(namespace, heap)!!.asBooleanValue
|
||||
carryParam = arg.first.constValue(program)!!.asBooleanValue
|
||||
else
|
||||
throw CompilerException("no support for status flag parameter: ${arg.second.statusflag}")
|
||||
} else {
|
||||
@ -1075,7 +1004,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
val valueA: IExpression
|
||||
val valueX: IExpression
|
||||
val paramDt = arg.first.resultingDatatype(namespace, heap)
|
||||
val paramDt = arg.first.inferType(program)
|
||||
when (paramDt) {
|
||||
DataType.UBYTE -> {
|
||||
valueA = arg.first
|
||||
@ -1098,7 +1027,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
AY -> {
|
||||
val valueA: IExpression
|
||||
val valueY: IExpression
|
||||
val paramDt = arg.first.resultingDatatype(namespace, heap)
|
||||
val paramDt = arg.first.inferType(program)
|
||||
when (paramDt) {
|
||||
DataType.UBYTE -> {
|
||||
valueA = arg.first
|
||||
@ -1125,7 +1054,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
val valueX: IExpression
|
||||
val valueY: IExpression
|
||||
val paramDt = arg.first.resultingDatatype(namespace, heap)
|
||||
val paramDt = arg.first.inferType(program)
|
||||
when (paramDt) {
|
||||
DataType.UBYTE -> {
|
||||
valueX = arg.first
|
||||
@ -1382,7 +1311,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
|
||||
private fun translate(arrayindexed: ArrayIndexedExpression, write: Boolean) {
|
||||
val variable = arrayindexed.identifier.targetVarDecl(namespace)!!
|
||||
val variable = arrayindexed.identifier.targetVarDecl(program.namespace)!!
|
||||
translate(arrayindexed.arrayspec.index)
|
||||
if (write)
|
||||
prog.instr(opcodeWriteindexedvar(variable.datatype), callLabel = variable.scopedname)
|
||||
@ -1398,11 +1327,11 @@ internal class Compiler(private val rootModule: Module,
|
||||
"FUNC_$funcname"
|
||||
).toUpperCase()
|
||||
val callNr = Syscall.valueOf(function).callNr
|
||||
prog.instr(Opcode.SYSCALL, Value(DataType.UBYTE, callNr))
|
||||
prog.instr(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, callNr))
|
||||
}
|
||||
|
||||
private fun translate(stmt: Jump, branchOpcode: Opcode?) {
|
||||
var jumpAddress: Value? = null
|
||||
var jumpAddress: RuntimeValue? = null
|
||||
var jumpLabel: String? = null
|
||||
|
||||
when {
|
||||
@ -1410,10 +1339,10 @@ internal class Compiler(private val rootModule: Module,
|
||||
stmt.address!=null -> {
|
||||
if(branchOpcode in branchOpcodes)
|
||||
throw CompilerException("cannot branch to address, should use absolute jump instead")
|
||||
jumpAddress = Value(DataType.UWORD, stmt.address)
|
||||
jumpAddress = RuntimeValue(DataType.UWORD, stmt.address)
|
||||
}
|
||||
else -> {
|
||||
val target = stmt.identifier!!.targetStatement(namespace)!!
|
||||
val target = stmt.identifier!!.targetStatement(program.namespace)!!
|
||||
jumpLabel = when(target) {
|
||||
is Label -> target.scopedname
|
||||
is Subroutine -> target.scopedname
|
||||
@ -1433,14 +1362,14 @@ internal class Compiler(private val rootModule: Module,
|
||||
"--" -> prog.instr(Opcode.DEC_VAR_UB, callLabel = stmt.target.register!!.name)
|
||||
}
|
||||
stmt.target.identifier != null -> {
|
||||
val targetStatement = stmt.target.identifier!!.targetVarDecl(namespace)!!
|
||||
val targetStatement = stmt.target.identifier!!.targetVarDecl(program.namespace)!!
|
||||
when(stmt.operator) {
|
||||
"++" -> prog.instr(opcodeIncvar(targetStatement.datatype), callLabel = targetStatement.scopedname)
|
||||
"--" -> prog.instr(opcodeDecvar(targetStatement.datatype), callLabel = targetStatement.scopedname)
|
||||
}
|
||||
}
|
||||
stmt.target.arrayindexed != null -> {
|
||||
val variable = stmt.target.arrayindexed!!.identifier.targetVarDecl(namespace)!!
|
||||
val variable = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
|
||||
translate(stmt.target.arrayindexed!!.arrayspec.index)
|
||||
when(stmt.operator) {
|
||||
"++" -> prog.instr(opcodeIncArrayindexedVar(variable.datatype), callLabel = variable.scopedname)
|
||||
@ -1448,11 +1377,11 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
}
|
||||
stmt.target.memoryAddress != null -> {
|
||||
val address = stmt.target.memoryAddress?.addressExpression?.constValue(namespace, heap)?.asIntegerValue
|
||||
val address = stmt.target.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue
|
||||
if(address!=null) {
|
||||
when(stmt.operator) {
|
||||
"++" -> prog.instr(Opcode.INC_MEMORY, Value(DataType.UWORD, address))
|
||||
"--" -> prog.instr(Opcode.DEC_MEMORY, Value(DataType.UWORD, address))
|
||||
"++" -> prog.instr(Opcode.INC_MEMORY, RuntimeValue(DataType.UWORD, address))
|
||||
"--" -> prog.instr(Opcode.DEC_MEMORY, RuntimeValue(DataType.UWORD, address))
|
||||
}
|
||||
} else {
|
||||
translate(stmt.target.memoryAddress!!.addressExpression)
|
||||
@ -1477,8 +1406,8 @@ internal class Compiler(private val rootModule: Module,
|
||||
return
|
||||
}
|
||||
|
||||
val valueDt = stmt.value.resultingDatatype(namespace, heap)
|
||||
val targetDt = assignTarget.determineDatatype(namespace, heap, stmt)
|
||||
val valueDt = stmt.value.inferType(program)
|
||||
val targetDt = assignTarget.inferType(program, stmt)
|
||||
if(valueDt!=targetDt) {
|
||||
// convert value to target datatype if possible
|
||||
// @todo use convertType()????
|
||||
@ -1500,7 +1429,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
DataType.STR, DataType.STR_S -> pushHeapVarAddress(stmt.value, true)
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_W, DataType.ARRAY_UW, DataType.ARRAY_F -> {
|
||||
if (stmt.value is IdentifierReference) {
|
||||
val vardecl = (stmt.value as IdentifierReference).targetVarDecl(namespace)!!
|
||||
val vardecl = (stmt.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
prog.removeLastInstruction()
|
||||
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
|
||||
}
|
||||
@ -1529,7 +1458,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
throw CompilerException("augmented assignment should have been converted to regular assignment already")
|
||||
|
||||
// pop the result value back into the assignment target
|
||||
val datatype = assignTarget.determineDatatype(namespace, heap, stmt)!!
|
||||
val datatype = assignTarget.inferType(program, stmt)!!
|
||||
popValueIntoTarget(assignTarget, datatype)
|
||||
}
|
||||
|
||||
@ -1537,7 +1466,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
when (value) {
|
||||
is LiteralValue -> throw CompilerException("can only push address of string or array (value on the heap)")
|
||||
is IdentifierReference -> {
|
||||
val vardecl = value.targetVarDecl(namespace)!!
|
||||
val vardecl = value.targetVarDecl(program.namespace)!!
|
||||
if(removeLastOpcode) prog.removeLastInstruction()
|
||||
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
|
||||
}
|
||||
@ -1549,7 +1478,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
when (value) {
|
||||
is LiteralValue -> throw CompilerException("can only push address of float that is a variable on the heap")
|
||||
is IdentifierReference -> {
|
||||
val vardecl = value.targetVarDecl(namespace)!!
|
||||
val vardecl = value.targetVarDecl(program.namespace)!!
|
||||
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
|
||||
}
|
||||
else -> throw CompilerException("can only take address of a the float as constant literal or variable")
|
||||
@ -1557,14 +1486,14 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
|
||||
private fun translateMultiReturnAssignment(stmt: Assignment) {
|
||||
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(namespace)
|
||||
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(program.namespace)
|
||||
if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) {
|
||||
// this is the only case where multiple assignment targets are allowed: a call to an asmsub with multiple return values
|
||||
// the return values are already on the stack (the subroutine call puts them there)
|
||||
if(stmt.targets.size!=targetStmt.asmReturnvaluesRegisters.size)
|
||||
throw CompilerException("asmsub number of return values doesn't match number of assignment targets ${stmt.position}")
|
||||
for(target in stmt.targets) {
|
||||
val dt = target.determineDatatype(namespace, heap, stmt)
|
||||
val dt = target.inferType(program, stmt)
|
||||
popValueIntoTarget(target, dt!!)
|
||||
}
|
||||
} else throw CompilerException("can only use multiple assignment targets on an asmsub call")
|
||||
@ -1573,7 +1502,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) {
|
||||
when {
|
||||
assignTarget.identifier != null -> {
|
||||
val target = assignTarget.identifier.targetStatement(namespace)!!
|
||||
val target = assignTarget.identifier.targetStatement(program.namespace)!!
|
||||
if (target is VarDecl) {
|
||||
when (target.type) {
|
||||
VarDeclType.VAR -> {
|
||||
@ -1582,8 +1511,8 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
VarDeclType.MEMORY -> {
|
||||
val opcode = opcodePopmem(datatype)
|
||||
val address = target.value?.constValue(namespace, heap)!!.asIntegerValue!!
|
||||
prog.instr(opcode, Value(DataType.UWORD, address))
|
||||
val address = target.value?.constValue(program)!!.asIntegerValue!!
|
||||
prog.instr(opcode, RuntimeValue(DataType.UWORD, address))
|
||||
}
|
||||
VarDeclType.CONST -> throw CompilerException("cannot assign to const")
|
||||
}
|
||||
@ -1592,10 +1521,10 @@ internal class Compiler(private val rootModule: Module,
|
||||
assignTarget.register != null -> prog.instr(Opcode.POP_VAR_BYTE, callLabel = assignTarget.register.name)
|
||||
assignTarget.arrayindexed != null -> translate(assignTarget.arrayindexed, true) // write value to it
|
||||
assignTarget.memoryAddress != null -> {
|
||||
val address = assignTarget.memoryAddress?.addressExpression?.constValue(namespace, heap)?.asIntegerValue
|
||||
val address = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue
|
||||
if(address!=null) {
|
||||
// const integer address given
|
||||
prog.instr(Opcode.POP_MEM_BYTE, arg=Value(DataType.UWORD, address))
|
||||
prog.instr(Opcode.POP_MEM_BYTE, arg= RuntimeValue(DataType.UWORD, address))
|
||||
} else {
|
||||
translate(assignTarget.memoryAddress!!)
|
||||
}
|
||||
@ -1618,7 +1547,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
|
||||
private fun translate(loop: ForLoop) {
|
||||
if(loop.body.isEmpty()) return
|
||||
if(loop.body.containsNoCodeNorVars()) return
|
||||
prog.line(loop.position)
|
||||
val loopVarName: String
|
||||
val loopVarDt: DataType
|
||||
@ -1628,13 +1557,13 @@ internal class Compiler(private val rootModule: Module,
|
||||
loopVarName = reg.name
|
||||
loopVarDt = DataType.UBYTE
|
||||
} else {
|
||||
val loopvar = loop.loopVar!!.targetVarDecl(namespace)!!
|
||||
val loopvar = loop.loopVar!!.targetVarDecl(program.namespace)!!
|
||||
loopVarName = loopvar.scopedname
|
||||
loopVarDt = loopvar.datatype
|
||||
}
|
||||
|
||||
if(loop.iterable is RangeExpr) {
|
||||
val range = (loop.iterable as RangeExpr).toConstantIntegerRange(heap)
|
||||
val range = (loop.iterable as RangeExpr).toConstantIntegerRange()
|
||||
if(range!=null) {
|
||||
// loop over a range with constant start, last and step values
|
||||
if (range.isEmpty())
|
||||
@ -1677,9 +1606,9 @@ internal class Compiler(private val rootModule: Module,
|
||||
when {
|
||||
loop.iterable is IdentifierReference -> {
|
||||
val idRef = loop.iterable as IdentifierReference
|
||||
val vardecl = idRef.targetVarDecl(namespace)!!
|
||||
val vardecl = idRef.targetVarDecl(program.namespace)!!
|
||||
val iterableValue = vardecl.value as LiteralValue
|
||||
if(!iterableValue.isIterable(namespace, heap))
|
||||
if(iterableValue.type !in IterableDatatypes)
|
||||
throw CompilerException("loop over something that isn't iterable ${loop.iterable}")
|
||||
translateForOverIterableVar(loop, loopVarDt, iterableValue)
|
||||
}
|
||||
@ -1701,16 +1630,16 @@ internal class Compiler(private val rootModule: Module,
|
||||
when(iterableValue.type) {
|
||||
!in IterableDatatypes -> throw CompilerException("non-iterableValue type")
|
||||
DataType.STR, DataType.STR_S -> {
|
||||
numElements = iterableValue.strvalue(heap).length
|
||||
numElements = iterableValue.strvalue!!.length
|
||||
if(numElements>255) throw CompilerException("string length > 255")
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B,
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
numElements = iterableValue.arrayvalue?.size ?: heap.get(iterableValue.heapId!!).arraysize
|
||||
numElements = iterableValue.arrayvalue?.size ?: program.heap.get(iterableValue.heapId!!).arraysize
|
||||
if(numElements>255) throw CompilerException("string length > 255")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
numElements = iterableValue.arrayvalue?.size ?: heap.get(iterableValue.heapId!!).arraysize
|
||||
numElements = iterableValue.arrayvalue?.size ?: program.heap.get(iterableValue.heapId!!).arraysize
|
||||
if(numElements>255) throw CompilerException("string length > 255")
|
||||
}
|
||||
else -> throw CompilerException("weird datatype")
|
||||
@ -1743,7 +1672,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
breakStmtLabelStack.push(breakLabel)
|
||||
|
||||
// set the index var to zero before the loop
|
||||
prog.instr(opcodePush(indexVarType), Value(indexVarType, 0))
|
||||
prog.instr(opcodePush(indexVarType), RuntimeValue(indexVarType, 0))
|
||||
prog.instr(opcodePopvar(indexVarType), callLabel = indexVar.scopedname)
|
||||
|
||||
// loop starts here
|
||||
@ -1764,7 +1693,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
|
||||
prog.instr(opcodeIncvar(indexVarType), callLabel = indexVar.scopedname)
|
||||
prog.instr(opcodePushvar(indexVarType), callLabel = indexVar.scopedname)
|
||||
prog.instr(opcodeCompare(indexVarType), Value(indexVarType, numElements))
|
||||
prog.instr(opcodeCompare(indexVarType), RuntimeValue(indexVarType, numElements))
|
||||
prog.instr(Opcode.BNZ, callLabel = loopLabel)
|
||||
|
||||
prog.label(breakLabel)
|
||||
@ -1801,7 +1730,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
continueStmtLabelStack.push(continueLabel)
|
||||
breakStmtLabelStack.push(breakLabel)
|
||||
|
||||
prog.instr(opcodePush(varDt), Value(varDt, range.first))
|
||||
prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.first))
|
||||
prog.instr(opcodePopvar(varDt), callLabel = varname)
|
||||
prog.label(loopLabel)
|
||||
translate(body)
|
||||
@ -1820,13 +1749,13 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
range.step>numberOfIncDecsForOptimize -> {
|
||||
prog.instr(opcodePushvar(varDt), callLabel = varname)
|
||||
prog.instr(opcodePush(varDt), Value(varDt, range.step))
|
||||
prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.step))
|
||||
prog.instr(opcodeAdd(varDt))
|
||||
prog.instr(opcodePopvar(varDt), callLabel = varname)
|
||||
}
|
||||
range.step<numberOfIncDecsForOptimize -> {
|
||||
prog.instr(opcodePushvar(varDt), callLabel = varname)
|
||||
prog.instr(opcodePush(varDt), Value(varDt, abs(range.step)))
|
||||
prog.instr(opcodePush(varDt), RuntimeValue(varDt, abs(range.step)))
|
||||
prog.instr(opcodeSub(varDt))
|
||||
prog.instr(opcodePopvar(varDt), callLabel = varname)
|
||||
}
|
||||
@ -1844,7 +1773,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
DataType.BYTE, DataType.WORD -> range.last + range.step
|
||||
else -> throw CompilerException("invalid loop var dt $varDt")
|
||||
}
|
||||
prog.instr(opcodeCompare(varDt), Value(varDt, checkValue))
|
||||
prog.instr(opcodeCompare(varDt), RuntimeValue(varDt, checkValue))
|
||||
prog.instr(Opcode.BNZ, callLabel = loopLabel)
|
||||
}
|
||||
prog.label(breakLabel)
|
||||
@ -1941,7 +1870,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
lvTarget.linkParents(body)
|
||||
val targetStatement: VarDecl? =
|
||||
if(lvTarget.identifier!=null) {
|
||||
lvTarget.identifier.targetVarDecl(namespace)
|
||||
lvTarget.identifier.targetVarDecl(program.namespace)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@ -2014,7 +1943,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
translate(stmt.body)
|
||||
prog.label(continueLabel)
|
||||
translate(stmt.condition)
|
||||
val conditionJumpOpcode = when(stmt.condition.resultingDatatype(namespace, heap)) {
|
||||
val conditionJumpOpcode = when(stmt.condition.inferType(program)) {
|
||||
in ByteDatatypes -> Opcode.JNZ
|
||||
in WordDatatypes -> Opcode.JNZW
|
||||
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
|
||||
@ -2051,7 +1980,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
translate(stmt.body)
|
||||
prog.label(continueLabel)
|
||||
translate(stmt.untilCondition)
|
||||
val conditionJumpOpcode = when(stmt.untilCondition.resultingDatatype(namespace, heap)) {
|
||||
val conditionJumpOpcode = when(stmt.untilCondition.inferType(program)) {
|
||||
in ByteDatatypes -> Opcode.JZ
|
||||
in WordDatatypes -> Opcode.JZW
|
||||
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
|
||||
@ -2065,7 +1994,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
|
||||
private fun translate(expr: TypecastExpression) {
|
||||
translate(expr.expression)
|
||||
val sourceDt = expr.expression.resultingDatatype(namespace, heap) ?: throw CompilerException("don't know what type to cast")
|
||||
val sourceDt = expr.expression.inferType(program) ?: throw CompilerException("don't know what type to cast")
|
||||
if(sourceDt==expr.type)
|
||||
return
|
||||
|
||||
@ -2116,9 +2045,9 @@ internal class Compiler(private val rootModule: Module,
|
||||
|
||||
private fun translate(memread: DirectMemoryRead) {
|
||||
// for now, only a single memory location (ubyte) is read at a time.
|
||||
val address = memread.addressExpression.constValue(namespace, heap)?.asIntegerValue
|
||||
val address = memread.addressExpression.constValue(program)?.asIntegerValue
|
||||
if(address!=null) {
|
||||
prog.instr(Opcode.PUSH_MEM_UB, arg = Value(DataType.UWORD, address))
|
||||
prog.instr(Opcode.PUSH_MEM_UB, arg = RuntimeValue(DataType.UWORD, address))
|
||||
} else {
|
||||
translate(memread.addressExpression)
|
||||
prog.instr(Opcode.PUSH_MEMREAD)
|
||||
@ -2127,9 +2056,9 @@ internal class Compiler(private val rootModule: Module,
|
||||
|
||||
private fun translate(memwrite: DirectMemoryWrite) {
|
||||
// for now, only a single memory location (ubyte) is written at a time.
|
||||
val address = memwrite.addressExpression.constValue(namespace, heap)?.asIntegerValue
|
||||
val address = memwrite.addressExpression.constValue(program)?.asIntegerValue
|
||||
if(address!=null) {
|
||||
prog.instr(Opcode.POP_MEM_BYTE, arg = Value(DataType.UWORD, address))
|
||||
prog.instr(Opcode.POP_MEM_BYTE, arg = RuntimeValue(DataType.UWORD, address))
|
||||
} else {
|
||||
translate(memwrite.addressExpression)
|
||||
prog.instr(Opcode.POP_MEMWRITE)
|
||||
@ -2137,7 +2066,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
|
||||
private fun translate(addrof: AddressOf) {
|
||||
val target = addrof.identifier.targetVarDecl(namespace)!!
|
||||
val target = addrof.identifier.targetVarDecl(program.namespace)!!
|
||||
if(target.datatype in ArrayDatatypes || target.datatype in StringDatatypes|| target.datatype==DataType.FLOAT) {
|
||||
pushHeapVarAddress(addrof.identifier, false)
|
||||
}
|
||||
@ -2148,32 +2077,37 @@ internal class Compiler(private val rootModule: Module,
|
||||
throw CompilerException("cannot take memory pointer $addrof")
|
||||
}
|
||||
|
||||
private fun translateAsmInclude(args: List<DirectiveArg>, importedFrom: Path) {
|
||||
private fun translateAsmInclude(args: List<DirectiveArg>, source: Path) {
|
||||
val scopeprefix = if(args[1].str!!.isNotBlank()) "${args[1].str}\t.proc\n" else ""
|
||||
val scopeprefixEnd = if(args[1].str!!.isNotBlank()) "\t.pend\n" else ""
|
||||
val filename=args[0].str!!
|
||||
val sourcecode =
|
||||
if(filename.startsWith("library:")) {
|
||||
val resource = tryGetEmbeddedResource(filename.substring(8)) ?: throw IllegalArgumentException("library file '$filename' not found")
|
||||
resource.bufferedReader().use { it.readText() }
|
||||
} else {
|
||||
// first try in the same folder as where the containing file was imported from
|
||||
val sib = importedFrom.resolveSibling(filename)
|
||||
if(sib.toFile().isFile)
|
||||
sib.toFile().readText()
|
||||
else
|
||||
File(filename).readText()
|
||||
}
|
||||
val sourcecode = loadAsmIncludeFile(filename, source)
|
||||
|
||||
prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=null, callLabel2=scopeprefix+sourcecode+scopeprefixEnd)
|
||||
}
|
||||
|
||||
private fun translateAsmBinary(args: List<DirectiveArg>) {
|
||||
val offset = if(args.size>=2) Value(DataType.UWORD, args[1].int!!) else null
|
||||
val length = if(args.size==3) Value(DataType.UWORD, args[2].int!!) else null
|
||||
val offset = if(args.size>=2) RuntimeValue(DataType.UWORD, args[1].int!!) else null
|
||||
val length = if(args.size==3) RuntimeValue(DataType.UWORD, args[2].int!!) else null
|
||||
val filename = args[0].str!!
|
||||
// reading the actual data is not performed by the compiler but is delegated to the assembler
|
||||
prog.instr(Opcode.INCLUDE_FILE, offset, length, filename)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
fun loadAsmIncludeFile(filename: String, source: Path): String {
|
||||
return if (filename.startsWith("library:")) {
|
||||
val resource = tryGetEmbeddedResource(filename.substring(8))
|
||||
?: throw IllegalArgumentException("library file '$filename' not found")
|
||||
resource.bufferedReader().use { it.readText() }
|
||||
} else {
|
||||
// first try in the isSameAs folder as where the containing file was imported from
|
||||
val sib = source.resolveSibling(filename)
|
||||
if (sib.toFile().isFile)
|
||||
sib.toFile().readText()
|
||||
else
|
||||
File(filename).readText()
|
||||
}
|
||||
}
|
||||
|
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()
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ abstract class Zeropage(protected 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) {
|
||||
|
@ -1,10 +1,11 @@
|
||||
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)
|
||||
{
|
||||
|
@ -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,12 +10,12 @@ 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 name: String,
|
||||
var address: Int?,
|
||||
val instructions: MutableList<Instruction> = mutableListOf(),
|
||||
val variables: MutableMap<String, Value> = mutableMapOf(), // names are fully scoped
|
||||
val variables: MutableMap<String, RuntimeValue> = mutableMapOf(), // names are fully scoped
|
||||
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
|
||||
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
|
||||
val force_output: Boolean)
|
||||
@ -28,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
|
||||
@ -88,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) {
|
||||
@ -256,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)
|
||||
}
|
||||
@ -296,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)
|
||||
}
|
||||
@ -316,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)
|
||||
}
|
||||
@ -326,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}")
|
||||
}
|
||||
}
|
||||
@ -388,18 +390,18 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR -> {
|
||||
val value = when(decl.datatype) {
|
||||
in NumericDatatypes -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
|
||||
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)
|
||||
}
|
||||
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")
|
||||
}
|
||||
@ -424,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))
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
in ByteDatatypes -> byteval!!
|
||||
in WordDatatypes -> wordval!!
|
||||
DataType.FLOAT -> floatval!!
|
||||
else -> throw ValueException("invalid datatype for numeric value: $type")
|
||||
}
|
||||
}
|
||||
|
||||
fun integerValue(): Int {
|
||||
return when(type) {
|
||||
in ByteDatatypes -> byteval!!.toInt()
|
||||
in WordDatatypes -> 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) {
|
||||
in ByteDatatypes -> Value(DataType.UBYTE, 0)
|
||||
in WordDatatypes -> 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) {
|
||||
in ByteDatatypes -> 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) {
|
||||
in ByteDatatypes -> 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,7 +17,8 @@ import kotlin.math.abs
|
||||
class AssemblyError(msg: String) : RuntimeException(msg)
|
||||
|
||||
|
||||
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
|
||||
@ -281,7 +283,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
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_S -> {
|
||||
val rawStr = heap.get(v.second.heapId).str!!
|
||||
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).replace("\u0000", "<NULL>")}\"")
|
||||
for (chunk in bytes.chunked(16))
|
||||
@ -333,7 +335,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
// float arraysize
|
||||
val array = heap.get(v.second.heapId).doubleArray!!
|
||||
val array = heap.get(v.second.heapId!!).doubleArray!!
|
||||
val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) }
|
||||
out(v.first)
|
||||
for(f in array.zip(floatFills))
|
||||
@ -357,8 +359,8 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeArrayFillDataUnsigned(value: Value): List<String> {
|
||||
val array = heap.get(value.heapId).array!!
|
||||
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
|
||||
@ -374,8 +376,8 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -417,7 +419,7 @@ 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")
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -183,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
|
||||
|
@ -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) },
|
||||
"sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { Math.sqrt(it.toDouble()).toInt() } },
|
||||
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sqrt) },
|
||||
"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) },
|
||||
"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),
|
||||
@ -111,12 +107,12 @@ val BuiltinFunctions = mapOf(
|
||||
)
|
||||
|
||||
|
||||
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope, heap: HeapValues): DataType? {
|
||||
fun builtinFunctionReturnType(function: String, args: List<IExpression>, program: Program): DataType? {
|
||||
|
||||
fun datatypeFromIterableArg(arglist: IExpression): DataType {
|
||||
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 arraysize of numeric values")
|
||||
}
|
||||
@ -126,15 +122,11 @@ 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) {
|
||||
in NumericDatatypes -> dt!!
|
||||
in StringDatatypes -> 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
|
||||
in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!)
|
||||
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
|
||||
}
|
||||
}
|
||||
@ -148,7 +140,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
|
||||
|
||||
return when (function) {
|
||||
"abs" -> {
|
||||
val dt = args.single().resultingDatatype(namespace, heap)
|
||||
val dt = args.single().inferType(program)
|
||||
when(dt) {
|
||||
in ByteDatatypes -> DataType.UBYTE
|
||||
in WordDatatypes -> DataType.UWORD
|
||||
@ -161,17 +153,12 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
|
||||
when(dt) {
|
||||
in NumericDatatypes -> dt
|
||||
in StringDatatypes -> 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 ArrayDatatypes -> ArrayElementTypes.getValue(dt)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
"sum" -> {
|
||||
val dt=datatypeFromIterableArg(args.single())
|
||||
when(dt) {
|
||||
when(datatypeFromIterableArg(args.single())) {
|
||||
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
|
||||
DataType.BYTE, DataType.WORD -> DataType.WORD
|
||||
DataType.FLOAT -> DataType.FLOAT
|
||||
@ -195,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)
|
||||
|
||||
@ -206,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)
|
||||
|
||||
@ -227,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()
|
||||
@ -242,9 +229,8 @@ 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)
|
||||
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()
|
||||
@ -258,19 +244,19 @@ 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)
|
||||
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()
|
||||
@ -281,32 +267,33 @@ private fun collectionArgOutputBoolean(args: List<IExpression>, position: Positi
|
||||
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 integerarray = heap.get(iterable.heapId!!).array
|
||||
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()
|
||||
@ -314,20 +301,20 @@ private fun builtinAvg(args: List<IExpression>, position: Position, namespace:IN
|
||||
throw ExpressionError("cannot avg() over array that does not only contain constant numerical values", position)
|
||||
}
|
||||
} else {
|
||||
val doublearray = heap.get(iterable.heapId).doubleArray
|
||||
val doublearray = program.heap.get(heapId).doubleArray
|
||||
doublearray?.average() ?: throw SyntaxError("avg requires array argument", position)
|
||||
}
|
||||
}
|
||||
return numericLiteral(result, args[0].position)
|
||||
}
|
||||
|
||||
private fun builtinStrlen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
||||
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(namespace, heap) ?: throw NotConstArgumentException()
|
||||
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(heap)
|
||||
val string = argument.strvalue!!
|
||||
val zeroIdx = string.indexOf('\u0000')
|
||||
return if(zeroIdx>=0)
|
||||
LiteralValue.optimalInteger(zeroIdx, position=position)
|
||||
@ -335,38 +322,38 @@ private fun builtinStrlen(args: List<IExpression>, position: Position, namespace
|
||||
LiteralValue.optimalInteger(string.length, position=position)
|
||||
}
|
||||
|
||||
private fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
|
||||
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(namespace)
|
||||
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 argument should be an identifier, but is ${args[0]}", position)
|
||||
val target = (args[0] as IdentifierReference).targetStatement(namespace)
|
||||
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
|
||||
val argValue = (target as? VarDecl)?.value
|
||||
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.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.optimalInteger(arraySize, args[0].position)
|
||||
}
|
||||
in StringDatatypes -> {
|
||||
val str = argument.strvalue(heap)
|
||||
val str = argument.strvalue!!
|
||||
if(str.length>255)
|
||||
throw CompilerException("string length exceeds byte limit ${argument.position}")
|
||||
LiteralValue.optimalInteger(str.length, args[0].position)
|
||||
@ -377,75 +364,75 @@ private fun builtinLen(args: List<IExpression>, position: Position, namespace:IN
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
@ -453,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)
|
||||
}
|
||||
|
@ -9,14 +9,14 @@ 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)
|
||||
@ -30,18 +30,37 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
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 && 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 -> {
|
||||
@ -49,11 +68,11 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
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(heap))
|
||||
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(heap)
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.resultingDatatype(namespace, heap)!!
|
||||
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 ) }
|
||||
@ -64,14 +83,14 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
.toTypedArray(), position=decl.value!!.position)
|
||||
}
|
||||
decl.value!!.linkParents(decl)
|
||||
optimizationsDone++
|
||||
return decl
|
||||
}
|
||||
}
|
||||
if(litval?.type==DataType.FLOAT)
|
||||
errors.add(ExpressionError("arraysize requires only integers here", litval.position))
|
||||
if(decl.arraysize==null)
|
||||
return decl
|
||||
val size = decl.arraysize!!.size()
|
||||
if ((litval==null || !litval.isArray) && size != null && rangeExpr==null) {
|
||||
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){
|
||||
@ -93,29 +112,34 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
val heapId = heap.addIntegerArray(decl.datatype, Array(size) { IntegerOrAddressOf(fillvalue, null) })
|
||||
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 -> {
|
||||
if(decl.arraysize==null)
|
||||
return decl
|
||||
val size = decl.arraysize!!.size()
|
||||
if ((litval==null || !litval.isArray) && size != null) {
|
||||
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 heapId = heap.addDoublesArray(DoubleArray(size) { fillvalue })
|
||||
decl.value = LiteralValue(DataType.ARRAY_F, 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) {
|
||||
@ -125,20 +149,20 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
if(decl.datatype==litval.type)
|
||||
return // already correct datatype
|
||||
val heapId = litval.heapId ?: throw FatalAstException("expected array to be on heap $litval")
|
||||
val array=heap.get(heapId)
|
||||
val array = program.heap.get(heapId)
|
||||
when(decl.datatype) {
|
||||
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.integer!!.toDouble() }.toDoubleArray()
|
||||
heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray))
|
||||
decl.value = LiteralValue(decl.datatype, heapId = heapId, position = litval.position)
|
||||
program.heap.update(heapId, HeapValues.HeapValue(DataType.ARRAY_F, null, null, doubleArray))
|
||||
decl.value = LiteralValue(decl.datatype, initHeapId = heapId, position = litval.position)
|
||||
}
|
||||
}
|
||||
else -> throw FatalAstException("invalid array vardecl type ${decl.datatype}")
|
||||
@ -150,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
|
||||
@ -167,7 +191,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
return try {
|
||||
super.process(functionCall)
|
||||
typeCastConstArguments(functionCall)
|
||||
functionCall.constValue(namespace, heap) ?: functionCall
|
||||
functionCall.constValue(program) ?: functionCall
|
||||
} catch (ax: AstException) {
|
||||
addError(ax)
|
||||
functionCall
|
||||
@ -181,12 +205,12 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
}
|
||||
|
||||
private fun typeCastConstArguments(functionCall: IFunctionCall) {
|
||||
val subroutine = functionCall.target.targetSubroutine(namespace)
|
||||
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(namespace, heap)
|
||||
val argConst = arg.first.value.constValue(program)
|
||||
if(argConst!=null && argConst.type!=expectedDt) {
|
||||
val convertedValue = argConst.intoDatatype(expectedDt)
|
||||
if(convertedValue!=null) {
|
||||
@ -287,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
|
||||
@ -296,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,
|
||||
@ -311,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
|
||||
}
|
||||
@ -330,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) {
|
||||
@ -533,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!!.targetVarDecl(namespace)
|
||||
val loopvar = resultStmt.loopVar?.targetVarDecl(program.namespace)
|
||||
if(loopvar!=null) {
|
||||
val stepLiteral = iterableRange.step as? LiteralValue
|
||||
when(loopvar.datatype) {
|
||||
@ -568,91 +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.addString(literalValue.type, literalValue.strvalue(heap)) // TODO: we don't know the actual string type yet, STR != STR_S etc...
|
||||
val newValue = LiteralValue(literalValue.type, heapId = heapId, position = literalValue.position)
|
||||
return super.process(newValue)
|
||||
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 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", arraylit.position))
|
||||
return arraylit
|
||||
} else if(array.any {it is AddressOf}) {
|
||||
val arrayDt = DataType.ARRAY_UW
|
||||
val intArrayWithAddressOfs = array.map {
|
||||
when (it) {
|
||||
is AddressOf -> IntegerOrAddressOf(null, it)
|
||||
is LiteralValue -> IntegerOrAddressOf(it.asIntegerValue, null)
|
||||
else -> throw CompilerException("invalid datatype in array")
|
||||
}
|
||||
}
|
||||
val heapId = heap.addIntegerArray(arrayDt, intArrayWithAddressOfs.toTypedArray())
|
||||
return LiteralValue(arrayDt, heapId = heapId, position = arraylit.position)
|
||||
} else {
|
||||
// array is only constant numerical values
|
||||
val valuesInArray = array.map { it.constValue(namespace, heap)!!.asNumericValue!! }
|
||||
val integerArray = valuesInArray.map{ it.toInt() }
|
||||
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.addIntegerArray(arrayDt, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
|
||||
DataType.ARRAY_F -> heap.addDoublesArray(doubleArray)
|
||||
else -> throw CompilerException("invalid arraysize 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)
|
||||
|
@ -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 {
|
||||
@ -34,6 +36,27 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
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
|
||||
@ -83,13 +106,13 @@ 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 (where ddd
|
||||
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
|
||||
@ -296,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
|
||||
}
|
||||
}
|
||||
@ -363,7 +386,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
|
||||
if(leftConstVal==null && rightConstVal!=null) {
|
||||
if(isBiggerType(leftDt, rightDt)) {
|
||||
if(leftDt biggerThan rightDt) {
|
||||
val (adjusted, newValue) = adjust(rightConstVal, leftDt)
|
||||
if (adjusted) {
|
||||
expr.right = newValue
|
||||
@ -373,7 +396,7 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
return false
|
||||
} else if(leftConstVal!=null && rightConstVal==null) {
|
||||
if(isBiggerType(rightDt, leftDt)) {
|
||||
if(rightDt biggerThan leftDt) {
|
||||
val (adjusted, newValue) = adjust(leftConstVal, rightDt)
|
||||
if (adjusted) {
|
||||
expr.left = newValue
|
||||
@ -387,14 +410,6 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBiggerType(type: DataType, other: DataType) =
|
||||
when(type) {
|
||||
in ByteDatatypes -> false
|
||||
in WordDatatypes -> other in ByteDatatypes
|
||||
else -> true
|
||||
}
|
||||
|
||||
|
||||
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: LiteralValue?, val rightVal: LiteralValue?)
|
||||
|
||||
private fun reorderAssociative(expr: BinaryExpression, leftVal: LiteralValue?): ReorderedAssociativeBinaryExpr {
|
||||
@ -404,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 {
|
||||
@ -550,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 = "&"
|
||||
@ -573,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
|
||||
@ -644,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++
|
||||
@ -662,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()
|
||||
@ -670,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,7 +1,6 @@
|
||||
package prog8.optimizing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.target.c64.Petscii
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import kotlin.math.floor
|
||||
@ -9,44 +8,151 @@ import kotlin.math.floor
|
||||
|
||||
/*
|
||||
todo: subroutines with 1 or 2 byte args or 1 word arg can be converted to asm sub calling convention (args in registers)
|
||||
|
||||
|
||||
todo: 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 (max 3?)
|
||||
todo inline subroutines 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 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,11 +173,29 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
|
||||
}
|
||||
|
||||
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
|
||||
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
|
||||
optimizationsDone++
|
||||
return NopStatement(subroutine.position) // remove unused subroutine
|
||||
}
|
||||
|
||||
return subroutine
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -82,9 +206,9 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
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
|
||||
@ -95,32 +219,13 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
return linesToRemove
|
||||
}
|
||||
|
||||
private fun isNotMemory(target: AssignTarget): Boolean {
|
||||
if(target.register!=null)
|
||||
return true
|
||||
if(target.memoryAddress!=null)
|
||||
return false
|
||||
if(target.arrayindexed!=null) {
|
||||
val targetStmt = target.arrayindexed.identifier.targetVarDecl(namespace)
|
||||
if(targetStmt!=null)
|
||||
return targetStmt.type!=VarDeclType.MEMORY
|
||||
}
|
||||
if(target.identifier!=null) {
|
||||
val targetStmt = target.identifier.targetVarDecl(namespace)
|
||||
if(targetStmt!=null)
|
||||
return targetStmt.type!=VarDeclType.MEMORY
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
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)", functionCallStatement.position)
|
||||
statementsToRemove.add(functionCallStatement)
|
||||
return functionCallStatement
|
||||
optimizationsDone++
|
||||
return NopStatement(functionCallStatement.position)
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,8 +236,8 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
throw AstException("string argument should be on heap already")
|
||||
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]
|
||||
functionCallStatement.arglist.clear()
|
||||
@ -156,7 +261,7 @@ 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 = functionCallStatement.target.targetSubroutine(namespace)
|
||||
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) {
|
||||
@ -176,7 +281,7 @@ 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 with constant value, replace with the constant value
|
||||
val subroutine = functionCall.target.targetSubroutine(namespace)
|
||||
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) {
|
||||
@ -184,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
|
||||
}
|
||||
@ -195,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)
|
||||
@ -210,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
|
||||
@ -229,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)
|
||||
@ -261,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)
|
||||
@ -287,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)
|
||||
@ -341,7 +443,7 @@ class StatementOptimizer(private val namespace: INameScope, private val heap: He
|
||||
}
|
||||
|
||||
override fun process(jump: Jump): IStatement {
|
||||
val subroutine = jump.identifier?.targetSubroutine(namespace)
|
||||
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()
|
||||
@ -350,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
|
||||
}
|
||||
|
||||
@ -359,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++
|
||||
@ -377,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?.targetVarDecl(namespace))?.type
|
||||
val vardeclDt = (target.identifier?.targetVarDecl(program.namespace))?.type
|
||||
|
||||
when (bexpr.operator) {
|
||||
"+" -> {
|
||||
@ -486,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.index.constValue(namespace, heap)
|
||||
val x2 = target2.arrayindexed.arrayspec.index.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.index, left.arrayspec.index))
|
||||
}
|
||||
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() {
|
||||
@ -29,8 +22,36 @@ private class LexerErrorListener: BaseErrorListener() {
|
||||
internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexer(input)
|
||||
|
||||
|
||||
fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
|
||||
val moduleName = modulePath.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()
|
||||
if(importloc.startsWith(curdir))
|
||||
importloc = "." + importloc.substring(curdir.length)
|
||||
println(" (from '$importloc')")
|
||||
}
|
||||
else
|
||||
println("")
|
||||
if(!Files.isReadable(filePath))
|
||||
throw ParsingFailedError("No such file: $filePath")
|
||||
|
||||
val input = CharStreams.fromPath(filePath)
|
||||
return importModule(program, input, filePath, filePath.parent==null)
|
||||
}
|
||||
|
||||
internal fun importLibraryModule(program: Program, name: String): Module? {
|
||||
val import = Directive("%import", listOf(
|
||||
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0 ,0))
|
||||
), Position("<<<implicit-import>>>", 0, 0 ,0))
|
||||
return executeImportDirective(program, import, Paths.get(""))
|
||||
}
|
||||
|
||||
internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
|
||||
val moduleName = moduleName(modulePath.fileName)
|
||||
val lexer = CustomLexer(modulePath, stream)
|
||||
val lexerErrors = LexerErrorListener()
|
||||
lexer.addErrorListener(lexerErrors)
|
||||
@ -45,68 +66,25 @@ fun importModule(stream: CharStream, modulePath: Path, isLibrary: Boolean): Modu
|
||||
// tokens.commentTokens().forEach { println(it) }
|
||||
|
||||
// convert to Ast
|
||||
val moduleAst = parseTree.toAst(moduleName.toString(), isLibrary, modulePath)
|
||||
importedModules[moduleAst.name] = moduleAst
|
||||
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
|
||||
moduleAst.program = program
|
||||
moduleAst.linkParents(program.namespace)
|
||||
program.modules.add(moduleAst)
|
||||
|
||||
// process imports
|
||||
// process additional 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, modulePath)) }
|
||||
.toList()
|
||||
|
||||
imports.reversed().forEach {
|
||||
if(it.second==null) {
|
||||
// this import was already satisfied. just remove this line.
|
||||
lines.removeAt(it.first)
|
||||
} else {
|
||||
// merge imported lines at this spot
|
||||
lines.addAll(it.first, it.second!!.statements)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
fun importModule(filePath: Path) : Module {
|
||||
print("importing '${filePath.fileName}'")
|
||||
if(filePath.parent!=null) {
|
||||
var importloc = filePath.toString()
|
||||
val curdir = Paths.get("").toAbsolutePath().toString()
|
||||
if(importloc.startsWith(curdir))
|
||||
importloc = "." + importloc.substring(curdir.length)
|
||||
println(" (from '$importloc')")
|
||||
}
|
||||
else
|
||||
println("")
|
||||
if(!Files.isReadable(filePath))
|
||||
throw ParsingFailedError("No such file: $filePath")
|
||||
|
||||
val input = CharStreams.fromPath(filePath)
|
||||
return importModule(input, filePath, filePath.parent==null)
|
||||
}
|
||||
|
||||
|
||||
private fun discoverImportedModuleFile(name: String, importedFrom: Path, position: Position?): Path {
|
||||
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)
|
||||
@ -124,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")
|
||||
@ -138,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), Paths.get("@embedded@/$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,6 +1,7 @@
|
||||
package prog8.stackvm
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.RuntimeValue
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.compiler.IntegerOrAddressOf
|
||||
import prog8.compiler.intermediate.*
|
||||
@ -10,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, Int>,
|
||||
val memory: Map<Int, List<Value>>,
|
||||
val memory: Map<Int, List<RuntimeValue>>,
|
||||
val heap: HeapValues)
|
||||
{
|
||||
init {
|
||||
@ -26,10 +27,10 @@ class Program (val name: String,
|
||||
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, Int>()
|
||||
|
||||
@ -51,7 +52,7 @@ 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, Int>)
|
||||
{
|
||||
@ -122,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()
|
||||
@ -143,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)
|
||||
}
|
||||
@ -158,15 +159,15 @@ class Program (val name: String,
|
||||
Opcode.SYSCALL -> {
|
||||
if(args!! in syscallNames) {
|
||||
val call = Syscall.valueOf(args)
|
||||
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
|
||||
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
|
||||
} else {
|
||||
val args2 = args.replace('.', '_')
|
||||
if(args2 in syscallNames) {
|
||||
val call = Syscall.valueOf(args2)
|
||||
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
|
||||
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
|
||||
} else {
|
||||
// the syscall is not yet implemented. emit a stub.
|
||||
Instruction(Opcode.SYSCALL, Value(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
|
||||
Instruction(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -190,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]=='"') {
|
||||
@ -198,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()
|
||||
@ -221,13 +222,12 @@ 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())
|
||||
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")
|
||||
@ -235,7 +235,7 @@ class Program (val name: String,
|
||||
throw VmExecutionException("invalid string value, should be a heap reference")
|
||||
else {
|
||||
val heapId = valueStr.substring(5).toInt()
|
||||
Value(type, heapId)
|
||||
RuntimeValue(type, heapId = heapId)
|
||||
}
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
@ -243,7 +243,7 @@ class Program (val name: String,
|
||||
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")
|
||||
@ -269,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")
|
||||
@ -280,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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -47,20 +48,20 @@ 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 printText(text: String, color: Int, lowercase: Boolean) {
|
||||
fun printText(text: String, color: Short, lowercase: Boolean) {
|
||||
val lines = text.split('\n')
|
||||
for(line in lines.withIndex()) {
|
||||
printTextSingleLine(line.value, color, lowercase)
|
||||
@ -70,15 +71,12 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun printTextSingleLine(text: String, color: Int, lowercase: Boolean) {
|
||||
if(color!=1) {
|
||||
TODO("text can only be white for now")
|
||||
}
|
||||
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)
|
||||
setChar(cursorX, cursorY, sc, color)
|
||||
cursorX++
|
||||
if(cursorX>=(SCREENWIDTH/8)) {
|
||||
cursorY++
|
||||
@ -92,7 +90,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
cursorX=0
|
||||
cursorY++
|
||||
} else {
|
||||
setChar(cursorX, cursorY, char)
|
||||
setChar(cursorX, cursorY, char, 1)
|
||||
cursorX++
|
||||
if (cursorX >= (SCREENWIDTH / 8)) {
|
||||
cursorY++
|
||||
@ -101,9 +99,11 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
}
|
||||
}
|
||||
|
||||
fun setChar(x: Int, y: Int, screenCode: Short) {
|
||||
fun setChar(x: Int, y: Int, screenCode: Short, color: Short) {
|
||||
g2d.clearRect(8*x, 8*y, 8, 8)
|
||||
g2d.drawImage(Charset.shiftedChars[screenCode.toInt()], 8*x, 8*y , null)
|
||||
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) {
|
||||
@ -115,16 +115,13 @@ class BitmapScreenPanel : KeyListener, JPanel() {
|
||||
return Pair(cursorX, cursorY)
|
||||
}
|
||||
|
||||
fun writeText(x: Int, y: Int, text: String, color: Int, lowercase: Boolean) {
|
||||
fun writeText(x: Int, y: Int, text: String, color: Short, lowercase: Boolean) {
|
||||
var xx=x
|
||||
if(color!=1) {
|
||||
TODO("text can only be white for now")
|
||||
}
|
||||
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)
|
||||
setChar(xx++, y, sc, color)
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,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,181 +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, 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.*
|
||||
@ -268,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(
|
||||
@ -358,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)
|
||||
|
@ -2,9 +2,7 @@
|
||||
<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>
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.7 (py3)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
|
@ -523,7 +523,8 @@ And this is a loop over the values of the array ``fibonacci_numbers`` where the
|
||||
}
|
||||
|
||||
|
||||
You can inline the loop variable declaration in the for statement, including optional zp-tag::
|
||||
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
|
||||
|
@ -2,8 +2,9 @@
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/compiled" />
|
||||
</content>
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -38,9 +38,13 @@
|
||||
; found next one, mark the multiples and return it.
|
||||
sieve[candidate_prime] = true
|
||||
uword multiple = candidate_prime
|
||||
|
||||
|
||||
while multiple < len(sieve) {
|
||||
sieve[lsb(multiple)] = true
|
||||
multiple += candidate_prime
|
||||
; c64scr.print_uw(multiple) ; TODO
|
||||
; c4.CHROUT('\n') ; TODO
|
||||
}
|
||||
return candidate_prime
|
||||
}
|
||||
|
@ -1,28 +1,57 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
%option enable_floats
|
||||
%import c64flt
|
||||
|
||||
|
||||
~ main {
|
||||
|
||||
float[] fa = [1.1,2.2,3.3]
|
||||
ubyte[] uba = [10,2,3,4]
|
||||
byte[] ba = [-10,2,3,4]
|
||||
uword[] uwa = [100,20,30,40]
|
||||
word[] wa = [-100,20,30,40]
|
||||
|
||||
sub start() {
|
||||
|
||||
float a
|
||||
a=avg([1,2,3,4])
|
||||
c64flt.print_f(a)
|
||||
c64.CHROUT('\n')
|
||||
a=avg([100,200,300,400])
|
||||
c64flt.print_f(a)
|
||||
c64.CHROUT('\n')
|
||||
a=avg([1.1,2.2,3.3,4.4])
|
||||
c64flt.print_f(a)
|
||||
c64.CHROUT('\n')
|
||||
ubyte x = 99
|
||||
|
||||
return
|
||||
|
||||
startqqq:
|
||||
|
||||
sub startzzz() {
|
||||
if_cc goto startqqq
|
||||
c64.EXTCOL++
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
; for ubyte y in 0 to 3 {
|
||||
; for ubyte x in 0 to 10 {
|
||||
; ubyte product = x*y
|
||||
; c64scr.setcc(x, y, 160, product)
|
||||
; }
|
||||
; }
|
||||
; c64.CHROUT('\n')
|
||||
; c64.CHROUT('\n')
|
||||
;
|
||||
; for ubyte y in 12 to 15 {
|
||||
; for ubyte x in 0 to 10 {
|
||||
; ubyte sumv = x+y
|
||||
; c64scr.setcc(x, y, 160, sumv)
|
||||
; }
|
||||
; }
|
||||
|
||||
;ubyte bb = len(xcoor)
|
||||
|
||||
; storage for rotated coordinates
|
||||
; ubyte[len(xcoor)] xx = 2
|
||||
; float[len(xcoor)] rotatedx=0.0
|
||||
;
|
||||
; ubyte[4] x = 23
|
||||
; float[4] yy = 4.4
|
||||
|
||||
; c64flt.print_f(xcoor[1])
|
||||
; c64.CHROUT(',')
|
||||
; c64flt.print_f(xcoor[2])
|
||||
; c64.CHROUT('\n')
|
||||
; swap(xcoor[1], xcoor[2])
|
||||
; c64flt.print_f(xcoor[1])
|
||||
; c64.CHROUT(',')
|
||||
; c64flt.print_f(xcoor[2])
|
||||
; c64.CHROUT('\n')
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
@echo off
|
||||
|
||||
set PROG8CLASSPATH=./out/production/compiler_main;./out/production/parser_main
|
||||
set PROG8CLASSPATH=./compiler/build/classes/kotlin/main;./compiler/build/resources/main;./parser/build/classes/java/main
|
||||
set KOTLINPATH=%USERPROFILE%/.IdeaIC2019.1/config/plugins/Kotlin
|
||||
set LIBJARS=%KOTLINPATH%/lib/kotlin-stdlib.jar;%KOTLINPATH%/lib/kotlin-reflect.jar;./parser/antlr/lib/antlr-runtime-4.7.2.jar
|
||||
|
||||
java -cp %PROG8CLASSPATH%;%LIBJARS% prog8.CompilerMainKt %*
|
||||
|
||||
@REM if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar
|
||||
|
@ -5,3 +5,5 @@ KOTLINPATH=${HOME}/.IntelliJIdea2019.1/config/plugins/Kotlin
|
||||
LIBJARS=${KOTLINPATH}/lib/kotlin-stdlib.jar:${KOTLINPATH}/lib/kotlin-reflect.jar:./parser/antlr/lib/antlr-runtime-4.7.2.jar
|
||||
|
||||
java -cp ${PROG8CLASSPATH}:${LIBJARS} prog8.CompilerMainKt $*
|
||||
|
||||
# if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar
|
||||
|
4
p8vm.cmd
4
p8vm.cmd
@ -1,7 +1,9 @@
|
||||
@echo off
|
||||
|
||||
set PROG8CLASSPATH=./out/production/compiler_main
|
||||
set PROG8CLASSPATH=./compiler/build/classes/kotlin/main;./compiler/build/resources/main
|
||||
set KOTLINPATH=%USERPROFILE%/.IdeaIC2019.1/config/plugins/Kotlin
|
||||
set LIBJARS=%KOTLINPATH%/lib/kotlin-stdlib.jar;%KOTLINPATH%/lib/kotlin-reflect.jar
|
||||
|
||||
java -cp %PROG8CLASSPATH%;%LIBJARS% prog8.StackVmMainKt %*
|
||||
|
||||
@REM if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar -vm
|
||||
|
2
p8vm.sh
2
p8vm.sh
@ -5,3 +5,5 @@ KOTLINPATH=${HOME}/.IntelliJIdea2019.1/config/plugins/Kotlin
|
||||
LIBJARS=${KOTLINPATH}/lib/kotlin-stdlib.jar:${KOTLINPATH}/lib/kotlin-reflect.jar
|
||||
|
||||
java -cp ${PROG8CLASSPATH}:${LIBJARS} prog8.StackVmMainKt $*
|
||||
|
||||
# if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar -vm
|
||||
|
13
parser/parser.iml
Normal file
13
parser/parser.iml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="antlr-4.7.2-complete" level="project" />
|
||||
</component>
|
||||
</module>
|
Reference in New Issue
Block a user