Compare commits

..

49 Commits
v1.7 ... v1.8

Author SHA1 Message Date
ac70ae6a76 scripts 2019-07-02 04:39:53 +02:00
d83f49d84f remove unused variables, subroutines, blocks 2019-07-02 04:29:51 +02:00
ff1294207e improved parameter name shadowing check 2019-07-02 00:32:55 +02:00
a56956797a chars can now have a color 2019-07-01 23:41:30 +02:00
3242495b0b slightly improved warning about implicit float casts 2019-07-01 18:43:39 +02:00
49eb7e7803 remove bogus 2019-07-01 18:11:16 +02:00
1d7f0d3537 streamline moving values to heap 2019-07-01 18:01:36 +02:00
31137743f0 simplified string handling a little in LiteralValue 2019-07-01 14:19:41 +02:00
2c69e10489 heapId writable 2019-07-01 14:10:52 +02:00
3a1fa9e069 fixed constantfolding of array values 2019-07-01 13:53:29 +02:00
2c08d2f9c6 fix array size in vardecls 2019-06-30 20:10:53 +02:00
4743cacb73 fix swap() 2019-06-30 18:06:11 +02:00
5f5a1447e0 array on heap fix 2019-06-30 17:58:08 +02:00
a3004555a8 branch 2019-06-30 17:07:08 +02:00
267c678292 more swap logic, some typing fixes 2019-06-28 22:10:01 +02:00
6c50043a4a swap isn't yet finished 2019-06-28 02:57:13 +02:00
3ee1b2efdd left and right of a binary expression should usually have the same datatype, insert typecast if needed 2019-06-28 02:39:55 +02:00
75d8c832ad implemented Jump 2019-06-28 01:21:31 +02:00
53a4379c45 implemented all builtin functions in the AstVm 2019-06-28 00:10:27 +02:00
29b3a7e94e optimize redundant typecasts, fix some runtime type casting errors 2019-06-27 21:09:21 +02:00
0782f6ecf1 function call arguments 2019-06-27 00:07:41 +02:00
595e58ec46 taking care of memory mapped vars 2019-06-26 03:28:34 +02:00
060e05c868 strlen and strings with zeros in them should terminate at the zero 2019-06-26 02:34:43 +02:00
f49eefad6f some builtin functions 2019-06-26 00:01:23 +02:00
d68360461b registers 2019-06-25 22:48:40 +02:00
343978d164 for loop and cleaner iteration over values 2019-06-25 21:49:02 +02:00
b11d10e2ff fix Return when dealing with non-subroutine scopes 2019-06-25 01:44:57 +02:00
268856823a got rid of old Value in favor of new RuntimeValue implementation 2019-06-24 22:45:27 +02:00
4bac5043b6 fix integer wraparounds for RuntimeValue 2019-06-24 22:18:50 +02:00
eb25b4c800 fix some initial value datatypes and type casting in assignments 2019-06-24 04:09:30 +02:00
a079e44b02 fix some initial value datatypes and type casting in assignments 2019-06-24 01:31:25 +02:00
e53c860f1a first go at ast-based virtual machine (rather than the stackvm that uses intermediate code) 2019-06-24 00:17:48 +02:00
99121004bf more sensible subroutine inlining 2019-06-23 20:06:35 +02:00
6dd3371781 some infix functions 2019-06-23 15:43:52 +02:00
f473be8951 simple cleaup script 2019-06-23 14:10:50 +02:00
ebd38f27e6 cleaned up some symbol visibilities 2019-06-23 13:49:35 +02:00
a6c3251668 simple subroutine inlining 2019-06-23 03:15:23 +02:00
560047adee variables init subroutine must never be optimized away (fixes primes example) 2019-06-21 23:56:45 +02:00
a86852874f readme 2019-06-21 23:41:20 +02:00
6d44d6a901 travis ci 2019-06-21 23:22:34 +02:00
968f02823f travis ci 2019-06-21 23:14:53 +02:00
5d321d759e travis ci 2019-06-21 23:12:25 +02:00
7de7d5234f callgraph fixed scanning asm subroutines, and deletion of unused subs and modules 2019-06-21 23:08:29 +02:00
b374af3526 remove unused/empty modules 2019-06-21 00:12:22 +02:00
b35430214b some more program node cleanups 2019-06-20 21:46:59 +02:00
e96d3d4455 update kotlin version
cleaning up the way the root of the Ast and the global namespace work (introduced ProgramAst node)
2019-06-20 20:15:18 +02:00
6a17f7a0ad Merge remote-tracking branch 'origin/master' 2019-05-30 16:04:09 +02:00
c559682c0b refresh IDE project files 2019-05-30 16:03:53 +02:00
6ce1277438 fix classpaths in windows command files 2019-05-06 17:14:13 +02:00
66 changed files with 4756 additions and 2346 deletions

20
.idea/gradle.xml generated
View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="/usr/share/java/gradle" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/compiler" />
<option value="$PROJECT_DIR$/parser" />
</set>
</option>
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>
</project>

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

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

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

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

View File

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

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

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

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

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

3
.idea/misc.xml generated
View File

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

3
.idea/modules.xml generated
View File

@ -2,7 +2,10 @@
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" />
<module fileurl="file://$PROJECT_DIR$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
<module fileurl="file://$PROJECT_DIR$/parser/parser.iml" filepath="$PROJECT_DIR$/parser/parser.iml" />
</modules>
</component>
</project>

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

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

2
.idea/vcs.xml generated
View File

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

11
.travis.yml Normal file
View File

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

View File

@ -1,3 +1,6 @@
[![saythanks](https://img.shields.io/badge/say-thanks-ff69b4.svg)](https://saythanks.io/to/irmen)
[![Build Status](https://travis-ci.org/irmen/prog8.svg?branch=master)](https://travis-ci.org/irmen/prog8)
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
===========================================================================
@ -11,23 +14,29 @@ as used in many home computers from that era. It is a medium to low level progra
which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
- reduction of source code length
- easier program understanding (because it's higher level, and more terse)
- option to automatically run the compiled program in the Vice emulator
- easier program understanding (because it's higher level, and way more compact)
- modularity, symbol scoping, subroutines
- subroutines have enforced input- and output parameter definitions
- various data types other than just bytes (16-bit words, floats, strings, 16-bit register pairs)
- automatic variable allocations, automatic string variables and string sharing
- constant folding in expressions (compile-time evaluation)
- conditional branches
- automatic type conversions
- floating point operations
- floating point operations (uses the C64 Basic ROM routines for this)
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
Rapid edit-compile-run-debug cycle:
- use modern PC to work on
- quick compilation times (less than 1 second)
- option to automatically run the program in the Vice emulator
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
- conditional gotos
- various code optimizations (code structure, logical and numerical expressions, ...)
It is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome.
Documentation is online at https://prog8.readthedocs.io/
@ -39,8 +48,8 @@ Required tools:
A recent .exe version of this tool for Windows can be obtained from my [clone](https://github.com/irmen/64tass/releases) of this project.
For other platforms it is very easy to compile it yourself (make ; make install).
A **Java runtime (jre or jdk), version 8 or newer** is required to run the packaged compiler.
If you want to build it from source, you'll need a Kotlin 1.3 SDK as well (or for instance,
A **Java runtime (jre or jdk), version 8 or newer** is required to run a prepackaged version of the compiler.
If you want to build it from source, you'll need a Java SDK + Kotlin 1.3.x SDK (or for instance,
IntelliJ IDEA with the Kotlin plugin).
It's handy to have a C-64 emulator or a real C-64 to run the programs on. The compiler assumes the presence

5
clean.sh Executable file
View File

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

View File

@ -1,6 +1,7 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.3.30"
id "org.jetbrains.kotlin.jvm" version "1.3.40"
id 'application'
id 'org.jetbrains.dokka' version "0.9.18"
}
repositories {
@ -8,7 +9,7 @@ repositories {
jcenter()
}
def kotlinVersion = '1.3.30'
def kotlinVersion = '1.3.40'
dependencies {
implementation project(':parser')
@ -28,6 +29,7 @@ dependencies {
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
@ -95,3 +97,9 @@ test {
events "passed", "skipped", "failed"
}
}
dokka {
outputFormat = 'html'
outputDirectory = "$buildDir/kdoc"
}

19
compiler/compiler.iml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/res" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" />
</component>
</module>

View File

@ -1 +1 @@
1.7
1.8

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,6 @@ import prog8.compiler.HeapValues
import prog8.compiler.target.c64.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.FLOAT_MAX_POSITIVE
import prog8.functions.BuiltinFunctions
import prog8.optimizing.same
import prog8.parser.ParsingFailedError
import java.io.File
@ -13,9 +12,9 @@ import java.io.File
* General checks on the Ast
*/
fun Module.checkValid(globalNamespace: INameScope, compilerOptions: CompilationOptions, heap: HeapValues) {
val checker = AstChecker(globalNamespace, compilerOptions, heap)
this.process(checker)
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(this, compilerOptions)
checker.process(this)
printErrors(checker.result(), name)
}
@ -52,20 +51,71 @@ fun printWarning(msg: String) {
print("\u001b[0m\n") // normal
}
private class AstChecker(private val namespace: INameScope,
private val compilerOptions: CompilationOptions,
private val heap: HeapValues) : IAstProcessor {
private class AstChecker(private val program: Program,
private val compilerOptions: CompilationOptions) : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf()
private val heapStringSentinel: Int
init {
val stringSentinel = heap.allEntries().firstOrNull {it.value.str==""}
heapStringSentinel = stringSentinel?.key ?: heap.addString(DataType.STR, "")
val stringSentinel = program.heap.allEntries().firstOrNull {it.value.str==""}
heapStringSentinel = stringSentinel?.key ?: program.heap.addString(DataType.STR, "")
}
fun result(): List<AstException> {
return checkResult
}
override fun process(program: Program) {
assert(program === this.program)
// there must be a single 'main' block with a 'start' subroutine for the program entry point.
val mainBlocks = program.modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block }
if(mainBlocks.size>1)
checkResult.add(SyntaxError("more than one 'main' block", mainBlocks[0].position))
for(mainBlock in mainBlocks) {
val startSub = mainBlock.subScopes()["start"] as? Subroutine
if (startSub == null) {
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", mainBlock.position))
} else {
if (startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", startSub.position))
}
// the main module cannot contain 'regular' statements (they will never be executed!)
for (statement in mainBlock.statements) {
val ok = when (statement) {
is Block -> true
is Directive -> true
is Label -> true
is VarDecl -> true
is InlineAssembly -> true
is INameScope -> true
is VariableInitializationAssignment -> true
is NopStatement -> true
else -> false
}
if (!ok) {
checkResult.add(SyntaxError("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position))
break
}
}
}
// there can be an optional single 'irq' block with a 'irq' subroutine in it,
// which will be used as the 60hz irq routine in the vm if it's present
val irqBlocks = program.modules.flatMap { it.statements }.filter { it is Block && it.name=="irq" }.map { it as Block }
if(irqBlocks.size>1)
checkResult.add(SyntaxError("more than one 'irq' block", irqBlocks[0].position))
for(irqBlock in irqBlocks) {
val irqSub = irqBlock.subScopes()["irq"] as? Subroutine
if (irqSub != null) {
if (irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position))
}
}
super.process(program)
}
override fun process(module: Module) {
super.process(module)
val directives = module.statements.filterIsInstance<Directive>().groupBy { it.directive }
@ -75,45 +125,6 @@ private class AstChecker(private val namespace: INameScope,
entry.value.mapTo(checkResult) { SyntaxError("directive can just occur once", it.position) }
}
}
// there must be a 'main' block with a 'start' subroutine for the program entry point.
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" } as? Block?
val startSub = mainBlock?.subScopes()?.get("start") as? Subroutine
if(startSub==null) {
checkResult.add(SyntaxError("missing program entrypoint ('start' subroutine in 'main' block)", module.position))
} else {
if(startSub.parameters.isNotEmpty() || startSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("program entrypoint subroutine can't have parameters and/or return values", startSub.position))
}
if(mainBlock!=null) {
// the main module cannot contain 'regular' statements (they will never be executed!)
for (statement in mainBlock.statements) {
val ok = when(statement) {
is Block->true
is Directive->true
is Label->true
is VarDecl->true
is InlineAssembly->true
is INameScope->true
is VariableInitializationAssignment->true
else->false
}
if(!ok) {
checkResult.add(SyntaxError("main block contains regular statements, this is not allowed (they'll never get executed). Use subroutines.", statement.position))
break
}
}
}
// there can be an optional 'irq' block with a 'irq' subroutine in it,
// which will be used as the 60hz irq routine in the vm if it's present
val irqBlock = module.statements.singleOrNull { it is Block && it.name=="irq" } as? Block?
val irqSub = irqBlock?.subScopes()?.get("irq") as? Subroutine
if(irqSub!=null) {
if(irqSub.parameters.isNotEmpty() || irqSub.returntypes.isNotEmpty())
checkResult.add(SyntaxError("irq entrypoint subroutine can't have parameters and/or return values", irqSub.position))
}
}
override fun process(returnStmt: Return): IStatement {
@ -121,7 +132,7 @@ private class AstChecker(private val namespace: INameScope,
if(expectedReturnValues.size != returnStmt.values.size) {
// if the return value is a function call, check the result of that call instead
if(returnStmt.values.size==1 && returnStmt.values[0] is FunctionCall) {
val dt = (returnStmt.values[0] as FunctionCall).resultingDatatype(namespace, heap)
val dt = (returnStmt.values[0] as FunctionCall).inferType(program)
if(dt!=null && expectedReturnValues.isEmpty())
checkResult.add(SyntaxError("invalid number of return values", returnStmt.position))
} else
@ -129,7 +140,7 @@ private class AstChecker(private val namespace: INameScope,
}
for (rv in expectedReturnValues.withIndex().zip(returnStmt.values)) {
val valueDt=rv.second.resultingDatatype(namespace, heap)
val valueDt=rv.second.inferType(program)
if(rv.first.value!=valueDt)
checkResult.add(ExpressionError("type $valueDt of return value #${rv.first.index+1} doesn't match subroutine return type ${rv.first.value}", rv.second.position))
}
@ -137,13 +148,13 @@ private class AstChecker(private val namespace: INameScope,
}
override fun process(forLoop: ForLoop): IStatement {
if(forLoop.body.isEmpty())
if(forLoop.body.containsNoCodeNorVars())
printWarning("for loop body is empty", forLoop.position)
if(!forLoop.iterable.isIterable(namespace, heap)) {
val iterableDt = forLoop.iterable.inferType(program)
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
} else {
val iterableDt = forLoop.iterable.resultingDatatype(namespace, heap)
if (forLoop.loopRegister != null) {
printWarning("using a register as loop variable is risky (it could get clobbered in the body)", forLoop.position)
// loop register
@ -151,7 +162,7 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position))
} else {
// loop variable
val loopvar = forLoop.loopVar!!.targetVarDecl(namespace)
val loopvar = forLoop.loopVar!!.targetVarDecl(program.namespace)
if(loopvar==null || loopvar.type==VarDeclType.CONST) {
checkResult.add(SyntaxError("for loop requires a variable to loop with", forLoop.position))
@ -258,9 +269,9 @@ private class AstChecker(private val namespace: INameScope,
if(subroutine.isAsmSubroutine) {
if(subroutine.asmParameterRegisters.size != subroutine.parameters.size)
err("number of asm parameter registers is not the same as number of parameters")
err("number of asm parameter registers is not the isSameAs as number of parameters")
if(subroutine.asmReturnvaluesRegisters.size != subroutine.returntypes.size)
err("number of return registers is not the same as number of return values")
err("number of return registers is not the isSameAs as number of return values")
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
if(param.second.registerOrPair in setOf(RegisterOrPair.A, RegisterOrPair.X, RegisterOrPair.Y)) {
if (param.first.type != DataType.UBYTE && param.first.type != DataType.BYTE)
@ -355,14 +366,14 @@ private class AstChecker(private val namespace: INameScope,
// assigning from a functioncall COULD return multiple values (from an asm subroutine)
if(assignment.value is FunctionCall) {
val stmt = (assignment.value as FunctionCall).target.targetStatement(namespace)
val stmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if (stmt is Subroutine) {
if (stmt.isAsmSubroutine) {
if (stmt.returntypes.size != assignment.targets.size)
checkResult.add(ExpressionError("number of return values doesn't match number of assignment targets", assignment.value.position))
else {
for (thing in stmt.returntypes.zip(assignment.targets)) {
if (thing.second.determineDatatype(namespace, heap, assignment) != thing.first)
if (thing.second.inferType(program, assignment) != thing.first)
checkResult.add(ExpressionError("return type mismatch for target ${thing.second.shortString()}", assignment.value.position))
}
}
@ -379,7 +390,7 @@ private class AstChecker(private val namespace: INameScope,
}
private fun processAssignmentTarget(assignment: Assignment, target: AssignTarget): Assignment {
val memAddr = target.memoryAddress?.addressExpression?.constValue(namespace, heap)?.asIntegerValue
val memAddr = target.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue
if(memAddr!=null) {
if(memAddr<0 || memAddr>=65536)
checkResult.add(ExpressionError("address out of range", target.position))
@ -387,7 +398,7 @@ private class AstChecker(private val namespace: INameScope,
if(target.identifier!=null) {
val targetName = target.identifier.nameInSource
val targetSymbol = namespace.lookup(targetName, assignment)
val targetSymbol = program.namespace.lookup(targetName, assignment)
when (targetSymbol) {
null -> {
checkResult.add(ExpressionError("undefined symbol: ${targetName.joinToString(".")}", assignment.position))
@ -425,23 +436,23 @@ private class AstChecker(private val namespace: INameScope,
return assignment2
}
val targetDatatype = target.determineDatatype(namespace, heap, assignment)
val targetDatatype = target.inferType(program, assignment)
if(targetDatatype!=null) {
val constVal = assignment.value.constValue(namespace, heap)
val constVal = assignment.value.constValue(program)
if(constVal!=null) {
val arrayspec = if(target.identifier!=null) {
val targetVar = namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
val targetVar = program.namespace.lookup(target.identifier.nameInSource, assignment) as? VarDecl
targetVar?.arraysize
} else null
checkValueTypeAndRange(targetDatatype,
arrayspec ?: ArrayIndex(LiteralValue.optimalInteger(-1, assignment.position), assignment.position),
constVal, heap)
constVal, program.heap)
} else {
val sourceDatatype: DataType? = assignment.value.resultingDatatype(namespace, heap)
val sourceDatatype: DataType? = assignment.value.inferType(program)
if(sourceDatatype==null) {
if(assignment.targets.size<=1) {
if (assignment.value is FunctionCall) {
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(namespace)
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if(targetStmt!=null)
checkResult.add(ExpressionError("function call doesn't return a suitable value to use in assignment", assignment.value.position))
}
@ -457,7 +468,7 @@ private class AstChecker(private val namespace: INameScope,
}
override fun process(addressOf: AddressOf): IExpression {
val variable=addressOf.identifier.targetVarDecl(namespace)
val variable=addressOf.identifier.targetVarDecl(program.namespace)
if(variable==null)
checkResult.add(ExpressionError("pointer-of operand must be the name of a heap variable", addressOf.position))
else {
@ -489,12 +500,12 @@ private class AstChecker(private val namespace: INameScope,
}
// FLOATS
if(!compilerOptions.floats && decl.datatype==DataType.FLOAT && decl.type!=VarDeclType.MEMORY) {
if(!compilerOptions.floats && decl.datatype in setOf(DataType.FLOAT, DataType.ARRAY_F) && decl.type!=VarDeclType.MEMORY) {
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", decl.position))
}
// ARRAY without size specifier MUST have an iterable initializer value
if(decl.isUnsizedArray) {
if(decl.isArray && decl.arraysize==null) {
if(decl.type==VarDeclType.MEMORY)
checkResult.add(SyntaxError("memory mapped array must have a size specification", decl.position))
if(decl.value==null) {
@ -515,12 +526,17 @@ private class AstChecker(private val namespace: INameScope,
when {
decl.datatype in NumericDatatypes -> {
// initialize numeric var with value zero by default.
val litVal = LiteralValue(DataType.UBYTE, 0, position = decl.position)
val litVal =
when {
decl.datatype in ByteDatatypes -> LiteralValue(decl.datatype, bytevalue=0, position = decl.position)
decl.datatype in WordDatatypes -> LiteralValue(decl.datatype, wordvalue=0, position = decl.position)
else -> LiteralValue(decl.datatype, floatvalue=0.0, position = decl.position)
}
litVal.parent = decl
decl.value = litVal
}
decl.type==VarDeclType.VAR -> {
val litVal = LiteralValue(decl.datatype, heapId = heapStringSentinel, position=decl.position) // point to the sentinel heap value instead
val litVal = LiteralValue(decl.datatype, initHeapId = heapStringSentinel, position=decl.position) // point to the sentinel heap value instead
litVal.parent=decl
decl.value = litVal
}
@ -531,17 +547,17 @@ private class AstChecker(private val namespace: INameScope,
}
when {
decl.value is RangeExpr -> {
if(!decl.isUnsizedArray)
if(decl.arraysize!=null)
checkValueTypeAndRange(decl.datatype, decl.arraysize!!, decl.value as RangeExpr)
}
decl.value is LiteralValue -> {
val arraySpec = decl.arraysize ?: (
if((decl.value as LiteralValue).isArray)
ArrayIndex.forArray(decl.value as LiteralValue, heap)
ArrayIndex.forArray(decl.value as LiteralValue, program.heap)
else
ArrayIndex(LiteralValue.optimalInteger(-2, decl.position), decl.position)
)
checkValueTypeAndRange(decl.datatype, arraySpec, decl.value as LiteralValue, heap)
checkValueTypeAndRange(decl.datatype, arraySpec, decl.value as LiteralValue, program.heap)
}
else -> {
err("var/const declaration needs a compile-time constant initializer value, or range, instead found: ${decl.value!!::class.simpleName}")
@ -661,20 +677,20 @@ private class AstChecker(private val namespace: INameScope,
var definingModule = directive.parent
while (definingModule !is Module)
definingModule = definingModule.parent
if (!(filename.startsWith("library:") || definingModule.importedFrom.resolveSibling(filename).toFile().isFile || File(filename).isFile))
if (!(filename.startsWith("library:") || definingModule.source.resolveSibling(filename).toFile().isFile || File(filename).isFile))
checkResult.add(NameError("included file not found: $filename", directive.position))
}
override fun process(literalValue: LiteralValue): LiteralValue {
if(!compilerOptions.floats && literalValue.type==DataType.FLOAT) {
if(!compilerOptions.floats && literalValue.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", literalValue.position))
}
val arrayspec =
if(literalValue.isArray)
ArrayIndex.forArray(literalValue, heap)
ArrayIndex.forArray(literalValue, program.heap)
else
ArrayIndex(LiteralValue.optimalInteger(-3, literalValue.position), literalValue.position)
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, heap)
checkValueTypeAndRange(literalValue.type, arrayspec, literalValue, program.heap)
val lv = super.process(literalValue)
when(lv.type) {
@ -693,7 +709,7 @@ private class AstChecker(private val namespace: INameScope,
override fun process(expr: PrefixExpression): IExpression {
if(expr.operator=="-") {
val dt = expr.resultingDatatype(namespace, heap)
val dt = expr.inferType(program)
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
checkResult.add(ExpressionError("can only take negative of a signed number type", expr.position))
}
@ -702,12 +718,12 @@ private class AstChecker(private val namespace: INameScope,
}
override fun process(expr: BinaryExpression): IExpression {
val leftDt = expr.left.resultingDatatype(namespace, heap)
val rightDt = expr.right.resultingDatatype(namespace, heap)
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
when(expr.operator){
"/", "%" -> {
val constvalRight = expr.right.constValue(namespace, heap)
val constvalRight = expr.right.constValue(program)
val divisor = constvalRight?.asNumericValue?.toDouble()
if(divisor==0.0)
checkResult.add(ExpressionError("division by zero", expr.right.position))
@ -724,8 +740,8 @@ private class AstChecker(private val namespace: INameScope,
// only integer numeric operands accepted, and if literal constants, only boolean values accepted (0 or 1)
if(leftDt !in IntegerDatatypes || rightDt !in IntegerDatatypes)
checkResult.add(ExpressionError("logical operator can only be used on boolean operands", expr.right.position))
val constLeft = expr.left.constValue(namespace, heap)
val constRight = expr.right.constValue(namespace, heap)
val constLeft = expr.left.constValue(program)
val constRight = expr.right.constValue(program)
if(constLeft!=null && constLeft.asIntegerValue !in 0..1 || constRight!=null && constRight.asIntegerValue !in 0..1)
checkResult.add(ExpressionError("const literal argument of logical operator must be boolean (0 or 1)", expr.position))
}
@ -754,9 +770,9 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(SyntaxError(msg, range.position))
}
super.process(range)
val from = range.from.constValue(namespace, heap)
val to = range.to.constValue(namespace, heap)
val stepLv = range.step.constValue(namespace, heap) ?: LiteralValue(DataType.UBYTE, 1, position = range.position)
val from = range.from.constValue(program)
val to = range.to.constValue(program)
val stepLv = range.step.constValue(program) ?: LiteralValue(DataType.UBYTE, 1, position = range.position)
if (stepLv.asIntegerValue == null || stepLv.asIntegerValue == 0) {
err("range step must be an integer != 0")
return range
@ -773,8 +789,8 @@ private class AstChecker(private val namespace: INameScope,
err("descending range requires step < 0")
}
from.isString && to.isString -> {
val fromString = from.strvalue(heap)
val toString = to.strvalue(heap)
val fromString = from.strvalue!!
val toString = to.strvalue!!
if(fromString.length!=1 || toString.length!=1)
err("range from and to must be a single character")
if(fromString[0] == toString[0])
@ -821,20 +837,20 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(SyntaxError("invalid number of arguments", position))
else {
for (arg in args.withIndex().zip(func.parameters)) {
val argDt=arg.first.value.resultingDatatype(namespace, heap)
if(argDt!=null && !argDt.assignableTo(arg.second.possibleDatatypes)) {
val argDt=arg.first.value.inferType(program)
if(argDt!=null && !(argDt isAssignableTo arg.second.possibleDatatypes)) {
checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
}
}
if(target.name=="swap") {
// swap() is a bit weird because this one is translated into a sequence of bytecodes, instead of being an actual function call
val dt1 = args[0].resultingDatatype(namespace, heap)!!
val dt2 = args[1].resultingDatatype(namespace, heap)!!
val dt1 = args[0].inferType(program)!!
val dt2 = args[1].inferType(program)!!
if (dt1 != dt2)
checkResult.add(ExpressionError("swap requires 2 args of identical type", position))
else if (args[0].constValue(namespace, heap) != null || args[1].constValue(namespace, heap) != null)
else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
checkResult.add(ExpressionError("swap requires 2 variables, not constant value(s)", position))
else if(same(args[0], args[1]))
else if(args[0] isSameAs args[1])
checkResult.add(ExpressionError("swap should have 2 different args", position))
else if(dt1 !in NumericDatatypes)
checkResult.add(ExpressionError("swap requires args of numerical type", position))
@ -845,8 +861,8 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(SyntaxError("invalid number of arguments", position))
else {
for (arg in args.withIndex().zip(target.parameters)) {
val argDt = arg.first.value.resultingDatatype(namespace, heap)
if(argDt!=null && !argDt.assignableTo(arg.second.type)) {
val argDt = arg.first.value.inferType(program)
if(argDt!=null && !(argDt isAssignableTo arg.second.type)) {
// for asm subroutines having STR param it's okay to provide a UWORD too (pointer value)
if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt==DataType.UWORD))
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position))
@ -879,7 +895,7 @@ private class AstChecker(private val namespace: INameScope,
override fun process(postIncrDecr: PostIncrDecr): IStatement {
if(postIncrDecr.target.identifier != null) {
val targetName = postIncrDecr.target.identifier!!.nameInSource
val target = namespace.lookup(targetName, postIncrDecr)
val target = program.namespace.lookup(targetName, postIncrDecr)
if(target==null) {
checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position))
} else {
@ -890,7 +906,7 @@ private class AstChecker(private val namespace: INameScope,
}
}
} else if(postIncrDecr.target.arrayindexed != null) {
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(namespace)
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
if(target==null) {
checkResult.add(SyntaxError("undefined symbol", postIncrDecr.position))
}
@ -906,7 +922,7 @@ private class AstChecker(private val namespace: INameScope,
}
override fun process(arrayIndexedExpression: ArrayIndexedExpression): IExpression {
val target = arrayIndexedExpression.identifier.targetStatement(namespace)
val target = arrayIndexedExpression.identifier.targetStatement(program.namespace)
if(target is VarDecl) {
if(target.datatype !in IterableDatatypes)
checkResult.add(SyntaxError("indexing requires an iterable variable", arrayIndexedExpression.position))
@ -919,7 +935,7 @@ private class AstChecker(private val namespace: INameScope,
} else if(target.datatype in StringDatatypes) {
// check string lengths
val heapId = (target.value as LiteralValue).heapId!!
val stringLen = heap.get(heapId).str!!.length
val stringLen = program.heap.get(heapId).str!!.length
val index = (arrayIndexedExpression.arrayspec.index as? LiteralValue)?.asIntegerValue
if(index!=null && (index<0 || index>=stringLen))
checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position))
@ -928,7 +944,7 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position))
// check index value 0..255
val dtx = arrayIndexedExpression.arrayspec.index.resultingDatatype(namespace, heap)
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program)
if(dtx!=DataType.UBYTE && dtx!=DataType.BYTE)
checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position))
@ -936,7 +952,7 @@ private class AstChecker(private val namespace: INameScope,
}
private fun checkFunctionOrLabelExists(target: IdentifierReference, statement: IStatement): IStatement? {
val targetStatement = target.targetStatement(namespace)
val targetStatement = target.targetStatement(program.namespace)
if(targetStatement is Label || targetStatement is Subroutine || targetStatement is BuiltinFunctionStatementPlaceholder)
return targetStatement
checkResult.add(NameError("undefined function or subroutine: ${target.nameInSource.joinToString(".")}", statement.position))
@ -944,8 +960,8 @@ private class AstChecker(private val namespace: INameScope,
}
private fun checkValueTypeAndRange(targetDt: DataType, arrayspec: ArrayIndex, range: RangeExpr) : Boolean {
val from = range.from.constValue(namespace, heap)
val to = range.to.constValue(namespace, heap)
val from = range.from.constValue(program)
val to = range.to.constValue(program)
if(from==null || to==null) {
checkResult.add(SyntaxError("range from and to values must be constants", range.position))
return false
@ -962,7 +978,7 @@ private class AstChecker(private val namespace: INameScope,
checkResult.add(ExpressionError("range for string must have single characters from and to values", range.position))
return false
}
val rangeSize=range.size(heap)
val rangeSize=range.size()
if(rangeSize!=null && (rangeSize<0 || rangeSize>255)) {
checkResult.add(ExpressionError("size of range for string must be 0..255, instead of $rangeSize", range.position))
return false
@ -972,7 +988,7 @@ private class AstChecker(private val namespace: INameScope,
in ArrayDatatypes -> {
// range and length check bytes
val expectedSize = arrayspec.size()
val rangeSize=range.size(heap)
val rangeSize=range.size()
if(rangeSize!=null && rangeSize != expectedSize) {
checkResult.add(ExpressionError("range size doesn't match array size, expected $expectedSize found $rangeSize", range.position))
return false
@ -1034,8 +1050,7 @@ private class AstChecker(private val namespace: INameScope,
DataType.STR, DataType.STR_S -> {
if(!value.isString)
return err("string value expected")
val str = value.strvalue(heap)
if (str.length > 255)
if (value.strvalue!!.length > 255)
return err("string length must be 0-255")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
@ -1048,7 +1063,7 @@ private class AstChecker(private val namespace: INameScope,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>256)
return err("byte array length must be 1-256")
val constX = arrayspec.index.constValue(namespace, heap)
val constX = arrayspec.index.constValue(program)
if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue
@ -1070,7 +1085,7 @@ private class AstChecker(private val namespace: INameScope,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>128)
return err("word array length must be 1-128")
val constX = arrayspec.index.constValue(namespace, heap)
val constX = arrayspec.index.constValue(program)
if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue
@ -1092,7 +1107,7 @@ private class AstChecker(private val namespace: INameScope,
if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize < 1 || arraySpecSize>51)
return err("float array length must be 1-51")
val constX = arrayspec.index.constValue(namespace, heap)
val constX = arrayspec.index.constValue(program)
if(constX?.asIntegerValue==null)
return err("array size specifier must be constant integer value")
val expectedSize = constX.asIntegerValue
@ -1103,7 +1118,7 @@ private class AstChecker(private val namespace: INameScope,
// check if the floating point values are all within range
val doubles = if(value.arrayvalue!=null)
value.arrayvalue.map {it.constValue(namespace, heap)?.asNumericValue!!.toDouble()}.toDoubleArray()
value.arrayvalue.map {it.constValue(program)?.asNumericValue!!.toDouble()}.toDoubleArray()
else
heap.get(value.heapId!!).doubleArray!!
if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE})
@ -1117,7 +1132,32 @@ private class AstChecker(private val namespace: INameScope,
}
private fun checkArrayValues(value: LiteralValue, type: DataType): Boolean {
val array = heap.get(value.heapId!!)
if(value.isArray && value.heapId==null) {
// TODO weird, array literal that hasn't been moved to the heap yet?
val array = value.arrayvalue!!.map { it.constValue(program)!! }
val correct: Boolean
when(type) {
DataType.ARRAY_UB -> {
correct=array.all { it.bytevalue!=null && it.bytevalue in 0..255 }
}
DataType.ARRAY_B -> {
correct=array.all { it.bytevalue!=null && it.bytevalue in -128..127 }
}
DataType.ARRAY_UW -> {
correct=array.all { it.wordvalue!=null && it.wordvalue in 0..65535 }
}
DataType.ARRAY_W -> {
correct=array.all { it.wordvalue!=null && it.wordvalue in -32768..32767}
}
DataType.ARRAY_F -> correct = true
else -> throw AstException("invalid array type $type")
}
if(!correct)
checkResult.add(ExpressionError("array value out of range for type $type", value.position))
return correct
}
val array = program.heap.get(value.heapId!!)
val correct: Boolean
when(type) {
DataType.ARRAY_UB -> {

View File

@ -8,9 +8,13 @@ import prog8.functions.BuiltinFunctions
* Finally, it also makes sure the datatype of all Var decls and sub Return values is set correctly.
*/
fun Module.checkIdentifiers(namespace: INameScope) {
internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(namespace)
this.process(checker)
checker.process(this)
if(modules.map {it.name}.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
}
// add any anonymous variables for heap values that are used,
// and replace an iterable literalvalue by identifierref to new local variable
@ -47,10 +51,9 @@ fun Module.checkIdentifiers(namespace: INameScope) {
private class AstIdentifiersChecker(private val namespace: INameScope) : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf()
var blocks: MutableMap<String, Block> = mutableMapOf()
private set
private var blocks: MutableMap<String, Block> = mutableMapOf()
fun result(): List<AstException> {
internal fun result(): List<AstException> {
return checkResult
}
@ -58,6 +61,11 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
}
override fun process(module: Module) {
blocks.clear() // blocks may be redefined within a different module
super.process(module)
}
override fun process(block: Block): IStatement {
val existing = blocks[block.name]
if(existing!=null)
@ -71,7 +79,7 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
override fun process(functionCall: FunctionCall): IExpression {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, functionCall.position)
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position)
typecast.linkParents(functionCall.parent)
return super.process(typecast)
}
@ -106,14 +114,18 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
if (existing != null && existing !== subroutine)
nameError(subroutine.name, subroutine.position, existing)
// check that there are no local variables that redefine the subroutine's parameters
val allDefinedNames = subroutine.allLabelsAndVariables()
// check that there are no local variables, labels, or other subs that redefine the subroutine's parameters
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
val paramNames = subroutine.parameters.map { it.name }.toSet()
val paramsToCheck = paramNames.intersect(allDefinedNames)
val paramsToCheck = paramNames.intersect(namesInSub)
for(name in paramsToCheck) {
val thing = subroutine.getLabelOrVariable(name)!!
if(thing.position != subroutine.position)
nameError(name, thing.position, subroutine)
val labelOrVar = subroutine.getLabelOrVariable(name)
if(labelOrVar!=null && labelOrVar.position != subroutine.position)
nameError(name, labelOrVar.position, subroutine)
val sub = subroutine.statements.singleOrNull { it is Subroutine && it.name==name}
if(sub!=null)
nameError(name, sub.position, subroutine)
}
// inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters)
@ -124,9 +136,10 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
if(subroutine.asmAddress==null && !subroutine.canBeAsmSubroutine) {
if(subroutine.asmParameterRegisters.isEmpty()) {
subroutine.parameters
.filter { it.name !in allDefinedNames }
.filter { it.name !in namesInSub }
.forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, false, it.name, null, subroutine.position)
val vardecl = VarDecl(VarDeclType.VAR, it.type, false, null, it.name, null,
isArray = false, autoGenerated = true, position = subroutine.position)
vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl)
}
@ -155,16 +168,17 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
// additional interation count variable in their scope.
if(forLoop.loopRegister!=null) {
if(forLoop.decltype!=null)
checkResult.add(SyntaxError("register loop variables cannot be explicitly declared with a datatype", forLoop.position))
checkResult.add(SyntaxError("register loop variables have a fixed implicit datatype", forLoop.position))
if(forLoop.loopRegister == Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
} else if(forLoop.loopVar!=null) {
val varName = forLoop.loopVar.nameInSource.last()
if(forLoop.decltype!=null) {
val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(forLoop.loopVar.nameInSource, forLoop.body.statements.first())
if(existing==null) {
// create the local scoped for loop variable itself
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, true, null, false, varName, null, forLoop.loopVar.position)
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null,
isArray = false, autoGenerated = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@ -173,10 +187,11 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
}
if(forLoop.iterable !is RangeExpr) {
val existing = if(forLoop.body.isEmpty()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first())
val existing = if(forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(ForLoop.iteratorLoopcounterVarname), forLoop.body.statements.first())
if(existing==null) {
// create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, false, ForLoop.iteratorLoopcounterVarname, null, forLoop.loopVar.position)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, true, null, ForLoop.iteratorLoopcounterVarname, null,
isArray = false, autoGenerated = true, position = forLoop.loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
forLoop.loopVar.parent = forLoop.body // loopvar 'is defined in the body'
@ -224,7 +239,8 @@ private class AstIdentifiersChecker(private val namespace: INameScope) : IAstPro
if(literalValue.heapId!=null && literalValue.parent !is VarDecl) {
// a literal value that's not declared as a variable, which refers to something on the heap.
// we need to introduce an auto-generated variable for this to be able to refer to the value!
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, false, "$autoHeapValuePrefix${literalValue.heapId}", literalValue, literalValue.position)
val variable = VarDecl(VarDeclType.VAR, literalValue.type, false, null, "$autoHeapValuePrefix${literalValue.heapId}", literalValue,
isArray = false, autoGenerated = false, position = literalValue.position)
anonymousVariablesFromHeap[variable.name] = Pair(literalValue, variable)
}
return super.process(literalValue)

View File

@ -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()

View File

@ -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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,10 +4,8 @@ import prog8.ast.*
import prog8.ast.RegisterOrPair.*
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.Opcode
import prog8.compiler.intermediate.Value
import prog8.compiler.intermediate.branchOpcodes
import prog8.functions.BuiltinFunctions
import prog8.optimizing.same
import prog8.parser.tryGetEmbeddedResource
import prog8.stackvm.Syscall
import java.io.File
@ -68,7 +66,7 @@ class HeapValues {
if (str.length > 255)
throw IllegalArgumentException("string length must be 0-255")
// strings are 'interned' and shared if they're the same
// strings are 'interned' and shared if they're the isSameAs
val value = HeapValue(type, str, null, null)
val existing = heap.filter { it.value==value }.map { it.key }.firstOrNull()
@ -145,19 +143,18 @@ data class CompilationOptions(val output: OutputType,
val floats: Boolean)
internal class Compiler(private val rootModule: Module,
private val namespace: INameScope,
private val heap: HeapValues): IAstProcessor {
val prog: IntermediateProgram = IntermediateProgram(rootModule.name, rootModule.loadAddress, heap, rootModule.importedFrom)
internal class Compiler(private val program: Program): IAstProcessor {
private val prog: IntermediateProgram = IntermediateProgram(program.name, program.loadAddress, program.heap, program.modules.first().source)
private var generatedLabelSequenceNumber = 0
private val breakStmtLabelStack : Stack<String> = Stack()
private val continueStmtLabelStack : Stack<String> = Stack()
fun compile(options: CompilationOptions) : IntermediateProgram {
println("Creating stackVM code...")
process(rootModule)
program.modules.forEach {
process(it)
}
return prog
}
@ -189,7 +186,7 @@ internal class Compiler(private val rootModule: Module,
return r
} else {
// asmsub
if(subroutine.isNotEmpty())
if(subroutine.containsCodeOrVars())
throw CompilerException("kernel subroutines (with memory address) can't have a body: $subroutine")
prog.memoryPointer(subroutine.scopedname, subroutine.asmAddress, DataType.UBYTE) // the datatype is a bit of a dummy in this case
@ -218,7 +215,7 @@ internal class Compiler(private val rootModule: Module,
is Return -> translate(stmt)
is Directive -> {
when(stmt.directive) {
"%asminclude" -> translateAsmInclude(stmt.args, prog.importedFrom)
"%asminclude" -> translateAsmInclude(stmt.args, prog.source)
"%asmbinary" -> translateAsmBinary(stmt.args)
"%breakpoint" -> {
prog.line(stmt.position)
@ -424,7 +421,7 @@ internal class Compiler(private val rootModule: Module,
* if the branch statement just contains jumps, more efficient code is generated.
* (just the appropriate branching instruction is outputted!)
*/
if(branch.elsepart.isEmpty() && branch.truepart.isEmpty())
if(branch.elsepart.containsNoCodeNorVars() && branch.truepart.containsNoCodeNorVars())
return
fun branchOpcode(branch: BranchStatement, complement: Boolean) =
@ -468,7 +465,7 @@ internal class Compiler(private val rootModule: Module,
val labelElse = makeLabel(branch, "else")
val labelEnd = makeLabel(branch, "end")
val opcode = branchOpcode(branch, true)
if (branch.elsepart.isEmpty()) {
if (branch.elsepart.containsNoCodeNorVars()) {
prog.instr(opcode, callLabel = labelEnd)
translate(branch.truepart)
prog.label(labelEnd)
@ -517,7 +514,7 @@ internal class Compiler(private val rootModule: Module,
val trueGoto = stmt.truepart.statements.singleOrNull() as? Jump
if(trueGoto!=null) {
// optimization for if (condition) goto ....
val conditionJumpOpcode = when(stmt.condition.resultingDatatype(namespace, heap)) {
val conditionJumpOpcode = when(stmt.condition.inferType(program)) {
in ByteDatatypes -> Opcode.JNZ
in WordDatatypes -> Opcode.JNZW
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
@ -527,13 +524,13 @@ internal class Compiler(private val rootModule: Module,
return
}
val conditionJumpOpcode = when(stmt.condition.resultingDatatype(namespace, heap)) {
val conditionJumpOpcode = when(stmt.condition.inferType(program)) {
in ByteDatatypes -> Opcode.JZ
in WordDatatypes -> Opcode.JZW
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
}
val labelEnd = makeLabel(stmt, "end")
if(stmt.elsepart.isEmpty()) {
if(stmt.elsepart.containsNoCodeNorVars()) {
prog.instr(conditionJumpOpcode, callLabel = labelEnd)
translate(stmt.truepart)
prog.label(labelEnd)
@ -549,70 +546,6 @@ internal class Compiler(private val rootModule: Module,
prog.instr(Opcode.NOP)
}
private fun commonDatatype(leftDt: DataType, rightDt: DataType, leftpos: Position, rightpos: Position): DataType {
// byte + byte -> byte
// byte + word -> word
// word + byte -> word
// word + word -> word
// a combination with a float will be float (but give a warning about this!)
val floatWarning = "byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic"
return when(leftDt) {
DataType.UBYTE -> {
when(rightDt) {
DataType.UBYTE -> DataType.UBYTE
DataType.BYTE -> DataType.BYTE
DataType.UWORD -> DataType.UWORD
DataType.WORD -> DataType.WORD
DataType.FLOAT -> {
printWarning(floatWarning, leftpos)
DataType.FLOAT
}
else -> throw CompilerException("non-numeric datatype $rightDt")
}
}
DataType.BYTE -> {
when(rightDt) {
in ByteDatatypes -> DataType.BYTE
in WordDatatypes -> DataType.WORD
DataType.FLOAT -> {
printWarning(floatWarning, leftpos)
DataType.FLOAT
}
else -> throw CompilerException("non-numeric datatype $rightDt")
}
}
DataType.UWORD -> {
when(rightDt) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> {
printWarning(floatWarning, leftpos)
DataType.FLOAT
}
else -> throw CompilerException("non-numeric datatype $rightDt")
}
}
DataType.WORD -> {
when(rightDt) {
DataType.UBYTE, DataType.UWORD, DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> {
printWarning(floatWarning, leftpos)
DataType.FLOAT
}
else -> throw CompilerException("non-numeric datatype $rightDt")
}
}
DataType.FLOAT -> {
if(rightDt!=DataType.FLOAT)
printWarning(floatWarning, rightpos)
DataType.FLOAT
}
else -> throw CompilerException("non-numeric datatype $leftDt")
}
}
private fun translate(expr: IExpression) {
when(expr) {
is RegisterExpr -> {
@ -620,16 +553,12 @@ internal class Compiler(private val rootModule: Module,
}
is PrefixExpression -> {
translate(expr.expression)
translatePrefixOperator(expr.operator, expr.expression.resultingDatatype(namespace, heap))
translatePrefixOperator(expr.operator, expr.expression.inferType(program))
}
is BinaryExpression -> {
val leftDt = expr.left.resultingDatatype(namespace, heap)!!
val rightDt = expr.right.resultingDatatype(namespace, heap)!!
val commonDt =
if(expr.operator=="/")
BinaryExpression.divisionOpDt(leftDt, rightDt)
else
commonDatatype(leftDt, rightDt, expr.left.position, expr.right.position)
val leftDt = expr.left.inferType(program)!!
val rightDt = expr.right.inferType(program)!!
val (commonDt, _) = expr.commonDatatype(leftDt, rightDt, expr.left, expr.right)
translate(expr.left)
if(leftDt!=commonDt)
convertType(leftDt, commonDt)
@ -637,12 +566,12 @@ internal class Compiler(private val rootModule: Module,
if(rightDt!=commonDt)
convertType(rightDt, commonDt)
if(expr.operator=="<<" || expr.operator==">>")
translateBitshiftedOperator(expr.operator, leftDt, expr.right.constValue(namespace, heap))
translateBitshiftedOperator(expr.operator, leftDt, expr.right.constValue(program))
else
translateBinaryOperator(expr.operator, commonDt)
}
is FunctionCall -> {
val target = expr.target.targetStatement(namespace)
val target = expr.target.targetStatement(program.namespace)
if(target is BuiltinFunctionStatementPlaceholder) {
// call to a builtin function (some will just be an opcode!)
val funcname = expr.target.nameInSource[0]
@ -660,11 +589,11 @@ internal class Compiler(private val rootModule: Module,
is DirectMemoryWrite -> translate(expr)
is AddressOf -> translate(expr)
else -> {
val lv = expr.constValue(namespace, heap) ?: throw CompilerException("constant expression required, not $expr")
val lv = expr.constValue(program) ?: throw CompilerException("constant expression required, not $expr")
when(lv.type) {
in ByteDatatypes -> prog.instr(Opcode.PUSH_BYTE, Value(lv.type, lv.bytevalue!!))
in WordDatatypes -> prog.instr(Opcode.PUSH_WORD, Value(lv.type, lv.wordvalue!!))
DataType.FLOAT -> prog.instr(Opcode.PUSH_FLOAT, Value(lv.type, lv.floatvalue!!))
in ByteDatatypes -> prog.instr(Opcode.PUSH_BYTE, RuntimeValue(lv.type, lv.bytevalue!!))
in WordDatatypes -> prog.instr(Opcode.PUSH_WORD, RuntimeValue(lv.type, lv.wordvalue!!))
DataType.FLOAT -> prog.instr(Opcode.PUSH_FLOAT, RuntimeValue(lv.type, lv.floatvalue!!))
in StringDatatypes -> {
if(lv.heapId==null)
throw CompilerException("string should have been moved into heap ${lv.position}")
@ -729,7 +658,7 @@ internal class Compiler(private val rootModule: Module,
}
private fun translate(identifierRef: IdentifierReference) {
val target = identifierRef.targetStatement(namespace)
val target = identifierRef.targetStatement(program.namespace)
when (target) {
is VarDecl -> {
when (target.type) {
@ -741,11 +670,11 @@ internal class Compiler(private val rootModule: Module,
throw CompilerException("const ref should have been const-folded away")
VarDeclType.MEMORY -> {
when (target.datatype) {
DataType.UBYTE -> prog.instr(Opcode.PUSH_MEM_UB, Value(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.BYTE-> prog.instr(Opcode.PUSH_MEM_B, Value(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.UWORD -> prog.instr(Opcode.PUSH_MEM_UW, Value(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.WORD -> prog.instr(Opcode.PUSH_MEM_W, Value(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.FLOAT -> prog.instr(Opcode.PUSH_MEM_FLOAT, Value(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.UBYTE -> prog.instr(Opcode.PUSH_MEM_UB, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.BYTE-> prog.instr(Opcode.PUSH_MEM_B, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.UWORD -> prog.instr(Opcode.PUSH_MEM_UW, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.WORD -> prog.instr(Opcode.PUSH_MEM_W, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
DataType.FLOAT -> prog.instr(Opcode.PUSH_MEM_FLOAT, RuntimeValue(DataType.UWORD, (target.value as LiteralValue).asNumericValue!!))
else -> throw CompilerException("invalid datatype for memory variable expression: $target")
}
}
@ -758,7 +687,7 @@ internal class Compiler(private val rootModule: Module,
private fun translate(stmt: FunctionCallStatement) {
prog.line(stmt.position)
val targetStmt = stmt.target.targetStatement(namespace)!!
val targetStmt = stmt.target.targetStatement(program.namespace)!!
if(targetStmt is BuiltinFunctionStatementPlaceholder) {
val funcname = stmt.target.nameInSource[0]
translateBuiltinFunctionCall(funcname, stmt.arglist)
@ -796,7 +725,7 @@ internal class Compiler(private val rootModule: Module,
// cast type if needed
if(builtinFuncParams!=null) {
val paramDts = builtinFuncParams[index].possibleDatatypes
val argDt = arg.resultingDatatype(namespace, heap)!!
val argDt = arg.inferType(program)!!
if(argDt !in paramDts) {
for(paramDt in paramDts.sorted())
if(tryConvertType(argDt, paramDt))
@ -809,7 +738,7 @@ internal class Compiler(private val rootModule: Module,
"len" -> {
// 1 argument, type determines the exact syscall to use
val arg=args.single()
when (arg.resultingDatatype(namespace, heap)) {
when (arg.inferType(program)) {
DataType.STR, DataType.STR_S -> createSyscall("${funcname}_str")
else -> throw CompilerException("wrong datatype for len()")
}
@ -817,10 +746,10 @@ internal class Compiler(private val rootModule: Module,
"any", "all" -> {
// 1 array argument, type determines the exact syscall to use
val arg=args.single() as IdentifierReference
val target=arg.targetVarDecl(namespace)!!
val length=Value(DataType.UBYTE, target.arraysize!!.size()!!)
val target=arg.targetVarDecl(program.namespace)!!
val length= RuntimeValue(DataType.UBYTE, target.arraysize!!.size()!!)
prog.instr(Opcode.PUSH_BYTE, length)
when (arg.resultingDatatype(namespace, heap)) {
when (arg.inferType(program)) {
DataType.ARRAY_B, DataType.ARRAY_UB -> createSyscall("${funcname}_b")
DataType.ARRAY_W, DataType.ARRAY_UW -> createSyscall("${funcname}_w")
DataType.ARRAY_F -> createSyscall("${funcname}_f")
@ -830,9 +759,9 @@ internal class Compiler(private val rootModule: Module,
"avg" -> {
// 1 array argument, type determines the exact syscall to use
val arg=args.single() as IdentifierReference
val target=arg.targetVarDecl(namespace)!!
val length=Value(DataType.UBYTE, target.arraysize!!.size()!!)
val arrayDt=arg.resultingDatatype(namespace, heap)
val target=arg.targetVarDecl(program.namespace)!!
val length= RuntimeValue(DataType.UBYTE, target.arraysize!!.size()!!)
val arrayDt=arg.inferType(program)
prog.instr(Opcode.PUSH_BYTE, length)
when (arrayDt) {
DataType.ARRAY_UB -> {
@ -855,16 +784,16 @@ internal class Compiler(private val rootModule: Module,
else -> throw CompilerException("wrong datatype for avg")
}
// divide by the number of elements
prog.instr(opcodePush(DataType.FLOAT), Value(DataType.FLOAT, length.numericValue()))
prog.instr(opcodePush(DataType.FLOAT), RuntimeValue(DataType.FLOAT, length.numericValue()))
prog.instr(Opcode.DIV_F)
}
"min", "max", "sum" -> {
// 1 array argument, type determines the exact syscall to use
val arg=args.single() as IdentifierReference
val target=arg.targetVarDecl(namespace)!!
val length=Value(DataType.UBYTE, target.arraysize!!.size()!!)
val target=arg.targetVarDecl(program.namespace)!!
val length= RuntimeValue(DataType.UBYTE, target.arraysize!!.size()!!)
prog.instr(Opcode.PUSH_BYTE, length)
when (arg.resultingDatatype(namespace, heap)) {
when (arg.inferType(program)) {
DataType.ARRAY_UB -> createSyscall("${funcname}_ub")
DataType.ARRAY_B -> createSyscall("${funcname}_b")
DataType.ARRAY_UW -> createSyscall("${funcname}_uw")
@ -876,7 +805,7 @@ internal class Compiler(private val rootModule: Module,
"abs" -> {
// 1 argument, type determines the exact opcode to use
val arg = args.single()
when (arg.resultingDatatype(namespace, heap)) {
when (arg.inferType(program)) {
DataType.UBYTE, DataType.UWORD -> {}
DataType.BYTE -> prog.instr(Opcode.ABS_B)
DataType.WORD -> prog.instr(Opcode.ABS_W)
@ -888,7 +817,7 @@ internal class Compiler(private val rootModule: Module,
"mkword" -> prog.instr(Opcode.MKWORD)
"lsl" -> {
val arg = args.single()
val dt = arg.resultingDatatype(namespace, heap)
val dt = arg.inferType(program)
when (dt) {
in ByteDatatypes -> prog.instr(Opcode.SHL_BYTE)
in WordDatatypes -> prog.instr(Opcode.SHL_WORD)
@ -899,7 +828,7 @@ internal class Compiler(private val rootModule: Module,
}
"lsr" -> {
val arg = args.single()
val dt = arg.resultingDatatype(namespace, heap)
val dt = arg.inferType(program)
when (dt) {
DataType.UBYTE -> prog.instr(Opcode.SHR_UBYTE)
DataType.BYTE -> prog.instr(Opcode.SHR_SBYTE)
@ -912,18 +841,18 @@ internal class Compiler(private val rootModule: Module,
}
"rol" -> {
val arg = args.single()
val dt = arg.resultingDatatype(namespace, heap)
val dt = arg.inferType(program)
when (dt) {
DataType.UBYTE -> prog.instr(Opcode.ROL_BYTE)
DataType.UWORD -> prog.instr(Opcode.ROL_WORD)
in ByteDatatypes -> prog.instr(Opcode.ROL_BYTE)
in WordDatatypes -> prog.instr(Opcode.ROL_WORD)
else -> throw CompilerException("wrong datatype")
}
// this function doesn't return a value on the stack so we pop it directly into the argument register/variable again
popValueIntoTarget(AssignTarget.fromExpr(arg), dt)
popValueIntoTarget(AssignTarget.fromExpr(arg), dt!!)
}
"ror" -> {
val arg = args.single()
val dt = arg.resultingDatatype(namespace, heap)
val dt = arg.inferType(program)
when (dt) {
in ByteDatatypes -> prog.instr(Opcode.ROR_BYTE)
in WordDatatypes -> prog.instr(Opcode.ROR_WORD)
@ -934,7 +863,7 @@ internal class Compiler(private val rootModule: Module,
}
"rol2" -> {
val arg = args.single()
val dt = arg.resultingDatatype(namespace, heap)
val dt = arg.inferType(program)
when (dt) {
in ByteDatatypes -> prog.instr(Opcode.ROL2_BYTE)
in WordDatatypes -> prog.instr(Opcode.ROL2_WORD)
@ -945,7 +874,7 @@ internal class Compiler(private val rootModule: Module,
}
"ror2" -> {
val arg = args.single()
val dt = arg.resultingDatatype(namespace, heap)
val dt = arg.inferType(program)
when (dt) {
in ByteDatatypes -> prog.instr(Opcode.ROR2_BYTE)
in WordDatatypes -> prog.instr(Opcode.ROR2_WORD)
@ -968,13 +897,13 @@ internal class Compiler(private val rootModule: Module,
// swap(x,y) is treated differently, it's not a normal function call
if (args.size != 2)
throw AstException("swap requires 2 arguments")
val dt1 = args[0].resultingDatatype(namespace, heap)!!
val dt2 = args[1].resultingDatatype(namespace, heap)!!
val dt1 = args[0].inferType(program)!!
val dt2 = args[1].inferType(program)!!
if (dt1 != dt2)
throw AstException("swap requires 2 args of identical type")
if (args[0].constValue(namespace, heap) != null || args[1].constValue(namespace, heap) != null)
if (args[0].constValue(program) != null || args[1].constValue(program) != null)
throw AstException("swap requires 2 variables, not constant value(s)")
if(same(args[0], args[1]))
if(args[0] isSameAs args[1])
throw AstException("swap should have 2 different args")
if(dt1 !in NumericDatatypes)
throw AstException("swap requires args of numerical type")
@ -1002,7 +931,7 @@ internal class Compiler(private val rootModule: Module,
// (subroutine arguments are not passed via the stack!)
for (arg in arguments.zip(subroutine.parameters)) {
translate(arg.first)
convertType(arg.first.resultingDatatype(namespace, heap)!!, arg.second.type) // convert types of arguments to required parameter type
convertType(arg.first.inferType(program)!!, arg.second.type) // convert types of arguments to required parameter type
val opcode = opcodePopvar(arg.second.type)
prog.instr(opcode, callLabel = subroutine.scopedname + "." + arg.second.name)
}
@ -1044,7 +973,7 @@ internal class Compiler(private val rootModule: Module,
for (arg in arguments.zip(subroutine.asmParameterRegisters)) {
if (arg.second.statusflag != null) {
if (arg.second.statusflag == Statusflag.Pc)
carryParam = arg.first.constValue(namespace, heap)!!.asBooleanValue
carryParam = arg.first.constValue(program)!!.asBooleanValue
else
throw CompilerException("no support for status flag parameter: ${arg.second.statusflag}")
} else {
@ -1075,7 +1004,7 @@ internal class Compiler(private val rootModule: Module,
}
val valueA: IExpression
val valueX: IExpression
val paramDt = arg.first.resultingDatatype(namespace, heap)
val paramDt = arg.first.inferType(program)
when (paramDt) {
DataType.UBYTE -> {
valueA = arg.first
@ -1098,7 +1027,7 @@ internal class Compiler(private val rootModule: Module,
AY -> {
val valueA: IExpression
val valueY: IExpression
val paramDt = arg.first.resultingDatatype(namespace, heap)
val paramDt = arg.first.inferType(program)
when (paramDt) {
DataType.UBYTE -> {
valueA = arg.first
@ -1125,7 +1054,7 @@ internal class Compiler(private val rootModule: Module,
}
val valueX: IExpression
val valueY: IExpression
val paramDt = arg.first.resultingDatatype(namespace, heap)
val paramDt = arg.first.inferType(program)
when (paramDt) {
DataType.UBYTE -> {
valueX = arg.first
@ -1382,7 +1311,7 @@ internal class Compiler(private val rootModule: Module,
}
private fun translate(arrayindexed: ArrayIndexedExpression, write: Boolean) {
val variable = arrayindexed.identifier.targetVarDecl(namespace)!!
val variable = arrayindexed.identifier.targetVarDecl(program.namespace)!!
translate(arrayindexed.arrayspec.index)
if (write)
prog.instr(opcodeWriteindexedvar(variable.datatype), callLabel = variable.scopedname)
@ -1398,11 +1327,11 @@ internal class Compiler(private val rootModule: Module,
"FUNC_$funcname"
).toUpperCase()
val callNr = Syscall.valueOf(function).callNr
prog.instr(Opcode.SYSCALL, Value(DataType.UBYTE, callNr))
prog.instr(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, callNr))
}
private fun translate(stmt: Jump, branchOpcode: Opcode?) {
var jumpAddress: Value? = null
var jumpAddress: RuntimeValue? = null
var jumpLabel: String? = null
when {
@ -1410,10 +1339,10 @@ internal class Compiler(private val rootModule: Module,
stmt.address!=null -> {
if(branchOpcode in branchOpcodes)
throw CompilerException("cannot branch to address, should use absolute jump instead")
jumpAddress = Value(DataType.UWORD, stmt.address)
jumpAddress = RuntimeValue(DataType.UWORD, stmt.address)
}
else -> {
val target = stmt.identifier!!.targetStatement(namespace)!!
val target = stmt.identifier!!.targetStatement(program.namespace)!!
jumpLabel = when(target) {
is Label -> target.scopedname
is Subroutine -> target.scopedname
@ -1433,14 +1362,14 @@ internal class Compiler(private val rootModule: Module,
"--" -> prog.instr(Opcode.DEC_VAR_UB, callLabel = stmt.target.register!!.name)
}
stmt.target.identifier != null -> {
val targetStatement = stmt.target.identifier!!.targetVarDecl(namespace)!!
val targetStatement = stmt.target.identifier!!.targetVarDecl(program.namespace)!!
when(stmt.operator) {
"++" -> prog.instr(opcodeIncvar(targetStatement.datatype), callLabel = targetStatement.scopedname)
"--" -> prog.instr(opcodeDecvar(targetStatement.datatype), callLabel = targetStatement.scopedname)
}
}
stmt.target.arrayindexed != null -> {
val variable = stmt.target.arrayindexed!!.identifier.targetVarDecl(namespace)!!
val variable = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
translate(stmt.target.arrayindexed!!.arrayspec.index)
when(stmt.operator) {
"++" -> prog.instr(opcodeIncArrayindexedVar(variable.datatype), callLabel = variable.scopedname)
@ -1448,11 +1377,11 @@ internal class Compiler(private val rootModule: Module,
}
}
stmt.target.memoryAddress != null -> {
val address = stmt.target.memoryAddress?.addressExpression?.constValue(namespace, heap)?.asIntegerValue
val address = stmt.target.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue
if(address!=null) {
when(stmt.operator) {
"++" -> prog.instr(Opcode.INC_MEMORY, Value(DataType.UWORD, address))
"--" -> prog.instr(Opcode.DEC_MEMORY, Value(DataType.UWORD, address))
"++" -> prog.instr(Opcode.INC_MEMORY, RuntimeValue(DataType.UWORD, address))
"--" -> prog.instr(Opcode.DEC_MEMORY, RuntimeValue(DataType.UWORD, address))
}
} else {
translate(stmt.target.memoryAddress!!.addressExpression)
@ -1477,8 +1406,8 @@ internal class Compiler(private val rootModule: Module,
return
}
val valueDt = stmt.value.resultingDatatype(namespace, heap)
val targetDt = assignTarget.determineDatatype(namespace, heap, stmt)
val valueDt = stmt.value.inferType(program)
val targetDt = assignTarget.inferType(program, stmt)
if(valueDt!=targetDt) {
// convert value to target datatype if possible
// @todo use convertType()????
@ -1500,7 +1429,7 @@ internal class Compiler(private val rootModule: Module,
DataType.STR, DataType.STR_S -> pushHeapVarAddress(stmt.value, true)
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_W, DataType.ARRAY_UW, DataType.ARRAY_F -> {
if (stmt.value is IdentifierReference) {
val vardecl = (stmt.value as IdentifierReference).targetVarDecl(namespace)!!
val vardecl = (stmt.value as IdentifierReference).targetVarDecl(program.namespace)!!
prog.removeLastInstruction()
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
}
@ -1529,7 +1458,7 @@ internal class Compiler(private val rootModule: Module,
throw CompilerException("augmented assignment should have been converted to regular assignment already")
// pop the result value back into the assignment target
val datatype = assignTarget.determineDatatype(namespace, heap, stmt)!!
val datatype = assignTarget.inferType(program, stmt)!!
popValueIntoTarget(assignTarget, datatype)
}
@ -1537,7 +1466,7 @@ internal class Compiler(private val rootModule: Module,
when (value) {
is LiteralValue -> throw CompilerException("can only push address of string or array (value on the heap)")
is IdentifierReference -> {
val vardecl = value.targetVarDecl(namespace)!!
val vardecl = value.targetVarDecl(program.namespace)!!
if(removeLastOpcode) prog.removeLastInstruction()
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
}
@ -1549,7 +1478,7 @@ internal class Compiler(private val rootModule: Module,
when (value) {
is LiteralValue -> throw CompilerException("can only push address of float that is a variable on the heap")
is IdentifierReference -> {
val vardecl = value.targetVarDecl(namespace)!!
val vardecl = value.targetVarDecl(program.namespace)!!
prog.instr(Opcode.PUSH_ADDR_HEAPVAR, callLabel = vardecl.scopedname)
}
else -> throw CompilerException("can only take address of a the float as constant literal or variable")
@ -1557,14 +1486,14 @@ internal class Compiler(private val rootModule: Module,
}
private fun translateMultiReturnAssignment(stmt: Assignment) {
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(namespace)
val targetStmt = (stmt.value as? FunctionCall)?.target?.targetStatement(program.namespace)
if(targetStmt is Subroutine && targetStmt.isAsmSubroutine) {
// this is the only case where multiple assignment targets are allowed: a call to an asmsub with multiple return values
// the return values are already on the stack (the subroutine call puts them there)
if(stmt.targets.size!=targetStmt.asmReturnvaluesRegisters.size)
throw CompilerException("asmsub number of return values doesn't match number of assignment targets ${stmt.position}")
for(target in stmt.targets) {
val dt = target.determineDatatype(namespace, heap, stmt)
val dt = target.inferType(program, stmt)
popValueIntoTarget(target, dt!!)
}
} else throw CompilerException("can only use multiple assignment targets on an asmsub call")
@ -1573,7 +1502,7 @@ internal class Compiler(private val rootModule: Module,
private fun popValueIntoTarget(assignTarget: AssignTarget, datatype: DataType) {
when {
assignTarget.identifier != null -> {
val target = assignTarget.identifier.targetStatement(namespace)!!
val target = assignTarget.identifier.targetStatement(program.namespace)!!
if (target is VarDecl) {
when (target.type) {
VarDeclType.VAR -> {
@ -1582,8 +1511,8 @@ internal class Compiler(private val rootModule: Module,
}
VarDeclType.MEMORY -> {
val opcode = opcodePopmem(datatype)
val address = target.value?.constValue(namespace, heap)!!.asIntegerValue!!
prog.instr(opcode, Value(DataType.UWORD, address))
val address = target.value?.constValue(program)!!.asIntegerValue!!
prog.instr(opcode, RuntimeValue(DataType.UWORD, address))
}
VarDeclType.CONST -> throw CompilerException("cannot assign to const")
}
@ -1592,10 +1521,10 @@ internal class Compiler(private val rootModule: Module,
assignTarget.register != null -> prog.instr(Opcode.POP_VAR_BYTE, callLabel = assignTarget.register.name)
assignTarget.arrayindexed != null -> translate(assignTarget.arrayindexed, true) // write value to it
assignTarget.memoryAddress != null -> {
val address = assignTarget.memoryAddress?.addressExpression?.constValue(namespace, heap)?.asIntegerValue
val address = assignTarget.memoryAddress?.addressExpression?.constValue(program)?.asIntegerValue
if(address!=null) {
// const integer address given
prog.instr(Opcode.POP_MEM_BYTE, arg=Value(DataType.UWORD, address))
prog.instr(Opcode.POP_MEM_BYTE, arg= RuntimeValue(DataType.UWORD, address))
} else {
translate(assignTarget.memoryAddress!!)
}
@ -1618,7 +1547,7 @@ internal class Compiler(private val rootModule: Module,
}
private fun translate(loop: ForLoop) {
if(loop.body.isEmpty()) return
if(loop.body.containsNoCodeNorVars()) return
prog.line(loop.position)
val loopVarName: String
val loopVarDt: DataType
@ -1628,13 +1557,13 @@ internal class Compiler(private val rootModule: Module,
loopVarName = reg.name
loopVarDt = DataType.UBYTE
} else {
val loopvar = loop.loopVar!!.targetVarDecl(namespace)!!
val loopvar = loop.loopVar!!.targetVarDecl(program.namespace)!!
loopVarName = loopvar.scopedname
loopVarDt = loopvar.datatype
}
if(loop.iterable is RangeExpr) {
val range = (loop.iterable as RangeExpr).toConstantIntegerRange(heap)
val range = (loop.iterable as RangeExpr).toConstantIntegerRange()
if(range!=null) {
// loop over a range with constant start, last and step values
if (range.isEmpty())
@ -1677,9 +1606,9 @@ internal class Compiler(private val rootModule: Module,
when {
loop.iterable is IdentifierReference -> {
val idRef = loop.iterable as IdentifierReference
val vardecl = idRef.targetVarDecl(namespace)!!
val vardecl = idRef.targetVarDecl(program.namespace)!!
val iterableValue = vardecl.value as LiteralValue
if(!iterableValue.isIterable(namespace, heap))
if(iterableValue.type !in IterableDatatypes)
throw CompilerException("loop over something that isn't iterable ${loop.iterable}")
translateForOverIterableVar(loop, loopVarDt, iterableValue)
}
@ -1701,16 +1630,16 @@ internal class Compiler(private val rootModule: Module,
when(iterableValue.type) {
!in IterableDatatypes -> throw CompilerException("non-iterableValue type")
DataType.STR, DataType.STR_S -> {
numElements = iterableValue.strvalue(heap).length
numElements = iterableValue.strvalue!!.length
if(numElements>255) throw CompilerException("string length > 255")
}
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W -> {
numElements = iterableValue.arrayvalue?.size ?: heap.get(iterableValue.heapId!!).arraysize
numElements = iterableValue.arrayvalue?.size ?: program.heap.get(iterableValue.heapId!!).arraysize
if(numElements>255) throw CompilerException("string length > 255")
}
DataType.ARRAY_F -> {
numElements = iterableValue.arrayvalue?.size ?: heap.get(iterableValue.heapId!!).arraysize
numElements = iterableValue.arrayvalue?.size ?: program.heap.get(iterableValue.heapId!!).arraysize
if(numElements>255) throw CompilerException("string length > 255")
}
else -> throw CompilerException("weird datatype")
@ -1743,7 +1672,7 @@ internal class Compiler(private val rootModule: Module,
breakStmtLabelStack.push(breakLabel)
// set the index var to zero before the loop
prog.instr(opcodePush(indexVarType), Value(indexVarType, 0))
prog.instr(opcodePush(indexVarType), RuntimeValue(indexVarType, 0))
prog.instr(opcodePopvar(indexVarType), callLabel = indexVar.scopedname)
// loop starts here
@ -1764,7 +1693,7 @@ internal class Compiler(private val rootModule: Module,
prog.instr(opcodeIncvar(indexVarType), callLabel = indexVar.scopedname)
prog.instr(opcodePushvar(indexVarType), callLabel = indexVar.scopedname)
prog.instr(opcodeCompare(indexVarType), Value(indexVarType, numElements))
prog.instr(opcodeCompare(indexVarType), RuntimeValue(indexVarType, numElements))
prog.instr(Opcode.BNZ, callLabel = loopLabel)
prog.label(breakLabel)
@ -1801,7 +1730,7 @@ internal class Compiler(private val rootModule: Module,
continueStmtLabelStack.push(continueLabel)
breakStmtLabelStack.push(breakLabel)
prog.instr(opcodePush(varDt), Value(varDt, range.first))
prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.first))
prog.instr(opcodePopvar(varDt), callLabel = varname)
prog.label(loopLabel)
translate(body)
@ -1820,13 +1749,13 @@ internal class Compiler(private val rootModule: Module,
}
range.step>numberOfIncDecsForOptimize -> {
prog.instr(opcodePushvar(varDt), callLabel = varname)
prog.instr(opcodePush(varDt), Value(varDt, range.step))
prog.instr(opcodePush(varDt), RuntimeValue(varDt, range.step))
prog.instr(opcodeAdd(varDt))
prog.instr(opcodePopvar(varDt), callLabel = varname)
}
range.step<numberOfIncDecsForOptimize -> {
prog.instr(opcodePushvar(varDt), callLabel = varname)
prog.instr(opcodePush(varDt), Value(varDt, abs(range.step)))
prog.instr(opcodePush(varDt), RuntimeValue(varDt, abs(range.step)))
prog.instr(opcodeSub(varDt))
prog.instr(opcodePopvar(varDt), callLabel = varname)
}
@ -1844,7 +1773,7 @@ internal class Compiler(private val rootModule: Module,
DataType.BYTE, DataType.WORD -> range.last + range.step
else -> throw CompilerException("invalid loop var dt $varDt")
}
prog.instr(opcodeCompare(varDt), Value(varDt, checkValue))
prog.instr(opcodeCompare(varDt), RuntimeValue(varDt, checkValue))
prog.instr(Opcode.BNZ, callLabel = loopLabel)
}
prog.label(breakLabel)
@ -1941,7 +1870,7 @@ internal class Compiler(private val rootModule: Module,
lvTarget.linkParents(body)
val targetStatement: VarDecl? =
if(lvTarget.identifier!=null) {
lvTarget.identifier.targetVarDecl(namespace)
lvTarget.identifier.targetVarDecl(program.namespace)
} else {
null
}
@ -2014,7 +1943,7 @@ internal class Compiler(private val rootModule: Module,
translate(stmt.body)
prog.label(continueLabel)
translate(stmt.condition)
val conditionJumpOpcode = when(stmt.condition.resultingDatatype(namespace, heap)) {
val conditionJumpOpcode = when(stmt.condition.inferType(program)) {
in ByteDatatypes -> Opcode.JNZ
in WordDatatypes -> Opcode.JNZW
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
@ -2051,7 +1980,7 @@ internal class Compiler(private val rootModule: Module,
translate(stmt.body)
prog.label(continueLabel)
translate(stmt.untilCondition)
val conditionJumpOpcode = when(stmt.untilCondition.resultingDatatype(namespace, heap)) {
val conditionJumpOpcode = when(stmt.untilCondition.inferType(program)) {
in ByteDatatypes -> Opcode.JZ
in WordDatatypes -> Opcode.JZW
else -> throw CompilerException("invalid condition datatype (expected byte or word) $stmt")
@ -2065,7 +1994,7 @@ internal class Compiler(private val rootModule: Module,
private fun translate(expr: TypecastExpression) {
translate(expr.expression)
val sourceDt = expr.expression.resultingDatatype(namespace, heap) ?: throw CompilerException("don't know what type to cast")
val sourceDt = expr.expression.inferType(program) ?: throw CompilerException("don't know what type to cast")
if(sourceDt==expr.type)
return
@ -2116,9 +2045,9 @@ internal class Compiler(private val rootModule: Module,
private fun translate(memread: DirectMemoryRead) {
// for now, only a single memory location (ubyte) is read at a time.
val address = memread.addressExpression.constValue(namespace, heap)?.asIntegerValue
val address = memread.addressExpression.constValue(program)?.asIntegerValue
if(address!=null) {
prog.instr(Opcode.PUSH_MEM_UB, arg = Value(DataType.UWORD, address))
prog.instr(Opcode.PUSH_MEM_UB, arg = RuntimeValue(DataType.UWORD, address))
} else {
translate(memread.addressExpression)
prog.instr(Opcode.PUSH_MEMREAD)
@ -2127,9 +2056,9 @@ internal class Compiler(private val rootModule: Module,
private fun translate(memwrite: DirectMemoryWrite) {
// for now, only a single memory location (ubyte) is written at a time.
val address = memwrite.addressExpression.constValue(namespace, heap)?.asIntegerValue
val address = memwrite.addressExpression.constValue(program)?.asIntegerValue
if(address!=null) {
prog.instr(Opcode.POP_MEM_BYTE, arg = Value(DataType.UWORD, address))
prog.instr(Opcode.POP_MEM_BYTE, arg = RuntimeValue(DataType.UWORD, address))
} else {
translate(memwrite.addressExpression)
prog.instr(Opcode.POP_MEMWRITE)
@ -2137,7 +2066,7 @@ internal class Compiler(private val rootModule: Module,
}
private fun translate(addrof: AddressOf) {
val target = addrof.identifier.targetVarDecl(namespace)!!
val target = addrof.identifier.targetVarDecl(program.namespace)!!
if(target.datatype in ArrayDatatypes || target.datatype in StringDatatypes|| target.datatype==DataType.FLOAT) {
pushHeapVarAddress(addrof.identifier, false)
}
@ -2148,32 +2077,37 @@ internal class Compiler(private val rootModule: Module,
throw CompilerException("cannot take memory pointer $addrof")
}
private fun translateAsmInclude(args: List<DirectiveArg>, importedFrom: Path) {
private fun translateAsmInclude(args: List<DirectiveArg>, source: Path) {
val scopeprefix = if(args[1].str!!.isNotBlank()) "${args[1].str}\t.proc\n" else ""
val scopeprefixEnd = if(args[1].str!!.isNotBlank()) "\t.pend\n" else ""
val filename=args[0].str!!
val sourcecode =
if(filename.startsWith("library:")) {
val resource = tryGetEmbeddedResource(filename.substring(8)) ?: throw IllegalArgumentException("library file '$filename' not found")
resource.bufferedReader().use { it.readText() }
} else {
// first try in the same folder as where the containing file was imported from
val sib = importedFrom.resolveSibling(filename)
if(sib.toFile().isFile)
sib.toFile().readText()
else
File(filename).readText()
}
val sourcecode = loadAsmIncludeFile(filename, source)
prog.instr(Opcode.INLINE_ASSEMBLY, callLabel=null, callLabel2=scopeprefix+sourcecode+scopeprefixEnd)
}
private fun translateAsmBinary(args: List<DirectiveArg>) {
val offset = if(args.size>=2) Value(DataType.UWORD, args[1].int!!) else null
val length = if(args.size==3) Value(DataType.UWORD, args[2].int!!) else null
val offset = if(args.size>=2) RuntimeValue(DataType.UWORD, args[1].int!!) else null
val length = if(args.size==3) RuntimeValue(DataType.UWORD, args[2].int!!) else null
val filename = args[0].str!!
// reading the actual data is not performed by the compiler but is delegated to the assembler
prog.instr(Opcode.INCLUDE_FILE, offset, length, filename)
}
}
fun loadAsmIncludeFile(filename: String, source: Path): String {
return if (filename.startsWith("library:")) {
val resource = tryGetEmbeddedResource(filename.substring(8))
?: throw IllegalArgumentException("library file '$filename' not found")
resource.bufferedReader().use { it.readText() }
} else {
// first try in the isSameAs folder as where the containing file was imported from
val sib = source.resolveSibling(filename)
if (sib.toFile().isFile)
sib.toFile().readText()
else
File(filename).readText()
}
}

View File

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

View File

@ -16,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) {

View File

@ -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)
{

View File

@ -1,6 +1,7 @@
package prog8.compiler.intermediate
import prog8.ast.*
import prog8.compiler.RuntimeValue
import prog8.compiler.CompilerException
import prog8.compiler.HeapValues
import prog8.compiler.Zeropage
@ -9,12 +10,12 @@ import java.io.PrintStream
import java.nio.file.Path
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val importedFrom: Path) {
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
class ProgramBlock(val name: String,
var address: Int?,
val instructions: MutableList<Instruction> = mutableListOf(),
val variables: MutableMap<String, Value> = mutableMapOf(), // names are fully scoped
val variables: MutableMap<String, RuntimeValue> = mutableMapOf(), // names are fully scoped
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
val force_output: Boolean)
@ -28,7 +29,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
val blocks = mutableListOf<ProgramBlock>()
val memory = mutableMapOf<Int, List<Value>>()
val memory = mutableMapOf<Int, List<RuntimeValue>>()
private lateinit var currentBlock: ProgramBlock
val numVariables: Int
@ -88,7 +89,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
blk.instructions.asSequence().withIndex().filter {it.value.opcode!=Opcode.LINE}.windowed(2).toList().forEach {
if (it[1].value.opcode in branchOpcodes) {
if (it[0].value.opcode in pushvalue) {
val value = it[0].value.arg!!.asBooleanValue
val value = it[0].value.arg!!.asBoolean
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
val replacement: Instruction =
if (value) {
@ -256,17 +257,17 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_W_TO_UB, Opcode.CAST_UW_TO_UB -> {
val ins = Instruction(Opcode.PUSH_BYTE, Value(DataType.UBYTE, ins0.arg!!.integerValue() and 255))
val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() and 255))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.MSB -> {
val ins = Instruction(Opcode.PUSH_BYTE, Value(DataType.UBYTE, ins0.arg!!.integerValue() ushr 8 and 255))
val ins = Instruction(Opcode.PUSH_BYTE, RuntimeValue(DataType.UBYTE, ins0.arg!!.integerValue() ushr 8 and 255))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> {
val ins = Instruction(Opcode.PUSH_FLOAT, Value(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
@ -296,12 +297,12 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
Opcode.CAST_UW_TO_B, Opcode.CAST_UW_TO_UB -> instructionsToReplace[index1] = Instruction(Opcode.NOP)
Opcode.MSB -> throw CompilerException("msb of a byte")
Opcode.CAST_UB_TO_UW -> {
val ins = Instruction(Opcode.PUSH_WORD, Value(DataType.UWORD, ins0.arg!!.integerValue()))
val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.UWORD, ins0.arg!!.integerValue()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_B_TO_W -> {
val ins = Instruction(Opcode.PUSH_WORD, Value(DataType.WORD, ins0.arg!!.integerValue()))
val ins = Instruction(Opcode.PUSH_WORD, RuntimeValue(DataType.WORD, ins0.arg!!.integerValue()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
@ -316,7 +317,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F-> {
val ins = Instruction(Opcode.PUSH_FLOAT, Value(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
@ -326,6 +327,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_WORD, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
Opcode.MKWORD -> {}
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode}")
}
}
@ -388,18 +390,18 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
when(decl.type) {
VarDeclType.VAR -> {
val value = when(decl.datatype) {
in NumericDatatypes -> Value(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
in NumericDatatypes -> RuntimeValue(decl.datatype, (decl.value as LiteralValue).asNumericValue!!)
in StringDatatypes -> {
val litval = (decl.value as LiteralValue)
if(litval.heapId==null)
throw CompilerException("string should already be in the heap")
Value(decl.datatype, litval.heapId)
RuntimeValue(decl.datatype, heapId = litval.heapId)
}
in ArrayDatatypes -> {
val litval = (decl.value as LiteralValue)
if(litval.heapId==null)
throw CompilerException("array should already be in the heap")
Value(decl.datatype, litval.heapId)
RuntimeValue(decl.datatype, heapId = litval.heapId)
}
else -> throw CompilerException("weird datatype")
}
@ -424,7 +426,7 @@ class IntermediateProgram(val name: String, var loadAddress: Int, val heap: Heap
}
}
fun instr(opcode: Opcode, arg: Value? = null, arg2: Value? = null, callLabel: String? = null, callLabel2: String? = null) {
fun instr(opcode: Opcode, arg: RuntimeValue? = null, arg2: RuntimeValue? = null, callLabel: String? = null, callLabel2: String? = null) {
currentBlock.instructions.add(Instruction(opcode, arg, arg2, callLabel, callLabel2))
}

View File

@ -1,478 +0,0 @@
package prog8.compiler.intermediate
import prog8.ast.*
import java.lang.Exception
import kotlin.math.abs
import kotlin.math.pow
class ValueException(msg: String?) : Exception(msg)
class Value(val type: DataType, numericvalueOrHeapId: Number) {
private var byteval: Short? = null
private var wordval: Int? = null
private var floatval: Double? = null
var heapId: Int = -1
private set
val asBooleanValue: Boolean
init {
when(type) {
DataType.UBYTE -> {
if(numericvalueOrHeapId.toInt() !in 0..255)
throw ValueException("value out of range: $numericvalueOrHeapId")
byteval = numericvalueOrHeapId.toShort()
asBooleanValue = byteval != (0.toShort())
}
DataType.BYTE -> {
if(numericvalueOrHeapId.toInt() !in -128..127)
throw ValueException("value out of range: $numericvalueOrHeapId")
byteval = numericvalueOrHeapId.toShort()
asBooleanValue = byteval != (0.toShort())
}
DataType.UWORD -> {
if(numericvalueOrHeapId.toInt() !in 0..65535)
throw ValueException("value out of range: $numericvalueOrHeapId")
wordval = numericvalueOrHeapId.toInt()
asBooleanValue = wordval != 0
}
DataType.WORD -> {
if(numericvalueOrHeapId.toInt() !in -32768..32767)
throw ValueException("value out of range: $numericvalueOrHeapId")
wordval = numericvalueOrHeapId.toInt()
asBooleanValue = wordval != 0
}
DataType.FLOAT -> {
floatval = numericvalueOrHeapId.toDouble()
asBooleanValue = floatval != 0.0
}
else -> {
if(numericvalueOrHeapId !is Int || numericvalueOrHeapId<0)
throw ValueException("for non-numeric types, the value should be a integer heapId >= 0")
heapId = numericvalueOrHeapId
asBooleanValue=true
}
}
}
override fun toString(): String {
return when(type) {
DataType.UBYTE -> "ub:%02x".format(byteval)
DataType.BYTE -> {
if(byteval!!<0)
"b:-%02x".format(abs(byteval!!.toInt()))
else
"b:%02x".format(byteval)
}
DataType.UWORD -> "uw:%04x".format(wordval)
DataType.WORD -> {
if(wordval!!<0)
"w:-%04x".format(abs(wordval!!))
else
"w:%04x".format(wordval)
}
DataType.FLOAT -> "f:$floatval"
else -> "heap:$heapId"
}
}
fun numericValue(): Number {
return when(type) {
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")
}
}
}

View File

@ -4,6 +4,7 @@ package prog8.compiler.target.c64
// possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles
import prog8.ast.*
import prog8.compiler.RuntimeValue
import prog8.compiler.*
import prog8.compiler.intermediate.*
import prog8.stackvm.Syscall
@ -16,7 +17,8 @@ import kotlin.math.abs
class AssemblyError(msg: String) : RuntimeException(msg)
class AsmGen(val options: CompilationOptions, val program: IntermediateProgram, val heap: HeapValues, val zeropage: Zeropage) {
class AsmGen(private val options: CompilationOptions, private val program: IntermediateProgram,
private val heap: HeapValues, private val zeropage: Zeropage) {
private val globalFloatConsts = mutableMapOf<Double, String>()
private val assemblyLines = mutableListOf<String>()
private lateinit var block: IntermediateProgram.ProgramBlock
@ -281,7 +283,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
DataType.WORD -> out("${v.first}\t.sint 0")
DataType.FLOAT -> out("${v.first}\t.byte 0,0,0,0,0 ; float")
DataType.STR, DataType.STR_S -> {
val rawStr = heap.get(v.second.heapId).str!!
val rawStr = heap.get(v.second.heapId!!).str!!
val bytes = encodeStr(rawStr, v.second.type).map { "$" + it.toString(16).padStart(2, '0') }
out("${v.first}\t; ${v.second.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
for (chunk in bytes.chunked(16))
@ -333,7 +335,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
DataType.ARRAY_F -> {
// float arraysize
val array = heap.get(v.second.heapId).doubleArray!!
val array = heap.get(v.second.heapId!!).doubleArray!!
val floatFills = array.map { makeFloatFill(Mflpt5.fromNumber(it)) }
out(v.first)
for(f in array.zip(floatFills))
@ -357,8 +359,8 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
}
private fun makeArrayFillDataUnsigned(value: Value): List<String> {
val array = heap.get(value.heapId).array!!
private fun makeArrayFillDataUnsigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId!!).array!!
return when {
value.type==DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
@ -374,8 +376,8 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
}
private fun makeArrayFillDataSigned(value: Value): List<String> {
val array = heap.get(value.heapId).array!!
private fun makeArrayFillDataSigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId!!).array!!
// note: array of signed value can never contain pointer-to type, so simply process values as being all integers
return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W) {
array.map {
@ -417,7 +419,7 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
}
}
private fun getFloatConst(value: Value): String =
private fun getFloatConst(value: RuntimeValue): String =
globalFloatConsts[value.numericValue().toDouble()]
?: throw AssemblyError("should have a global float const for number $value")

View File

@ -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)
}
}

View File

@ -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
)
}

View File

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

View File

@ -2,11 +2,7 @@ package prog8.functions
import prog8.ast.*
import prog8.compiler.CompilerException
import prog8.compiler.HeapValues
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.log2
import kotlin.math.sin
import kotlin.math.*
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
@ -14,7 +10,7 @@ class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType
class FunctionSignature(val pure: Boolean, // does it have side effects?
val parameters: List<BuiltinFunctionParam>,
val returntype: DataType?,
val constExpressionFunc: ((args: List<IExpression>, position: Position, namespace: INameScope, heap: HeapValues) -> LiteralValue)? = null)
val constExpressionFunc: ((args: List<IExpression>, position: Position, program: Program) -> LiteralValue)? = null)
val BuiltinFunctions = mapOf(
@ -26,38 +22,38 @@ val BuiltinFunctions = mapOf(
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
// these few have a return value depending on the argument(s):
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.max()!! }}, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.min()!! }}, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, n, h -> collectionArgOutputNumber(a, p, n, h) { it.sum() }}, // type depends on args
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.max()!! }}, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.min()!! }}, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArgOutputNumber(a, p, prg) { it.sum() }}, // type depends on args
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
// normal functions follow:
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sin) },
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::cos) },
"cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::tan) },
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::atan) },
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::log) },
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, ::log2) },
"sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { Math.sqrt(it.toDouble()).toInt() } },
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArg(a, p, n, h, Math::toDegrees) },
"tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
"sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT, ::builtinAvg),
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, n, h -> oneDoubleArgOutputWord(a, p, n, h, Math::ceil) },
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.any { v -> v != 0.0} }},
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, n, h -> collectionArgOutputBoolean(a, p, n, h) { it.all { v -> v != 0.0} }},
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x and 255 }},
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, n, h -> oneIntArgOutputInt(a, p, n, h) { x: Int -> x ushr 8 and 255}},
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.any { v -> v != 0.0} }},
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArgOutputBoolean(a, p, prg) { it.all { v -> v != 0.0} }},
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FunctionSignature(true, listOf(
BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)),
BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
@ -111,12 +107,12 @@ val BuiltinFunctions = mapOf(
)
fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespace: INameScope, heap: HeapValues): DataType? {
fun builtinFunctionReturnType(function: String, args: List<IExpression>, program: Program): DataType? {
fun datatypeFromIterableArg(arglist: IExpression): DataType {
if(arglist is LiteralValue) {
if(arglist.type==DataType.ARRAY_UB || arglist.type==DataType.ARRAY_UW || arglist.type==DataType.ARRAY_F) {
val dt = arglist.arrayvalue!!.map {it.resultingDatatype(namespace, heap)}
val dt = arglist.arrayvalue!!.map {it.inferType(program)}
if(dt.any { it!=DataType.UBYTE && it!=DataType.UWORD && it!=DataType.FLOAT}) {
throw FatalAstException("fuction $function only accepts arraysize of numeric values")
}
@ -126,15 +122,11 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
}
}
if(arglist is IdentifierReference) {
val dt = arglist.resultingDatatype(namespace, heap)
val dt = arglist.inferType(program)
return when(dt) {
in NumericDatatypes -> dt!!
in StringDatatypes -> dt!!
DataType.ARRAY_UB -> DataType.UBYTE
DataType.ARRAY_B -> DataType.BYTE
DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!)
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
}
}
@ -148,7 +140,7 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
return when (function) {
"abs" -> {
val dt = args.single().resultingDatatype(namespace, heap)
val dt = args.single().inferType(program)
when(dt) {
in ByteDatatypes -> DataType.UBYTE
in WordDatatypes -> DataType.UWORD
@ -161,17 +153,12 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
when(dt) {
in NumericDatatypes -> dt
in StringDatatypes -> DataType.UBYTE
DataType.ARRAY_UB -> DataType.UBYTE
DataType.ARRAY_B -> DataType.BYTE
DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
else -> null
}
}
"sum" -> {
val dt=datatypeFromIterableArg(args.single())
when(dt) {
when(datatypeFromIterableArg(args.single())) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
@ -195,10 +182,10 @@ fun builtinFunctionReturnType(function: String, args: List<IExpression>, namespa
class NotConstArgumentException: AstException("not a const argument to a built-in function")
private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue {
private fun oneDoubleArg(args: List<IExpression>, position: Position, program: Program, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type!=DataType.FLOAT)
throw SyntaxError("built-in function requires one floating point argument", position)
@ -206,19 +193,19 @@ private fun oneDoubleArg(args: List<IExpression>, position: Position, namespace:
return numericLiteral(function(float), args[0].position)
}
private fun oneDoubleArgOutputWord(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Double)->Number): LiteralValue {
private fun oneDoubleArgOutputWord(args: List<IExpression>, position: Position, program: Program, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type!=DataType.FLOAT)
throw SyntaxError("built-in function requires one floating point argument", position)
return LiteralValue(DataType.WORD, wordvalue=function(constval.asNumericValue!!.toDouble()).toInt(), position=args[0].position)
}
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues, function: (arg: Int)->Number): LiteralValue {
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, program: Program, function: (arg: Int)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
if(constval.type!=DataType.UBYTE && constval.type!=DataType.UWORD)
throw SyntaxError("built-in function requires one integer argument", position)
@ -227,14 +214,14 @@ private fun oneIntArgOutputInt(args: List<IExpression>, position: Position, name
}
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position,
namespace:INameScope, heap: HeapValues,
program: Program,
function: (arg: Collection<Double>)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
val result = if(iterable.arrayvalue != null) {
val constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue }
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
if(null in constants)
throw NotConstArgumentException()
function(constants.map { it!!.toDouble() }).toDouble()
@ -242,9 +229,8 @@ private fun collectionArgOutputNumber(args: List<IExpression>, position: Positio
when(iterable.type) {
DataType.UBYTE, DataType.UWORD, DataType.FLOAT -> throw SyntaxError("function expects an iterable type", position)
else -> {
if(iterable.heapId==null)
throw FatalAstException("iterable value should be on the heap")
val array = heap.get(iterable.heapId).array ?: throw SyntaxError("function expects an iterable type", position)
val heapId = iterable.heapId ?: throw FatalAstException("iterable value should be on the heap")
val array = program.heap.get(heapId).array ?: throw SyntaxError("function expects an iterable type", position)
function(array.map {
if(it.integer!=null)
it.integer.toDouble()
@ -258,19 +244,19 @@ private fun collectionArgOutputNumber(args: List<IExpression>, position: Positio
}
private fun collectionArgOutputBoolean(args: List<IExpression>, position: Position,
namespace:INameScope, heap: HeapValues,
program: Program,
function: (arg: Collection<Double>)->Boolean): LiteralValue {
if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position)
val iterable = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
val result = if(iterable.arrayvalue != null) {
val constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue }
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
if(null in constants)
throw NotConstArgumentException()
function(constants.map { it!!.toDouble() })
} else {
val array = heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array argument", position)
val array = program.heap.get(iterable.heapId!!).array ?: throw SyntaxError("function requires array argument", position)
function(array.map {
if(it.integer!=null)
it.integer.toDouble()
@ -281,32 +267,33 @@ private fun collectionArgOutputBoolean(args: List<IExpression>, position: Positi
return LiteralValue.fromBoolean(result, position)
}
private fun builtinAbs(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
// 1 arg, type = float or int, result type= same as argument type
private fun builtinAbs(args: List<IExpression>, position: Position, program: Program): LiteralValue {
// 1 arg, type = float or int, result type= isSameAs as argument type
if(args.size!=1)
throw SyntaxError("abs requires one numeric argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val number = constval.asNumericValue
return when (number) {
is Int, is Byte, is Short -> numericLiteral(Math.abs(number.toInt()), args[0].position)
is Double -> numericLiteral(Math.abs(number.toDouble()), args[0].position)
is Int, is Byte, is Short -> numericLiteral(abs(number.toInt()), args[0].position)
is Double -> numericLiteral(abs(number.toDouble()), args[0].position)
else -> throw SyntaxError("abs requires one numeric argument", position)
}
}
private fun builtinAvg(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinAvg(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if(args.size!=1)
throw SyntaxError("avg requires array argument", position)
val iterable = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val iterable = args[0].constValue(program) ?: throw NotConstArgumentException()
val result = if(iterable.arrayvalue!=null) {
val constants = iterable.arrayvalue.map { it.constValue(namespace, heap)?.asNumericValue }
val constants = iterable.arrayvalue.map { it.constValue(program)?.asNumericValue }
if (null in constants)
throw NotConstArgumentException()
(constants.map { it!!.toDouble() }).average()
}
else {
val integerarray = heap.get(iterable.heapId!!).array
val heapId = iterable.heapId!!
val integerarray = program.heap.get(heapId).array
if(integerarray!=null) {
if (integerarray.all { it.integer != null }) {
integerarray.map { it.integer!! }.average()
@ -314,20 +301,20 @@ private fun builtinAvg(args: List<IExpression>, position: Position, namespace:IN
throw ExpressionError("cannot avg() over array that does not only contain constant numerical values", position)
}
} else {
val doublearray = heap.get(iterable.heapId).doubleArray
val doublearray = program.heap.get(heapId).doubleArray
doublearray?.average() ?: throw SyntaxError("avg requires array argument", position)
}
}
return numericLiteral(result, args[0].position)
}
private fun builtinStrlen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinStrlen(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
val argument = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
if(argument.type !in StringDatatypes)
throw SyntaxError("strlen must have string argument", position)
val string = argument.strvalue(heap)
val string = argument.strvalue!!
val zeroIdx = string.indexOf('\u0000')
return if(zeroIdx>=0)
LiteralValue.optimalInteger(zeroIdx, position=position)
@ -335,38 +322,38 @@ private fun builtinStrlen(args: List<IExpression>, position: Position, namespace
LiteralValue.optimalInteger(string.length, position=position)
}
private fun builtinLen(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinLen(args: List<IExpression>, position: Position, program: Program): LiteralValue {
// note: in some cases the length is > 255 and then we have to return a UWORD type instead of a UBYTE.
if(args.size!=1)
throw SyntaxError("len requires one argument", position)
var argument = args[0].constValue(namespace, heap)
var argument = args[0].constValue(program)
if(argument==null) {
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(namespace)
val directMemVar = ((args[0] as? DirectMemoryRead)?.addressExpression as? IdentifierReference)?.targetVarDecl(program.namespace)
val arraySize = directMemVar?.arraysize?.size()
if(arraySize != null)
return LiteralValue.optimalInteger(arraySize, position)
if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetStatement(namespace)
val target = (args[0] as IdentifierReference).targetStatement(program.namespace)
val argValue = (target as? VarDecl)?.value
argument = argValue?.constValue(namespace, heap)
argument = argValue?.constValue(program)
?: throw NotConstArgumentException()
}
return when(argument.type) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize
val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${argument.position}")
LiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.ARRAY_F -> {
val arraySize = argument.arrayvalue?.size ?: heap.get(argument.heapId!!).arraysize
val arraySize = argument.arrayvalue?.size ?: program.heap.get(argument.heapId!!).arraysize
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${argument.position}")
LiteralValue.optimalInteger(arraySize, args[0].position)
}
in StringDatatypes -> {
val str = argument.strvalue(heap)
val str = argument.strvalue!!
if(str.length>255)
throw CompilerException("string length exceeds byte limit ${argument.position}")
LiteralValue.optimalInteger(str.length, args[0].position)
@ -377,75 +364,75 @@ private fun builtinLen(args: List<IExpression>, position: Position, namespace:IN
}
private fun builtinMkword(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinMkword(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 2)
throw SyntaxError("mkword requires lsb and msb arguments", position)
val constLsb = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constMsb = args[1].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constLsb = args[0].constValue(program) ?: throw NotConstArgumentException()
val constMsb = args[1].constValue(program) ?: throw NotConstArgumentException()
val result = (constMsb.asIntegerValue!! shl 8) or constLsb.asIntegerValue!!
return LiteralValue(DataType.UWORD, wordvalue = result, position = position)
}
private fun builtinSin8(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinSin8(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1)
throw SyntaxError("sin8 requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.BYTE, bytevalue = (127.0* sin(rad)).toShort(), position = position)
}
private fun builtinSin8u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinSin8u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1)
throw SyntaxError("sin8u requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UBYTE, bytevalue = (128.0+127.5*sin(rad)).toShort(), position = position)
}
private fun builtinCos8(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinCos8(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1)
throw SyntaxError("cos8 requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.BYTE, bytevalue = (127.0* cos(rad)).toShort(), position = position)
}
private fun builtinCos8u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinCos8u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1)
throw SyntaxError("cos8u requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UBYTE, bytevalue = (128.0 + 127.5*cos(rad)).toShort(), position = position)
}
private fun builtinSin16(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinSin16(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1)
throw SyntaxError("sin16 requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.WORD, wordvalue = (32767.0* sin(rad)).toInt(), position = position)
}
private fun builtinSin16u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinSin16u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1)
throw SyntaxError("sin16u requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5*sin(rad)).toInt(), position = position)
}
private fun builtinCos16(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinCos16(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1)
throw SyntaxError("cos16 requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.WORD, wordvalue = (32767.0* cos(rad)).toInt(), position = position)
}
private fun builtinCos16u(args: List<IExpression>, position: Position, namespace:INameScope, heap: HeapValues): LiteralValue {
private fun builtinCos16u(args: List<IExpression>, position: Position, program: Program): LiteralValue {
if (args.size != 1)
throw SyntaxError("cos16u requires one argument", position)
val constval = args[0].constValue(namespace, heap) ?: throw NotConstArgumentException()
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
val rad = constval.asNumericValue!!.toDouble() /256.0 * 2.0 * PI
return LiteralValue(DataType.UWORD, wordvalue = (32768.0+32767.5* cos(rad)).toInt(), position = position)
}
@ -453,7 +440,7 @@ private fun builtinCos16u(args: List<IExpression>, position: Position, namespace
private fun numericLiteral(value: Number, position: Position): LiteralValue {
val floatNum=value.toDouble()
val tweakedValue: Number =
if(floatNum==Math.floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
if(floatNum== floor(floatNum) && (floatNum>=-32768 && floatNum<=65535))
floatNum.toInt() // we have an integer disguised as a float.
else
floatNum

View File

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

View File

@ -10,11 +10,11 @@ val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "=
class ConstExprEvaluator {
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)
}

View File

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

View File

@ -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
}

View File

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

View File

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

View File

@ -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)

View File

@ -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")
}

View File

@ -1,6 +1,7 @@
package prog8.stackvm
import prog8.ast.*
import prog8.compiler.RuntimeValue
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.intermediate.*
@ -10,10 +11,10 @@ import java.util.regex.Pattern
class Program (val name: String,
val program: MutableList<Instruction>,
val variables: Map<String, Value>,
val variables: Map<String, RuntimeValue>,
val memoryPointers: Map<String, Pair<Int, DataType>>,
val labels: Map<String, Int>,
val memory: Map<Int, List<Value>>,
val memory: Map<Int, List<RuntimeValue>>,
val heap: HeapValues)
{
init {
@ -26,10 +27,10 @@ class Program (val name: String,
companion object {
fun load(filename: String): Program {
val lines = File(filename).readLines().withIndex().iterator()
val memory = mutableMapOf<Int, List<Value>>()
val memory = mutableMapOf<Int, List<RuntimeValue>>()
val heap = HeapValues()
val program = mutableListOf<Instruction>()
val variables = mutableMapOf<String, Value>()
val variables = mutableMapOf<String, RuntimeValue>()
val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>()
val labels = mutableMapOf<String, Int>()
@ -51,7 +52,7 @@ class Program (val name: String,
private fun loadBlock(lines: Iterator<IndexedValue<String>>,
heap: HeapValues,
program: MutableList<Instruction>,
variables: MutableMap<String, Value>,
variables: MutableMap<String, RuntimeValue>,
memoryPointers: MutableMap<String, Pair<Int, DataType>>,
labels: MutableMap<String, Int>)
{
@ -122,7 +123,7 @@ class Program (val name: String,
val instructions = mutableListOf<Instruction>()
val labels = mutableMapOf<String, Instruction>()
val splitpattern = Pattern.compile("\\s+")
val nextInstructionLabels = Stack<String>() // more than one label can occur on the same line
val nextInstructionLabels = Stack<String>() // more than one label can occur on the isSameAs line
while(true) {
val (lineNr, line) = lines.next()
@ -143,7 +144,7 @@ class Program (val name: String,
Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC,
Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> {
if(args!!.startsWith('$')) {
Instruction(opcode, Value(DataType.UWORD, args.substring(1).toInt(16)))
Instruction(opcode, RuntimeValue(DataType.UWORD, args.substring(1).toInt(16)))
} else {
Instruction(opcode, callLabel = args)
}
@ -158,15 +159,15 @@ class Program (val name: String,
Opcode.SYSCALL -> {
if(args!! in syscallNames) {
val call = Syscall.valueOf(args)
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
} else {
val args2 = args.replace('.', '_')
if(args2 in syscallNames) {
val call = Syscall.valueOf(args2)
Instruction(opcode, Value(DataType.UBYTE, call.callNr))
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
} else {
// the syscall is not yet implemented. emit a stub.
Instruction(Opcode.SYSCALL, Value(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
Instruction(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
}
}
}
@ -190,7 +191,7 @@ class Program (val name: String,
}
}
private fun getArgValue(args: String?, heap: HeapValues): Value? {
private fun getArgValue(args: String?, heap: HeapValues): RuntimeValue? {
if(args==null)
return null
if(args[0]=='"' && args[args.length-1]=='"') {
@ -198,21 +199,21 @@ class Program (val name: String,
}
val (type, valueStr) = args.split(':')
return when(type) {
"b" -> Value(DataType.BYTE, valueStr.toShort(16))
"ub" -> Value(DataType.UBYTE, valueStr.toShort(16))
"w" -> Value(DataType.WORD, valueStr.toInt(16))
"uw" -> Value(DataType.UWORD, valueStr.toInt(16))
"f" -> Value(DataType.FLOAT, valueStr.toDouble())
"b" -> RuntimeValue(DataType.BYTE, valueStr.toShort(16))
"ub" -> RuntimeValue(DataType.UBYTE, valueStr.toShort(16))
"w" -> RuntimeValue(DataType.WORD, valueStr.toInt(16))
"uw" -> RuntimeValue(DataType.UWORD, valueStr.toInt(16))
"f" -> RuntimeValue(DataType.FLOAT, valueStr.toDouble())
"heap" -> {
val heapId = valueStr.toInt()
Value(heap.get(heapId).type, heapId)
RuntimeValue(heap.get(heapId).type, heapId = heapId)
}
else -> throw VmExecutionException("invalid datatype $type")
}
}
private fun loadVars(lines: Iterator<IndexedValue<String>>,
vars: MutableMap<String, Value>) {
vars: MutableMap<String, RuntimeValue>) {
val splitpattern = Pattern.compile("\\s+")
while(true) {
val (_, line) = lines.next()
@ -221,13 +222,12 @@ class Program (val name: String,
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
if(valueStr[0] !='"' && ':' !in valueStr)
throw VmExecutionException("missing value type character")
val type = DataType.valueOf(typeStr.toUpperCase())
val value = when(type) {
DataType.UBYTE -> Value(DataType.UBYTE, valueStr.substring(3).toShort(16))
DataType.BYTE -> Value(DataType.BYTE, valueStr.substring(2).toShort(16))
DataType.UWORD -> Value(DataType.UWORD, valueStr.substring(3).toInt(16))
DataType.WORD -> Value(DataType.WORD, valueStr.substring(2).toInt(16))
DataType.FLOAT -> Value(DataType.FLOAT, valueStr.substring(2).toDouble())
val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).toShort(16))
DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).toShort(16))
DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).toInt(16))
DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).toInt(16))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).toDouble())
in StringDatatypes -> {
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap")
@ -235,7 +235,7 @@ class Program (val name: String,
throw VmExecutionException("invalid string value, should be a heap reference")
else {
val heapId = valueStr.substring(5).toInt()
Value(type, heapId)
RuntimeValue(type, heapId = heapId)
}
}
in ArrayDatatypes -> {
@ -243,7 +243,7 @@ class Program (val name: String,
throw VmExecutionException("invalid array value, should be a heap reference")
else {
val heapId = valueStr.substring(5).toInt()
Value(type, heapId)
RuntimeValue(type, heapId = heapId)
}
}
else -> throw VmExecutionException("weird datatype")
@ -269,7 +269,7 @@ class Program (val name: String,
}
}
private fun loadMemory(lines: Iterator<IndexedValue<String>>, memory: MutableMap<Int, List<Value>>): Map<Int, List<Value>> {
private fun loadMemory(lines: Iterator<IndexedValue<String>>, memory: MutableMap<Int, List<RuntimeValue>>): Map<Int, List<RuntimeValue>> {
while(true) {
val (lineNr, line) = lines.next()
if(line=="%end_memory")
@ -280,11 +280,11 @@ class Program (val name: String,
TODO("memory init with char/string")
} else {
val valueStrings = rest.split(' ')
val values = mutableListOf<Value>()
val values = mutableListOf<RuntimeValue>()
valueStrings.forEach {
when(it.length) {
2 -> values.add(Value(DataType.UBYTE, it.toShort(16)))
4 -> values.add(Value(DataType.UWORD, it.toInt(16)))
2 -> values.add(RuntimeValue(DataType.UBYTE, it.toShort(16)))
4 -> values.add(RuntimeValue(DataType.UWORD, it.toInt(16)))
else -> throw VmExecutionException("invalid value at line $lineNr+1")
}
}

View File

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

View File

@ -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 {

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,8 @@ import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.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)

View File

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

View File

@ -523,7 +523,8 @@ And this is a loop over the values of the array ``fibonacci_numbers`` where the
}
You can inline the loop variable declaration in the for statement, including optional zp-tag::
You can inline the loop variable declaration in the for statement, including optional zp-tag. In this case
the variable is not visible outside of the for loop::
for ubyte @zp fastindex in 10 to 20 {
; do something

View File

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

View File

@ -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
}

View File

@ -1,28 +1,57 @@
%import c64utils
%zeropage basicsafe
%option enable_floats
%import c64flt
~ main {
float[] fa = [1.1,2.2,3.3]
ubyte[] uba = [10,2,3,4]
byte[] ba = [-10,2,3,4]
uword[] uwa = [100,20,30,40]
word[] wa = [-100,20,30,40]
sub start() {
float a
a=avg([1,2,3,4])
c64flt.print_f(a)
c64.CHROUT('\n')
a=avg([100,200,300,400])
c64flt.print_f(a)
c64.CHROUT('\n')
a=avg([1.1,2.2,3.3,4.4])
c64flt.print_f(a)
c64.CHROUT('\n')
ubyte x = 99
return
startqqq:
sub startzzz() {
if_cc goto startqqq
c64.EXTCOL++
}
}
; for ubyte y in 0 to 3 {
; for ubyte x in 0 to 10 {
; ubyte product = x*y
; c64scr.setcc(x, y, 160, product)
; }
; }
; c64.CHROUT('\n')
; c64.CHROUT('\n')
;
; for ubyte y in 12 to 15 {
; for ubyte x in 0 to 10 {
; ubyte sumv = x+y
; c64scr.setcc(x, y, 160, sumv)
; }
; }
;ubyte bb = len(xcoor)
; storage for rotated coordinates
; ubyte[len(xcoor)] xx = 2
; float[len(xcoor)] rotatedx=0.0
;
; ubyte[4] x = 23
; float[4] yy = 4.4
; c64flt.print_f(xcoor[1])
; c64.CHROUT(',')
; c64flt.print_f(xcoor[2])
; c64.CHROUT('\n')
; swap(xcoor[1], xcoor[2])
; c64flt.print_f(xcoor[1])
; c64.CHROUT(',')
; c64flt.print_f(xcoor[2])
; c64.CHROUT('\n')
}

View File

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

View File

@ -5,3 +5,5 @@ KOTLINPATH=${HOME}/.IntelliJIdea2019.1/config/plugins/Kotlin
LIBJARS=${KOTLINPATH}/lib/kotlin-stdlib.jar:${KOTLINPATH}/lib/kotlin-reflect.jar:./parser/antlr/lib/antlr-runtime-4.7.2.jar
java -cp ${PROG8CLASSPATH}:${LIBJARS} prog8.CompilerMainKt $*
# if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar

View File

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

View File

@ -5,3 +5,5 @@ KOTLINPATH=${HOME}/.IntelliJIdea2019.1/config/plugins/Kotlin
LIBJARS=${KOTLINPATH}/lib/kotlin-stdlib.jar:${KOTLINPATH}/lib/kotlin-reflect.jar
java -cp ${PROG8CLASSPATH}:${LIBJARS} prog8.StackVmMainKt $*
# if you have created a .jar file using the 'create_compiler_jar' script, you can simply do: java -jar prog8compiler.jar -vm

13
parser/parser.iml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="antlr-4.7.2-complete" level="project" />
</component>
</module>