mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 20:02:27 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac70ae6a76 | ||
|
|
d83f49d84f | ||
|
|
ff1294207e | ||
|
|
a56956797a | ||
|
|
3242495b0b | ||
|
|
49eb7e7803 | ||
|
|
1d7f0d3537 | ||
|
|
31137743f0 | ||
|
|
2c69e10489 | ||
|
|
3a1fa9e069 | ||
|
|
2c08d2f9c6 | ||
|
|
4743cacb73 | ||
|
|
5f5a1447e0 | ||
|
|
a3004555a8 | ||
|
|
267c678292 | ||
|
|
6c50043a4a | ||
|
|
3ee1b2efdd | ||
|
|
75d8c832ad | ||
|
|
53a4379c45 | ||
|
|
29b3a7e94e | ||
|
|
0782f6ecf1 | ||
|
|
595e58ec46 | ||
|
|
060e05c868 | ||
|
|
f49eefad6f | ||
|
|
d68360461b | ||
|
|
343978d164 | ||
|
|
b11d10e2ff | ||
|
|
268856823a | ||
|
|
4bac5043b6 | ||
|
|
eb25b4c800 | ||
|
|
a079e44b02 | ||
|
|
e53c860f1a | ||
|
|
99121004bf | ||
|
|
6dd3371781 | ||
|
|
f473be8951 | ||
|
|
ebd38f27e6 | ||
|
|
a6c3251668 | ||
|
|
560047adee | ||
|
|
a86852874f | ||
|
|
6d44d6a901 | ||
|
|
968f02823f | ||
|
|
5d321d759e | ||
|
|
7de7d5234f | ||
|
|
b374af3526 | ||
|
|
b35430214b | ||
|
|
e96d3d4455 | ||
|
|
6a17f7a0ad | ||
|
|
c559682c0b | ||
|
|
6ce1277438 | ||
|
|
262e0bd6b9 | ||
|
|
755af6010e | ||
|
|
0298cf8b90 | ||
|
|
a6d0aecd66 | ||
|
|
ef6e364339 | ||
|
|
3b37e0f99d | ||
|
|
78fbbf7119 | ||
|
|
0ee43294c4 | ||
|
|
a81b82495c | ||
|
|
390043e9e8 | ||
|
|
e384822b2c | ||
|
|
730e08698d | ||
|
|
5497de4234 | ||
|
|
c71b78dee6 | ||
|
|
dfcb57a0b0 |
No files matched your search
+1
-1
@@ -1,4 +1,4 @@
|
||||
**/.idea/
|
||||
.idea/workspace.xml
|
||||
/build/
|
||||
/dist/
|
||||
/output/
|
||||
|
||||
Generated
+6
@@ -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>
|
||||
Generated
+19
@@ -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>
|
||||
Generated
+9
@@ -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>
|
||||
Generated
+9
@@ -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>
|
||||
Generated
+10
@@ -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>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
Generated
+11
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+124
@@ -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>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
language: java
|
||||
# jdk: openjdk8
|
||||
# dist: xenial
|
||||
# sudo: false
|
||||
|
||||
before_install:
|
||||
- chmod +x gradlew
|
||||
|
||||
script:
|
||||
- ./gradlew test
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
rm *.jar *.asm *.prg *.vm.txt *.vice-mon-list
|
||||
rm -r build
|
||||
|
||||
+37
-5
@@ -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')
|
||||
@@ -18,11 +19,19 @@ dependencies {
|
||||
runtime 'org.antlr:antlr4-runtime:4.7.2'
|
||||
runtime project(':parser')
|
||||
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion"
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
|
||||
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
|
||||
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
|
||||
}
|
||||
|
||||
compileKotlin {
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
// freeCompilerArgs += "-XXLanguage:+NewInference"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
@@ -55,8 +64,8 @@ task p8vmScript(type: CreateStartScripts) {
|
||||
}
|
||||
|
||||
applicationDistribution.into("bin") {
|
||||
from(p8vmScript)
|
||||
fileMode = 0755
|
||||
from(p8vmScript)
|
||||
fileMode = 0755
|
||||
}
|
||||
|
||||
task fatJar(type: Jar) {
|
||||
@@ -71,3 +80,26 @@ task fatJar(type: Jar) {
|
||||
with jar
|
||||
}
|
||||
// build.finalizedBy(fatJar)
|
||||
|
||||
|
||||
// Java target version
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
test {
|
||||
// Enable JUnit 5 (Gradle 4.6+).
|
||||
useJUnitPlatform()
|
||||
|
||||
// Always run tests, even when nothing changed.
|
||||
dependsOn 'cleanTest'
|
||||
|
||||
// Show test results.
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dokka {
|
||||
outputFormat = 'html'
|
||||
outputDirectory = "$buildDir/kdoc"
|
||||
}
|
||||
@@ -3,15 +3,17 @@
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
|
||||
<orderEntry type="library" name="testlibs" level="project" />
|
||||
<orderEntry type="module" module-name="parser" />
|
||||
<orderEntry type="library" name="unittest-libs" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1 +1 @@
|
||||
1.6
|
||||
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)
|
||||
}
|
||||
+511
-223
File diff suppressed because it is too large.
Load diff
@@ -5,16 +5,16 @@ import prog8.compiler.HeapValues
|
||||
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
|
||||
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
|
||||
import prog8.functions.BuiltinFunctions
|
||||
import prog8.optimizing.same
|
||||
import prog8.parser.ParsingFailedError
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* General checks on the Ast
|
||||
*/
|
||||
|
||||
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions, heap: HeapValues) {
|
||||
val checker = AstChecker(globalNamespace, compilerOptions, heap)
|
||||
this.process(checker)
|
||||
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
|
||||
val checker = AstChecker(this, compilerOptions)
|
||||
checker.process(this)
|
||||
printErrors(checker.result(), name)
|
||||
}
|
||||
|
||||
@@ -51,20 +51,71 @@ fun printWarning(msg: String) {
|
||||
print("\u001b[0m\n") // normal
|
||||
}
|
||||
|
||||
private class AstChecker(private val namespace: INameScope,
|
||||
private val compilerOptions: CompilationOptions,
|
||||
private val heap: HeapValues) : IAstProcessor {
|
||||
private class AstChecker(private val program: Program,
|
||||
private val compilerOptions: CompilationOptions) : IAstProcessor {
|
||||
private val checkResult: MutableList<AstException> = mutableListOf()
|
||||
private val heapStringSentinel: Int
|
||||
init {
|
||||
val stringSentinel = heap.allEntries().firstOrNull {it.value.str==""}
|
||||
heapStringSentinel = stringSentinel?.key ?: heap.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 }
|
||||
@@ -74,45 +125,6 @@ private class AstChecker(private val namespace: INameScope,
|
||||
entry.value.mapTo(checkResult) { SyntaxError("directive can just occur once", it.position) }
|
||||
}
|
||||
}
|
||||
|
||||
// there must be a 'main' block with a 'start' subroutine for the program entry point.
|
||||
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" } as? Block?
|
||||
val startSub = mainBlock?.subScopes()?.get("start") as? Subroutine
|
||||
if(startSub==null) {
|
||||
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", module.position))
|
||||
} else {
|
||||
if(startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
|
||||
checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", startSub.position))
|
||||
}
|
||||
|
||||
if(mainBlock!=null) {
|
||||
// the main module cannot contain 'regular' statements (they will never be executed!)
|
||||
for (statement in mainBlock.statements) {
|
||||
val ok = when(statement) {
|
||||
is Block->true
|
||||
is Directive->true
|
||||
is Label->true
|
||||
is VarDecl->true
|
||||
is InlineAssembly->true
|
||||
is INameScope->true
|
||||
is VariableInitializationAssignment->true
|
||||
else->false
|
||||
}
|
||||
if(!ok) {
|
||||
checkResult.add(SyntaxError("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// there can be an optional 'irq' block with a 'irq' subroutine in it,
|
||||
// which will be used as the 60hz irq routine in the vm if it's present
|
||||
val irqBlock = module.statements.singleOrNull { it is Block && it.name=="irq" } as? Block?
|
||||
val irqSub = irqBlock?.subScopes()?.get("irq") as? Subroutine
|
||||
if(irqSub!=null) {
|
||||
if(irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
|
||||
checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position))
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(returnStmt: Return): IStatement {
|
||||
@@ -120,7 +132,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(expectedReturnValues.size != returnStmt.values.size) {
|
||||
// if the return value is a function call, check the result of that call instead
|
||||
if(returnStmt.values.size==1 && returnStmt.values[0] is FunctionCall) {
|
||||
val dt = (returnStmt.values[0] as FunctionCall).resultingDatatype(namespace, heap)
|
||||
val dt = (returnStmt.values[0] as FunctionCall).inferType(program)
|
||||
if(dt!=null && expectedReturnValues.isEmpty())
|
||||
checkResult.add(SyntaxError("invalid number of return values", returnStmt.position))
|
||||
} else
|
||||
@@ -128,7 +140,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
for (rv in expectedReturnValues.withIndex().zip(returnStmt.values)) {
|
||||
val valueDt=rv.second.resultingDatatype(namespace, heap)
|
||||
val valueDt=rv.second.inferType(program)
|
||||
if(rv.first.value!=valueDt)
|
||||
checkResult.add(ExpressionError("type $valueDt of return value #${rv.first.index+1} doesn't match subroutine return type ${rv.first.value}", rv.second.position))
|
||||
}
|
||||
@@ -136,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
|
||||
@@ -150,7 +162,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position))
|
||||
} else {
|
||||
// loop variable
|
||||
val loopvar = forLoop.loopVar!!.targetStatement(namespace) as? VarDecl
|
||||
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
|
||||
if(loopvar==null || loopvar.type==VarDeclType.CONST) {
|
||||
checkResult.add(SyntaxError("for loop requires a variable to loop with", forLoop.position))
|
||||
|
||||
@@ -257,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)
|
||||
@@ -354,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))
|
||||
}
|
||||
}
|
||||
@@ -378,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))
|
||||
@@ -386,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))
|
||||
@@ -424,23 +436,23 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return assignment2
|
||||
}
|
||||
|
||||
val targetDatatype = target.determineDatatype(namespace, heap, assignment)
|
||||
val targetDatatype = target.inferType(program, assignment)
|
||||
if(targetDatatype!=null) {
|
||||
val constVal = assignment.value.constValue(namespace, heap)
|
||||
val constVal = assignment.value.constValue(program)
|
||||
if(constVal!=null) {
|
||||
val arrayspec = if(target.identifier!=null) {
|
||||
val targetVar = namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
|
||||
targetVar?.arrayspec
|
||||
val targetVar = program.namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
|
||||
targetVar?.arraysize
|
||||
} else null
|
||||
checkValueTypeAndRange(targetDatatype,
|
||||
arrayspec ?: ArraySpec(LiteralValue.optimalInteger(-1, assignment.position), assignment.position),
|
||||
constVal, heap)
|
||||
arrayspec ?: ArrayIndex(LiteralValue.optimalInteger(-1, assignment.position), assignment.position),
|
||||
constVal, program.heap)
|
||||
} else {
|
||||
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap)
|
||||
val sourceDatatype: DataType? = assignment.value.inferType(program)
|
||||
if(sourceDatatype==null) {
|
||||
if(assignment.targets.size<=1) {
|
||||
if (assignment.value is FunctionCall) {
|
||||
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(namespace)
|
||||
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
|
||||
if(targetStmt!=null)
|
||||
checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position))
|
||||
}
|
||||
@@ -456,7 +468,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
override fun process(addressOf: AddressOf): IExpression {
|
||||
val variable=addressOf.identifier.targetStatement(namespace) as? VarDecl
|
||||
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 {
|
||||
@@ -477,7 +489,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arrayspec?.x?.referencesIdentifier(decl.name) == true) {
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
|
||||
err("recursive var declaration")
|
||||
}
|
||||
|
||||
@@ -488,22 +500,43 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
// FLOATS
|
||||
if(!compilerOptions.floats && decl.datatype==DataType.FLOAT && decl.type!=VarDeclType.MEMORY) {
|
||||
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!=VarDeclType.MEMORY) {
|
||||
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", decl.position))
|
||||
}
|
||||
|
||||
// ARRAY without size specifier MUST have an iterable initializer value
|
||||
if(decl.isArray && decl.arraysize==null) {
|
||||
if(decl.type==VarDeclType.MEMORY)
|
||||
checkResult.add(SyntaxError("memory mapped array must have a size specification", decl.position))
|
||||
if(decl.value==null) {
|
||||
checkResult.add(SyntaxError("array variable is missing a size specification or an initialization value", decl.position))
|
||||
return decl
|
||||
}
|
||||
if(decl.value is LiteralValue && !(decl.value as LiteralValue).isArray) {
|
||||
checkResult.add(SyntaxError("unsized array declaration cannot use a single literal initialization value", decl.position))
|
||||
return decl
|
||||
}
|
||||
if(decl.value is RangeExpr)
|
||||
throw FatalAstException("range expressions in vardecls should have been converted into array values during constFolding $decl")
|
||||
}
|
||||
|
||||
when(decl.type) {
|
||||
VarDeclType.VAR, VarDeclType.CONST -> {
|
||||
if (decl.value == null) {
|
||||
when {
|
||||
decl.datatype in NumericDatatypes -> {
|
||||
// initialize numeric var with value zero by default.
|
||||
val litVal = LiteralValue(DataType.UBYTE, 0, position = decl.position)
|
||||
val litVal =
|
||||
when {
|
||||
decl.datatype in ByteDatatypes -> LiteralValue(decl.datatype, bytevalue=0, position = decl.position)
|
||||
decl.datatype in WordDatatypes -> LiteralValue(decl.datatype, wordvalue=0, position = decl.position)
|
||||
else -> LiteralValue(decl.datatype, floatvalue=0.0, position = decl.position)
|
||||
}
|
||||
litVal.parent = decl
|
||||
decl.value = litVal
|
||||
}
|
||||
decl.type==VarDeclType.VAR -> {
|
||||
val litVal = LiteralValue(decl.datatype, heapId = heapStringSentinel, position=decl.position) // point to the sentinel heap value instead
|
||||
val litVal = LiteralValue(decl.datatype, initHeapId = heapStringSentinel, position=decl.position) // point to the sentinel heap value instead
|
||||
litVal.parent=decl
|
||||
decl.value = litVal
|
||||
}
|
||||
@@ -513,15 +546,18 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return super.process(decl)
|
||||
}
|
||||
when {
|
||||
decl.value is RangeExpr -> checkValueTypeAndRange(decl.datatype, decl.arrayspec, decl.value as RangeExpr)
|
||||
decl.value is RangeExpr -> {
|
||||
if(decl.arraysize!=null)
|
||||
checkValueTypeAndRange(decl.datatype, decl.arraysize!!, decl.value as RangeExpr)
|
||||
}
|
||||
decl.value is LiteralValue -> {
|
||||
val arraySpec = decl.arrayspec ?: (
|
||||
val arraySpec = decl.arraysize ?: (
|
||||
if((decl.value as LiteralValue).isArray)
|
||||
ArraySpec.forArray(decl.value as LiteralValue, heap)
|
||||
ArrayIndex.forArray(decl.value as LiteralValue, program.heap)
|
||||
else
|
||||
ArraySpec(LiteralValue.optimalInteger(-2, decl.position), decl.position)
|
||||
ArrayIndex(LiteralValue.optimalInteger(-2, decl.position), decl.position)
|
||||
)
|
||||
checkValueTypeAndRange(decl.datatype, arraySpec, decl.value as LiteralValue, heap)
|
||||
checkValueTypeAndRange(decl.datatype, arraySpec, decl.value as LiteralValue, program.heap)
|
||||
}
|
||||
else -> {
|
||||
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!::class.simpleName}")
|
||||
@@ -530,8 +566,8 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
}
|
||||
VarDeclType.MEMORY -> {
|
||||
if(decl.arrayspec!=null) {
|
||||
val arraySize = decl.arrayspec.size() ?: 1
|
||||
if(decl.arraysize!=null) {
|
||||
val arraySize = decl.arraysize!!.size() ?: 1
|
||||
when(decl.datatype) {
|
||||
DataType.ARRAY_B, DataType.ARRAY_UB ->
|
||||
if(arraySize > 256)
|
||||
@@ -613,6 +649,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
|
||||
if(directive.args.size!=2 || directive.args[0].str==null || directive.args[1].str==null)
|
||||
err("invalid asminclude directive, expected arguments: \"filename\", \"scopelabel\"")
|
||||
checkFileExists(directive, directive.args[0].str!!)
|
||||
}
|
||||
"%asmbinary" -> {
|
||||
if(directive.parent !is INameScope || directive.parent is Module) err("this directive may only occur in a block")
|
||||
@@ -622,6 +659,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(directive.args.size>=2 && directive.args[1].int==null) err(errormsg)
|
||||
if(directive.args.size==3 && directive.args[2].int==null) err(errormsg)
|
||||
if(directive.args.size>3) err(errormsg)
|
||||
checkFileExists(directive, directive.args[0].str!!)
|
||||
}
|
||||
"%option" -> {
|
||||
if(directive.parent !is Block && directive.parent !is Module) err("this directive may only occur in a block or at module level")
|
||||
@@ -635,16 +673,24 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return super.process(directive)
|
||||
}
|
||||
|
||||
private fun checkFileExists(directive: Directive, filename: String) {
|
||||
var definingModule = directive.parent
|
||||
while (definingModule !is Module)
|
||||
definingModule = definingModule.parent
|
||||
if (!(filename.startsWith("library:") || definingModule.source.resolveSibling(filename).toFile().isFile || File(filename).isFile))
|
||||
checkResult.add(NameError("included file not found: $filename", directive.position))
|
||||
}
|
||||
|
||||
override fun process(literalValue: LiteralValue): LiteralValue {
|
||||
if(!compilerOptions.floats && literalValue.type==DataType.FLOAT) {
|
||||
if(!compilerOptions.floats && literalValue.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
|
||||
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", literalValue.position))
|
||||
}
|
||||
val arrayspec =
|
||||
if(literalValue.isArray)
|
||||
ArraySpec.forArray(literalValue, heap)
|
||||
ArrayIndex.forArray(literalValue, program.heap)
|
||||
else
|
||||
ArraySpec(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position)
|
||||
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, heap)
|
||||
ArrayIndex(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position)
|
||||
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, program.heap)
|
||||
|
||||
val lv = super.process(literalValue)
|
||||
when(lv.type) {
|
||||
@@ -663,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))
|
||||
}
|
||||
@@ -672,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))
|
||||
@@ -694,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))
|
||||
}
|
||||
@@ -724,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
|
||||
@@ -743,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])
|
||||
@@ -791,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))
|
||||
@@ -815,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))
|
||||
@@ -849,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 {
|
||||
@@ -860,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))
|
||||
}
|
||||
@@ -876,21 +922,21 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
override fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
|
||||
val target = arrayIndexedExpression.identifier.targetStatement(namespace)
|
||||
val target = arrayIndexedExpression.identifier.targetStatement(program.namespace)
|
||||
if(target is VarDecl) {
|
||||
if(target.datatype !in IterableDatatypes)
|
||||
checkResult.add(SyntaxError("indexing requires an iterable variable", arrayIndexedExpression.position))
|
||||
val arraysize = target.arrayspec?.size()
|
||||
val arraysize = target.arraysize?.size()
|
||||
if(arraysize!=null) {
|
||||
// check out of bounds
|
||||
val index = (arrayIndexedExpression.arrayspec.x as? LiteralValue)?.asIntegerValue
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? LiteralValue)?.asIntegerValue
|
||||
if(index!=null && (index<0 || index>=arraysize))
|
||||
checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position))
|
||||
} else if(target.datatype in StringDatatypes) {
|
||||
// check string lengths
|
||||
val heapId = (target.value as LiteralValue).heapId!!
|
||||
val stringLen = heap.get(heapId).str!!.length
|
||||
val index = (arrayIndexedExpression.arrayspec.x as? LiteralValue)?.asIntegerValue
|
||||
val stringLen = program.heap.get(heapId).str!!.length
|
||||
val index = (arrayIndexedExpression.arrayspec.index as? LiteralValue)?.asIntegerValue
|
||||
if(index!=null && (index<0 || index>=stringLen))
|
||||
checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position))
|
||||
}
|
||||
@@ -898,7 +944,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position))
|
||||
|
||||
// check index value 0..255
|
||||
val dtx = arrayIndexedExpression.arrayspec.x.resultingDatatype(namespace, heap)
|
||||
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program)
|
||||
if(dtx!=DataType.UBYTE && dtx!=DataType.BYTE)
|
||||
checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position))
|
||||
|
||||
@@ -906,16 +952,16 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
|
||||
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? {
|
||||
val targetStatement = target.targetStatement(namespace)
|
||||
val targetStatement = target.targetStatement(program.namespace)
|
||||
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
|
||||
return targetStatement
|
||||
checkResult.add(NameError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
|
||||
return null
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec?, range: RangeExpr) : Boolean {
|
||||
val from = range.from.constValue(namespace, heap)
|
||||
val to = range.to.constValue(namespace, heap)
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArrayIndex, range: RangeExpr) : Boolean {
|
||||
val from = range.from.constValue(program)
|
||||
val to = range.to.constValue(program)
|
||||
if(from==null || to==null) {
|
||||
checkResult.add(SyntaxError("range from and to values must be constants", range.position))
|
||||
return false
|
||||
@@ -932,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
|
||||
@@ -941,8 +987,8 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
in ArrayDatatypes -> {
|
||||
// range and length check bytes
|
||||
val expectedSize = arrayspec!!.size()
|
||||
val rangeSize=range.size(heap)
|
||||
val expectedSize = arrayspec.size()
|
||||
val rangeSize=range.size()
|
||||
if(rangeSize!=null && rangeSize != expectedSize) {
|
||||
checkResult.add(ExpressionError("range size doesn't match array size, expected $expectedSize found $rangeSize", range.position))
|
||||
return false
|
||||
@@ -953,7 +999,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArraySpec, value: LiteralValue, heap: HeapValues) : Boolean {
|
||||
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArrayIndex, value: LiteralValue, heap: HeapValues) : Boolean {
|
||||
fun err(msg: String) : Boolean {
|
||||
checkResult.add(ExpressionError(msg, value.position))
|
||||
return false
|
||||
@@ -1004,12 +1050,11 @@ 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 -> {
|
||||
// value may be either a single byte, or a byte arrayspec (of all constant values)
|
||||
// value may be either a single byte, or a byte arraysize (of all constant values), or a range
|
||||
if(value.type==targetDt) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
@@ -1018,7 +1063,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>256)
|
||||
return err("byte array length must be 1-256")
|
||||
val constX = arrayspec.x.constValue(namespace, heap)
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.asIntegerValue==null)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX.asIntegerValue
|
||||
@@ -1031,7 +1076,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return err("invalid byte array initialization value ${value.type}, expected $targetDt")
|
||||
}
|
||||
DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
// value may be either a single word, or a word arrayspec
|
||||
// value may be either a single word, or a word arraysize, or a range
|
||||
if(value.type==targetDt) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
@@ -1040,7 +1085,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize<1 || arraySpecSize>128)
|
||||
return err("word array length must be 1-128")
|
||||
val constX = arrayspec.x.constValue(namespace, heap)
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.asIntegerValue==null)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX.asIntegerValue
|
||||
@@ -1053,7 +1098,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
return err("invalid word array initialization value ${value.type}, expected $targetDt")
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
// value may be either a single float, or a float arrayspec
|
||||
// value may be either a single float, or a float arraysize
|
||||
if(value.type==targetDt) {
|
||||
if(!checkArrayValues(value, targetDt))
|
||||
return false
|
||||
@@ -1062,7 +1107,7 @@ private class AstChecker(private val namespace: INameScope,
|
||||
if(arraySpecSize!=null && arraySpecSize>0) {
|
||||
if(arraySpecSize < 1 || arraySpecSize>51)
|
||||
return err("float array length must be 1-51")
|
||||
val constX = arrayspec.x.constValue(namespace, heap)
|
||||
val constX = arrayspec.index.constValue(program)
|
||||
if(constX?.asIntegerValue==null)
|
||||
return err("array size specifier must be constant integer value")
|
||||
val expectedSize = constX.asIntegerValue
|
||||
@@ -1073,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})
|
||||
@@ -1087,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, 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, 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, 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, "$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)
|
||||
@@ -232,7 +248,7 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
|
||||
|
||||
override fun process(addressOf: AddressOf): IExpression {
|
||||
// register the scoped name of the referenced identifier
|
||||
val variable= addressOf.identifier.targetStatement(namespace) as? VarDecl ?: return addressOf
|
||||
val variable= addressOf.identifier.targetVarDecl(namespace) ?: return addressOf
|
||||
addressOf.scopedname = variable.scopedname
|
||||
return super.process(addressOf)
|
||||
}
|
||||
|
||||
@@ -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,13 +161,36 @@ private class StatementReorderer(private val namespace: INameScope, private val
|
||||
return scope
|
||||
}
|
||||
|
||||
override fun process(expr: BinaryExpression): IExpression {
|
||||
val leftDt = expr.left.inferType(program)
|
||||
val rightDt = expr.right.inferType(program)
|
||||
if(leftDt!=null && rightDt!=null && leftDt!=rightDt) {
|
||||
// determine common datatype and add typecast as required to make left and right equal types
|
||||
val (commonDt, toFix) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right)
|
||||
if(toFix!=null) {
|
||||
when {
|
||||
toFix===expr.left -> {
|
||||
expr.left = TypecastExpression(expr.left, commonDt, true, expr.left.position)
|
||||
expr.left.linkParents(expr)
|
||||
}
|
||||
toFix===expr.right -> {
|
||||
expr.right = TypecastExpression(expr.right, commonDt, true, expr.right.position)
|
||||
expr.right.linkParents(expr)
|
||||
}
|
||||
else -> throw FatalAstException("confused binary expression side")
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.process(expr)
|
||||
}
|
||||
|
||||
private fun sortConstantAssignments(statements: MutableList<IStatement>) {
|
||||
// sort assignments by datatype and value, so multiple initializations with the same value can be optimized (to load the value just once)
|
||||
// sort assignments by datatype and value, so multiple initializations with the isSameAs value can be optimized (to load the value just once)
|
||||
val result = mutableListOf<IStatement>()
|
||||
val stmtIter = statements.iterator()
|
||||
for(stmt in stmtIter) {
|
||||
if(stmt is Assignment) {
|
||||
val constval = stmt.value.constValue(namespace, heap)
|
||||
if(stmt is Assignment && !stmt.targets.any { it.isMemoryMapped(program.namespace) }) {
|
||||
val constval = stmt.value.constValue(program)
|
||||
if(constval!=null) {
|
||||
val (sorted, trailing) = sortConstantAssignmentSequence(stmt, stmtIter)
|
||||
result.addAll(sorted)
|
||||
@@ -178,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
|
||||
@@ -196,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.
|
||||
@@ -213,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
|
||||
@@ -243,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
|
||||
@@ -254,7 +368,7 @@ private class VarInitValueAndAddressOfCreator(private val namespace: INameScope)
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
val targetStatement = functionCall.target.targetStatement(namespace) as? Subroutine
|
||||
val targetStatement = functionCall.target.targetSubroutine(namespace)
|
||||
if(targetStatement!=null) {
|
||||
var node: Node = functionCall
|
||||
while(node !is IStatement)
|
||||
@@ -265,7 +379,7 @@ private class VarInitValueAndAddressOfCreator(private val namespace: INameScope)
|
||||
}
|
||||
|
||||
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
|
||||
val targetStatement = functionCallStatement.target.targetStatement(namespace) as? Subroutine
|
||||
val targetStatement = functionCallStatement.target.targetSubroutine(namespace)
|
||||
if(targetStatement!=null)
|
||||
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
|
||||
return functionCallStatement
|
||||
@@ -280,7 +394,7 @@ private class VarInitValueAndAddressOfCreator(private val namespace: INameScope)
|
||||
val idref = argparam.second as? IdentifierReference
|
||||
val strvalue = argparam.second as? LiteralValue
|
||||
if(idref!=null) {
|
||||
val variable = idref.targetStatement(namespace) as? VarDecl
|
||||
val variable = idref.targetVarDecl(namespace)
|
||||
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) {
|
||||
val pointerExpr = AddressOf(idref, idref.position)
|
||||
pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single())
|
||||
@@ -298,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, 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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,16 +4,13 @@ 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
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import javax.lang.model.type.ArrayType
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
@@ -69,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()
|
||||
@@ -146,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
|
||||
}
|
||||
|
||||
@@ -190,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
|
||||
@@ -219,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)
|
||||
@@ -425,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) =
|
||||
@@ -469,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)
|
||||
@@ -518,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")
|
||||
@@ -528,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)
|
||||
@@ -550,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 -> {
|
||||
@@ -621,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)
|
||||
@@ -638,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]
|
||||
@@ -661,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}")
|
||||
@@ -730,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) {
|
||||
@@ -742,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")
|
||||
}
|
||||
}
|
||||
@@ -759,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)
|
||||
@@ -797,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))
|
||||
@@ -810,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()")
|
||||
}
|
||||
@@ -818,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.targetStatement(namespace) as VarDecl
|
||||
val length=Value(DataType.UBYTE, target.arrayspec!!.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")
|
||||
@@ -831,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.targetStatement(namespace) as VarDecl
|
||||
val length=Value(DataType.UBYTE, target.arrayspec!!.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 -> {
|
||||
@@ -856,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.targetStatement(namespace) as VarDecl
|
||||
val length=Value(DataType.UBYTE, target.arrayspec!!.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")
|
||||
@@ -877,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)
|
||||
@@ -889,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)
|
||||
@@ -900,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)
|
||||
@@ -913,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)
|
||||
@@ -935,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)
|
||||
@@ -946,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)
|
||||
@@ -969,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")
|
||||
@@ -1003,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)
|
||||
}
|
||||
@@ -1045,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 {
|
||||
@@ -1076,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
|
||||
@@ -1099,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
|
||||
@@ -1126,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
|
||||
@@ -1383,8 +1311,8 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
|
||||
private fun translate(arrayindexed: ArrayIndexedExpression, write: Boolean) {
|
||||
val variable = arrayindexed.identifier.targetStatement(namespace) as VarDecl
|
||||
translate(arrayindexed.arrayspec.x)
|
||||
val variable = arrayindexed.identifier.targetVarDecl(program.namespace)!!
|
||||
translate(arrayindexed.arrayspec.index)
|
||||
if (write)
|
||||
prog.instr(opcodeWriteindexedvar(variable.datatype), callLabel = variable.scopedname)
|
||||
else
|
||||
@@ -1399,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 {
|
||||
@@ -1411,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
|
||||
@@ -1434,26 +1362,26 @@ 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!!.targetStatement(namespace) as VarDecl
|
||||
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.targetStatement(namespace) as VarDecl
|
||||
translate(stmt.target.arrayindexed!!.arrayspec.x)
|
||||
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)
|
||||
"--" -> prog.instr(opcodeDecArrayindexedVar(variable.datatype), callLabel = variable.scopedname)
|
||||
}
|
||||
}
|
||||
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)
|
||||
@@ -1478,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()????
|
||||
@@ -1501,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).targetStatement(namespace) as VarDecl
|
||||
val vardecl = (stmt.value as IdentifierReference).targetVarDecl(program.namespace)!!
|
||||
prog.removeLastInstruction()
|
||||
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
|
||||
}
|
||||
@@ -1530,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)
|
||||
}
|
||||
|
||||
@@ -1538,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.targetStatement(namespace) as VarDecl
|
||||
val vardecl = value.targetVarDecl(program.namespace)!!
|
||||
if(removeLastOpcode) prog.removeLastInstruction()
|
||||
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
|
||||
}
|
||||
@@ -1550,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.targetStatement(namespace) as VarDecl
|
||||
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")
|
||||
@@ -1558,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")
|
||||
@@ -1574,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 -> {
|
||||
@@ -1583,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")
|
||||
}
|
||||
@@ -1593,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!!)
|
||||
}
|
||||
@@ -1619,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
|
||||
@@ -1629,13 +1557,13 @@ internal class Compiler(private val rootModule: Module,
|
||||
loopVarName = reg.name
|
||||
loopVarDt = DataType.UBYTE
|
||||
} else {
|
||||
val loopvar = (loop.loopVar!!.targetStatement(namespace) as VarDecl)
|
||||
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())
|
||||
@@ -1678,9 +1606,9 @@ internal class Compiler(private val rootModule: Module,
|
||||
when {
|
||||
loop.iterable is IdentifierReference -> {
|
||||
val idRef = loop.iterable as IdentifierReference
|
||||
val vardecl = (idRef.targetStatement(namespace) as VarDecl)
|
||||
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)
|
||||
}
|
||||
@@ -1702,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")
|
||||
@@ -1744,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
|
||||
@@ -1753,7 +1681,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
AssignTarget(loop.loopRegister, null, null, null, loop.position)
|
||||
else
|
||||
AssignTarget(null, loop.loopVar!!.copy(), null, null, loop.position)
|
||||
val arrayspec = ArraySpec(IdentifierReference(listOf(ForLoop.iteratorLoopcounterVarname), loop.position), loop.position)
|
||||
val arrayspec = ArrayIndex(IdentifierReference(listOf(ForLoop.iteratorLoopcounterVarname), loop.position), loop.position)
|
||||
val assignLv = Assignment(
|
||||
listOf(assignTarget), null,
|
||||
ArrayIndexedExpression((loop.iterable as IdentifierReference).copy(), arrayspec, loop.position),
|
||||
@@ -1765,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)
|
||||
@@ -1802,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)
|
||||
@@ -1821,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)
|
||||
}
|
||||
@@ -1845,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)
|
||||
@@ -1942,7 +1870,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
lvTarget.linkParents(body)
|
||||
val targetStatement: VarDecl? =
|
||||
if(lvTarget.identifier!=null) {
|
||||
lvTarget.identifier.targetStatement(namespace) as VarDecl
|
||||
lvTarget.identifier.targetVarDecl(program.namespace)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -2015,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")
|
||||
@@ -2052,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")
|
||||
@@ -2066,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
|
||||
|
||||
@@ -2117,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)
|
||||
@@ -2128,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)
|
||||
@@ -2138,7 +2066,7 @@ internal class Compiler(private val rootModule: Module,
|
||||
}
|
||||
|
||||
private fun translate(addrof: AddressOf) {
|
||||
val target = addrof.identifier.targetStatement(namespace) as VarDecl
|
||||
val target = addrof.identifier.targetVarDecl(program.namespace)!!
|
||||
if(target.datatype in ArrayDatatypes || target.datatype in StringDatatypes|| target.datatype==DataType.FLOAT) {
|
||||
pushHeapVarAddress(addrof.identifier, false)
|
||||
}
|
||||
@@ -2149,28 +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>) {
|
||||
TODO("asmbinary not implemented yet $args")
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
@@ -23,6 +24,9 @@ open class Instruction(val opcode: Opcode,
|
||||
else
|
||||
"inline_assembly"
|
||||
}
|
||||
opcode==Opcode.INCLUDE_FILE -> {
|
||||
"include_file \"$callLabel\" $arg $arg2"
|
||||
}
|
||||
opcode==Opcode.SYSCALL -> {
|
||||
val syscall = Syscall.values().find { it.callNr==arg!!.numericValue() }
|
||||
"syscall $syscall"
|
||||
|
||||
@@ -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
|
||||
@@ -69,7 +70,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
|
||||
optimizeMultipleSequentialLineInstrs()
|
||||
optimizeCallReturnIntoJump()
|
||||
optimizeConditionalBranches()
|
||||
// todo: add more optimizations to stackvm code
|
||||
// todo: add more optimizations to intermediate code!
|
||||
|
||||
optimizeRemoveNops() // must be done as the last step
|
||||
optimizeMultipleSequentialLineInstrs() // once more
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
@@ -262,7 +262,8 @@ enum class Opcode {
|
||||
BREAKPOINT, // breakpoint
|
||||
TERMINATE, // end the program
|
||||
LINE, // track source file line number
|
||||
INLINE_ASSEMBLY // container to hold inline raw assembly code
|
||||
INLINE_ASSEMBLY, // container to hold inline raw assembly code
|
||||
INCLUDE_FILE // directive to include a file at this position in the memory of the program
|
||||
}
|
||||
|
||||
val opcodesWithVarArgument = setOf(
|
||||
|
||||
@@ -1,478 +0,0 @@
|
||||
package prog8.compiler.intermediate
|
||||
|
||||
import prog8.ast.*
|
||||
import java.lang.Exception
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
class ValueException(msg: String?) : Exception(msg)
|
||||
|
||||
|
||||
class Value(val type: DataType, numericvalueOrHeapId: Number) {
|
||||
private var byteval: Short? = null
|
||||
private var wordval: Int? = null
|
||||
private var floatval: Double? = null
|
||||
var heapId: Int = -1
|
||||
private set
|
||||
val asBooleanValue: Boolean
|
||||
|
||||
init {
|
||||
when(type) {
|
||||
DataType.UBYTE -> {
|
||||
if(numericvalueOrHeapId.toInt() !in 0..255)
|
||||
throw ValueException("value out of range: $numericvalueOrHeapId")
|
||||
byteval = numericvalueOrHeapId.toShort()
|
||||
asBooleanValue = byteval != (0.toShort())
|
||||
}
|
||||
DataType.BYTE -> {
|
||||
if(numericvalueOrHeapId.toInt() !in -128..127)
|
||||
throw ValueException("value out of range: $numericvalueOrHeapId")
|
||||
byteval = numericvalueOrHeapId.toShort()
|
||||
asBooleanValue = byteval != (0.toShort())
|
||||
}
|
||||
DataType.UWORD -> {
|
||||
if(numericvalueOrHeapId.toInt() !in 0..65535)
|
||||
throw ValueException("value out of range: $numericvalueOrHeapId")
|
||||
wordval = numericvalueOrHeapId.toInt()
|
||||
asBooleanValue = wordval != 0
|
||||
}
|
||||
DataType.WORD -> {
|
||||
if(numericvalueOrHeapId.toInt() !in -32768..32767)
|
||||
throw ValueException("value out of range: $numericvalueOrHeapId")
|
||||
wordval = numericvalueOrHeapId.toInt()
|
||||
asBooleanValue = wordval != 0
|
||||
}
|
||||
DataType.FLOAT -> {
|
||||
floatval = numericvalueOrHeapId.toDouble()
|
||||
asBooleanValue = floatval != 0.0
|
||||
}
|
||||
else -> {
|
||||
if(numericvalueOrHeapId !is Int || numericvalueOrHeapId<0)
|
||||
throw ValueException("for non-numeric types, the value should be a integer heapId >= 0")
|
||||
heapId = numericvalueOrHeapId
|
||||
asBooleanValue=true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return when(type) {
|
||||
DataType.UBYTE -> "ub:%02x".format(byteval)
|
||||
DataType.BYTE -> {
|
||||
if(byteval!!<0)
|
||||
"b:-%02x".format(abs(byteval!!.toInt()))
|
||||
else
|
||||
"b:%02x".format(byteval)
|
||||
}
|
||||
DataType.UWORD -> "uw:%04x".format(wordval)
|
||||
DataType.WORD -> {
|
||||
if(wordval!!<0)
|
||||
"w:-%04x".format(abs(wordval!!))
|
||||
else
|
||||
"w:%04x".format(wordval)
|
||||
}
|
||||
DataType.FLOAT -> "f:$floatval"
|
||||
else -> "heap:$heapId"
|
||||
}
|
||||
}
|
||||
|
||||
fun numericValue(): Number {
|
||||
return when(type) {
|
||||
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,14 +283,14 @@ 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))
|
||||
out(" .byte " + chunk.joinToString())
|
||||
}
|
||||
DataType.ARRAY_UB -> {
|
||||
// unsigned integer byte arrayspec
|
||||
// unsigned integer byte arraysize
|
||||
val data = makeArrayFillDataUnsigned(v.second)
|
||||
if (data.size <= 16)
|
||||
out("${v.first}\t.byte ${data.joinToString()}")
|
||||
@@ -299,7 +301,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_B -> {
|
||||
// signed integer byte arrayspec
|
||||
// signed integer byte arraysize
|
||||
val data = makeArrayFillDataSigned(v.second)
|
||||
if (data.size <= 16)
|
||||
out("${v.first}\t.char ${data.joinToString()}")
|
||||
@@ -310,7 +312,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UW -> {
|
||||
// unsigned word arrayspec
|
||||
// unsigned word arraysize
|
||||
val data = makeArrayFillDataUnsigned(v.second)
|
||||
if (data.size <= 16)
|
||||
out("${v.first}\t.word ${data.joinToString()}")
|
||||
@@ -321,7 +323,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_W -> {
|
||||
// signed word arrayspec
|
||||
// signed word arraysize
|
||||
val data = makeArrayFillDataSigned(v.second)
|
||||
if (data.size <= 16)
|
||||
out("${v.first}\t.sint ${data.joinToString()}")
|
||||
@@ -332,8 +334,8 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_F -> {
|
||||
// float arrayspec
|
||||
val array = heap.get(v.second.heapId).doubleArray!!
|
||||
// float arraysize
|
||||
val array = heap.get(v.second.heapId!!).doubleArray!!
|
||||
val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) }
|
||||
out(v.first)
|
||||
for(f in array.zip(floatFills))
|
||||
@@ -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
|
||||
@@ -370,12 +372,12 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
else -> throw AssemblyError("weird type in array")
|
||||
}
|
||||
}
|
||||
else -> throw AssemblyError("invalid arrayspec type")
|
||||
else -> throw AssemblyError("invalid arraysize type")
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeArrayFillDataSigned(value: Value): List<String> {
|
||||
val array = heap.get(value.heapId).array!!
|
||||
private fun makeArrayFillDataSigned(value: RuntimeValue): List<String> {
|
||||
val array = heap.get(value.heapId!!).array!!
|
||||
// note: array of signed value can never contain pointer-to type, so simply process values as being all integers
|
||||
return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W) {
|
||||
array.map {
|
||||
@@ -385,7 +387,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
"-$"+abs(it.integer).toString(16).padStart(2, '0')
|
||||
}
|
||||
}
|
||||
else throw AssemblyError("invalid arrayspec type")
|
||||
else throw AssemblyError("invalid arraysize type")
|
||||
}
|
||||
|
||||
private fun instr2asm(ins: List<Instruction>): Int {
|
||||
@@ -417,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")
|
||||
|
||||
@@ -472,6 +474,11 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
Opcode.DISCARD_WORD -> " inx"
|
||||
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
|
||||
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to process it.
|
||||
Opcode.INCLUDE_FILE -> {
|
||||
val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}"
|
||||
val length = if(ins.arg2==null) "" else ", ${ins.arg2.integerValue()}"
|
||||
" .binary \"${ins.callLabel}\" $offset $length"
|
||||
}
|
||||
Opcode.SYSCALL -> {
|
||||
if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr })
|
||||
throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}")
|
||||
|
||||
@@ -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,14 +107,14 @@ val BuiltinFunctions = mapOf(
|
||||
)
|
||||
|
||||
|
||||
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope, heap: HeapValues): DataType? {
|
||||
fun builtinFunctionReturnType(function: String, args: List<IExpression>, program: Program): DataType? {
|
||||
|
||||
fun datatypeFromIterableArg(arglist: IExpression): DataType {
|
||||
if(arglist is LiteralValue) {
|
||||
if(arglist.type==DataType.ARRAY_UB || arglist.type==DataType.ARRAY_UW || arglist.type==DataType.ARRAY_F) {
|
||||
val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace, heap)}
|
||||
val dt = arglist.arrayvalue!!.map {it.inferType(program)}
|
||||
if(dt.any { it!=DataType.UBYTE && it!=DataType.UWORD && it!=DataType.FLOAT}) {
|
||||
throw FatalAstException("fuction $function only accepts arrayspec of numeric values")
|
||||
throw FatalAstException("fuction $function only accepts arraysize of numeric values")
|
||||
}
|
||||
if(dt.any { it==DataType.FLOAT }) return DataType.FLOAT
|
||||
if(dt.any { it==DataType.UWORD }) return DataType.UWORD
|
||||
@@ -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,50 +267,54 @@ 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 array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("avg requires array argument", position)
|
||||
if(array.all {it.integer!=null}) {
|
||||
array.map { it.integer!! }.average()
|
||||
val heapId = iterable.heapId!!
|
||||
val integerarray = program.heap.get(heapId).array
|
||||
if(integerarray!=null) {
|
||||
if (integerarray.all { it.integer != null }) {
|
||||
integerarray.map { it.integer!! }.average()
|
||||
} else {
|
||||
throw ExpressionError("cannot avg() over array that does not only contain constant numerical values", position)
|
||||
}
|
||||
} else {
|
||||
throw ExpressionError("cannot avg() over array that does not only contain constant integer values", position)
|
||||
val doublearray = program.heap.get(heapId).doubleArray
|
||||
doublearray?.average() ?: throw SyntaxError("avg requires array argument", position)
|
||||
}
|
||||
// TODO what about avg() on floating point array variable!
|
||||
}
|
||||
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)
|
||||
@@ -332,34 +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(program.namespace)
|
||||
val arraySize = directMemVar?.arraysize?.size()
|
||||
if(arraySize != null)
|
||||
return LiteralValue.optimalInteger(arraySize, position)
|
||||
if(args[0] !is IdentifierReference)
|
||||
throw SyntaxError("len over weird argument ${args[0]}", position)
|
||||
val target = (args[0] as IdentifierReference).targetStatement(namespace)
|
||||
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
|
||||
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
|
||||
val argValue = (target as? VarDecl)?.value
|
||||
argument = argValue?.constValue(namespace, heap)
|
||||
argument = argValue?.constValue(program)
|
||||
?: throw NotConstArgumentException()
|
||||
}
|
||||
return when(argument.type) {
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize
|
||||
val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize
|
||||
if(arraySize>256)
|
||||
throw CompilerException("array length exceeds byte limit ${argument.position}")
|
||||
LiteralValue.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)
|
||||
@@ -370,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)
|
||||
}
|
||||
@@ -446,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
|
||||
|
||||
@@ -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)
|
||||
@@ -25,31 +25,73 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
|
||||
override fun process(decl: VarDecl): IStatement {
|
||||
// the initializer value can't refer to the variable itself (recursive definition)
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arrayspec?.x?.referencesIdentifier(decl.name) == true) {
|
||||
if(decl.value?.referencesIdentifier(decl.name) == true || decl.arraysize?.index?.referencesIdentifier(decl.name) == true) {
|
||||
errors.add(ExpressionError("recursive var declaration", decl.position))
|
||||
return decl
|
||||
}
|
||||
|
||||
val result = super.process(decl)
|
||||
|
||||
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
|
||||
val litval = decl.value as? LiteralValue
|
||||
if(litval!=null && litval.isArray && litval.heapId!=null)
|
||||
fixupArrayTypeOnHeap(decl, litval)
|
||||
|
||||
if(decl.isArray){
|
||||
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
|
||||
if(decl.arraysize==null) {
|
||||
val arrayval = (decl.value as? LiteralValue)?.arrayvalue
|
||||
if(arrayval!=null) {
|
||||
decl.arraysize = ArrayIndex(LiteralValue.optimalInteger(arrayval.size, decl.position), decl.position)
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
else if(decl.arraysize?.size()==null) {
|
||||
val size = decl.arraysize!!.index.process(this)
|
||||
if(size is LiteralValue) {
|
||||
decl.arraysize = ArrayIndex(size, decl.position)
|
||||
optimizationsDone++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when(decl.datatype) {
|
||||
DataType.FLOAT -> {
|
||||
// vardecl: for scalar float vars, promote constant integer initialization values to floats
|
||||
if (litval != null && litval.type in IntegerDatatypes) {
|
||||
val newValue = LiteralValue(DataType.FLOAT, floatvalue = litval.asNumericValue!!.toDouble(), position = litval.position)
|
||||
decl.value = newValue
|
||||
optimizationsDone++
|
||||
return decl
|
||||
}
|
||||
}
|
||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
|
||||
val rangeExpr = decl.value as? RangeExpr
|
||||
if(rangeExpr!=null) {
|
||||
// convert the initializer range expression to an actual array (will be put on heap later)
|
||||
val declArraySize = decl.arraysize?.size()
|
||||
if(declArraySize!=null && declArraySize!=rangeExpr.size())
|
||||
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
|
||||
val constRange = rangeExpr.toConstantIntegerRange()
|
||||
if(constRange!=null) {
|
||||
val eltType = rangeExpr.inferType(program)!!
|
||||
if(eltType in ByteDatatypes) {
|
||||
decl.value = LiteralValue(decl.datatype,
|
||||
arrayvalue = constRange.map { LiteralValue(eltType, bytevalue=it.toShort(), position = decl.value!!.position ) }
|
||||
.toTypedArray(), position=decl.value!!.position)
|
||||
} else {
|
||||
decl.value = LiteralValue(decl.datatype,
|
||||
arrayvalue = constRange.map { LiteralValue(eltType, wordvalue= it, position = decl.value!!.position ) }
|
||||
.toTypedArray(), position=decl.value!!.position)
|
||||
}
|
||||
decl.value!!.linkParents(decl)
|
||||
optimizationsDone++
|
||||
return decl
|
||||
}
|
||||
}
|
||||
if(litval?.type==DataType.FLOAT)
|
||||
errors.add(ExpressionError("arrayspec requires only integers here", litval.position))
|
||||
val size = decl.arrayspec!!.size()
|
||||
if ((litval==null || !litval.isArray) && size != null) {
|
||||
// arrayspec initializer is empty or a single int, and we know the size; create the arrayspec.
|
||||
errors.add(ExpressionError("arraysize requires only integers here", litval.position))
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
if ((litval==null || !litval.isArray) && rangeExpr==null) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = if (litval == null) 0 else litval.asIntegerValue ?: 0
|
||||
when(decl.datatype){
|
||||
DataType.ARRAY_UB -> {
|
||||
@@ -70,27 +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 -> {
|
||||
val size = decl.arrayspec!!.size()
|
||||
if ((litval==null || !litval.isArray) && size != null) {
|
||||
// arrayspec initializer is empty or a single int, and we know the size; create the arrayspec.
|
||||
val size = decl.arraysize?.size() ?: return decl
|
||||
if (litval==null || !litval.isArray) {
|
||||
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
|
||||
val fillvalue = if (litval == null) 0.0 else litval.asNumericValue?.toDouble() ?: 0.0
|
||||
if(fillvalue< FLOAT_MAX_NEGATIVE || fillvalue> FLOAT_MAX_POSITIVE)
|
||||
errors.add(ExpressionError("float value overflow", litval?.position ?: decl.position))
|
||||
else {
|
||||
val 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) {
|
||||
@@ -100,23 +149,23 @@ 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 AstException("invalid array vardecl type")
|
||||
else -> throw FatalAstException("invalid array vardecl type ${decl.datatype}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +174,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
*/
|
||||
override fun process(identifier: IdentifierReference): IExpression {
|
||||
return try {
|
||||
val cval = identifier.constValue(namespace, heap) ?: return identifier
|
||||
val cval = identifier.constValue(program) ?: return identifier
|
||||
return if(cval.isNumeric) {
|
||||
val copy = LiteralValue(cval.type, cval.bytevalue, cval.wordvalue, cval.floatvalue, null, cval.arrayvalue, position = identifier.position)
|
||||
copy.parent = identifier.parent
|
||||
@@ -142,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
|
||||
@@ -156,12 +205,12 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
}
|
||||
|
||||
private fun typeCastConstArguments(functionCall: IFunctionCall) {
|
||||
val subroutine = functionCall.target.targetStatement(namespace) as? Subroutine
|
||||
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) {
|
||||
@@ -173,6 +222,21 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(memread: DirectMemoryRead): IExpression {
|
||||
// @( &thing ) --> thing
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf!=null)
|
||||
return super.process(addrOf.identifier)
|
||||
return super.process(memread)
|
||||
}
|
||||
|
||||
override fun process(memwrite: DirectMemoryWrite): IExpression {
|
||||
// @( &thing ) --> thing
|
||||
val addrOf = memwrite.addressExpression as? AddressOf
|
||||
if(addrOf!=null)
|
||||
return super.process(addrOf.identifier)
|
||||
return super.process(memwrite)
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to process a unary prefix expression.
|
||||
@@ -247,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
|
||||
@@ -256,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,
|
||||
@@ -271,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
|
||||
}
|
||||
@@ -290,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) {
|
||||
@@ -493,7 +557,7 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
val rangeTo = iterableRange.to as? LiteralValue
|
||||
if(rangeFrom==null || rangeTo==null) return resultStmt
|
||||
|
||||
val loopvar = resultStmt.loopVar!!.targetStatement(namespace) as? VarDecl
|
||||
val loopvar = resultStmt.loopVar?.targetVarDecl(program.namespace)
|
||||
if(loopvar!=null) {
|
||||
val stepLiteral = iterableRange.step as? LiteralValue
|
||||
when(loopvar.datatype) {
|
||||
@@ -528,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 arrayspec type")
|
||||
}
|
||||
return LiteralValue(arrayDt, heapId = heapId, position = arraylit.position)
|
||||
if(arrayDt!=litval.type) {
|
||||
return LiteralValue(arrayDt, arrayvalue = litval.arrayvalue, position = litval.position)
|
||||
}
|
||||
return litval
|
||||
}
|
||||
|
||||
override fun process(assignment: Assignment): IStatement {
|
||||
super.process(assignment)
|
||||
val lv = assignment.value as? LiteralValue
|
||||
if(lv!=null) {
|
||||
val targetDt = assignment.singleTarget?.determineDatatype(namespace, heap, assignment)
|
||||
// see if we can promote/convert a literal value to the required datatype
|
||||
when(targetDt) {
|
||||
when(assignment.singleTarget?.inferType(program, assignment)) {
|
||||
DataType.UWORD -> {
|
||||
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
|
||||
if(lv.type==DataType.UBYTE)
|
||||
@@ -673,5 +715,3 @@ class ConstantFolding(private val namespace: INameScope, private val heap: HeapV
|
||||
return assignment
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,48 +1,49 @@
|
||||
package prog8.optimizing
|
||||
|
||||
import prog8.ast.AstException
|
||||
import prog8.ast.INameScope
|
||||
import prog8.ast.Module
|
||||
import prog8.compiler.HeapValues
|
||||
import prog8.ast.*
|
||||
import prog8.parser.ParsingFailedError
|
||||
|
||||
|
||||
fun Module.constantFold(globalNamespace: INameScope, heap: HeapValues) {
|
||||
val optimizer = ConstantFolding(globalNamespace, heap)
|
||||
internal fun Program.constantFold() {
|
||||
val optimizer = ConstantFolding(this)
|
||||
try {
|
||||
this.process(optimizer)
|
||||
optimizer.process(this)
|
||||
} catch (ax: AstException) {
|
||||
optimizer.addError(ax)
|
||||
}
|
||||
|
||||
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
|
||||
optimizer.optimizationsDone = 0
|
||||
this.process(optimizer)
|
||||
optimizer.process(this)
|
||||
}
|
||||
|
||||
if(optimizer.errors.isNotEmpty()) {
|
||||
optimizer.errors.forEach { System.err.println(it) }
|
||||
throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
|
||||
} else {
|
||||
this.linkParents() // re-link in final configuration
|
||||
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Module.optimizeStatements(globalNamespace: INameScope, heap: HeapValues): Int {
|
||||
val optimizer = StatementOptimizer(globalNamespace, heap)
|
||||
this.process(optimizer)
|
||||
for(stmt in optimizer.statementsToRemove) {
|
||||
val scope=stmt.definingScope()
|
||||
scope.remove(stmt)
|
||||
internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int {
|
||||
val optimizer = StatementOptimizer(this, optimizeInlining)
|
||||
optimizer.process(this)
|
||||
for(scope in optimizer.scopesToFlatten.reversed()) {
|
||||
val namescope = scope.parent as INameScope
|
||||
val idx = namescope.statements.indexOf(scope as IStatement)
|
||||
if(idx>=0) {
|
||||
namescope.statements[idx] = NopStatement(scope.position)
|
||||
namescope.statements.addAll(idx, scope.statements)
|
||||
}
|
||||
}
|
||||
this.linkParents() // re-link in final configuration
|
||||
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
|
||||
|
||||
return optimizer.optimizationsDone
|
||||
}
|
||||
|
||||
fun Module.simplifyExpressions(namespace: INameScope, heap: HeapValues) : Int {
|
||||
val optimizer = SimplifyExpressions(namespace, heap)
|
||||
this.process(optimizer)
|
||||
internal fun Program.simplifyExpressions() : Int {
|
||||
val optimizer = SimplifyExpressions(this)
|
||||
optimizer.process(this)
|
||||
return optimizer.optimizationsDone
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
package prog8.optimizing
|
||||
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.HeapValues
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log2
|
||||
|
||||
/*
|
||||
todo advanced expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call + introduce variable to hold it)
|
||||
|
||||
Also see https://egorbo.com/peephole-optimizations.html
|
||||
|
||||
*/
|
||||
|
||||
class SimplifyExpressions(private val namespace: INameScope, private val heap: HeapValues) : IAstProcessor {
|
||||
internal class SimplifyExpressions(private val program: Program) : IAstProcessor {
|
||||
var optimizationsDone: Int = 0
|
||||
|
||||
override fun process(assignment: Assignment): IStatement {
|
||||
@@ -18,6 +20,43 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
return super.process(assignment)
|
||||
}
|
||||
|
||||
override fun process(memread: DirectMemoryRead): IExpression {
|
||||
// @( &thing ) --> thing
|
||||
val addrOf = memread.addressExpression as? AddressOf
|
||||
if(addrOf!=null)
|
||||
return super.process(addrOf.identifier)
|
||||
return super.process(memread)
|
||||
}
|
||||
|
||||
override fun process(memwrite: DirectMemoryWrite): IExpression {
|
||||
// @( &thing ) --> thing
|
||||
val addrOf = memwrite.addressExpression as? AddressOf
|
||||
if(addrOf!=null)
|
||||
return super.process(addrOf.identifier)
|
||||
return super.process(memwrite)
|
||||
}
|
||||
|
||||
override fun process(typecast: TypecastExpression): IExpression {
|
||||
// remove redundant typecasts
|
||||
var tc = typecast
|
||||
while(true) {
|
||||
val expr = tc.expression
|
||||
if(expr !is TypecastExpression || expr.type!=tc.type) {
|
||||
val assignment = typecast.parent as? Assignment
|
||||
if(assignment!=null) {
|
||||
val targetDt = assignment.singleTarget?.inferType(program, assignment)
|
||||
if(tc.expression.inferType(program)==targetDt) {
|
||||
optimizationsDone++
|
||||
return tc.expression
|
||||
}
|
||||
}
|
||||
return super.process(tc)
|
||||
}
|
||||
optimizationsDone++
|
||||
tc = expr
|
||||
}
|
||||
}
|
||||
|
||||
override fun process(expr: PrefixExpression): IExpression {
|
||||
if (expr.operator == "+") {
|
||||
// +X --> X
|
||||
@@ -67,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)) {
|
||||
@@ -280,8 +319,8 @@ class SimplifyExpressions(private val namespace: INameScope, private val heap: H
|
||||
|
||||
private fun determineY(x: IExpression, subBinExpr: BinaryExpression): IExpression? {
|
||||
return when {
|
||||
same(subBinExpr.left, x) -> subBinExpr.right
|
||||
same(subBinExpr.right, x) -> subBinExpr.left
|
||||
subBinExpr.left isSameAs x -> subBinExpr.right
|
||||
subBinExpr.right isSameAs x -> subBinExpr.left
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -347,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
|
||||
@@ -357,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
|
||||
@@ -371,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 {
|
||||
@@ -388,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 {
|
||||
@@ -534,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 = "&"
|
||||
@@ -557,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
|
||||
@@ -628,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++
|
||||
@@ -646,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()
|
||||
@@ -654,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.targetStatement(namespace) as? VarDecl
|
||||
if(targetStmt!=null)
|
||||
return targetStmt.type!=VarDeclType.MEMORY
|
||||
}
|
||||
if(target.identifier!=null) {
|
||||
val targetStmt = target.identifier.targetStatement(namespace) as? VarDecl
|
||||
if(targetStmt!=null)
|
||||
return targetStmt.type!=VarDeclType.MEMORY
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
override fun process(functionCallStatement: FunctionCallStatement): IStatement {
|
||||
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.targetStatement(namespace) as? Subroutine
|
||||
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump && first.identifier!=null) {
|
||||
@@ -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.targetStatement(namespace) as? Subroutine
|
||||
val subroutine = functionCall.target.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
if(first is Jump && first.identifier!=null) {
|
||||
@@ -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?.targetStatement(namespace) as? Subroutine
|
||||
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
|
||||
if(subroutine!=null) {
|
||||
// if the first instruction in the subroutine is another jump, shortcut this one
|
||||
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
|
||||
@@ -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?.targetStatement(namespace) as? VarDecl)?.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.x.constValue(namespace, heap)
|
||||
val x2 = target2.arrayindexed.arrayspec.x.constValue(namespace, heap)
|
||||
return x1!=null && x2!=null && x1==x2
|
||||
}
|
||||
}
|
||||
return false
|
||||
return super.process(label)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun same(left: IExpression, right: IExpression): Boolean {
|
||||
if(left===right)
|
||||
return true
|
||||
when(left) {
|
||||
is RegisterExpr ->
|
||||
return (right is RegisterExpr && right.register==left.register)
|
||||
is IdentifierReference ->
|
||||
return (right is IdentifierReference && right.nameInSource==left.nameInSource)
|
||||
is PrefixExpression ->
|
||||
return (right is PrefixExpression && right.operator==left.operator && same(right.expression, left.expression))
|
||||
is BinaryExpression ->
|
||||
return (right is BinaryExpression && right.operator==left.operator
|
||||
&& same(right.left, left.left)
|
||||
&& same(right.right, left.right))
|
||||
is ArrayIndexedExpression -> {
|
||||
return (right is ArrayIndexedExpression && right.identifier.nameInSource == left.identifier.nameInSource
|
||||
&& same(right.arrayspec.x, left.arrayspec.x))
|
||||
}
|
||||
is LiteralValue -> return (right is LiteralValue && right==left)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.antlr.v4.runtime.CommonTokenStream
|
||||
import org.antlr.v4.runtime.Lexer
|
||||
|
||||
|
||||
class CommentHandlingTokenStream(lexer: Lexer) : CommonTokenStream(lexer) {
|
||||
internal class CommentHandlingTokenStream(lexer: Lexer) : CommonTokenStream(lexer) {
|
||||
|
||||
data class Comment(val type: String, val line: Int, val comment: String)
|
||||
|
||||
|
||||
@@ -2,20 +2,13 @@ package prog8.parser
|
||||
|
||||
import org.antlr.v4.runtime.*
|
||||
import prog8.ast.*
|
||||
import prog8.compiler.LauncherType
|
||||
import prog8.compiler.OutputType
|
||||
import prog8.determineCompilationOptions
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.*
|
||||
|
||||
|
||||
class ParsingFailedError(override var message: String) : Exception(message)
|
||||
|
||||
|
||||
private val importedModules : HashMap<String, Module> = hashMapOf()
|
||||
internal class ParsingFailedError(override var message: String) : Exception(message)
|
||||
|
||||
|
||||
private class LexerErrorListener: BaseErrorListener() {
|
||||
@@ -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,18 +159,25 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Opcode.INCLUDE_FILE -> {
|
||||
val argparts = args!!.split(' ')
|
||||
val filename = argparts[0]
|
||||
val offset = if(argparts.size>=2 && argparts[1]!="null") getArgValue(argparts[1], heap) else null
|
||||
val length = if(argparts.size>=3 && argparts[2]!="null") getArgValue(argparts[2], heap) else null
|
||||
Instruction(opcode, offset, length, filename)
|
||||
}
|
||||
else -> {
|
||||
Instruction(opcode, getArgValue(args, heap))
|
||||
}
|
||||
@@ -183,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]=='"') {
|
||||
@@ -191,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()
|
||||
@@ -214,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")
|
||||
@@ -228,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 -> {
|
||||
@@ -236,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")
|
||||
@@ -262,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")
|
||||
@@ -273,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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
+301
-302
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)
|
||||
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
# this script uses the Gradle build to compile the code,
|
||||
# and then adds the contents of several jar files into one output jar.
|
||||
|
||||
./gradlew jar
|
||||
|
||||
mkdir -p compiler_jar/extracted
|
||||
mkdir -p compiler_jar/source
|
||||
cp compiler/build/libs/compiler.jar parser/build/libs/parser.jar parser/antlr/lib/antlr-runtime-4.7.2.jar compiler_jar/source/
|
||||
|
||||
KOTLINLIBS=$(kotlinc -verbose -script 2>&1 | grep home | cut -d ' ' -f 6-)/lib
|
||||
cp ${KOTLINLIBS}/kotlin-stdlib-jdk8.jar ${KOTLINLIBS}/kotlin-stdlib.jar compiler_jar/source/
|
||||
|
||||
pushd compiler_jar/extracted
|
||||
for i in ../source/*.jar; do jar xf $i; done
|
||||
cd ..
|
||||
jar cfe ../prog8compiler.jar prog8.CompilerMainKt -C extracted .
|
||||
popd
|
||||
rm -r compiler_jar
|
||||
ls -l prog8compiler.jar
|
||||
+1
-3
@@ -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>
|
||||
|
||||
@@ -237,9 +237,9 @@ Arrays
|
||||
^^^^^^
|
||||
Array types are also supported. They can be made of bytes, words or floats::
|
||||
|
||||
byte[4] array = [1, 2, 3, 4] ; initialize the array
|
||||
byte[99] array = 255 ; initialize array with all 255's [255, 255, 255, 255, ...]
|
||||
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
|
||||
byte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
|
||||
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
|
||||
value = array[3] ; the fourth value in the array (index is 0-based)
|
||||
char = string[4] ; the fifth character (=byte) in the string
|
||||
|
||||
@@ -123,6 +123,7 @@ Directives
|
||||
This directive can only be used inside a block.
|
||||
The assembler will include the file as binary bytes at this point, prog8 will not process this at all.
|
||||
The optional offset and length can be used to select a particular piece of the file.
|
||||
The file is located relative to the current working directory!
|
||||
|
||||
.. data:: %asminclude "<filename>", "scopelabel"
|
||||
|
||||
@@ -133,6 +134,8 @@ Directives
|
||||
The scopelabel argument will be used as a prefix to access the labels from the included source code,
|
||||
otherwise you would risk symbol redefinitions or duplications.
|
||||
If you know what you are doing you can leave it as an empty string to not have a scope prefix.
|
||||
The compiler first looks for the file relative to the same directory as the module containing this statement is in,
|
||||
if the file can't be found there it is searched relative to the current directory.
|
||||
|
||||
.. data:: %breakpoint
|
||||
|
||||
@@ -227,7 +230,7 @@ Various examples::
|
||||
float wallet = 55.25
|
||||
str name = "my name is Irmen"
|
||||
uword address = &counter
|
||||
byte[5] values = [11, 22, 33, 44, 55]
|
||||
byte[] values = [11, 22, 33, 44, 55]
|
||||
byte[5] values = 255 ; initialize with five 255 bytes
|
||||
|
||||
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
|
||||
@@ -251,18 +254,24 @@ type identifier type storage size example var declara
|
||||
``uword`` unsigned word 2 bytes = 16 bits ``uword myvar = $8fee``
|
||||
``float`` floating-point 5 bytes = 40 bits ``float myvar = 1.2345``
|
||||
stored in 5-byte cbm MFLPT format
|
||||
``byte[x]`` signed byte array x bytes ``byte[4] myvar = [1, 2, 3, 4]``
|
||||
``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar = [1, 2, 3, 4]``
|
||||
``word[x]`` signed word array 2*x bytes ``word[4] myvar = [1, 2, 3, 4]``
|
||||
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar = [1, 2, 3, 4]``
|
||||
``float[x]`` floating-point array 5*x bytes ``float[4] myvar = [1.1, 2.2, 3.3, 4.4]``
|
||||
``byte[x]`` signed byte array x bytes ``byte[4] myvar``
|
||||
``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar``
|
||||
``word[x]`` signed word array 2*x bytes ``word[4] myvar``
|
||||
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar``
|
||||
``float[x]`` floating-point array 5*x bytes ``float[4] myvar``
|
||||
``byte[]`` signed byte array depends on value ``byte[] myvar = [1, 2, 3, 4]``
|
||||
``ubyte[]`` unsigned byte array depends on value ``ubyte[] myvar = [1, 2, 3, 4]``
|
||||
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
|
||||
``uword[]`` unsigned word array depends on value ``uword[] myvar = [1, 2, 3, 4]``
|
||||
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
|
||||
``str`` string (petscii) varies ``str myvar = "hello."``
|
||||
implicitly terminated by a 0-byte
|
||||
``str_s`` string (screencodes) varies ``str_s myvar = "hello."``
|
||||
implicitly terminated by a 0-byte
|
||||
=============== ======================= ================= =========================================
|
||||
|
||||
**arrays:** you can split an array initializer list over several lines if you want.
|
||||
**arrays:** you can split an array initializer list over several lines if you want. When an initialization
|
||||
value is given, the array size in the declaration can be omitted.
|
||||
|
||||
**hexadecimal numbers:** you can use a dollar prefix to write hexadecimal numbers: ``$20ac``
|
||||
|
||||
@@ -336,7 +345,7 @@ which represents a range of numbers or characters,
|
||||
from the starting value to (and including) the ending value.
|
||||
If used in the place of a literal value, it expands into the actual array of values::
|
||||
|
||||
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
|
||||
|
||||
|
||||
Array indexing
|
||||
@@ -394,7 +403,7 @@ range creation: ``to``
|
||||
X = 10
|
||||
A to X ; range of 5, 6, 7, 8, 9, 10
|
||||
|
||||
byte[4] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
|
||||
byte[] array = 10 to 13 ; sets the array to [1, 2, 3, 4]
|
||||
|
||||
for i in 0 to 127 {
|
||||
; i loops 0, 1, 2, ... 127
|
||||
@@ -507,14 +516,15 @@ For example, this is a for loop using the existing byte variable ``i`` to loop o
|
||||
|
||||
And this is a loop over the values of the array ``fibonacci_numbers`` where the loop variable is declared in the loop itself::
|
||||
|
||||
word[20] fibonacci_numbers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
|
||||
word[] fibonacci_numbers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
|
||||
|
||||
for word fibnr in fibonacci_numbers {
|
||||
; do something
|
||||
}
|
||||
|
||||
|
||||
You can inline the loop variable declaration in the for statement, including optional zp-tag::
|
||||
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
|
||||
|
||||
@@ -60,7 +60,7 @@ of values together (and use it multiple times). Something like::
|
||||
|
||||
struct Point {
|
||||
ubyte color
|
||||
word[3] vec = [0,0,0]
|
||||
word[] vec = [0,0,0]
|
||||
}
|
||||
|
||||
Point p1
|
||||
@@ -74,6 +74,4 @@ of values together (and use it multiple times). Something like::
|
||||
Misc
|
||||
^^^^
|
||||
|
||||
- implement %asmbinary
|
||||
- make the array size optional in the var decl if an initialization array is given
|
||||
- are there any other missing instructions in the code generator?
|
||||
+2
-2
@@ -56,7 +56,7 @@ sub start() {
|
||||
; details about the boulderdash music can be found here:
|
||||
; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune
|
||||
|
||||
uword[128] notes = [
|
||||
uword[] notes = [
|
||||
$1622, $1d26, $2229, $252e, $1424, $1f27, $2029, $2730,
|
||||
$122a, $122c, $1e2e, $1231, $202c, $3337, $212d, $3135,
|
||||
$1622, $162e, $161d, $1624, $1420, $1430, $1424, $1420,
|
||||
@@ -76,7 +76,7 @@ sub start() {
|
||||
]
|
||||
|
||||
|
||||
uword[59] music_freq_table = [
|
||||
uword[] music_freq_table = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
732, 778, 826, 876, 928, 978, 1042, 1100, 1170, 1238, 1312, 1390, 1464, 1556,
|
||||
1652, 1752, 1856, 1956, 2084, 2200, 2340, 2476, 2624, 2780, 2928, 3112, 3304,
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
const uword height = 25
|
||||
|
||||
; vertices
|
||||
float[8] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
|
||||
float[8] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
|
||||
float[8] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
|
||||
float[] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
|
||||
float[] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
|
||||
float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
|
||||
|
||||
; storage for rotated coordinates
|
||||
float[len(xcoor)] rotatedx=0.0
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
; it must start on an address aligned to 64 bytes.
|
||||
%option force_output ; make sure the data in this block appears in the resulting program
|
||||
|
||||
ubyte[128] sprites = [
|
||||
ubyte[] sprites = [
|
||||
%00000000,%00000000,%00000000,
|
||||
%00000000,%00111100,%00000000,
|
||||
%00000000,%11111111,%00000000,
|
||||
@@ -64,9 +64,9 @@
|
||||
const uword height = 200
|
||||
|
||||
; vertices
|
||||
byte[8] xcoor = [ -100, -100, -100, -100, 100, 100, 100, 100 ]
|
||||
byte[8] ycoor = [ -100, -100, 100, 100, -100, -100, 100, 100 ]
|
||||
byte[8] zcoor = [ -100, 100, -100, 100, -100, 100, -100, 100 ]
|
||||
byte[] xcoor = [ -100, -100, -100, -100, 100, 100, 100, 100 ]
|
||||
byte[] ycoor = [ -100, -100, 100, 100, -100, -100, 100, 100 ]
|
||||
byte[] zcoor = [ -100, 100, -100, 100, -100, 100, -100, 100 ]
|
||||
|
||||
; storage for rotated coordinates
|
||||
word[len(xcoor)] rotatedx
|
||||
@@ -146,7 +146,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
ubyte[8] spritecolors = [1,1,7,15,12,11,9,9]
|
||||
ubyte[] spritecolors = [1,1,7,15,12,11,9,9]
|
||||
|
||||
for ubyte i in 0 to 7 {
|
||||
word zc = rotatedz[i]
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
const uword height = 200
|
||||
|
||||
; vertices
|
||||
float[8] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
|
||||
float[8] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
|
||||
float[8] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
|
||||
float[] xcoor = [ -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0 ]
|
||||
float[] ycoor = [ -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0 ]
|
||||
float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
|
||||
|
||||
; edges (msb=from vertex, lsb=to vertex)
|
||||
uword[12] edges = [$0001, $0103, $0302, $0200, $0405, $0507, $0706, $0604, $0004, $0105, $0206, $0307]
|
||||
uword[] edges = [$0001, $0103, $0302, $0200, $0405, $0507, $0706, $0604, $0004, $0105, $0206, $0307]
|
||||
|
||||
; storage for rotated coordinates
|
||||
float[len(xcoor)] rotatedx
|
||||
|
||||
+4
-4
@@ -7,9 +7,9 @@
|
||||
const uword height = 25
|
||||
|
||||
; vertices
|
||||
byte[8] xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
|
||||
byte[8] ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
|
||||
byte[8] zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
|
||||
byte[] xcoor = [ -40, -40, -40, -40, 40, 40, 40, 40 ]
|
||||
byte[] ycoor = [ -40, -40, 40, 40, -40, -40, 40, 40 ]
|
||||
byte[] zcoor = [ -40, 40, -40, 40, -40, 40, -40, 40 ]
|
||||
|
||||
; storage for rotated coordinates
|
||||
word[len(xcoor)] rotatedx
|
||||
@@ -68,7 +68,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
ubyte[6] vertexcolors = [1,7,7,12,11,6]
|
||||
ubyte[] vertexcolors = [1,7,7,12,11,6]
|
||||
|
||||
sub draw_edges() {
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
~ irq {
|
||||
|
||||
const ubyte barheight = 4
|
||||
ubyte[13] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]
|
||||
ubyte[] colors = [6,2,4,5,15,7,1,13,3,12,8,11,9]
|
||||
ubyte color = 0
|
||||
ubyte ypos = 0
|
||||
|
||||
|
||||
+21
-21
@@ -8,27 +8,27 @@
|
||||
; it must start on an address aligned to 64 bytes.
|
||||
%option force_output ; make sure the data in this block appears in the resulting program
|
||||
|
||||
ubyte[63] balloonsprite = [ %00000000,%01111111,%00000000,
|
||||
%00000001,%11111111,%11000000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000111,%11011101,%11110000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000010,%11111111,%10100000,
|
||||
%00000001,%01111111,%01000000,
|
||||
%00000001,%00111110,%01000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00011100,%00000000 ]
|
||||
ubyte[] balloonsprite = [ %00000000,%01111111,%00000000,
|
||||
%00000001,%11111111,%11000000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000111,%11011101,%11110000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000010,%11111111,%10100000,
|
||||
%00000001,%01111111,%01000000,
|
||||
%00000001,%00111110,%01000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00011100,%00000000 ]
|
||||
}
|
||||
|
||||
~ main {
|
||||
|
||||
+32
-46
@@ -205,7 +205,7 @@ waitkey:
|
||||
if linepos and blocklogic.isLineFull(linepos)
|
||||
blocklogic.collapse(linepos)
|
||||
lines += num_lines
|
||||
uword[4] scores = [10, 25, 50, 100] ; can never clear more than 4 lines
|
||||
uword[] scores = [10, 25, 50, 100] ; can never clear more than 4 lines
|
||||
score += scores[num_lines-1]
|
||||
speedlevel = 1+lsb(lines/10)
|
||||
drawScore()
|
||||
@@ -320,7 +320,7 @@ waitkey:
|
||||
c64scr.setcc(boardOffsetX+boardWidth, i, 84, 11)
|
||||
}
|
||||
|
||||
ubyte[5] colors = [6,8,7,5,4]
|
||||
ubyte[] colors = [6,8,7,5,4]
|
||||
for i in len(colors)-1 to 0 step -1 {
|
||||
for ubyte x in 5 to 0 step -1 {
|
||||
c64scr.setcc(6+x-i, 11+2*i, 102, colors[i])
|
||||
@@ -387,54 +387,40 @@ waitkey:
|
||||
ubyte[16] rotated
|
||||
|
||||
; the 7 tetrominos
|
||||
ubyte[16] blockI = [0,0,0,0, ; cyan ; note: special rotation (around matrix center)
|
||||
3,3,3,3,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[16] blockJ = [6,0,0,0, ; blue
|
||||
6,6,6,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[16] blockL = [0,0,8,0, ; orange
|
||||
8,8,8,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[16] blockO = [0,7,7,0, ; yellow ; note: no rotation (square)
|
||||
0,7,7,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[16] blockS = [0,5,5,0, ; green
|
||||
5,5,0,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[16] blockT = [0,4,0,0, ; purple
|
||||
4,4,4,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[16] blockZ = [2,2,0,0, ; red
|
||||
0,2,2,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[] blockI = [0,0,0,0, ; cyan ; note: special rotation (around matrix center)
|
||||
3,3,3,3,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[] blockJ = [6,0,0,0, ; blue
|
||||
6,6,6,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[] blockL = [0,0,8,0, ; orange
|
||||
8,8,8,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[] blockO = [0,7,7,0, ; yellow ; note: no rotation (square)
|
||||
0,7,7,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[] blockS = [0,5,5,0, ; green
|
||||
5,5,0,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[] blockT = [0,4,0,0, ; purple
|
||||
4,4,4,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
ubyte[] blockZ = [2,2,0,0, ; red
|
||||
0,2,2,0,
|
||||
0,0,0,0,
|
||||
0,0,0,0]
|
||||
|
||||
; @todo use the '&' operator to create an array of pointers and use that, like so:
|
||||
; uword[7] blocks = [&blockI, &blockJ, &blockL, &blockO, &blockS, &blockT, &blockZ]
|
||||
uword[] blocks = [&blockI, &blockJ, &blockL, &blockO, &blockS, &blockT, &blockZ]
|
||||
|
||||
sub newCurrentBlock(ubyte block) {
|
||||
currentBlockNum = block
|
||||
if block==0
|
||||
memcopy(blockI, currentBlock, len(currentBlock))
|
||||
else if block==1
|
||||
memcopy(blockJ, currentBlock, len(currentBlock))
|
||||
else if block==2
|
||||
memcopy(blockL, currentBlock, len(currentBlock))
|
||||
else if block==3
|
||||
memcopy(blockO, currentBlock, len(currentBlock))
|
||||
else if block==4
|
||||
memcopy(blockS, currentBlock, len(currentBlock))
|
||||
else if block==5
|
||||
memcopy(blockT, currentBlock, len(currentBlock))
|
||||
else if block==6
|
||||
memcopy(blockZ, currentBlock, len(currentBlock))
|
||||
memcopy(blocks[block], currentBlock, len(currentBlock))
|
||||
}
|
||||
|
||||
sub rotateCW() {
|
||||
|
||||
+41
-73
@@ -1,89 +1,57 @@
|
||||
%import c64utils
|
||||
%zeropage basicsafe
|
||||
%option enable_floats
|
||||
|
||||
%import c64flt
|
||||
|
||||
|
||||
~ main {
|
||||
|
||||
sub start() {
|
||||
|
||||
ubyte[3] array1
|
||||
ubyte[3] array2
|
||||
ubyte[3] array3
|
||||
ubyte x = 99
|
||||
|
||||
str string1="hello"
|
||||
str string2="bye"
|
||||
return
|
||||
|
||||
uword pointer = &array1
|
||||
uword pointer2
|
||||
uword pointer3
|
||||
byte bt
|
||||
|
||||
pointer2 = &array2
|
||||
pointer3 = &string1
|
||||
|
||||
uword[4] pointers = [&array1, &array2, &string1, &string2]
|
||||
|
||||
|
||||
ptrsubasm("moet werken")
|
||||
pointersub("moet werken")
|
||||
myprintasm(string1)
|
||||
myprintasm(string2)
|
||||
myprintasm("moet werken3")
|
||||
myprintasm("moet werken3")
|
||||
myprintasm("moet werken4")
|
||||
|
||||
c64scr.print("this print must work\n")
|
||||
c64.CHROUT('\n')
|
||||
|
||||
ptrsubasm(&array1)
|
||||
ptrsubasm(&array2)
|
||||
ptrsubasm(&string1)
|
||||
ptrsubasm(&string2)
|
||||
pointersub(&array1)
|
||||
pointersub(&array2)
|
||||
pointersub(&string1)
|
||||
pointersub(&string2)
|
||||
c64scr.print_uwhex(1, pointers[0])
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_uwhex(1, pointers[1])
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_uwhex(1, pointers[2])
|
||||
c64.CHROUT(',')
|
||||
c64scr.print_uwhex(1, pointers[3])
|
||||
startqqq:
|
||||
|
||||
sub startzzz() {
|
||||
if_cc goto startqqq
|
||||
c64.EXTCOL++
|
||||
}
|
||||
}
|
||||
|
||||
sub pointersub(uword arg) {
|
||||
c64scr.print_uwhex(1, arg)
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
asmsub ptrsubasm(uword arg @ AY) -> clobbers() -> () {
|
||||
%asm {{
|
||||
sec
|
||||
jsr c64scr.print_uwhex
|
||||
lda #13
|
||||
jmp c64.CHROUT
|
||||
}}
|
||||
}
|
||||
; 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)
|
||||
; }
|
||||
; }
|
||||
|
||||
asmsub myprintasm(str arg @ AY) -> clobbers() -> () {
|
||||
%asm {{
|
||||
sec
|
||||
jsr c64scr.print_uwhex
|
||||
lda #13
|
||||
jmp c64.CHROUT
|
||||
}}
|
||||
}
|
||||
;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')
|
||||
|
||||
}
|
||||
|
||||
~ test {
|
||||
|
||||
sub testsub() {
|
||||
uword[4] pointers = [&main.start.array1, &main.start.array2, &main.start.string1, &main.start.string2] ; @todo make it possible to initialize array with pointers
|
||||
|
||||
}
|
||||
}
|
||||
+21
-21
@@ -8,27 +8,27 @@
|
||||
; it must start on an address aligned to 64 bytes.
|
||||
%option force_output ; make sure the data in this block appears in the resulting program
|
||||
|
||||
ubyte[63] balloonsprite = [ %00000000,%01111111,%00000000,
|
||||
%00000001,%11111111,%11000000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000111,%11011101,%11110000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000010,%11111111,%10100000,
|
||||
%00000001,%01111111,%01000000,
|
||||
%00000001,%00111110,%01000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00011100,%00000000 ]
|
||||
ubyte[] balloonsprite = [ %00000000,%01111111,%00000000,
|
||||
%00000001,%11111111,%11000000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000111,%11011101,%11110000,
|
||||
%00000111,%11011100,%11110000,
|
||||
%00000011,%11100011,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000011,%11111111,%11100000,
|
||||
%00000010,%11111111,%10100000,
|
||||
%00000001,%01111111,%01000000,
|
||||
%00000001,%00111110,%01000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%10011100,%10000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%01001001,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00111110,%00000000,
|
||||
%00000000,%00011100,%00000000 ]
|
||||
}
|
||||
|
||||
~ main {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# compile using regular Kotlin sdk command line tool
|
||||
|
||||
echo "Compiling the parser..."
|
||||
java -jar ./parser/antlr/lib/antlr-4.7.2-complete.jar -o ./parser/src/prog8/parser -Xexact-output-dir -no-listener -no-visitor ./parser/antlr/prog8.g4
|
||||
|
||||
+3
-1
@@ -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
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
PROG8CLASSPATH=./out/production/compiler_main:./out/production/parser_main
|
||||
PROG8CLASSPATH=./compiler/build/classes/kotlin/main:./compiler/build/resources/main:./parser/build/classes/java/main
|
||||
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
|
||||
@@ -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
|
||||
@@ -1,7 +1,9 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
PROG8CLASSPATH=./out/production/compiler_main
|
||||
PROG8CLASSPATH=./compiler/build/classes/kotlin/main:./compiler/build/resources/main
|
||||
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
|
||||
@@ -59,6 +59,10 @@ ZEROPAGE :
|
||||
'@zp'
|
||||
;
|
||||
|
||||
ARRAYSIG :
|
||||
'[]'
|
||||
;
|
||||
|
||||
|
||||
module : (modulestatement | EOL)* EOF ;
|
||||
|
||||
@@ -104,7 +108,7 @@ directive :
|
||||
|
||||
directivearg : stringliteral | identifier | integerliteral ;
|
||||
|
||||
vardecl: datatype ZEROPAGE? arrayspec? identifier ;
|
||||
vardecl: datatype ZEROPAGE? (arrayindex | ARRAYSIG) ? identifier ;
|
||||
|
||||
varinitializer : vardecl '=' expression ;
|
||||
|
||||
@@ -114,7 +118,7 @@ memoryvardecl: ADDRESS_OF varinitializer;
|
||||
|
||||
datatype: 'ubyte' | 'byte' | 'uword' | 'word' | 'float' | 'str' | 'str_s' ;
|
||||
|
||||
arrayspec: '[' expression ']' ;
|
||||
arrayindex: '[' expression ']' ;
|
||||
|
||||
assignment : assign_targets '=' expression ;
|
||||
|
||||
@@ -164,7 +168,7 @@ expression :
|
||||
typecast : 'as' datatype;
|
||||
|
||||
|
||||
arrayindexed : scoped_identifier arrayspec ;
|
||||
arrayindexed : scoped_identifier arrayindex ;
|
||||
|
||||
directmemory : '@' '(' expression ')';
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<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" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated from ./parser/antlr/prog8.g4 by ANTLR 4.7.2
|
||||
// Generated from prog8.g4 by ANTLR 4.7.2
|
||||
|
||||
package prog8.parser;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class prog8Lexer extends Lexer {
|
||||
T__101=102, T__102=103, T__103=104, T__104=105, T__105=106, T__106=107,
|
||||
T__107=108, LINECOMMENT=109, COMMENT=110, WS=111, EOL=112, NAME=113, DEC_INTEGER=114,
|
||||
HEX_INTEGER=115, BIN_INTEGER=116, ADDRESS_OF=117, FLOAT_NUMBER=118, STRING=119,
|
||||
INLINEASMBLOCK=120, SINGLECHAR=121, ZEROPAGE=122;
|
||||
INLINEASMBLOCK=120, SINGLECHAR=121, ZEROPAGE=122, ARRAYSIG=123;
|
||||
public static String[] channelNames = {
|
||||
"DEFAULT_TOKEN_CHANNEL", "HIDDEN"
|
||||
};
|
||||
@@ -63,7 +63,7 @@ public class prog8Lexer extends Lexer {
|
||||
"T__105", "T__106", "T__107", "LINECOMMENT", "COMMENT", "WS", "EOL",
|
||||
"NAME", "DEC_INTEGER", "HEX_INTEGER", "BIN_INTEGER", "ADDRESS_OF", "FLOAT_NUMBER",
|
||||
"FNUMBER", "STRING_ESCAPE_SEQ", "STRING", "INLINEASMBLOCK", "SINGLECHAR",
|
||||
"ZEROPAGE"
|
||||
"ZEROPAGE", "ARRAYSIG"
|
||||
};
|
||||
}
|
||||
public static final String[] ruleNames = makeRuleNames();
|
||||
@@ -84,7 +84,8 @@ public class prog8Lexer extends Lexer {
|
||||
"'if'", "'else'", "'if_cs'", "'if_cc'", "'if_eq'", "'if_z'", "'if_ne'",
|
||||
"'if_nz'", "'if_pl'", "'if_pos'", "'if_mi'", "'if_neg'", "'if_vs'", "'if_vc'",
|
||||
"'for'", "'in'", "'while'", "'repeat'", "'until'", null, null, null,
|
||||
null, null, null, null, null, "'&'", null, null, null, null, "'@zp'"
|
||||
null, null, null, null, null, "'&'", null, null, null, null, "'@zp'",
|
||||
"'[]'"
|
||||
};
|
||||
}
|
||||
private static final String[] _LITERAL_NAMES = makeLiteralNames();
|
||||
@@ -101,7 +102,7 @@ public class prog8Lexer extends Lexer {
|
||||
null, null, null, null, null, null, null, null, null, null, null, null,
|
||||
null, "LINECOMMENT", "COMMENT", "WS", "EOL", "NAME", "DEC_INTEGER", "HEX_INTEGER",
|
||||
"BIN_INTEGER", "ADDRESS_OF", "FLOAT_NUMBER", "STRING", "INLINEASMBLOCK",
|
||||
"SINGLECHAR", "ZEROPAGE"
|
||||
"SINGLECHAR", "ZEROPAGE", "ARRAYSIG"
|
||||
};
|
||||
}
|
||||
private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames();
|
||||
@@ -211,7 +212,7 @@ public class prog8Lexer extends Lexer {
|
||||
}
|
||||
|
||||
public static final String _serializedATN =
|
||||
"\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2|\u0359\b\1\4\2\t"+
|
||||
"\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2}\u035e\b\1\4\2\t"+
|
||||
"\2\4\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13"+
|
||||
"\t\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+
|
||||
"\4\23\t\23\4\24\t\24\4\25\t\25\4\26\t\26\4\27\t\27\4\30\t\30\4\31\t\31"+
|
||||
@@ -224,277 +225,278 @@ public class prog8Lexer extends Lexer {
|
||||
"\4U\tU\4V\tV\4W\tW\4X\tX\4Y\tY\4Z\tZ\4[\t[\4\\\t\\\4]\t]\4^\t^\4_\t_\4"+
|
||||
"`\t`\4a\ta\4b\tb\4c\tc\4d\td\4e\te\4f\tf\4g\tg\4h\th\4i\ti\4j\tj\4k\t"+
|
||||
"k\4l\tl\4m\tm\4n\tn\4o\to\4p\tp\4q\tq\4r\tr\4s\ts\4t\tt\4u\tu\4v\tv\4"+
|
||||
"w\tw\4x\tx\4y\ty\4z\tz\4{\t{\4|\t|\4}\t}\3\2\3\2\3\3\3\3\3\4\3\4\3\4\3"+
|
||||
"\4\3\4\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\6\3\6\3\6\3\6\3\6\3\6\3\6\3\6"+
|
||||
"\3\6\3\6\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\b\3\b\3\b\3\b\3\b\3"+
|
||||
"\b\3\b\3\b\3\b\3\b\3\b\3\b\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3\n\3\n"+
|
||||
"\3\n\3\n\3\n\3\n\3\n\3\n\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13"+
|
||||
"\3\13\3\13\3\13\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\r\3"+
|
||||
"\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\16\3\16\3\16\3\16\3\16\3\16\3"+
|
||||
"\16\3\16\3\17\3\17\3\20\3\20\3\21\3\21\3\21\3\21\3\21\3\21\3\22\3\22\3"+
|
||||
"\22\3\22\3\22\3\22\3\23\3\23\3\23\3\23\3\23\3\24\3\24\3\24\3\24\3\24\3"+
|
||||
"\24\3\25\3\25\3\25\3\25\3\25\3\26\3\26\3\26\3\26\3\26\3\26\3\27\3\27\3"+
|
||||
"\27\3\27\3\30\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3\32\3\32\3\33\3\33\3"+
|
||||
"\33\3\34\3\34\3\34\3\35\3\35\3\35\3\36\3\36\3\36\3\37\3\37\3\37\3\37\3"+
|
||||
" \3 \3 \3!\3!\3!\3\"\3\"\3\"\3#\3#\3#\3$\3$\3$\3$\3%\3%\3%\3%\3&\3&\3"+
|
||||
"&\3\'\3\'\3\'\3(\3(\3)\3)\3*\3*\3*\3+\3+\3,\3,\3-\3-\3.\3.\3.\3/\3/\3"+
|
||||
"/\3\60\3\60\3\61\3\61\3\62\3\62\3\62\3\63\3\63\3\63\3\64\3\64\3\64\3\65"+
|
||||
"\3\65\3\65\3\66\3\66\3\67\3\67\38\38\38\39\39\39\39\39\3:\3:\3:\3:\3;"+
|
||||
"\3;\3;\3<\3<\3<\3<\3=\3=\3=\3=\3>\3>\3?\3?\3@\3@\3@\3A\3A\3B\3B\3B\3B"+
|
||||
"\3B\3B\3B\3C\3C\3C\3C\3C\3C\3D\3D\3D\3D\3D\3D\3D\3D\3D\3E\3E\3F\3F\3G"+
|
||||
"\3G\3H\3H\3I\3I\3I\3J\3J\3J\3K\3K\3K\3L\3L\3L\3M\3M\3M\3N\3N\3N\3O\3O"+
|
||||
"\3O\3P\3P\3P\3Q\3Q\3Q\3Q\3Q\3R\3R\3R\3R\3R\3R\3S\3S\3S\3S\3S\3T\3T\3T"+
|
||||
"\3T\3U\3U\3U\3V\3V\3W\3W\3X\3X\3X\3X\3X\3X\3X\3Y\3Y\3Y\3Y\3Y\3Y\3Y\3Y"+
|
||||
"\3Y\3Z\3Z\3Z\3Z\3Z\3Z\3[\3[\3[\3\\\3\\\3\\\3\\\3\\\3]\3]\3]\3]\3]\3]\3"+
|
||||
"^\3^\3^\3^\3^\3^\3_\3_\3_\3_\3_\3_\3`\3`\3`\3`\3`\3a\3a\3a\3a\3a\3a\3"+
|
||||
"b\3b\3b\3b\3b\3b\3c\3c\3c\3c\3c\3c\3d\3d\3d\3d\3d\3d\3d\3e\3e\3e\3e\3"+
|
||||
"e\3e\3f\3f\3f\3f\3f\3f\3f\3g\3g\3g\3g\3g\3g\3h\3h\3h\3h\3h\3h\3i\3i\3"+
|
||||
"i\3i\3j\3j\3j\3k\3k\3k\3k\3k\3k\3l\3l\3l\3l\3l\3l\3l\3m\3m\3m\3m\3m\3"+
|
||||
"m\3n\3n\7n\u02e2\nn\fn\16n\u02e5\13n\3n\3n\3n\3n\3o\3o\7o\u02ed\no\fo"+
|
||||
"\16o\u02f0\13o\3o\3o\3p\3p\3p\3p\3q\6q\u02f9\nq\rq\16q\u02fa\3r\3r\7r"+
|
||||
"\u02ff\nr\fr\16r\u0302\13r\3s\3s\3s\6s\u0307\ns\rs\16s\u0308\5s\u030b"+
|
||||
"\ns\3t\3t\6t\u030f\nt\rt\16t\u0310\3u\3u\6u\u0315\nu\ru\16u\u0316\3v\3"+
|
||||
"v\3w\3w\3w\5w\u031e\nw\3w\5w\u0321\nw\3x\6x\u0324\nx\rx\16x\u0325\3x\3"+
|
||||
"x\6x\u032a\nx\rx\16x\u032b\5x\u032e\nx\3y\3y\3y\3y\5y\u0334\ny\3z\3z\3"+
|
||||
"z\7z\u0339\nz\fz\16z\u033c\13z\3z\3z\3z\3{\3{\3{\3{\6{\u0345\n{\r{\16"+
|
||||
"{\u0346\3{\3{\3{\3{\3{\3|\3|\3|\5|\u0351\n|\3|\3|\3|\3}\3}\3}\3}\3\u0346"+
|
||||
"\2~\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n\23\13\25\f\27\r\31\16\33\17\35"+
|
||||
"\20\37\21!\22#\23%\24\'\25)\26+\27-\30/\31\61\32\63\33\65\34\67\359\36"+
|
||||
";\37= ?!A\"C#E$G%I&K\'M(O)Q*S+U,W-Y.[/]\60_\61a\62c\63e\64g\65i\66k\67"+
|
||||
"m8o9q:s;u<w=y>{?}@\177A\u0081B\u0083C\u0085D\u0087E\u0089F\u008bG\u008d"+
|
||||
"H\u008fI\u0091J\u0093K\u0095L\u0097M\u0099N\u009bO\u009dP\u009fQ\u00a1"+
|
||||
"R\u00a3S\u00a5T\u00a7U\u00a9V\u00abW\u00adX\u00afY\u00b1Z\u00b3[\u00b5"+
|
||||
"\\\u00b7]\u00b9^\u00bb_\u00bd`\u00bfa\u00c1b\u00c3c\u00c5d\u00c7e\u00c9"+
|
||||
"f\u00cbg\u00cdh\u00cfi\u00d1j\u00d3k\u00d5l\u00d7m\u00d9n\u00dbo\u00dd"+
|
||||
"p\u00dfq\u00e1r\u00e3s\u00e5t\u00e7u\u00e9v\u00ebw\u00edx\u00ef\2\u00f1"+
|
||||
"\2\u00f3y\u00f5z\u00f7{\u00f9|\3\2\n\4\2\f\f\17\17\4\2\13\13\"\"\5\2C"+
|
||||
"\\aac|\6\2\62;C\\aac|\5\2\62;CHch\4\2GGgg\4\2--//\6\2\f\f\16\17$$^^\2"+
|
||||
"\u0368\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3\2\2\2\2\13\3\2\2\2\2"+
|
||||
"\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2\2\25\3\2\2\2\2\27\3"+
|
||||
"\2\2\2\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2"+
|
||||
"\2#\3\2\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3\2\2\2\2-\3\2\2\2\2"+
|
||||
"/\3\2\2\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2\67\3\2\2\2\29\3\2\2"+
|
||||
"\2\2;\3\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C\3\2\2\2\2E\3\2\2\2\2"+
|
||||
"G\3\2\2\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2\2\2\2Q\3\2\2\2\2S\3"+
|
||||
"\2\2\2\2U\3\2\2\2\2W\3\2\2\2\2Y\3\2\2\2\2[\3\2\2\2\2]\3\2\2\2\2_\3\2\2"+
|
||||
"\2\2a\3\2\2\2\2c\3\2\2\2\2e\3\2\2\2\2g\3\2\2\2\2i\3\2\2\2\2k\3\2\2\2\2"+
|
||||
"m\3\2\2\2\2o\3\2\2\2\2q\3\2\2\2\2s\3\2\2\2\2u\3\2\2\2\2w\3\2\2\2\2y\3"+
|
||||
"\2\2\2\2{\3\2\2\2\2}\3\2\2\2\2\177\3\2\2\2\2\u0081\3\2\2\2\2\u0083\3\2"+
|
||||
"\2\2\2\u0085\3\2\2\2\2\u0087\3\2\2\2\2\u0089\3\2\2\2\2\u008b\3\2\2\2\2"+
|
||||
"\u008d\3\2\2\2\2\u008f\3\2\2\2\2\u0091\3\2\2\2\2\u0093\3\2\2\2\2\u0095"+
|
||||
"\3\2\2\2\2\u0097\3\2\2\2\2\u0099\3\2\2\2\2\u009b\3\2\2\2\2\u009d\3\2\2"+
|
||||
"\2\2\u009f\3\2\2\2\2\u00a1\3\2\2\2\2\u00a3\3\2\2\2\2\u00a5\3\2\2\2\2\u00a7"+
|
||||
"\3\2\2\2\2\u00a9\3\2\2\2\2\u00ab\3\2\2\2\2\u00ad\3\2\2\2\2\u00af\3\2\2"+
|
||||
"\2\2\u00b1\3\2\2\2\2\u00b3\3\2\2\2\2\u00b5\3\2\2\2\2\u00b7\3\2\2\2\2\u00b9"+
|
||||
"\3\2\2\2\2\u00bb\3\2\2\2\2\u00bd\3\2\2\2\2\u00bf\3\2\2\2\2\u00c1\3\2\2"+
|
||||
"\2\2\u00c3\3\2\2\2\2\u00c5\3\2\2\2\2\u00c7\3\2\2\2\2\u00c9\3\2\2\2\2\u00cb"+
|
||||
"\3\2\2\2\2\u00cd\3\2\2\2\2\u00cf\3\2\2\2\2\u00d1\3\2\2\2\2\u00d3\3\2\2"+
|
||||
"\2\2\u00d5\3\2\2\2\2\u00d7\3\2\2\2\2\u00d9\3\2\2\2\2\u00db\3\2\2\2\2\u00dd"+
|
||||
"\3\2\2\2\2\u00df\3\2\2\2\2\u00e1\3\2\2\2\2\u00e3\3\2\2\2\2\u00e5\3\2\2"+
|
||||
"\2\2\u00e7\3\2\2\2\2\u00e9\3\2\2\2\2\u00eb\3\2\2\2\2\u00ed\3\2\2\2\2\u00f3"+
|
||||
"\3\2\2\2\2\u00f5\3\2\2\2\2\u00f7\3\2\2\2\2\u00f9\3\2\2\2\3\u00fb\3\2\2"+
|
||||
"\2\5\u00fd\3\2\2\2\7\u00ff\3\2\2\2\t\u0104\3\2\2\2\13\u010c\3\2\2\2\r"+
|
||||
"\u0116\3\2\2\2\17\u0120\3\2\2\2\21\u012c\3\2\2\2\23\u0135\3\2\2\2\25\u013d"+
|
||||
"\3\2\2\2\27\u0149\3\2\2\2\31\u0155\3\2\2\2\33\u0160\3\2\2\2\35\u0168\3"+
|
||||
"\2\2\2\37\u016a\3\2\2\2!\u016c\3\2\2\2#\u0172\3\2\2\2%\u0178\3\2\2\2\'"+
|
||||
"\u017d\3\2\2\2)\u0183\3\2\2\2+\u0188\3\2\2\2-\u018e\3\2\2\2/\u0192\3\2"+
|
||||
"\2\2\61\u0198\3\2\2\2\63\u019a\3\2\2\2\65\u019c\3\2\2\2\67\u019f\3\2\2"+
|
||||
"\29\u01a2\3\2\2\2;\u01a5\3\2\2\2=\u01a8\3\2\2\2?\u01ac\3\2\2\2A\u01af"+
|
||||
"\3\2\2\2C\u01b2\3\2\2\2E\u01b5\3\2\2\2G\u01b8\3\2\2\2I\u01bc\3\2\2\2K"+
|
||||
"\u01c0\3\2\2\2M\u01c3\3\2\2\2O\u01c6\3\2\2\2Q\u01c8\3\2\2\2S\u01ca\3\2"+
|
||||
"\2\2U\u01cd\3\2\2\2W\u01cf\3\2\2\2Y\u01d1\3\2\2\2[\u01d3\3\2\2\2]\u01d6"+
|
||||
"\3\2\2\2_\u01d9\3\2\2\2a\u01db\3\2\2\2c\u01dd\3\2\2\2e\u01e0\3\2\2\2g"+
|
||||
"\u01e3\3\2\2\2i\u01e6\3\2\2\2k\u01e9\3\2\2\2m\u01eb\3\2\2\2o\u01ed\3\2"+
|
||||
"\2\2q\u01f0\3\2\2\2s\u01f5\3\2\2\2u\u01f9\3\2\2\2w\u01fc\3\2\2\2y\u0200"+
|
||||
"\3\2\2\2{\u0204\3\2\2\2}\u0206\3\2\2\2\177\u0208\3\2\2\2\u0081\u020b\3"+
|
||||
"\2\2\2\u0083\u020d\3\2\2\2\u0085\u0214\3\2\2\2\u0087\u021a\3\2\2\2\u0089"+
|
||||
"\u0223\3\2\2\2\u008b\u0225\3\2\2\2\u008d\u0227\3\2\2\2\u008f\u0229\3\2"+
|
||||
"\2\2\u0091\u022b\3\2\2\2\u0093\u022e\3\2\2\2\u0095\u0231\3\2\2\2\u0097"+
|
||||
"\u0234\3\2\2\2\u0099\u0237\3\2\2\2\u009b\u023a\3\2\2\2\u009d\u023d\3\2"+
|
||||
"\2\2\u009f\u0240\3\2\2\2\u00a1\u0243\3\2\2\2\u00a3\u0248\3\2\2\2\u00a5"+
|
||||
"\u024e\3\2\2\2\u00a7\u0253\3\2\2\2\u00a9\u0257\3\2\2\2\u00ab\u025a\3\2"+
|
||||
"\2\2\u00ad\u025c\3\2\2\2\u00af\u025e\3\2\2\2\u00b1\u0265\3\2\2\2\u00b3"+
|
||||
"\u026e\3\2\2\2\u00b5\u0274\3\2\2\2\u00b7\u0277\3\2\2\2\u00b9\u027c\3\2"+
|
||||
"\2\2\u00bb\u0282\3\2\2\2\u00bd\u0288\3\2\2\2\u00bf\u028e\3\2\2\2\u00c1"+
|
||||
"\u0293\3\2\2\2\u00c3\u0299\3\2\2\2\u00c5\u029f\3\2\2\2\u00c7\u02a5\3\2"+
|
||||
"\2\2\u00c9\u02ac\3\2\2\2\u00cb\u02b2\3\2\2\2\u00cd\u02b9\3\2\2\2\u00cf"+
|
||||
"\u02bf\3\2\2\2\u00d1\u02c5\3\2\2\2\u00d3\u02c9\3\2\2\2\u00d5\u02cc\3\2"+
|
||||
"\2\2\u00d7\u02d2\3\2\2\2\u00d9\u02d9\3\2\2\2\u00db\u02df\3\2\2\2\u00dd"+
|
||||
"\u02ea\3\2\2\2\u00df\u02f3\3\2\2\2\u00e1\u02f8\3\2\2\2\u00e3\u02fc\3\2"+
|
||||
"\2\2\u00e5\u030a\3\2\2\2\u00e7\u030c\3\2\2\2\u00e9\u0312\3\2\2\2\u00eb"+
|
||||
"\u0318\3\2\2\2\u00ed\u031a\3\2\2\2\u00ef\u0323\3\2\2\2\u00f1\u0333\3\2"+
|
||||
"\2\2\u00f3\u0335\3\2\2\2\u00f5\u0340\3\2\2\2\u00f7\u034d\3\2\2\2\u00f9"+
|
||||
"\u0355\3\2\2\2\u00fb\u00fc\7\u0080\2\2\u00fc\4\3\2\2\2\u00fd\u00fe\7<"+
|
||||
"\2\2\u00fe\6\3\2\2\2\u00ff\u0100\7i\2\2\u0100\u0101\7q\2\2\u0101\u0102"+
|
||||
"\7v\2\2\u0102\u0103\7q\2\2\u0103\b\3\2\2\2\u0104\u0105\7\'\2\2\u0105\u0106"+
|
||||
"\7q\2\2\u0106\u0107\7w\2\2\u0107\u0108\7v\2\2\u0108\u0109\7r\2\2\u0109"+
|
||||
"\u010a\7w\2\2\u010a\u010b\7v\2\2\u010b\n\3\2\2\2\u010c\u010d\7\'\2\2\u010d"+
|
||||
"\u010e\7n\2\2\u010e\u010f\7c\2\2\u010f\u0110\7w\2\2\u0110\u0111\7p\2\2"+
|
||||
"\u0111\u0112\7e\2\2\u0112\u0113\7j\2\2\u0113\u0114\7g\2\2\u0114\u0115"+
|
||||
"\7t\2\2\u0115\f\3\2\2\2\u0116\u0117\7\'\2\2\u0117\u0118\7|\2\2\u0118\u0119"+
|
||||
"\7g\2\2\u0119\u011a\7t\2\2\u011a\u011b\7q\2\2\u011b\u011c\7r\2\2\u011c"+
|
||||
"\u011d\7c\2\2\u011d\u011e\7i\2\2\u011e\u011f\7g\2\2\u011f\16\3\2\2\2\u0120"+
|
||||
"\u0121\7\'\2\2\u0121\u0122\7|\2\2\u0122\u0123\7r\2\2\u0123\u0124\7t\2"+
|
||||
"\2\u0124\u0125\7g\2\2\u0125\u0126\7u\2\2\u0126\u0127\7g\2\2\u0127\u0128"+
|
||||
"\7t\2\2\u0128\u0129\7x\2\2\u0129\u012a\7g\2\2\u012a\u012b\7f\2\2\u012b"+
|
||||
"\20\3\2\2\2\u012c\u012d\7\'\2\2\u012d\u012e\7c\2\2\u012e\u012f\7f\2\2"+
|
||||
"\u012f\u0130\7f\2\2\u0130\u0131\7t\2\2\u0131\u0132\7g\2\2\u0132\u0133"+
|
||||
"\7u\2\2\u0133\u0134\7u\2\2\u0134\22\3\2\2\2\u0135\u0136\7\'\2\2\u0136"+
|
||||
"\u0137\7k\2\2\u0137\u0138\7o\2\2\u0138\u0139\7r\2\2\u0139\u013a\7q\2\2"+
|
||||
"\u013a\u013b\7t\2\2\u013b\u013c\7v\2\2\u013c\24\3\2\2\2\u013d\u013e\7"+
|
||||
"\'\2\2\u013e\u013f\7d\2\2\u013f\u0140\7t\2\2\u0140\u0141\7g\2\2\u0141"+
|
||||
"\u0142\7c\2\2\u0142\u0143\7m\2\2\u0143\u0144\7r\2\2\u0144\u0145\7q\2\2"+
|
||||
"\u0145\u0146\7k\2\2\u0146\u0147\7p\2\2\u0147\u0148\7v\2\2\u0148\26\3\2"+
|
||||
"\2\2\u0149\u014a\7\'\2\2\u014a\u014b\7c\2\2\u014b\u014c\7u\2\2\u014c\u014d"+
|
||||
"\7o\2\2\u014d\u014e\7k\2\2\u014e\u014f\7p\2\2\u014f\u0150\7e\2\2\u0150"+
|
||||
"\u0151\7n\2\2\u0151\u0152\7w\2\2\u0152\u0153\7f\2\2\u0153\u0154\7g\2\2"+
|
||||
"\u0154\30\3\2\2\2\u0155\u0156\7\'\2\2\u0156\u0157\7c\2\2\u0157\u0158\7"+
|
||||
"u\2\2\u0158\u0159\7o\2\2\u0159\u015a\7d\2\2\u015a\u015b\7k\2\2\u015b\u015c"+
|
||||
"\7p\2\2\u015c\u015d\7c\2\2\u015d\u015e\7t\2\2\u015e\u015f\7{\2\2\u015f"+
|
||||
"\32\3\2\2\2\u0160\u0161\7\'\2\2\u0161\u0162\7q\2\2\u0162\u0163\7r\2\2"+
|
||||
"\u0163\u0164\7v\2\2\u0164\u0165\7k\2\2\u0165\u0166\7q\2\2\u0166\u0167"+
|
||||
"\7p\2\2\u0167\34\3\2\2\2\u0168\u0169\7.\2\2\u0169\36\3\2\2\2\u016a\u016b"+
|
||||
"\7?\2\2\u016b \3\2\2\2\u016c\u016d\7e\2\2\u016d\u016e\7q\2\2\u016e\u016f"+
|
||||
"\7p\2\2\u016f\u0170\7u\2\2\u0170\u0171\7v\2\2\u0171\"\3\2\2\2\u0172\u0173"+
|
||||
"\7w\2\2\u0173\u0174\7d\2\2\u0174\u0175\7{\2\2\u0175\u0176\7v\2\2\u0176"+
|
||||
"\u0177\7g\2\2\u0177$\3\2\2\2\u0178\u0179\7d\2\2\u0179\u017a\7{\2\2\u017a"+
|
||||
"\u017b\7v\2\2\u017b\u017c\7g\2\2\u017c&\3\2\2\2\u017d\u017e\7w\2\2\u017e"+
|
||||
"\u017f\7y\2\2\u017f\u0180\7q\2\2\u0180\u0181\7t\2\2\u0181\u0182\7f\2\2"+
|
||||
"\u0182(\3\2\2\2\u0183\u0184\7y\2\2\u0184\u0185\7q\2\2\u0185\u0186\7t\2"+
|
||||
"\2\u0186\u0187\7f\2\2\u0187*\3\2\2\2\u0188\u0189\7h\2\2\u0189\u018a\7"+
|
||||
"n\2\2\u018a\u018b\7q\2\2\u018b\u018c\7c\2\2\u018c\u018d\7v\2\2\u018d,"+
|
||||
"\3\2\2\2\u018e\u018f\7u\2\2\u018f\u0190\7v\2\2\u0190\u0191\7t\2\2\u0191"+
|
||||
".\3\2\2\2\u0192\u0193\7u\2\2\u0193\u0194\7v\2\2\u0194\u0195\7t\2\2\u0195"+
|
||||
"\u0196\7a\2\2\u0196\u0197\7u\2\2\u0197\60\3\2\2\2\u0198\u0199\7]\2\2\u0199"+
|
||||
"\62\3\2\2\2\u019a\u019b\7_\2\2\u019b\64\3\2\2\2\u019c\u019d\7-\2\2\u019d"+
|
||||
"\u019e\7?\2\2\u019e\66\3\2\2\2\u019f\u01a0\7/\2\2\u01a0\u01a1\7?\2\2\u01a1"+
|
||||
"8\3\2\2\2\u01a2\u01a3\7\61\2\2\u01a3\u01a4\7?\2\2\u01a4:\3\2\2\2\u01a5"+
|
||||
"\u01a6\7,\2\2\u01a6\u01a7\7?\2\2\u01a7<\3\2\2\2\u01a8\u01a9\7,\2\2\u01a9"+
|
||||
"\u01aa\7,\2\2\u01aa\u01ab\7?\2\2\u01ab>\3\2\2\2\u01ac\u01ad\7(\2\2\u01ad"+
|
||||
"\u01ae\7?\2\2\u01ae@\3\2\2\2\u01af\u01b0\7~\2\2\u01b0\u01b1\7?\2\2\u01b1"+
|
||||
"B\3\2\2\2\u01b2\u01b3\7`\2\2\u01b3\u01b4\7?\2\2\u01b4D\3\2\2\2\u01b5\u01b6"+
|
||||
"\7\'\2\2\u01b6\u01b7\7?\2\2\u01b7F\3\2\2\2\u01b8\u01b9\7>\2\2\u01b9\u01ba"+
|
||||
"\7>\2\2\u01ba\u01bb\7?\2\2\u01bbH\3\2\2\2\u01bc\u01bd\7@\2\2\u01bd\u01be"+
|
||||
"\7@\2\2\u01be\u01bf\7?\2\2\u01bfJ\3\2\2\2\u01c0\u01c1\7-\2\2\u01c1\u01c2"+
|
||||
"\7-\2\2\u01c2L\3\2\2\2\u01c3\u01c4\7/\2\2\u01c4\u01c5\7/\2\2\u01c5N\3"+
|
||||
"\2\2\2\u01c6\u01c7\7-\2\2\u01c7P\3\2\2\2\u01c8\u01c9\7/\2\2\u01c9R\3\2"+
|
||||
"\2\2\u01ca\u01cb\7,\2\2\u01cb\u01cc\7,\2\2\u01ccT\3\2\2\2\u01cd\u01ce"+
|
||||
"\7,\2\2\u01ceV\3\2\2\2\u01cf\u01d0\7\61\2\2\u01d0X\3\2\2\2\u01d1\u01d2"+
|
||||
"\7\'\2\2\u01d2Z\3\2\2\2\u01d3\u01d4\7>\2\2\u01d4\u01d5\7>\2\2\u01d5\\"+
|
||||
"\3\2\2\2\u01d6\u01d7\7@\2\2\u01d7\u01d8\7@\2\2\u01d8^\3\2\2\2\u01d9\u01da"+
|
||||
"\7>\2\2\u01da`\3\2\2\2\u01db\u01dc\7@\2\2\u01dcb\3\2\2\2\u01dd\u01de\7"+
|
||||
">\2\2\u01de\u01df\7?\2\2\u01dfd\3\2\2\2\u01e0\u01e1\7@\2\2\u01e1\u01e2"+
|
||||
"\7?\2\2\u01e2f\3\2\2\2\u01e3\u01e4\7?\2\2\u01e4\u01e5\7?\2\2\u01e5h\3"+
|
||||
"\2\2\2\u01e6\u01e7\7#\2\2\u01e7\u01e8\7?\2\2\u01e8j\3\2\2\2\u01e9\u01ea"+
|
||||
"\7`\2\2\u01eal\3\2\2\2\u01eb\u01ec\7~\2\2\u01ecn\3\2\2\2\u01ed\u01ee\7"+
|
||||
"v\2\2\u01ee\u01ef\7q\2\2\u01efp\3\2\2\2\u01f0\u01f1\7u\2\2\u01f1\u01f2"+
|
||||
"\7v\2\2\u01f2\u01f3\7g\2\2\u01f3\u01f4\7r\2\2\u01f4r\3\2\2\2\u01f5\u01f6"+
|
||||
"\7c\2\2\u01f6\u01f7\7p\2\2\u01f7\u01f8\7f\2\2\u01f8t\3\2\2\2\u01f9\u01fa"+
|
||||
"\7q\2\2\u01fa\u01fb\7t\2\2\u01fbv\3\2\2\2\u01fc\u01fd\7z\2\2\u01fd\u01fe"+
|
||||
"\7q\2\2\u01fe\u01ff\7t\2\2\u01ffx\3\2\2\2\u0200\u0201\7p\2\2\u0201\u0202"+
|
||||
"\7q\2\2\u0202\u0203\7v\2\2\u0203z\3\2\2\2\u0204\u0205\7*\2\2\u0205|\3"+
|
||||
"\2\2\2\u0206\u0207\7+\2\2\u0207~\3\2\2\2\u0208\u0209\7c\2\2\u0209\u020a"+
|
||||
"\7u\2\2\u020a\u0080\3\2\2\2\u020b\u020c\7B\2\2\u020c\u0082\3\2\2\2\u020d"+
|
||||
"\u020e\7t\2\2\u020e\u020f\7g\2\2\u020f\u0210\7v\2\2\u0210\u0211\7w\2\2"+
|
||||
"\u0211\u0212\7t\2\2\u0212\u0213\7p\2\2\u0213\u0084\3\2\2\2\u0214\u0215"+
|
||||
"\7d\2\2\u0215\u0216\7t\2\2\u0216\u0217\7g\2\2\u0217\u0218\7c\2\2\u0218"+
|
||||
"\u0219\7m\2\2\u0219\u0086\3\2\2\2\u021a\u021b\7e\2\2\u021b\u021c\7q\2"+
|
||||
"\2\u021c\u021d\7p\2\2\u021d\u021e\7v\2\2\u021e\u021f\7k\2\2\u021f\u0220"+
|
||||
"\7p\2\2\u0220\u0221\7w\2\2\u0221\u0222\7g\2\2\u0222\u0088\3\2\2\2\u0223"+
|
||||
"\u0224\7\60\2\2\u0224\u008a\3\2\2\2\u0225\u0226\7C\2\2\u0226\u008c\3\2"+
|
||||
"\2\2\u0227\u0228\7Z\2\2\u0228\u008e\3\2\2\2\u0229\u022a\7[\2\2\u022a\u0090"+
|
||||
"\3\2\2\2\u022b\u022c\7C\2\2\u022c\u022d\7Z\2\2\u022d\u0092\3\2\2\2\u022e"+
|
||||
"\u022f\7C\2\2\u022f\u0230\7[\2\2\u0230\u0094\3\2\2\2\u0231\u0232\7Z\2"+
|
||||
"\2\u0232\u0233\7[\2\2\u0233\u0096\3\2\2\2\u0234\u0235\7R\2\2\u0235\u0236"+
|
||||
"\7e\2\2\u0236\u0098\3\2\2\2\u0237\u0238\7R\2\2\u0238\u0239\7|\2\2\u0239"+
|
||||
"\u009a\3\2\2\2\u023a\u023b\7R\2\2\u023b\u023c\7p\2\2\u023c\u009c\3\2\2"+
|
||||
"\2\u023d\u023e\7R\2\2\u023e\u023f\7x\2\2\u023f\u009e\3\2\2\2\u0240\u0241"+
|
||||
"\7\60\2\2\u0241\u0242\7y\2\2\u0242\u00a0\3\2\2\2\u0243\u0244\7v\2\2\u0244"+
|
||||
"\u0245\7t\2\2\u0245\u0246\7w\2\2\u0246\u0247\7g\2\2\u0247\u00a2\3\2\2"+
|
||||
"\2\u0248\u0249\7h\2\2\u0249\u024a\7c\2\2\u024a\u024b\7n\2\2\u024b\u024c"+
|
||||
"\7u\2\2\u024c\u024d\7g\2\2\u024d\u00a4\3\2\2\2\u024e\u024f\7\'\2\2\u024f"+
|
||||
"\u0250\7c\2\2\u0250\u0251\7u\2\2\u0251\u0252\7o\2\2\u0252\u00a6\3\2\2"+
|
||||
"\2\u0253\u0254\7u\2\2\u0254\u0255\7w\2\2\u0255\u0256\7d\2\2\u0256\u00a8"+
|
||||
"\3\2\2\2\u0257\u0258\7/\2\2\u0258\u0259\7@\2\2\u0259\u00aa\3\2\2\2\u025a"+
|
||||
"\u025b\7}\2\2\u025b\u00ac\3\2\2\2\u025c\u025d\7\177\2\2\u025d\u00ae\3"+
|
||||
"\2\2\2\u025e\u025f\7c\2\2\u025f\u0260\7u\2\2\u0260\u0261\7o\2\2\u0261"+
|
||||
"\u0262\7u\2\2\u0262\u0263\7w\2\2\u0263\u0264\7d\2\2\u0264\u00b0\3\2\2"+
|
||||
"\2\u0265\u0266\7e\2\2\u0266\u0267\7n\2\2\u0267\u0268\7q\2\2\u0268\u0269"+
|
||||
"\7d\2\2\u0269\u026a\7d\2\2\u026a\u026b\7g\2\2\u026b\u026c\7t\2\2\u026c"+
|
||||
"\u026d\7u\2\2\u026d\u00b2\3\2\2\2\u026e\u026f\7u\2\2\u026f\u0270\7v\2"+
|
||||
"\2\u0270\u0271\7c\2\2\u0271\u0272\7e\2\2\u0272\u0273\7m\2\2\u0273\u00b4"+
|
||||
"\3\2\2\2\u0274\u0275\7k\2\2\u0275\u0276\7h\2\2\u0276\u00b6\3\2\2\2\u0277"+
|
||||
"\u0278\7g\2\2\u0278\u0279\7n\2\2\u0279\u027a\7u\2\2\u027a\u027b\7g\2\2"+
|
||||
"\u027b\u00b8\3\2\2\2\u027c\u027d\7k\2\2\u027d\u027e\7h\2\2\u027e\u027f"+
|
||||
"\7a\2\2\u027f\u0280\7e\2\2\u0280\u0281\7u\2\2\u0281\u00ba\3\2\2\2\u0282"+
|
||||
"\u0283\7k\2\2\u0283\u0284\7h\2\2\u0284\u0285\7a\2\2\u0285\u0286\7e\2\2"+
|
||||
"\u0286\u0287\7e\2\2\u0287\u00bc\3\2\2\2\u0288\u0289\7k\2\2\u0289\u028a"+
|
||||
"\7h\2\2\u028a\u028b\7a\2\2\u028b\u028c\7g\2\2\u028c\u028d\7s\2\2\u028d"+
|
||||
"\u00be\3\2\2\2\u028e\u028f\7k\2\2\u028f\u0290\7h\2\2\u0290\u0291\7a\2"+
|
||||
"\2\u0291\u0292\7|\2\2\u0292\u00c0\3\2\2\2\u0293\u0294\7k\2\2\u0294\u0295"+
|
||||
"\7h\2\2\u0295\u0296\7a\2\2\u0296\u0297\7p\2\2\u0297\u0298\7g\2\2\u0298"+
|
||||
"\u00c2\3\2\2\2\u0299\u029a\7k\2\2\u029a\u029b\7h\2\2\u029b\u029c\7a\2"+
|
||||
"\2\u029c\u029d\7p\2\2\u029d\u029e\7|\2\2\u029e\u00c4\3\2\2\2\u029f\u02a0"+
|
||||
"\7k\2\2\u02a0\u02a1\7h\2\2\u02a1\u02a2\7a\2\2\u02a2\u02a3\7r\2\2\u02a3"+
|
||||
"\u02a4\7n\2\2\u02a4\u00c6\3\2\2\2\u02a5\u02a6\7k\2\2\u02a6\u02a7\7h\2"+
|
||||
"\2\u02a7\u02a8\7a\2\2\u02a8\u02a9\7r\2\2\u02a9\u02aa\7q\2\2\u02aa\u02ab"+
|
||||
"\7u\2\2\u02ab\u00c8\3\2\2\2\u02ac\u02ad\7k\2\2\u02ad\u02ae\7h\2\2\u02ae"+
|
||||
"\u02af\7a\2\2\u02af\u02b0\7o\2\2\u02b0\u02b1\7k\2\2\u02b1\u00ca\3\2\2"+
|
||||
"\2\u02b2\u02b3\7k\2\2\u02b3\u02b4\7h\2\2\u02b4\u02b5\7a\2\2\u02b5\u02b6"+
|
||||
"\7p\2\2\u02b6\u02b7\7g\2\2\u02b7\u02b8\7i\2\2\u02b8\u00cc\3\2\2\2\u02b9"+
|
||||
"\u02ba\7k\2\2\u02ba\u02bb\7h\2\2\u02bb\u02bc\7a\2\2\u02bc\u02bd\7x\2\2"+
|
||||
"\u02bd\u02be\7u\2\2\u02be\u00ce\3\2\2\2\u02bf\u02c0\7k\2\2\u02c0\u02c1"+
|
||||
"\7h\2\2\u02c1\u02c2\7a\2\2\u02c2\u02c3\7x\2\2\u02c3\u02c4\7e\2\2\u02c4"+
|
||||
"\u00d0\3\2\2\2\u02c5\u02c6\7h\2\2\u02c6\u02c7\7q\2\2\u02c7\u02c8\7t\2"+
|
||||
"\2\u02c8\u00d2\3\2\2\2\u02c9\u02ca\7k\2\2\u02ca\u02cb\7p\2\2\u02cb\u00d4"+
|
||||
"\3\2\2\2\u02cc\u02cd\7y\2\2\u02cd\u02ce\7j\2\2\u02ce\u02cf\7k\2\2\u02cf"+
|
||||
"\u02d0\7n\2\2\u02d0\u02d1\7g\2\2\u02d1\u00d6\3\2\2\2\u02d2\u02d3\7t\2"+
|
||||
"\2\u02d3\u02d4\7g\2\2\u02d4\u02d5\7r\2\2\u02d5\u02d6\7g\2\2\u02d6\u02d7"+
|
||||
"\7c\2\2\u02d7\u02d8\7v\2\2\u02d8\u00d8\3\2\2\2\u02d9\u02da\7w\2\2\u02da"+
|
||||
"\u02db\7p\2\2\u02db\u02dc\7v\2\2\u02dc\u02dd\7k\2\2\u02dd\u02de\7n\2\2"+
|
||||
"\u02de\u00da\3\2\2\2\u02df\u02e3\t\2\2\2\u02e0\u02e2\t\3\2\2\u02e1\u02e0"+
|
||||
"\3\2\2\2\u02e2\u02e5\3\2\2\2\u02e3\u02e1\3\2\2\2\u02e3\u02e4\3\2\2\2\u02e4"+
|
||||
"\u02e6\3\2\2\2\u02e5\u02e3\3\2\2\2\u02e6\u02e7\5\u00ddo\2\u02e7\u02e8"+
|
||||
"\3\2\2\2\u02e8\u02e9\bn\2\2\u02e9\u00dc\3\2\2\2\u02ea\u02ee\7=\2\2\u02eb"+
|
||||
"\u02ed\n\2\2\2\u02ec\u02eb\3\2\2\2\u02ed\u02f0\3\2\2\2\u02ee\u02ec\3\2"+
|
||||
"\2\2\u02ee\u02ef\3\2\2\2\u02ef\u02f1\3\2\2\2\u02f0\u02ee\3\2\2\2\u02f1"+
|
||||
"\u02f2\bo\2\2\u02f2\u00de\3\2\2\2\u02f3\u02f4\t\3\2\2\u02f4\u02f5\3\2"+
|
||||
"\2\2\u02f5\u02f6\bp\3\2\u02f6\u00e0\3\2\2\2\u02f7\u02f9\t\2\2\2\u02f8"+
|
||||
"\u02f7\3\2\2\2\u02f9\u02fa\3\2\2\2\u02fa\u02f8\3\2\2\2\u02fa\u02fb\3\2"+
|
||||
"\2\2\u02fb\u00e2\3\2\2\2\u02fc\u0300\t\4\2\2\u02fd\u02ff\t\5\2\2\u02fe"+
|
||||
"\u02fd\3\2\2\2\u02ff\u0302\3\2\2\2\u0300\u02fe\3\2\2\2\u0300\u0301\3\2"+
|
||||
"\2\2\u0301\u00e4\3\2\2\2\u0302\u0300\3\2\2\2\u0303\u030b\4\62;\2\u0304"+
|
||||
"\u0306\4\63;\2\u0305\u0307\4\62;\2\u0306\u0305\3\2\2\2\u0307\u0308\3\2"+
|
||||
"\2\2\u0308\u0306\3\2\2\2\u0308\u0309\3\2\2\2\u0309\u030b\3\2\2\2\u030a"+
|
||||
"\u0303\3\2\2\2\u030a\u0304\3\2\2\2\u030b\u00e6\3\2\2\2\u030c\u030e\7&"+
|
||||
"\2\2\u030d\u030f\t\6\2\2\u030e\u030d\3\2\2\2\u030f\u0310\3\2\2\2\u0310"+
|
||||
"\u030e\3\2\2\2\u0310\u0311\3\2\2\2\u0311\u00e8\3\2\2\2\u0312\u0314\7\'"+
|
||||
"\2\2\u0313\u0315\4\62\63\2\u0314\u0313\3\2\2\2\u0315\u0316\3\2\2\2\u0316"+
|
||||
"\u0314\3\2\2\2\u0316\u0317\3\2\2\2\u0317\u00ea\3\2\2\2\u0318\u0319\7("+
|
||||
"\2\2\u0319\u00ec\3\2\2\2\u031a\u0320\5\u00efx\2\u031b\u031d\t\7\2\2\u031c"+
|
||||
"\u031e\t\b\2\2\u031d\u031c\3\2\2\2\u031d\u031e\3\2\2\2\u031e\u031f\3\2"+
|
||||
"\2\2\u031f\u0321\5\u00efx\2\u0320\u031b\3\2\2\2\u0320\u0321\3\2\2\2\u0321"+
|
||||
"\u00ee\3\2\2\2\u0322\u0324\4\62;\2\u0323\u0322\3\2\2\2\u0324\u0325\3\2"+
|
||||
"\2\2\u0325\u0323\3\2\2\2\u0325\u0326\3\2\2\2\u0326\u032d\3\2\2\2\u0327"+
|
||||
"\u0329\7\60\2\2\u0328\u032a\4\62;\2\u0329\u0328\3\2\2\2\u032a\u032b\3"+
|
||||
"\2\2\2\u032b\u0329\3\2\2\2\u032b\u032c\3\2\2\2\u032c\u032e\3\2\2\2\u032d"+
|
||||
"\u0327\3\2\2\2\u032d\u032e\3\2\2\2\u032e\u00f0\3\2\2\2\u032f\u0330\7^"+
|
||||
"\2\2\u0330\u0334\13\2\2\2\u0331\u0332\7^\2\2\u0332\u0334\5\u00e1q\2\u0333"+
|
||||
"\u032f\3\2\2\2\u0333\u0331\3\2\2\2\u0334\u00f2\3\2\2\2\u0335\u033a\7$"+
|
||||
"\2\2\u0336\u0339\5\u00f1y\2\u0337\u0339\n\t\2\2\u0338\u0336\3\2\2\2\u0338"+
|
||||
"\u0337\3\2\2\2\u0339\u033c\3\2\2\2\u033a\u0338\3\2\2\2\u033a\u033b\3\2"+
|
||||
"\2\2\u033b\u033d\3\2\2\2\u033c\u033a\3\2\2\2\u033d\u033e\7$\2\2\u033e"+
|
||||
"\u033f\bz\4\2\u033f\u00f4\3\2\2\2\u0340\u0341\7}\2\2\u0341\u0342\7}\2"+
|
||||
"\2\u0342\u0344\3\2\2\2\u0343\u0345\13\2\2\2\u0344\u0343\3\2\2\2\u0345"+
|
||||
"\u0346\3\2\2\2\u0346\u0347\3\2\2\2\u0346\u0344\3\2\2\2\u0347\u0348\3\2"+
|
||||
"\2\2\u0348\u0349\7\177\2\2\u0349\u034a\7\177\2\2\u034a\u034b\3\2\2\2\u034b"+
|
||||
"\u034c\b{\5\2\u034c\u00f6\3\2\2\2\u034d\u0350\7)\2\2\u034e\u0351\5\u00f1"+
|
||||
"y\2\u034f\u0351\n\t\2\2\u0350\u034e\3\2\2\2\u0350\u034f\3\2\2\2\u0351"+
|
||||
"\u0352\3\2\2\2\u0352\u0353\7)\2\2\u0353\u0354\b|\6\2\u0354\u00f8\3\2\2"+
|
||||
"\2\u0355\u0356\7B\2\2\u0356\u0357\7|\2\2\u0357\u0358\7r\2\2\u0358\u00fa"+
|
||||
"\3\2\2\2\26\2\u02e3\u02ee\u02fa\u0300\u0308\u030a\u030e\u0310\u0316\u031d"+
|
||||
"\u0320\u0325\u032b\u032d\u0333\u0338\u033a\u0346\u0350\7\2\3\2\b\2\2\3"+
|
||||
"z\2\3{\3\3|\4";
|
||||
"w\tw\4x\tx\4y\ty\4z\tz\4{\t{\4|\t|\4}\t}\4~\t~\3\2\3\2\3\3\3\3\3\4\3\4"+
|
||||
"\3\4\3\4\3\4\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\5\3\6\3\6\3\6\3\6\3\6\3\6\3"+
|
||||
"\6\3\6\3\6\3\6\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\7\3\b\3\b\3\b\3\b"+
|
||||
"\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\b\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3\t\3"+
|
||||
"\n\3\n\3\n\3\n\3\n\3\n\3\n\3\n\3\13\3\13\3\13\3\13\3\13\3\13\3\13\3\13"+
|
||||
"\3\13\3\13\3\13\3\13\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3\f\3"+
|
||||
"\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\r\3\16\3\16\3\16\3\16\3\16\3"+
|
||||
"\16\3\16\3\16\3\17\3\17\3\20\3\20\3\21\3\21\3\21\3\21\3\21\3\21\3\22\3"+
|
||||
"\22\3\22\3\22\3\22\3\22\3\23\3\23\3\23\3\23\3\23\3\24\3\24\3\24\3\24\3"+
|
||||
"\24\3\24\3\25\3\25\3\25\3\25\3\25\3\26\3\26\3\26\3\26\3\26\3\26\3\27\3"+
|
||||
"\27\3\27\3\27\3\30\3\30\3\30\3\30\3\30\3\30\3\31\3\31\3\32\3\32\3\33\3"+
|
||||
"\33\3\33\3\34\3\34\3\34\3\35\3\35\3\35\3\36\3\36\3\36\3\37\3\37\3\37\3"+
|
||||
"\37\3 \3 \3 \3!\3!\3!\3\"\3\"\3\"\3#\3#\3#\3$\3$\3$\3$\3%\3%\3%\3%\3&"+
|
||||
"\3&\3&\3\'\3\'\3\'\3(\3(\3)\3)\3*\3*\3*\3+\3+\3,\3,\3-\3-\3.\3.\3.\3/"+
|
||||
"\3/\3/\3\60\3\60\3\61\3\61\3\62\3\62\3\62\3\63\3\63\3\63\3\64\3\64\3\64"+
|
||||
"\3\65\3\65\3\65\3\66\3\66\3\67\3\67\38\38\38\39\39\39\39\39\3:\3:\3:\3"+
|
||||
":\3;\3;\3;\3<\3<\3<\3<\3=\3=\3=\3=\3>\3>\3?\3?\3@\3@\3@\3A\3A\3B\3B\3"+
|
||||
"B\3B\3B\3B\3B\3C\3C\3C\3C\3C\3C\3D\3D\3D\3D\3D\3D\3D\3D\3D\3E\3E\3F\3"+
|
||||
"F\3G\3G\3H\3H\3I\3I\3I\3J\3J\3J\3K\3K\3K\3L\3L\3L\3M\3M\3M\3N\3N\3N\3"+
|
||||
"O\3O\3O\3P\3P\3P\3Q\3Q\3Q\3Q\3Q\3R\3R\3R\3R\3R\3R\3S\3S\3S\3S\3S\3T\3"+
|
||||
"T\3T\3T\3U\3U\3U\3V\3V\3W\3W\3X\3X\3X\3X\3X\3X\3X\3Y\3Y\3Y\3Y\3Y\3Y\3"+
|
||||
"Y\3Y\3Y\3Z\3Z\3Z\3Z\3Z\3Z\3[\3[\3[\3\\\3\\\3\\\3\\\3\\\3]\3]\3]\3]\3]"+
|
||||
"\3]\3^\3^\3^\3^\3^\3^\3_\3_\3_\3_\3_\3_\3`\3`\3`\3`\3`\3a\3a\3a\3a\3a"+
|
||||
"\3a\3b\3b\3b\3b\3b\3b\3c\3c\3c\3c\3c\3c\3d\3d\3d\3d\3d\3d\3d\3e\3e\3e"+
|
||||
"\3e\3e\3e\3f\3f\3f\3f\3f\3f\3f\3g\3g\3g\3g\3g\3g\3h\3h\3h\3h\3h\3h\3i"+
|
||||
"\3i\3i\3i\3j\3j\3j\3k\3k\3k\3k\3k\3k\3l\3l\3l\3l\3l\3l\3l\3m\3m\3m\3m"+
|
||||
"\3m\3m\3n\3n\7n\u02e4\nn\fn\16n\u02e7\13n\3n\3n\3n\3n\3o\3o\7o\u02ef\n"+
|
||||
"o\fo\16o\u02f2\13o\3o\3o\3p\3p\3p\3p\3q\6q\u02fb\nq\rq\16q\u02fc\3r\3"+
|
||||
"r\7r\u0301\nr\fr\16r\u0304\13r\3s\3s\3s\6s\u0309\ns\rs\16s\u030a\5s\u030d"+
|
||||
"\ns\3t\3t\6t\u0311\nt\rt\16t\u0312\3u\3u\6u\u0317\nu\ru\16u\u0318\3v\3"+
|
||||
"v\3w\3w\3w\5w\u0320\nw\3w\5w\u0323\nw\3x\6x\u0326\nx\rx\16x\u0327\3x\3"+
|
||||
"x\6x\u032c\nx\rx\16x\u032d\5x\u0330\nx\3y\3y\3y\3y\5y\u0336\ny\3z\3z\3"+
|
||||
"z\7z\u033b\nz\fz\16z\u033e\13z\3z\3z\3z\3{\3{\3{\3{\6{\u0347\n{\r{\16"+
|
||||
"{\u0348\3{\3{\3{\3{\3{\3|\3|\3|\5|\u0353\n|\3|\3|\3|\3}\3}\3}\3}\3~\3"+
|
||||
"~\3~\3\u0348\2\177\3\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n\23\13\25\f\27\r"+
|
||||
"\31\16\33\17\35\20\37\21!\22#\23%\24\'\25)\26+\27-\30/\31\61\32\63\33"+
|
||||
"\65\34\67\359\36;\37= ?!A\"C#E$G%I&K\'M(O)Q*S+U,W-Y.[/]\60_\61a\62c\63"+
|
||||
"e\64g\65i\66k\67m8o9q:s;u<w=y>{?}@\177A\u0081B\u0083C\u0085D\u0087E\u0089"+
|
||||
"F\u008bG\u008dH\u008fI\u0091J\u0093K\u0095L\u0097M\u0099N\u009bO\u009d"+
|
||||
"P\u009fQ\u00a1R\u00a3S\u00a5T\u00a7U\u00a9V\u00abW\u00adX\u00afY\u00b1"+
|
||||
"Z\u00b3[\u00b5\\\u00b7]\u00b9^\u00bb_\u00bd`\u00bfa\u00c1b\u00c3c\u00c5"+
|
||||
"d\u00c7e\u00c9f\u00cbg\u00cdh\u00cfi\u00d1j\u00d3k\u00d5l\u00d7m\u00d9"+
|
||||
"n\u00dbo\u00ddp\u00dfq\u00e1r\u00e3s\u00e5t\u00e7u\u00e9v\u00ebw\u00ed"+
|
||||
"x\u00ef\2\u00f1\2\u00f3y\u00f5z\u00f7{\u00f9|\u00fb}\3\2\n\4\2\f\f\17"+
|
||||
"\17\4\2\13\13\"\"\5\2C\\aac|\6\2\62;C\\aac|\5\2\62;CHch\4\2GGgg\4\2--"+
|
||||
"//\6\2\f\f\16\17$$^^\2\u036d\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2\2\2\t\3"+
|
||||
"\2\2\2\2\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\23\3\2\2\2"+
|
||||
"\2\25\3\2\2\2\2\27\3\2\2\2\2\31\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37"+
|
||||
"\3\2\2\2\2!\3\2\2\2\2#\3\2\2\2\2%\3\2\2\2\2\'\3\2\2\2\2)\3\2\2\2\2+\3"+
|
||||
"\2\2\2\2-\3\2\2\2\2/\3\2\2\2\2\61\3\2\2\2\2\63\3\2\2\2\2\65\3\2\2\2\2"+
|
||||
"\67\3\2\2\2\29\3\2\2\2\2;\3\2\2\2\2=\3\2\2\2\2?\3\2\2\2\2A\3\2\2\2\2C"+
|
||||
"\3\2\2\2\2E\3\2\2\2\2G\3\2\2\2\2I\3\2\2\2\2K\3\2\2\2\2M\3\2\2\2\2O\3\2"+
|
||||
"\2\2\2Q\3\2\2\2\2S\3\2\2\2\2U\3\2\2\2\2W\3\2\2\2\2Y\3\2\2\2\2[\3\2\2\2"+
|
||||
"\2]\3\2\2\2\2_\3\2\2\2\2a\3\2\2\2\2c\3\2\2\2\2e\3\2\2\2\2g\3\2\2\2\2i"+
|
||||
"\3\2\2\2\2k\3\2\2\2\2m\3\2\2\2\2o\3\2\2\2\2q\3\2\2\2\2s\3\2\2\2\2u\3\2"+
|
||||
"\2\2\2w\3\2\2\2\2y\3\2\2\2\2{\3\2\2\2\2}\3\2\2\2\2\177\3\2\2\2\2\u0081"+
|
||||
"\3\2\2\2\2\u0083\3\2\2\2\2\u0085\3\2\2\2\2\u0087\3\2\2\2\2\u0089\3\2\2"+
|
||||
"\2\2\u008b\3\2\2\2\2\u008d\3\2\2\2\2\u008f\3\2\2\2\2\u0091\3\2\2\2\2\u0093"+
|
||||
"\3\2\2\2\2\u0095\3\2\2\2\2\u0097\3\2\2\2\2\u0099\3\2\2\2\2\u009b\3\2\2"+
|
||||
"\2\2\u009d\3\2\2\2\2\u009f\3\2\2\2\2\u00a1\3\2\2\2\2\u00a3\3\2\2\2\2\u00a5"+
|
||||
"\3\2\2\2\2\u00a7\3\2\2\2\2\u00a9\3\2\2\2\2\u00ab\3\2\2\2\2\u00ad\3\2\2"+
|
||||
"\2\2\u00af\3\2\2\2\2\u00b1\3\2\2\2\2\u00b3\3\2\2\2\2\u00b5\3\2\2\2\2\u00b7"+
|
||||
"\3\2\2\2\2\u00b9\3\2\2\2\2\u00bb\3\2\2\2\2\u00bd\3\2\2\2\2\u00bf\3\2\2"+
|
||||
"\2\2\u00c1\3\2\2\2\2\u00c3\3\2\2\2\2\u00c5\3\2\2\2\2\u00c7\3\2\2\2\2\u00c9"+
|
||||
"\3\2\2\2\2\u00cb\3\2\2\2\2\u00cd\3\2\2\2\2\u00cf\3\2\2\2\2\u00d1\3\2\2"+
|
||||
"\2\2\u00d3\3\2\2\2\2\u00d5\3\2\2\2\2\u00d7\3\2\2\2\2\u00d9\3\2\2\2\2\u00db"+
|
||||
"\3\2\2\2\2\u00dd\3\2\2\2\2\u00df\3\2\2\2\2\u00e1\3\2\2\2\2\u00e3\3\2\2"+
|
||||
"\2\2\u00e5\3\2\2\2\2\u00e7\3\2\2\2\2\u00e9\3\2\2\2\2\u00eb\3\2\2\2\2\u00ed"+
|
||||
"\3\2\2\2\2\u00f3\3\2\2\2\2\u00f5\3\2\2\2\2\u00f7\3\2\2\2\2\u00f9\3\2\2"+
|
||||
"\2\2\u00fb\3\2\2\2\3\u00fd\3\2\2\2\5\u00ff\3\2\2\2\7\u0101\3\2\2\2\t\u0106"+
|
||||
"\3\2\2\2\13\u010e\3\2\2\2\r\u0118\3\2\2\2\17\u0122\3\2\2\2\21\u012e\3"+
|
||||
"\2\2\2\23\u0137\3\2\2\2\25\u013f\3\2\2\2\27\u014b\3\2\2\2\31\u0157\3\2"+
|
||||
"\2\2\33\u0162\3\2\2\2\35\u016a\3\2\2\2\37\u016c\3\2\2\2!\u016e\3\2\2\2"+
|
||||
"#\u0174\3\2\2\2%\u017a\3\2\2\2\'\u017f\3\2\2\2)\u0185\3\2\2\2+\u018a\3"+
|
||||
"\2\2\2-\u0190\3\2\2\2/\u0194\3\2\2\2\61\u019a\3\2\2\2\63\u019c\3\2\2\2"+
|
||||
"\65\u019e\3\2\2\2\67\u01a1\3\2\2\29\u01a4\3\2\2\2;\u01a7\3\2\2\2=\u01aa"+
|
||||
"\3\2\2\2?\u01ae\3\2\2\2A\u01b1\3\2\2\2C\u01b4\3\2\2\2E\u01b7\3\2\2\2G"+
|
||||
"\u01ba\3\2\2\2I\u01be\3\2\2\2K\u01c2\3\2\2\2M\u01c5\3\2\2\2O\u01c8\3\2"+
|
||||
"\2\2Q\u01ca\3\2\2\2S\u01cc\3\2\2\2U\u01cf\3\2\2\2W\u01d1\3\2\2\2Y\u01d3"+
|
||||
"\3\2\2\2[\u01d5\3\2\2\2]\u01d8\3\2\2\2_\u01db\3\2\2\2a\u01dd\3\2\2\2c"+
|
||||
"\u01df\3\2\2\2e\u01e2\3\2\2\2g\u01e5\3\2\2\2i\u01e8\3\2\2\2k\u01eb\3\2"+
|
||||
"\2\2m\u01ed\3\2\2\2o\u01ef\3\2\2\2q\u01f2\3\2\2\2s\u01f7\3\2\2\2u\u01fb"+
|
||||
"\3\2\2\2w\u01fe\3\2\2\2y\u0202\3\2\2\2{\u0206\3\2\2\2}\u0208\3\2\2\2\177"+
|
||||
"\u020a\3\2\2\2\u0081\u020d\3\2\2\2\u0083\u020f\3\2\2\2\u0085\u0216\3\2"+
|
||||
"\2\2\u0087\u021c\3\2\2\2\u0089\u0225\3\2\2\2\u008b\u0227\3\2\2\2\u008d"+
|
||||
"\u0229\3\2\2\2\u008f\u022b\3\2\2\2\u0091\u022d\3\2\2\2\u0093\u0230\3\2"+
|
||||
"\2\2\u0095\u0233\3\2\2\2\u0097\u0236\3\2\2\2\u0099\u0239\3\2\2\2\u009b"+
|
||||
"\u023c\3\2\2\2\u009d\u023f\3\2\2\2\u009f\u0242\3\2\2\2\u00a1\u0245\3\2"+
|
||||
"\2\2\u00a3\u024a\3\2\2\2\u00a5\u0250\3\2\2\2\u00a7\u0255\3\2\2\2\u00a9"+
|
||||
"\u0259\3\2\2\2\u00ab\u025c\3\2\2\2\u00ad\u025e\3\2\2\2\u00af\u0260\3\2"+
|
||||
"\2\2\u00b1\u0267\3\2\2\2\u00b3\u0270\3\2\2\2\u00b5\u0276\3\2\2\2\u00b7"+
|
||||
"\u0279\3\2\2\2\u00b9\u027e\3\2\2\2\u00bb\u0284\3\2\2\2\u00bd\u028a\3\2"+
|
||||
"\2\2\u00bf\u0290\3\2\2\2\u00c1\u0295\3\2\2\2\u00c3\u029b\3\2\2\2\u00c5"+
|
||||
"\u02a1\3\2\2\2\u00c7\u02a7\3\2\2\2\u00c9\u02ae\3\2\2\2\u00cb\u02b4\3\2"+
|
||||
"\2\2\u00cd\u02bb\3\2\2\2\u00cf\u02c1\3\2\2\2\u00d1\u02c7\3\2\2\2\u00d3"+
|
||||
"\u02cb\3\2\2\2\u00d5\u02ce\3\2\2\2\u00d7\u02d4\3\2\2\2\u00d9\u02db\3\2"+
|
||||
"\2\2\u00db\u02e1\3\2\2\2\u00dd\u02ec\3\2\2\2\u00df\u02f5\3\2\2\2\u00e1"+
|
||||
"\u02fa\3\2\2\2\u00e3\u02fe\3\2\2\2\u00e5\u030c\3\2\2\2\u00e7\u030e\3\2"+
|
||||
"\2\2\u00e9\u0314\3\2\2\2\u00eb\u031a\3\2\2\2\u00ed\u031c\3\2\2\2\u00ef"+
|
||||
"\u0325\3\2\2\2\u00f1\u0335\3\2\2\2\u00f3\u0337\3\2\2\2\u00f5\u0342\3\2"+
|
||||
"\2\2\u00f7\u034f\3\2\2\2\u00f9\u0357\3\2\2\2\u00fb\u035b\3\2\2\2\u00fd"+
|
||||
"\u00fe\7\u0080\2\2\u00fe\4\3\2\2\2\u00ff\u0100\7<\2\2\u0100\6\3\2\2\2"+
|
||||
"\u0101\u0102\7i\2\2\u0102\u0103\7q\2\2\u0103\u0104\7v\2\2\u0104\u0105"+
|
||||
"\7q\2\2\u0105\b\3\2\2\2\u0106\u0107\7\'\2\2\u0107\u0108\7q\2\2\u0108\u0109"+
|
||||
"\7w\2\2\u0109\u010a\7v\2\2\u010a\u010b\7r\2\2\u010b\u010c\7w\2\2\u010c"+
|
||||
"\u010d\7v\2\2\u010d\n\3\2\2\2\u010e\u010f\7\'\2\2\u010f\u0110\7n\2\2\u0110"+
|
||||
"\u0111\7c\2\2\u0111\u0112\7w\2\2\u0112\u0113\7p\2\2\u0113\u0114\7e\2\2"+
|
||||
"\u0114\u0115\7j\2\2\u0115\u0116\7g\2\2\u0116\u0117\7t\2\2\u0117\f\3\2"+
|
||||
"\2\2\u0118\u0119\7\'\2\2\u0119\u011a\7|\2\2\u011a\u011b\7g\2\2\u011b\u011c"+
|
||||
"\7t\2\2\u011c\u011d\7q\2\2\u011d\u011e\7r\2\2\u011e\u011f\7c\2\2\u011f"+
|
||||
"\u0120\7i\2\2\u0120\u0121\7g\2\2\u0121\16\3\2\2\2\u0122\u0123\7\'\2\2"+
|
||||
"\u0123\u0124\7|\2\2\u0124\u0125\7r\2\2\u0125\u0126\7t\2\2\u0126\u0127"+
|
||||
"\7g\2\2\u0127\u0128\7u\2\2\u0128\u0129\7g\2\2\u0129\u012a\7t\2\2\u012a"+
|
||||
"\u012b\7x\2\2\u012b\u012c\7g\2\2\u012c\u012d\7f\2\2\u012d\20\3\2\2\2\u012e"+
|
||||
"\u012f\7\'\2\2\u012f\u0130\7c\2\2\u0130\u0131\7f\2\2\u0131\u0132\7f\2"+
|
||||
"\2\u0132\u0133\7t\2\2\u0133\u0134\7g\2\2\u0134\u0135\7u\2\2\u0135\u0136"+
|
||||
"\7u\2\2\u0136\22\3\2\2\2\u0137\u0138\7\'\2\2\u0138\u0139\7k\2\2\u0139"+
|
||||
"\u013a\7o\2\2\u013a\u013b\7r\2\2\u013b\u013c\7q\2\2\u013c\u013d\7t\2\2"+
|
||||
"\u013d\u013e\7v\2\2\u013e\24\3\2\2\2\u013f\u0140\7\'\2\2\u0140\u0141\7"+
|
||||
"d\2\2\u0141\u0142\7t\2\2\u0142\u0143\7g\2\2\u0143\u0144\7c\2\2\u0144\u0145"+
|
||||
"\7m\2\2\u0145\u0146\7r\2\2\u0146\u0147\7q\2\2\u0147\u0148\7k\2\2\u0148"+
|
||||
"\u0149\7p\2\2\u0149\u014a\7v\2\2\u014a\26\3\2\2\2\u014b\u014c\7\'\2\2"+
|
||||
"\u014c\u014d\7c\2\2\u014d\u014e\7u\2\2\u014e\u014f\7o\2\2\u014f\u0150"+
|
||||
"\7k\2\2\u0150\u0151\7p\2\2\u0151\u0152\7e\2\2\u0152\u0153\7n\2\2\u0153"+
|
||||
"\u0154\7w\2\2\u0154\u0155\7f\2\2\u0155\u0156\7g\2\2\u0156\30\3\2\2\2\u0157"+
|
||||
"\u0158\7\'\2\2\u0158\u0159\7c\2\2\u0159\u015a\7u\2\2\u015a\u015b\7o\2"+
|
||||
"\2\u015b\u015c\7d\2\2\u015c\u015d\7k\2\2\u015d\u015e\7p\2\2\u015e\u015f"+
|
||||
"\7c\2\2\u015f\u0160\7t\2\2\u0160\u0161\7{\2\2\u0161\32\3\2\2\2\u0162\u0163"+
|
||||
"\7\'\2\2\u0163\u0164\7q\2\2\u0164\u0165\7r\2\2\u0165\u0166\7v\2\2\u0166"+
|
||||
"\u0167\7k\2\2\u0167\u0168\7q\2\2\u0168\u0169\7p\2\2\u0169\34\3\2\2\2\u016a"+
|
||||
"\u016b\7.\2\2\u016b\36\3\2\2\2\u016c\u016d\7?\2\2\u016d \3\2\2\2\u016e"+
|
||||
"\u016f\7e\2\2\u016f\u0170\7q\2\2\u0170\u0171\7p\2\2\u0171\u0172\7u\2\2"+
|
||||
"\u0172\u0173\7v\2\2\u0173\"\3\2\2\2\u0174\u0175\7w\2\2\u0175\u0176\7d"+
|
||||
"\2\2\u0176\u0177\7{\2\2\u0177\u0178\7v\2\2\u0178\u0179\7g\2\2\u0179$\3"+
|
||||
"\2\2\2\u017a\u017b\7d\2\2\u017b\u017c\7{\2\2\u017c\u017d\7v\2\2\u017d"+
|
||||
"\u017e\7g\2\2\u017e&\3\2\2\2\u017f\u0180\7w\2\2\u0180\u0181\7y\2\2\u0181"+
|
||||
"\u0182\7q\2\2\u0182\u0183\7t\2\2\u0183\u0184\7f\2\2\u0184(\3\2\2\2\u0185"+
|
||||
"\u0186\7y\2\2\u0186\u0187\7q\2\2\u0187\u0188\7t\2\2\u0188\u0189\7f\2\2"+
|
||||
"\u0189*\3\2\2\2\u018a\u018b\7h\2\2\u018b\u018c\7n\2\2\u018c\u018d\7q\2"+
|
||||
"\2\u018d\u018e\7c\2\2\u018e\u018f\7v\2\2\u018f,\3\2\2\2\u0190\u0191\7"+
|
||||
"u\2\2\u0191\u0192\7v\2\2\u0192\u0193\7t\2\2\u0193.\3\2\2\2\u0194\u0195"+
|
||||
"\7u\2\2\u0195\u0196\7v\2\2\u0196\u0197\7t\2\2\u0197\u0198\7a\2\2\u0198"+
|
||||
"\u0199\7u\2\2\u0199\60\3\2\2\2\u019a\u019b\7]\2\2\u019b\62\3\2\2\2\u019c"+
|
||||
"\u019d\7_\2\2\u019d\64\3\2\2\2\u019e\u019f\7-\2\2\u019f\u01a0\7?\2\2\u01a0"+
|
||||
"\66\3\2\2\2\u01a1\u01a2\7/\2\2\u01a2\u01a3\7?\2\2\u01a38\3\2\2\2\u01a4"+
|
||||
"\u01a5\7\61\2\2\u01a5\u01a6\7?\2\2\u01a6:\3\2\2\2\u01a7\u01a8\7,\2\2\u01a8"+
|
||||
"\u01a9\7?\2\2\u01a9<\3\2\2\2\u01aa\u01ab\7,\2\2\u01ab\u01ac\7,\2\2\u01ac"+
|
||||
"\u01ad\7?\2\2\u01ad>\3\2\2\2\u01ae\u01af\7(\2\2\u01af\u01b0\7?\2\2\u01b0"+
|
||||
"@\3\2\2\2\u01b1\u01b2\7~\2\2\u01b2\u01b3\7?\2\2\u01b3B\3\2\2\2\u01b4\u01b5"+
|
||||
"\7`\2\2\u01b5\u01b6\7?\2\2\u01b6D\3\2\2\2\u01b7\u01b8\7\'\2\2\u01b8\u01b9"+
|
||||
"\7?\2\2\u01b9F\3\2\2\2\u01ba\u01bb\7>\2\2\u01bb\u01bc\7>\2\2\u01bc\u01bd"+
|
||||
"\7?\2\2\u01bdH\3\2\2\2\u01be\u01bf\7@\2\2\u01bf\u01c0\7@\2\2\u01c0\u01c1"+
|
||||
"\7?\2\2\u01c1J\3\2\2\2\u01c2\u01c3\7-\2\2\u01c3\u01c4\7-\2\2\u01c4L\3"+
|
||||
"\2\2\2\u01c5\u01c6\7/\2\2\u01c6\u01c7\7/\2\2\u01c7N\3\2\2\2\u01c8\u01c9"+
|
||||
"\7-\2\2\u01c9P\3\2\2\2\u01ca\u01cb\7/\2\2\u01cbR\3\2\2\2\u01cc\u01cd\7"+
|
||||
",\2\2\u01cd\u01ce\7,\2\2\u01ceT\3\2\2\2\u01cf\u01d0\7,\2\2\u01d0V\3\2"+
|
||||
"\2\2\u01d1\u01d2\7\61\2\2\u01d2X\3\2\2\2\u01d3\u01d4\7\'\2\2\u01d4Z\3"+
|
||||
"\2\2\2\u01d5\u01d6\7>\2\2\u01d6\u01d7\7>\2\2\u01d7\\\3\2\2\2\u01d8\u01d9"+
|
||||
"\7@\2\2\u01d9\u01da\7@\2\2\u01da^\3\2\2\2\u01db\u01dc\7>\2\2\u01dc`\3"+
|
||||
"\2\2\2\u01dd\u01de\7@\2\2\u01deb\3\2\2\2\u01df\u01e0\7>\2\2\u01e0\u01e1"+
|
||||
"\7?\2\2\u01e1d\3\2\2\2\u01e2\u01e3\7@\2\2\u01e3\u01e4\7?\2\2\u01e4f\3"+
|
||||
"\2\2\2\u01e5\u01e6\7?\2\2\u01e6\u01e7\7?\2\2\u01e7h\3\2\2\2\u01e8\u01e9"+
|
||||
"\7#\2\2\u01e9\u01ea\7?\2\2\u01eaj\3\2\2\2\u01eb\u01ec\7`\2\2\u01ecl\3"+
|
||||
"\2\2\2\u01ed\u01ee\7~\2\2\u01een\3\2\2\2\u01ef\u01f0\7v\2\2\u01f0\u01f1"+
|
||||
"\7q\2\2\u01f1p\3\2\2\2\u01f2\u01f3\7u\2\2\u01f3\u01f4\7v\2\2\u01f4\u01f5"+
|
||||
"\7g\2\2\u01f5\u01f6\7r\2\2\u01f6r\3\2\2\2\u01f7\u01f8\7c\2\2\u01f8\u01f9"+
|
||||
"\7p\2\2\u01f9\u01fa\7f\2\2\u01fat\3\2\2\2\u01fb\u01fc\7q\2\2\u01fc\u01fd"+
|
||||
"\7t\2\2\u01fdv\3\2\2\2\u01fe\u01ff\7z\2\2\u01ff\u0200\7q\2\2\u0200\u0201"+
|
||||
"\7t\2\2\u0201x\3\2\2\2\u0202\u0203\7p\2\2\u0203\u0204\7q\2\2\u0204\u0205"+
|
||||
"\7v\2\2\u0205z\3\2\2\2\u0206\u0207\7*\2\2\u0207|\3\2\2\2\u0208\u0209\7"+
|
||||
"+\2\2\u0209~\3\2\2\2\u020a\u020b\7c\2\2\u020b\u020c\7u\2\2\u020c\u0080"+
|
||||
"\3\2\2\2\u020d\u020e\7B\2\2\u020e\u0082\3\2\2\2\u020f\u0210\7t\2\2\u0210"+
|
||||
"\u0211\7g\2\2\u0211\u0212\7v\2\2\u0212\u0213\7w\2\2\u0213\u0214\7t\2\2"+
|
||||
"\u0214\u0215\7p\2\2\u0215\u0084\3\2\2\2\u0216\u0217\7d\2\2\u0217\u0218"+
|
||||
"\7t\2\2\u0218\u0219\7g\2\2\u0219\u021a\7c\2\2\u021a\u021b\7m\2\2\u021b"+
|
||||
"\u0086\3\2\2\2\u021c\u021d\7e\2\2\u021d\u021e\7q\2\2\u021e\u021f\7p\2"+
|
||||
"\2\u021f\u0220\7v\2\2\u0220\u0221\7k\2\2\u0221\u0222\7p\2\2\u0222\u0223"+
|
||||
"\7w\2\2\u0223\u0224\7g\2\2\u0224\u0088\3\2\2\2\u0225\u0226\7\60\2\2\u0226"+
|
||||
"\u008a\3\2\2\2\u0227\u0228\7C\2\2\u0228\u008c\3\2\2\2\u0229\u022a\7Z\2"+
|
||||
"\2\u022a\u008e\3\2\2\2\u022b\u022c\7[\2\2\u022c\u0090\3\2\2\2\u022d\u022e"+
|
||||
"\7C\2\2\u022e\u022f\7Z\2\2\u022f\u0092\3\2\2\2\u0230\u0231\7C\2\2\u0231"+
|
||||
"\u0232\7[\2\2\u0232\u0094\3\2\2\2\u0233\u0234\7Z\2\2\u0234\u0235\7[\2"+
|
||||
"\2\u0235\u0096\3\2\2\2\u0236\u0237\7R\2\2\u0237\u0238\7e\2\2\u0238\u0098"+
|
||||
"\3\2\2\2\u0239\u023a\7R\2\2\u023a\u023b\7|\2\2\u023b\u009a\3\2\2\2\u023c"+
|
||||
"\u023d\7R\2\2\u023d\u023e\7p\2\2\u023e\u009c\3\2\2\2\u023f\u0240\7R\2"+
|
||||
"\2\u0240\u0241\7x\2\2\u0241\u009e\3\2\2\2\u0242\u0243\7\60\2\2\u0243\u0244"+
|
||||
"\7y\2\2\u0244\u00a0\3\2\2\2\u0245\u0246\7v\2\2\u0246\u0247\7t\2\2\u0247"+
|
||||
"\u0248\7w\2\2\u0248\u0249\7g\2\2\u0249\u00a2\3\2\2\2\u024a\u024b\7h\2"+
|
||||
"\2\u024b\u024c\7c\2\2\u024c\u024d\7n\2\2\u024d\u024e\7u\2\2\u024e\u024f"+
|
||||
"\7g\2\2\u024f\u00a4\3\2\2\2\u0250\u0251\7\'\2\2\u0251\u0252\7c\2\2\u0252"+
|
||||
"\u0253\7u\2\2\u0253\u0254\7o\2\2\u0254\u00a6\3\2\2\2\u0255\u0256\7u\2"+
|
||||
"\2\u0256\u0257\7w\2\2\u0257\u0258\7d\2\2\u0258\u00a8\3\2\2\2\u0259\u025a"+
|
||||
"\7/\2\2\u025a\u025b\7@\2\2\u025b\u00aa\3\2\2\2\u025c\u025d\7}\2\2\u025d"+
|
||||
"\u00ac\3\2\2\2\u025e\u025f\7\177\2\2\u025f\u00ae\3\2\2\2\u0260\u0261\7"+
|
||||
"c\2\2\u0261\u0262\7u\2\2\u0262\u0263\7o\2\2\u0263\u0264\7u\2\2\u0264\u0265"+
|
||||
"\7w\2\2\u0265\u0266\7d\2\2\u0266\u00b0\3\2\2\2\u0267\u0268\7e\2\2\u0268"+
|
||||
"\u0269\7n\2\2\u0269\u026a\7q\2\2\u026a\u026b\7d\2\2\u026b\u026c\7d\2\2"+
|
||||
"\u026c\u026d\7g\2\2\u026d\u026e\7t\2\2\u026e\u026f\7u\2\2\u026f\u00b2"+
|
||||
"\3\2\2\2\u0270\u0271\7u\2\2\u0271\u0272\7v\2\2\u0272\u0273\7c\2\2\u0273"+
|
||||
"\u0274\7e\2\2\u0274\u0275\7m\2\2\u0275\u00b4\3\2\2\2\u0276\u0277\7k\2"+
|
||||
"\2\u0277\u0278\7h\2\2\u0278\u00b6\3\2\2\2\u0279\u027a\7g\2\2\u027a\u027b"+
|
||||
"\7n\2\2\u027b\u027c\7u\2\2\u027c\u027d\7g\2\2\u027d\u00b8\3\2\2\2\u027e"+
|
||||
"\u027f\7k\2\2\u027f\u0280\7h\2\2\u0280\u0281\7a\2\2\u0281\u0282\7e\2\2"+
|
||||
"\u0282\u0283\7u\2\2\u0283\u00ba\3\2\2\2\u0284\u0285\7k\2\2\u0285\u0286"+
|
||||
"\7h\2\2\u0286\u0287\7a\2\2\u0287\u0288\7e\2\2\u0288\u0289\7e\2\2\u0289"+
|
||||
"\u00bc\3\2\2\2\u028a\u028b\7k\2\2\u028b\u028c\7h\2\2\u028c\u028d\7a\2"+
|
||||
"\2\u028d\u028e\7g\2\2\u028e\u028f\7s\2\2\u028f\u00be\3\2\2\2\u0290\u0291"+
|
||||
"\7k\2\2\u0291\u0292\7h\2\2\u0292\u0293\7a\2\2\u0293\u0294\7|\2\2\u0294"+
|
||||
"\u00c0\3\2\2\2\u0295\u0296\7k\2\2\u0296\u0297\7h\2\2\u0297\u0298\7a\2"+
|
||||
"\2\u0298\u0299\7p\2\2\u0299\u029a\7g\2\2\u029a\u00c2\3\2\2\2\u029b\u029c"+
|
||||
"\7k\2\2\u029c\u029d\7h\2\2\u029d\u029e\7a\2\2\u029e\u029f\7p\2\2\u029f"+
|
||||
"\u02a0\7|\2\2\u02a0\u00c4\3\2\2\2\u02a1\u02a2\7k\2\2\u02a2\u02a3\7h\2"+
|
||||
"\2\u02a3\u02a4\7a\2\2\u02a4\u02a5\7r\2\2\u02a5\u02a6\7n\2\2\u02a6\u00c6"+
|
||||
"\3\2\2\2\u02a7\u02a8\7k\2\2\u02a8\u02a9\7h\2\2\u02a9\u02aa\7a\2\2\u02aa"+
|
||||
"\u02ab\7r\2\2\u02ab\u02ac\7q\2\2\u02ac\u02ad\7u\2\2\u02ad\u00c8\3\2\2"+
|
||||
"\2\u02ae\u02af\7k\2\2\u02af\u02b0\7h\2\2\u02b0\u02b1\7a\2\2\u02b1\u02b2"+
|
||||
"\7o\2\2\u02b2\u02b3\7k\2\2\u02b3\u00ca\3\2\2\2\u02b4\u02b5\7k\2\2\u02b5"+
|
||||
"\u02b6\7h\2\2\u02b6\u02b7\7a\2\2\u02b7\u02b8\7p\2\2\u02b8\u02b9\7g\2\2"+
|
||||
"\u02b9\u02ba\7i\2\2\u02ba\u00cc\3\2\2\2\u02bb\u02bc\7k\2\2\u02bc\u02bd"+
|
||||
"\7h\2\2\u02bd\u02be\7a\2\2\u02be\u02bf\7x\2\2\u02bf\u02c0\7u\2\2\u02c0"+
|
||||
"\u00ce\3\2\2\2\u02c1\u02c2\7k\2\2\u02c2\u02c3\7h\2\2\u02c3\u02c4\7a\2"+
|
||||
"\2\u02c4\u02c5\7x\2\2\u02c5\u02c6\7e\2\2\u02c6\u00d0\3\2\2\2\u02c7\u02c8"+
|
||||
"\7h\2\2\u02c8\u02c9\7q\2\2\u02c9\u02ca\7t\2\2\u02ca\u00d2\3\2\2\2\u02cb"+
|
||||
"\u02cc\7k\2\2\u02cc\u02cd\7p\2\2\u02cd\u00d4\3\2\2\2\u02ce\u02cf\7y\2"+
|
||||
"\2\u02cf\u02d0\7j\2\2\u02d0\u02d1\7k\2\2\u02d1\u02d2\7n\2\2\u02d2\u02d3"+
|
||||
"\7g\2\2\u02d3\u00d6\3\2\2\2\u02d4\u02d5\7t\2\2\u02d5\u02d6\7g\2\2\u02d6"+
|
||||
"\u02d7\7r\2\2\u02d7\u02d8\7g\2\2\u02d8\u02d9\7c\2\2\u02d9\u02da\7v\2\2"+
|
||||
"\u02da\u00d8\3\2\2\2\u02db\u02dc\7w\2\2\u02dc\u02dd\7p\2\2\u02dd\u02de"+
|
||||
"\7v\2\2\u02de\u02df\7k\2\2\u02df\u02e0\7n\2\2\u02e0\u00da\3\2\2\2\u02e1"+
|
||||
"\u02e5\t\2\2\2\u02e2\u02e4\t\3\2\2\u02e3\u02e2\3\2\2\2\u02e4\u02e7\3\2"+
|
||||
"\2\2\u02e5\u02e3\3\2\2\2\u02e5\u02e6\3\2\2\2\u02e6\u02e8\3\2\2\2\u02e7"+
|
||||
"\u02e5\3\2\2\2\u02e8\u02e9\5\u00ddo\2\u02e9\u02ea\3\2\2\2\u02ea\u02eb"+
|
||||
"\bn\2\2\u02eb\u00dc\3\2\2\2\u02ec\u02f0\7=\2\2\u02ed\u02ef\n\2\2\2\u02ee"+
|
||||
"\u02ed\3\2\2\2\u02ef\u02f2\3\2\2\2\u02f0\u02ee\3\2\2\2\u02f0\u02f1\3\2"+
|
||||
"\2\2\u02f1\u02f3\3\2\2\2\u02f2\u02f0\3\2\2\2\u02f3\u02f4\bo\2\2\u02f4"+
|
||||
"\u00de\3\2\2\2\u02f5\u02f6\t\3\2\2\u02f6\u02f7\3\2\2\2\u02f7\u02f8\bp"+
|
||||
"\3\2\u02f8\u00e0\3\2\2\2\u02f9\u02fb\t\2\2\2\u02fa\u02f9\3\2\2\2\u02fb"+
|
||||
"\u02fc\3\2\2\2\u02fc\u02fa\3\2\2\2\u02fc\u02fd\3\2\2\2\u02fd\u00e2\3\2"+
|
||||
"\2\2\u02fe\u0302\t\4\2\2\u02ff\u0301\t\5\2\2\u0300\u02ff\3\2\2\2\u0301"+
|
||||
"\u0304\3\2\2\2\u0302\u0300\3\2\2\2\u0302\u0303\3\2\2\2\u0303\u00e4\3\2"+
|
||||
"\2\2\u0304\u0302\3\2\2\2\u0305\u030d\4\62;\2\u0306\u0308\4\63;\2\u0307"+
|
||||
"\u0309\4\62;\2\u0308\u0307\3\2\2\2\u0309\u030a\3\2\2\2\u030a\u0308\3\2"+
|
||||
"\2\2\u030a\u030b\3\2\2\2\u030b\u030d\3\2\2\2\u030c\u0305\3\2\2\2\u030c"+
|
||||
"\u0306\3\2\2\2\u030d\u00e6\3\2\2\2\u030e\u0310\7&\2\2\u030f\u0311\t\6"+
|
||||
"\2\2\u0310\u030f\3\2\2\2\u0311\u0312\3\2\2\2\u0312\u0310\3\2\2\2\u0312"+
|
||||
"\u0313\3\2\2\2\u0313\u00e8\3\2\2\2\u0314\u0316\7\'\2\2\u0315\u0317\4\62"+
|
||||
"\63\2\u0316\u0315\3\2\2\2\u0317\u0318\3\2\2\2\u0318\u0316\3\2\2\2\u0318"+
|
||||
"\u0319\3\2\2\2\u0319\u00ea\3\2\2\2\u031a\u031b\7(\2\2\u031b\u00ec\3\2"+
|
||||
"\2\2\u031c\u0322\5\u00efx\2\u031d\u031f\t\7\2\2\u031e\u0320\t\b\2\2\u031f"+
|
||||
"\u031e\3\2\2\2\u031f\u0320\3\2\2\2\u0320\u0321\3\2\2\2\u0321\u0323\5\u00ef"+
|
||||
"x\2\u0322\u031d\3\2\2\2\u0322\u0323\3\2\2\2\u0323\u00ee\3\2\2\2\u0324"+
|
||||
"\u0326\4\62;\2\u0325\u0324\3\2\2\2\u0326\u0327\3\2\2\2\u0327\u0325\3\2"+
|
||||
"\2\2\u0327\u0328\3\2\2\2\u0328\u032f\3\2\2\2\u0329\u032b\7\60\2\2\u032a"+
|
||||
"\u032c\4\62;\2\u032b\u032a\3\2\2\2\u032c\u032d\3\2\2\2\u032d\u032b\3\2"+
|
||||
"\2\2\u032d\u032e\3\2\2\2\u032e\u0330\3\2\2\2\u032f\u0329\3\2\2\2\u032f"+
|
||||
"\u0330\3\2\2\2\u0330\u00f0\3\2\2\2\u0331\u0332\7^\2\2\u0332\u0336\13\2"+
|
||||
"\2\2\u0333\u0334\7^\2\2\u0334\u0336\5\u00e1q\2\u0335\u0331\3\2\2\2\u0335"+
|
||||
"\u0333\3\2\2\2\u0336\u00f2\3\2\2\2\u0337\u033c\7$\2\2\u0338\u033b\5\u00f1"+
|
||||
"y\2\u0339\u033b\n\t\2\2\u033a\u0338\3\2\2\2\u033a\u0339\3\2\2\2\u033b"+
|
||||
"\u033e\3\2\2\2\u033c\u033a\3\2\2\2\u033c\u033d\3\2\2\2\u033d\u033f\3\2"+
|
||||
"\2\2\u033e\u033c\3\2\2\2\u033f\u0340\7$\2\2\u0340\u0341\bz\4\2\u0341\u00f4"+
|
||||
"\3\2\2\2\u0342\u0343\7}\2\2\u0343\u0344\7}\2\2\u0344\u0346\3\2\2\2\u0345"+
|
||||
"\u0347\13\2\2\2\u0346\u0345\3\2\2\2\u0347\u0348\3\2\2\2\u0348\u0349\3"+
|
||||
"\2\2\2\u0348\u0346\3\2\2\2\u0349\u034a\3\2\2\2\u034a\u034b\7\177\2\2\u034b"+
|
||||
"\u034c\7\177\2\2\u034c\u034d\3\2\2\2\u034d\u034e\b{\5\2\u034e\u00f6\3"+
|
||||
"\2\2\2\u034f\u0352\7)\2\2\u0350\u0353\5\u00f1y\2\u0351\u0353\n\t\2\2\u0352"+
|
||||
"\u0350\3\2\2\2\u0352\u0351\3\2\2\2\u0353\u0354\3\2\2\2\u0354\u0355\7)"+
|
||||
"\2\2\u0355\u0356\b|\6\2\u0356\u00f8\3\2\2\2\u0357\u0358\7B\2\2\u0358\u0359"+
|
||||
"\7|\2\2\u0359\u035a\7r\2\2\u035a\u00fa\3\2\2\2\u035b\u035c\7]\2\2\u035c"+
|
||||
"\u035d\7_\2\2\u035d\u00fc\3\2\2\2\26\2\u02e5\u02f0\u02fc\u0302\u030a\u030c"+
|
||||
"\u0310\u0312\u0318\u031f\u0322\u0327\u032d\u032f\u0335\u033a\u033c\u0348"+
|
||||
"\u0352\7\2\3\2\b\2\2\3z\2\3{\3\3|\4";
|
||||
public static final ATN _ATN =
|
||||
new ATNDeserializer().deserialize(_serializedATN.toCharArray());
|
||||
static {
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module external.linked.project.id="prog8" external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" external.system.module.group="" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.idea" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/docs/build" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/gradle" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/out" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Reference in new issue
Block a user