Compare commits

...

99 Commits
v1.50 ... v1.70

Author SHA1 Message Date
7facb4f372 correct version 1.70 2020-02-09 01:41:05 +01:00
ee90fed489 readme 2020-02-09 01:33:20 +01:00
4796c56c35 antlr code back 2020-02-09 01:29:58 +01:00
e2cb031386 added 'void' keyword to explicitly ignore subroutine return values (and no longer get a warning) 2020-02-09 01:29:09 +01:00
a0bc97b90c fix byte array iteration for bb in [1,2,3]
improved array literal datatype detection
2020-02-09 00:45:53 +01:00
fd240899bd fix CHROUT in simulator 2020-02-09 00:12:50 +01:00
885b22df40 fixed while and repeat warning messages line number
fixed invalid while and repeat asm label names
fixed boolean checking of numbers
2020-02-08 19:45:30 +01:00
11de3db25f simplified heapId for arrayvalues 2020-02-08 18:49:48 +01:00
14a13da7ec simplified heapId for stringvalue 2020-02-08 15:54:03 +01:00
875a71c786 removed datatype from StringValue classes (is always STR now) 2020-02-08 02:21:18 +01:00
0ff5b79353 code inspection cleanups 2020-02-08 01:31:41 +01:00
8c4d276810 improvements to string encoding/decoding and text output in the simulator 2020-02-08 01:12:30 +01:00
3dd38c0ac8 antlr library updated to 4.8 2020-02-07 23:58:07 +01:00
b8816a0e2f got rid of separate str_s datatype 2020-02-07 20:47:38 +01:00
a01a9e76f9 removed bogus clang target
fixed various simulator bugs regarding strings and chars
2020-02-07 01:22:07 +01:00
357d704aec clean up version specifier 2020-02-02 19:33:40 +01:00
868df1865c got rid of obsolete code 2020-02-02 19:18:40 +01:00
654d74da1e automatic selection of best Vice C64 emulator executable 2020-02-02 13:39:56 +01:00
59939c727a gradle updated 2020-02-02 13:39:25 +01:00
fbcf190324 sync gradle version with my manjaro packaged gradle 2020-01-27 21:32:42 +01:00
b9922a90cc update gradle wrapper to 6.1.1 2020-01-26 18:36:51 +01:00
66e0b07428 gradle updates 2020-01-07 01:29:25 +01:00
01e617ae8f new kotlin version 2019-12-09 16:17:20 +01:00
52769decd4 fix assembler float truncation warning 2019-11-27 22:36:59 +01:00
165eec4054 started a c++ language compiler code target
(meant to be an intermediate step before direct Wasm/binaryen, via clang compilation to wasm)
2019-10-30 00:15:03 +01:00
8c2e602cc7 preparing for multiple compiler backends/targets 2019-10-26 23:41:15 +02:00
b68f141568 some more old code cleanups 2019-10-21 00:12:26 +02:00
b5d1e8653d tiny cleanups 2019-10-20 23:52:26 +02:00
f6d4c90dea improved number-to-decimal routines 2019-09-23 20:44:41 +02:00
b5b24636ae removed sim65 because it was moved to a separate repository 2019-09-11 02:24:44 +02:00
9dedbbf47c use more modern java date/time api 2019-09-10 01:29:33 +02:00
c493c3e5c6 implemented IRQ handling 2019-09-09 23:28:41 +02:00
61d4ca1d24 added functional test files to git 2019-09-09 19:57:51 +02:00
2cf9af4a6e implemented sim timer and clock 2019-09-09 04:51:18 +02:00
bdcd10512f 6502 simulator passes all tests for regular opcodes 2019-09-09 00:27:06 +02:00
fec8db6a75 fixed sbc and adc 2019-09-08 22:35:08 +02:00
b400010426 separated the 6502 test suite into separate unit tests 2019-09-08 19:11:06 +02:00
28109a39ac clean up of c64 tests 2019-09-08 17:19:40 +02:00
651f0ec445 fixed IZY addressing mode address calc
added test harness for Wolfgang Lorenz's 6502 test suite
2019-09-08 16:40:46 +02:00
e61d3df380 added missing testfiles 2019-09-06 01:09:23 +02:00
15710207b2 fixed bcd (but the bcd test code still fails, strange) 2019-09-06 00:38:48 +02:00
adfddddac6 attempt to fix bcd 2019-09-05 21:38:40 +02:00
e46982f652 fixes 2019-09-05 01:41:48 +02:00
900c2aea23 fixed all instructions except BCD arithmetic 2019-09-05 01:26:01 +02:00
42f8e98cab cpu unit test suite ported from Py65 2019-09-04 22:23:31 +02:00
bed0e33b4f unit test 2019-09-04 02:41:09 +02:00
8d6542905d beginnings of 6502 cpu simulator 2019-09-03 23:58:46 +02:00
39798a1a4f todos 2019-08-29 22:31:29 +02:00
befe4b8e9f try to fix windows path issue with drive letter 2019-08-27 01:02:31 +02:00
772e48105e fixed some type cast compiler errors in for loops 2019-08-26 23:38:59 +02:00
9afe451b8d fix build script to target jdk 1.8 2019-08-26 21:27:45 +02:00
89d469e77e examples 2019-08-25 00:46:46 +02:00
59a43889a5 examples 2019-08-25 00:24:00 +02:00
7caa0daffc examples 2019-08-24 21:40:50 +02:00
5e854c2cf8 more forloop asm 2019-08-24 21:26:29 +02:00
9edc92ec29 more bitshift asm stubs (actual functions still to be done) 2019-08-23 23:06:36 +02:00
1d178080a3 more bitshift asm 2019-08-23 21:33:43 +02:00
aa94300bdd added output directory command line option
improved cli parser by using kotlinx.cli
2019-08-23 00:11:08 +02:00
2d768c3f28 code cleanups 2019-08-22 22:06:21 +02:00
b79af624ae added more asmgen for bitshift operations 2019-08-22 00:34:17 +02:00
38208a7c9e removed fake vm functions 2019-08-21 22:00:05 +02:00
8eff51904e taking down the heapvalue mess further 2019-08-21 00:29:31 +02:00
c717f4573d taking down the heapvalue mess further 2019-08-20 23:02:13 +02:00
984d251a6d taking down the heapvalue mess, RuntimeValue class separation 2019-08-20 00:01:31 +02:00
8c3b43f3ed taking down the heapvalue mess 2019-08-19 22:28:41 +02:00
0f1485f30b added sorted, sgn, reverse to the AstVm 2019-08-18 16:39:08 +02:00
eb94c678bd doc 2019-08-18 14:18:46 +02:00
50d792a121 fix doc about for loops 2019-08-18 14:14:14 +02:00
f0d4654917 v1.60 2019-08-18 14:06:30 +02:00
4ce93b5d9d restored proper compiler error when trying to modify a constant 2019-08-18 14:05:20 +02:00
fb0d7a1908 some array literals weren't put on the heap 2019-08-18 13:46:13 +02:00
bb7b063757 revert inline var declaration in for loops 2019-08-18 03:16:23 +02:00
c495f54bbb don't fall-through into nested subroutine 2019-08-18 02:33:42 +02:00
1cc1f2d91d reverse() added (byte+word) 2019-08-18 02:05:51 +02:00
d837cc11f9 sort() added (bytes+words) 2019-08-18 00:04:03 +02:00
cbb7083307 fix problem with typechecking of const arrays 2019-08-17 21:43:48 +02:00
d4a17dfad1 fixed builtin functions no longer const-folding over arrays 2019-08-17 20:16:39 +02:00
59f8b91e25 tweak 2019-08-17 18:44:44 +02:00
80113f9208 version 1.52 2019-08-17 16:44:46 +02:00
27f987f0ae fixed bit shifts, added sgn() function 2019-08-17 16:44:28 +02:00
3ae2597261 irq driven music player example 2019-08-17 13:13:15 +02:00
248e7b808c split codegen 2019-08-16 22:49:29 +02:00
a983a896f2 some asm and some for loop asm fixed, renamed asmgen2 back to just asmgen 2019-08-16 21:37:27 +02:00
68df1730f5 cleaned up some stuff, improved checking of asmsub statement body 2019-08-14 23:17:50 +02:00
d62ab93b24 word >> 8 optimized to msb(word) 2019-08-14 22:28:44 +02:00
47297f7e31 improved handling of inferredType 2019-08-14 02:25:27 +02:00
b64d611e02 split array and string literal classes 2019-08-13 03:00:17 +02:00
9fb9bcfebd correction 2019-08-12 23:25:19 +02:00
dff9c5f53e tweak travis 2019-08-11 22:58:45 +02:00
d4a77321d2 tweak gradle to work with openjdk-11 2019-08-11 22:56:54 +02:00
2665618fa6 zp test added, some cleanups 2019-08-11 22:23:18 +02:00
b5c5560af8 info 2019-08-11 18:21:15 +02:00
065587525e version 2019-08-11 17:43:14 +02:00
58e5d5c071 hash 2019-08-11 17:32:28 +02:00
b44e76db57 fix any/all assembly routine, added asm for min/max/sum/ etc aggregates
removed avg function because of hidden internal overflow issues
2019-08-11 16:13:09 +02:00
2ce6bc5946 fix strlen 2019-08-11 14:02:53 +02:00
fe5b225732 asmsub stack arg 2019-08-11 12:29:18 +02:00
d499e40a4b doc tweaks 2019-08-11 10:56:36 +02:00
62a66d89c6 was not needed 2019-08-11 10:15:34 +02:00
131 changed files with 9435 additions and 17465 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.idea/workspace.xml .idea/workspace.xml
.idea/discord.xml
/build/ /build/
/dist/ /dist/
/output/ /output/

View File

@ -1,6 +1,16 @@
<component name="InspectionProjectProfileManager"> <component name="InspectionProjectProfileManager">
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="100" isEnabled="false" name="JavaScript" />
<language isEnabled="false" name="Groovy" />
<language isEnabled="false" name="Style Sheets" />
<language minSize="70" name="Kotlin" />
<language isEnabled="false" name="TypeScript" />
<language isEnabled="false" name="ActionScript" />
</Languages>
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true"> <inspection_tool class="SpellCheckingInspection" enabled="true" level="TYPO" enabled_by_default="true">
<option name="processCode" value="false" /> <option name="processCode" value="false" />
<option name="processLiterals" value="true" /> <option name="processLiterals" value="true" />

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

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

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

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

View File

@ -0,0 +1,9 @@
<component name="libraryTable">
<library name="kotlinx-cli-jvm-0.1.0-dev-5">
<CLASSES>
<root url="jar://$PROJECT_DIR$/compiler/lib/kotlinx-cli-jvm-0.1.0-dev-5.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

29
.idea/markdown-navigator-enh.xml generated Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownEnhProjectSettings">
<AnnotatorSettings targetHasSpaces="true" linkCaseMismatch="true" wikiCaseMismatch="true" wikiLinkHasDashes="true" notUnderWikiHome="true" targetNotWikiPageExt="true" notUnderSourceWikiHome="true" targetNameHasAnchor="true" targetPathHasAnchor="true" wikiLinkHasSlash="true" wikiLinkHasSubdir="true" wikiLinkHasOnlyAnchor="true" linkTargetsWikiHasExt="true" linkTargetsWikiHasBadExt="true" notUnderSameRepo="true" targetNotUnderVcs="false" linkNeedsExt="true" linkHasBadExt="true" linkTargetNeedsExt="true" linkTargetHasBadExt="true" wikiLinkNotInWiki="true" imageTargetNotInRaw="true" repoRelativeAcrossVcsRoots="true" multipleWikiTargetsMatch="true" unresolvedLinkReference="true" linkIsIgnored="true" anchorIsIgnored="true" anchorIsUnresolved="true" anchorLineReferenceIsUnresolved="true" anchorLineReferenceFormat="true" anchorHasDuplicates="true" abbreviationDuplicates="true" abbreviationNotUsed="true" attributeIdDuplicateDefinition="true" attributeIdNotUsed="true" footnoteDuplicateDefinition="true" footnoteUnresolved="true" footnoteDuplicates="true" footnoteNotUsed="true" macroDuplicateDefinition="true" macroUnresolved="true" macroDuplicates="true" macroNotUsed="true" referenceDuplicateDefinition="true" referenceUnresolved="true" referenceDuplicates="true" referenceNotUsed="true" referenceUnresolvedNumericId="true" enumRefDuplicateDefinition="true" enumRefUnresolved="true" enumRefDuplicates="true" enumRefNotUsed="true" enumRefLinkUnresolved="true" enumRefLinkDuplicates="true" simTocUpdateNeeded="true" simTocTitleSpaceNeeded="true" />
<HtmlExportSettings updateOnSave="false" parentDir="" targetDir="" cssDir="css" scriptDir="js" plainHtml="false" imageDir="" copyLinkedImages="false" imagePathType="0" targetPathType="2" targetExt="" useTargetExt="false" noCssNoScripts="false" useElementStyleAttribute="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" linkFormatType="HTTP_ABSOLUTE" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
<component name="MarkdownNavigatorHistory">
<PasteImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30">
<highlightList />
<directories />
<filenames />
</PasteImageHistory>
<CopyImageHistory checkeredTransparentBackground="false" filename="image" directory="" onPasteImageTargetRef="3" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteReferenceElement="2" cornerRadius="20" borderColor="0" transparentColor="16777215" borderWidth="1" trimTop="0" trimBottom="0" trimLeft="0" trimRight="0" transparent="false" roundCorners="false" showPreview="true" bordered="false" scaled="false" cropped="false" hideInapplicableOperations="false" preserveLinkFormat="false" scale="50" scalingInterpolation="1" transparentTolerance="0" saveAsDefaultOnOK="false" linkFormat="0" addHighlights="false" showHighlightCoordinates="true" showHighlights="false" mouseSelectionAddsHighlight="false" outerFilled="false" outerFillColor="0" outerFillTransparent="true" outerFillAlpha="30">
<highlightList />
<directories />
<filenames />
</CopyImageHistory>
<PasteLinkHistory onPasteImageTargetRef="3" onPasteTargetRef="1" onPasteLinkText="0" onPasteImageElement="1" onPasteLinkElement="1" onPasteWikiElement="2" onPasteReferenceElement="2" hideInapplicableOperations="false" preserveLinkFormat="false" useHeadingForLinkText="false" linkFormat="0" saveAsDefaultOnOK="false" />
<TableToJsonHistory>
<entries />
</TableToJsonHistory>
<TableSortHistory>
<entries />
</TableSortHistory>
</component>
</project>

57
.idea/markdown-navigator.xml generated Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="FlexmarkProjectSettings">
<FlexmarkHtmlSettings flexmarkSpecExampleRendering="0" flexmarkSpecExampleRenderHtml="false">
<flexmarkSectionLanguages>
<option name="1" value="Markdown" />
<option name="2" value="HTML" />
<option name="3" value="flexmark-ast:1" />
</flexmarkSectionLanguages>
</FlexmarkHtmlSettings>
</component>
<component name="MarkdownProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true" lastLayoutSetsDefault="false">
<PanelProvider>
<provider providerId="com.vladsch.md.nav.editor.javafx.html.panel" providerName="JavaFX WebView" />
</PanelProvider>
</PreviewSettings>
<ParserSettings gitHubSyntaxChange="false" correctedInvalidSettings="false" emojiShortcuts="1" emojiImages="0">
<PegdownExtensions>
<option name="ANCHORLINKS" value="true" />
<option name="ATXHEADERSPACE" value="true" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="INTELLIJ_DUMMY_IDENTIFIER" value="true" />
<option name="RELAXEDHRULES" value="true" />
<option name="STRIKETHROUGH" value="true" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
</PegdownExtensions>
<ParserOptions>
<option name="COMMONMARK_LISTS" value="true" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="PRODUCTION_SPEC_PARSER" value="true" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" addPageHeader="false" imageUriSerials="false" addDocTypeHtml="true" noParaTags="false" plantUmlConversion="0">
<GeneratorProvider>
<provider providerId="com.vladsch.md.nav.editor.javafx.html.generator" providerName="JavaFx HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssUriSerial="true" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.md.nav.editor.javafx.html.css" providerName="Default JavaFx Stylesheet" />
</StylesheetProvider>
<ScriptProviders>
<provider providerId="com.vladsch.md.nav.editor.hljs.html.script" providerName="HighlightJS Script" />
</ScriptProviders>
<cssText />
<cssUriHistory />
</CssSettings>
</component>
</project>

16
.idea/misc.xml generated
View File

@ -1,5 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ANTLRGenerationPreferences">
<option name="perGrammarGenerationSettings">
<list>
<PerGrammarGenerationSettings>
<option name="fileName" value="$PROJECT_DIR$/parser/antlr/prog8.g4" />
<option name="autoGen" value="true" />
<option name="outputDir" value="$PROJECT_DIR$/parser/src/prog8/parser" />
<option name="libDir" value="" />
<option name="encoding" value="" />
<option name="pkg" value="" />
<option name="language" value="" />
<option name="generateListener" value="false" />
</PerGrammarGenerationSettings>
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="false" project-jdk-name="Kotlin SDK" project-jdk-type="KotlinSDK"> <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" /> <output url="file://$PROJECT_DIR$/out" />
</component> </component>

1
.idea/modules.xml generated
View File

@ -2,7 +2,6 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <modules>
<module fileurl="file://$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.iml" filepath="$PROJECT_DIR$/DeprecatedStackVm/DeprecatedStackVm.iml" />
<module fileurl="file://$PROJECT_DIR$/compiler/compiler.iml" filepath="$PROJECT_DIR$/compiler/compiler.iml" /> <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$/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$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />

2
.idea/vcs.xml generated
View File

@ -3,4 +3,4 @@
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

View File

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

View File

@ -1,10 +0,0 @@
<?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$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
</component>
</module>

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,548 +0,0 @@
package compiler.intermediate
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
import prog8.compiler.CompilerException
import prog8.compiler.HeapValues
import prog8.compiler.Zeropage
import prog8.compiler.ZeropageDepletedError
import prog8.vm.RuntimeValue
import java.io.PrintStream
import java.nio.file.Path
class IntermediateProgram(val name: String, var loadAddress: Int, val heap: HeapValues, val source: Path) {
class VariableParameters (val zp: ZeropageWish, val memberOfStruct: StructDecl?)
class Variable(val scopedname: String, val value: RuntimeValue, val params: VariableParameters)
class ProgramBlock(val name: String,
var address: Int?,
val instructions: MutableList<Instruction> = mutableListOf(),
val variables: MutableList<Variable> = mutableListOf(),
val memoryPointers: MutableMap<String, Pair<Int, DataType>> = mutableMapOf(),
val labels: MutableMap<String, Instruction> = mutableMapOf(), // names are fully scoped
val force_output: Boolean)
val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
val blocks = mutableListOf<ProgramBlock>()
val memory = mutableMapOf<Int, List<RuntimeValue>>()
private lateinit var currentBlock: ProgramBlock
fun allocateZeropage(zeropage: Zeropage) { // TODO not used anymore???
// allocates all @zp marked variables on the zeropage (for all blocks, as long as there is space in the ZP)
var notAllocated = 0
for(block in blocks) {
val zpVariables = block.variables.filter { it.params.zp==ZeropageWish.REQUIRE_ZEROPAGE || it.params.zp==ZeropageWish.PREFER_ZEROPAGE }
if (zpVariables.isNotEmpty()) {
for (variable in zpVariables) {
if(variable.params.zp==ZeropageWish.NOT_IN_ZEROPAGE || variable.params.memberOfStruct!=null)
throw CompilerException("zp conflict")
try {
val address = zeropage.allocate(variable.scopedname, variable.value.type, null)
allocatedZeropageVariables[variable.scopedname] = Pair(address, variable.value.type)
} catch (x: ZeropageDepletedError) {
printWarning(x.toString() + " variable ${variable.scopedname} type ${variable.value.type}")
notAllocated++
}
}
}
}
if(notAllocated>0)
printWarning("$notAllocated variables marked for Zeropage could not be allocated there")
}
fun optimize() {
println("Optimizing stackVM code...")
// remove nops (that are not a label)
for (blk in blocks) {
blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr }
}
optimizeDataConversionAndUselessDiscards()
optimizeVariableCopying()
optimizeMultipleSequentialLineInstrs()
optimizeCallReturnIntoJump()
optimizeConditionalBranches()
// todo: add more optimizations to intermediate code!
optimizeRemoveNops() // must be done as the last step
optimizeMultipleSequentialLineInstrs() // once more
optimizeRemoveNops() // once more
}
private fun optimizeConditionalBranches() {
// conditional branches that consume the value on the stack
// sometimes these are just constant values, so we can statically determine the branch
// or, they are preceded by a NOT instruction so we can simply remove that and flip the branch condition
val pushvalue = setOf(Opcode.PUSH_BYTE, Opcode.PUSH_WORD)
val notvalue = setOf(Opcode.NOT_BYTE, Opcode.NOT_WORD)
val branchOpcodes = setOf(Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW)
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
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!!.asBoolean
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
val replacement: Instruction =
if (value) {
when (it[1].value.opcode) {
Opcode.JNZ -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
Opcode.JNZW -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
else -> Instruction(Opcode.NOP)
}
} else {
when (it[1].value.opcode) {
Opcode.JZ -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
Opcode.JZW -> Instruction(Opcode.JUMP, callLabel = it[1].value.callLabel)
else -> Instruction(Opcode.NOP)
}
}
instructionsToReplace[it[1].index] = replacement
}
else if (it[0].value.opcode in notvalue) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
val replacement: Instruction =
when (it[1].value.opcode) {
Opcode.JZ -> Instruction(Opcode.JNZ, callLabel = it[1].value.callLabel)
Opcode.JZW -> Instruction(Opcode.JNZW, callLabel = it[1].value.callLabel)
Opcode.JNZ -> Instruction(Opcode.JZ, callLabel = it[1].value.callLabel)
Opcode.JNZW -> Instruction(Opcode.JZW, callLabel = it[1].value.callLabel)
else -> Instruction(Opcode.NOP)
}
instructionsToReplace[it[1].index] = replacement
}
}
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
private fun optimizeRemoveNops() {
// remove nops (that are not a label)
for (blk in blocks)
blk.instructions.removeIf { it.opcode== Opcode.NOP && it !is LabelInstr }
}
private fun optimizeCallReturnIntoJump() {
// replaces call X followed by return, by jump X
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().filter {it.value.opcode!= Opcode.LINE }.windowed(2).toList().forEach {
if(it[0].value.opcode== Opcode.CALL && it[1].value.opcode== Opcode.RETURN) {
instructionsToReplace[it[1].index] = Instruction(Opcode.JUMP, callLabel = it[0].value.callLabel)
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
}
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
private fun optimizeMultipleSequentialLineInstrs() {
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
if (it[0].value.opcode == Opcode.LINE && it[1].value.opcode == Opcode.LINE)
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
private fun optimizeVariableCopying() {
for(blk in blocks) {
val instructionsToReplace = mutableMapOf<Int, Instruction>()
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
when (it[0].value.opcode) {
Opcode.PUSH_VAR_BYTE ->
if (it[1].value.opcode == Opcode.POP_VAR_BYTE) {
if (it[0].value.callLabel == it[1].value.callLabel) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
Opcode.PUSH_VAR_WORD ->
if (it[1].value.opcode == Opcode.POP_VAR_WORD) {
if (it[0].value.callLabel == it[1].value.callLabel) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
Opcode.PUSH_VAR_FLOAT ->
if (it[1].value.opcode == Opcode.POP_VAR_FLOAT) {
if (it[0].value.callLabel == it[1].value.callLabel) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB ->
if(it[1].value.opcode == Opcode.POP_MEM_BYTE) {
if(it[0].value.arg == it[1].value.arg) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW ->
if(it[1].value.opcode == Opcode.POP_MEM_WORD) {
if(it[0].value.arg == it[1].value.arg) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
Opcode.PUSH_MEM_FLOAT ->
if(it[1].value.opcode == Opcode.POP_MEM_FLOAT) {
if(it[0].value.arg == it[1].value.arg) {
instructionsToReplace[it[0].index] = Instruction(Opcode.NOP)
instructionsToReplace[it[1].index] = Instruction(Opcode.NOP)
}
}
else -> {}
}
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
private fun optimizeDataConversionAndUselessDiscards() {
// - push value followed by a data type conversion -> push the value in the correct type and remove the conversion
// - push something followed by a discard -> remove both
val instructionsToReplace = mutableMapOf<Int, Instruction>()
fun optimizeDiscardAfterPush(index0: Int, index1: Int, ins1: Instruction) {
if (ins1.opcode == Opcode.DISCARD_FLOAT || ins1.opcode == Opcode.DISCARD_WORD || ins1.opcode == Opcode.DISCARD_BYTE) {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
}
fun optimizeFloatConversion(index0: Int, index1: Int, ins1: Instruction) {
when (ins1.opcode) {
Opcode.DISCARD_FLOAT -> {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_BYTE, Opcode.DISCARD_WORD -> throw CompilerException("invalid discard type following a float")
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode} following a float")
}
}
fun optimizeWordConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) {
when (ins1.opcode) {
Opcode.CAST_UW_TO_B, Opcode.CAST_W_TO_B -> {
val ins = Instruction(Opcode.PUSH_BYTE, ins0.arg!!.cast(DataType.BYTE))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_W_TO_UB, Opcode.CAST_UW_TO_UB -> {
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, 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, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_UW_TO_W -> {
val cv = ins0.arg!!.cast(DataType.WORD)
instructionsToReplace[index0] = Instruction(Opcode.PUSH_WORD, cv)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_W_TO_UW -> {
val cv = ins0.arg!!.cast(DataType.UWORD)
instructionsToReplace[index0] = Instruction(Opcode.PUSH_WORD, cv)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_WORD -> {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.DISCARD_BYTE, Opcode.DISCARD_FLOAT -> throw CompilerException("invalid discard type following a byte")
else -> throw CompilerException("invalid conversion opcode ${ins1.opcode} following a word")
}
}
fun optimizeByteConversion(index0: Int, ins0: Instruction, index1: Int, ins1: Instruction) {
when (ins1.opcode) {
Opcode.CAST_B_TO_UB, Opcode.CAST_UB_TO_B,
Opcode.CAST_W_TO_B, Opcode.CAST_W_TO_UB,
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, 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, RuntimeValue(DataType.WORD, ins0.arg!!.integerValue()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_B_TO_UW -> {
val ins = Instruction(Opcode.PUSH_WORD, ins0.arg!!.cast(DataType.UWORD))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_UB_TO_W -> {
val ins = Instruction(Opcode.PUSH_WORD, ins0.arg!!.cast(DataType.WORD))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_B_TO_F, Opcode.CAST_UB_TO_F -> {
val ins = Instruction(Opcode.PUSH_FLOAT, RuntimeValue(DataType.FLOAT, ins0.arg!!.integerValue().toDouble()))
instructionsToReplace[index0] = ins
instructionsToReplace[index1] = Instruction(Opcode.NOP)
}
Opcode.CAST_W_TO_F, Opcode.CAST_UW_TO_F -> throw CompilerException("invalid conversion following a byte")
Opcode.DISCARD_BYTE -> {
instructionsToReplace[index0] = Instruction(Opcode.NOP)
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}")
}
}
for(blk in blocks) {
instructionsToReplace.clear()
val typeConversionOpcodes = setOf(
Opcode.MSB,
Opcode.MKWORD,
Opcode.CAST_UB_TO_B,
Opcode.CAST_UB_TO_UW,
Opcode.CAST_UB_TO_W,
Opcode.CAST_UB_TO_F,
Opcode.CAST_B_TO_UB,
Opcode.CAST_B_TO_UW,
Opcode.CAST_B_TO_W,
Opcode.CAST_B_TO_F,
Opcode.CAST_UW_TO_UB,
Opcode.CAST_UW_TO_B,
Opcode.CAST_UW_TO_W,
Opcode.CAST_UW_TO_F,
Opcode.CAST_W_TO_UB,
Opcode.CAST_W_TO_B,
Opcode.CAST_W_TO_UW,
Opcode.CAST_W_TO_F,
Opcode.CAST_F_TO_UB,
Opcode.CAST_F_TO_B,
Opcode.CAST_F_TO_UW,
Opcode.CAST_F_TO_W,
Opcode.DISCARD_BYTE,
Opcode.DISCARD_WORD,
Opcode.DISCARD_FLOAT
)
blk.instructions.asSequence().withIndex().windowed(2).toList().forEach {
if (it[1].value.opcode in typeConversionOpcodes) {
when (it[0].value.opcode) {
Opcode.PUSH_BYTE -> optimizeByteConversion(it[0].index, it[0].value, it[1].index, it[1].value)
Opcode.PUSH_WORD -> optimizeWordConversion(it[0].index, it[0].value, it[1].index, it[1].value)
Opcode.PUSH_FLOAT -> optimizeFloatConversion(it[0].index, it[1].index, it[1].value)
Opcode.PUSH_VAR_FLOAT,
Opcode.PUSH_VAR_WORD,
Opcode.PUSH_VAR_BYTE,
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB,
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW,
Opcode.PUSH_MEM_FLOAT -> optimizeDiscardAfterPush(it[0].index, it[1].index, it[1].value)
else -> {
}
}
}
}
for (rins in instructionsToReplace) {
blk.instructions[rins.key] = rins.value
}
}
}
fun variable(scopedname: String, decl: VarDecl) {
when(decl.type) {
VarDeclType.VAR -> {
// var decls that are defined inside of a StructDecl are skipped in the output
// because every occurrence of the members will have a separate mangled vardecl for that occurrence
if(decl.parent is StructDecl)
return
val valueparams = VariableParameters(decl.zeropage, decl.struct)
val value = when(decl.datatype) {
in NumericDatatypes -> {
RuntimeValue(decl.datatype, (decl.value as NumericLiteralValue).number)
}
in StringDatatypes -> {
val litval = (decl.value as ReferenceLiteralValue)
if(litval.heapId==null)
throw CompilerException("string should already be in the heap")
RuntimeValue(decl.datatype, heapId = litval.heapId)
}
in ArrayDatatypes -> {
val litval = (decl.value as? ReferenceLiteralValue)
if(litval!=null && litval.heapId==null)
throw CompilerException("array should already be in the heap")
if(litval!=null){
RuntimeValue(decl.datatype, heapId = litval.heapId)
} else {
throw CompilerException("initialization value expected")
}
}
DataType.STRUCT -> {
// struct variables have been flattened already
return
}
else -> throw CompilerException("weird datatype")
}
currentBlock.variables.add(Variable(scopedname, value, valueparams))
}
VarDeclType.MEMORY -> {
// note that constants are all folded away, but assembly code may still refer to them
val lv = decl.value as NumericLiteralValue
if(lv.type!= DataType.UWORD && lv.type!= DataType.UBYTE)
throw CompilerException("expected integer memory address $lv")
currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), decl.datatype)
}
VarDeclType.CONST -> {
// note that constants are all folded away, but assembly code may still refer to them (if their integers)
// floating point constants are not generated at all!!
val lv = decl.value as NumericLiteralValue
if(lv.type in IntegerDatatypes)
currentBlock.memoryPointers[scopedname] = Pair(lv.number.toInt(), decl.datatype)
}
}
}
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))
}
fun label(labelname: String, asmProc: Boolean=false) {
val instr = LabelInstr(labelname, asmProc)
currentBlock.instructions.add(instr)
currentBlock.labels[labelname] = instr
}
fun line(position: Position) {
currentBlock.instructions.add(Instruction(Opcode.LINE, callLabel = "${position.line} ${position.file}"))
}
fun removeLastInstruction() {
currentBlock.instructions.removeAt(currentBlock.instructions.lastIndex)
}
fun memoryPointer(name: String, address: Int, datatype: DataType) {
currentBlock.memoryPointers[name] = Pair(address, datatype)
}
fun newBlock(name: String, address: Int?, options: Set<String>) {
currentBlock = ProgramBlock(name, address, force_output = "force_output" in options)
blocks.add(currentBlock)
}
fun writeCode(out: PrintStream, embeddedLabels: Boolean=true) {
out.println("; stackVM program code for '$name'")
writeMemory(out)
writeHeap(out)
for(blk in blocks) {
writeBlock(out, blk, embeddedLabels)
}
}
private fun writeHeap(out: PrintStream) {
out.println("%heap")
heap.allEntries().forEach {
out.print("${it.key} ${it.value.type.name.toLowerCase()} ")
when {
it.value.str!=null ->
out.println("\"${escape(it.value.str!!)}\"")
it.value.array!=null -> {
// this array can contain both normal integers, and pointer values
val arrayvalues = it.value.array!!.map { av ->
when {
av.integer!=null -> av.integer.toString()
av.addressOf!=null -> {
if(av.addressOf.scopedname==null)
throw CompilerException("AddressOf scopedname should have been set")
else
"&${av.addressOf.scopedname}"
}
else -> throw CompilerException("weird array value")
}
}
out.println(arrayvalues)
}
it.value.doubleArray!=null ->
out.println(it.value.doubleArray!!.toList())
else -> throw CompilerException("invalid heap entry $it")
}
}
out.println("%end_heap")
}
private fun writeBlock(out: PrintStream, blk: ProgramBlock, embeddedLabels: Boolean) {
out.println("\n%block ${blk.name} ${blk.address?.toString(16) ?: ""}")
out.println("%variables")
for (variable in blk.variables) {
if(variable.params.zp==ZeropageWish.REQUIRE_ZEROPAGE)
throw CompilerException("zp conflict")
val valuestr = variable.value.toString()
val struct = if(variable.params.memberOfStruct==null) "" else "struct=${variable.params.memberOfStruct.name}"
out.println("${variable.scopedname} ${variable.value.type.name.toLowerCase()} $valuestr zp=${variable.params.zp} s=$struct")
}
out.println("%end_variables")
out.println("%memorypointers")
for (iconst in blk.memoryPointers) {
out.println("${iconst.key} ${iconst.value.second.name.toLowerCase()} uw:${iconst.value.first.toString(16)}")
}
out.println("%end_memorypointers")
out.println("%instructions")
val labels = blk.labels.entries.associateBy({ it.value }) { it.key }
for (instr in blk.instructions) {
if (!embeddedLabels) {
val label = labels[instr]
if (label != null)
out.println("$label:")
} else {
out.println(instr)
}
}
out.println("%end_instructions")
out.println("%end_block")
}
private fun writeMemory(out: PrintStream) {
out.println("%memory")
if (memory.isNotEmpty())
TODO("add support for writing/reading initial memory values")
out.println("%end_memory")
}
}

View File

@ -1,291 +0,0 @@
package compiler.intermediate
enum class Opcode {
// pushing values on the (evaluation) stack
PUSH_BYTE, // push byte value
PUSH_WORD, // push word value (or 'address' of string / array)
PUSH_FLOAT, // push float value
PUSH_MEM_B, // push byte value from memory to stack
PUSH_MEM_UB, // push unsigned byte value from memory to stack
PUSH_MEM_W, // push word value from memory to stack
PUSH_MEM_UW, // push unsigned word value from memory to stack
PUSH_MEM_FLOAT, // push float value from memory to stack
PUSH_MEMREAD, // push memory value from address that's on the stack
PUSH_VAR_BYTE, // push byte variable (ubyte, byte)
PUSH_VAR_WORD, // push word variable (uword, word)
PUSH_VAR_FLOAT, // push float variable
PUSH_REGAX_WORD, // push registers A/X as a 16-bit word
PUSH_REGAY_WORD, // push registers A/Y as a 16-bit word
PUSH_REGXY_WORD, // push registers X/Y as a 16-bit word
PUSH_ADDR_HEAPVAR, // push the address of the variable that's on the heap (string or array)
DUP_B, // duplicate the top byte on the stack
DUP_W, // duplicate the top word on the stack
// popping values off the (evaluation) stack, possibly storing them in another location
DISCARD_BYTE, // discard top byte value
DISCARD_WORD, // discard top word value
DISCARD_FLOAT, // discard top float value
POP_MEM_BYTE, // pop (u)byte value into destination memory address
POP_MEM_WORD, // pop (u)word value into destination memory address
POP_MEM_FLOAT, // pop float value into destination memory address
POP_MEMWRITE, // pop address and byte stack and write the byte to the memory address
POP_VAR_BYTE, // pop (u)byte value into variable
POP_VAR_WORD, // pop (u)word value into variable
POP_VAR_FLOAT, // pop float value into variable
POP_REGAX_WORD, // pop uword from stack into A/X registers
POP_REGAY_WORD, // pop uword from stack into A/Y registers
POP_REGXY_WORD, // pop uword from stack into X/Y registers
// numeric arithmetic
ADD_UB,
ADD_B,
ADD_UW,
ADD_W,
ADD_F,
SUB_UB,
SUB_B,
SUB_UW,
SUB_W,
SUB_F,
MUL_UB,
MUL_B,
MUL_UW,
MUL_W,
MUL_F,
IDIV_UB,
IDIV_B,
IDIV_UW,
IDIV_W,
DIV_F,
REMAINDER_UB, // signed remainder is undefined/unimplemented
REMAINDER_UW, // signed remainder is undefined/unimplemented
POW_F,
NEG_B,
NEG_W,
NEG_F,
ABS_B,
ABS_W,
ABS_F,
// bit shifts and bitwise arithmetic
SHIFTEDL_BYTE, // shifts stack value rather than in-place mem/var
SHIFTEDL_WORD, // shifts stack value rather than in-place mem/var
SHIFTEDR_UBYTE, // shifts stack value rather than in-place mem/var
SHIFTEDR_SBYTE, // shifts stack value rather than in-place mem/var
SHIFTEDR_UWORD, // shifts stack value rather than in-place mem/var
SHIFTEDR_SWORD, // shifts stack value rather than in-place mem/var
SHL_BYTE,
SHL_WORD,
SHL_MEM_BYTE,
SHL_MEM_WORD,
SHL_VAR_BYTE,
SHL_VAR_WORD,
SHR_UBYTE,
SHR_SBYTE,
SHR_UWORD,
SHR_SWORD,
SHR_MEM_UBYTE,
SHR_MEM_SBYTE,
SHR_MEM_UWORD,
SHR_MEM_SWORD,
SHR_VAR_UBYTE,
SHR_VAR_SBYTE,
SHR_VAR_UWORD,
SHR_VAR_SWORD,
ROL_BYTE,
ROL_WORD,
ROL_MEM_BYTE,
ROL_MEM_WORD,
ROL_VAR_BYTE,
ROL_VAR_WORD,
ROR_BYTE,
ROR_WORD,
ROR_MEM_BYTE,
ROR_MEM_WORD,
ROR_VAR_BYTE,
ROR_VAR_WORD,
ROL2_BYTE,
ROL2_WORD,
ROL2_MEM_BYTE,
ROL2_MEM_WORD,
ROL2_VAR_BYTE,
ROL2_VAR_WORD,
ROR2_BYTE,
ROR2_WORD,
ROR2_MEM_BYTE,
ROR2_MEM_WORD,
ROR2_VAR_BYTE,
ROR2_VAR_WORD,
BITAND_BYTE,
BITAND_WORD,
BITOR_BYTE,
BITOR_WORD,
BITXOR_BYTE,
BITXOR_WORD,
INV_BYTE,
INV_WORD,
// numeric type conversions
MSB, // note: lsb is equivalent to CAST_UW_TO_UB or CAST_W_TO_UB
MKWORD, // create a word from lsb + msb
CAST_UB_TO_B,
CAST_UB_TO_UW,
CAST_UB_TO_W,
CAST_UB_TO_F,
CAST_B_TO_UB,
CAST_B_TO_UW,
CAST_B_TO_W,
CAST_B_TO_F,
CAST_W_TO_UB,
CAST_W_TO_B,
CAST_W_TO_UW,
CAST_W_TO_F,
CAST_UW_TO_UB,
CAST_UW_TO_B,
CAST_UW_TO_W,
CAST_UW_TO_F,
CAST_F_TO_UB,
CAST_F_TO_B,
CAST_F_TO_UW,
CAST_F_TO_W,
// logical operations
AND_BYTE,
AND_WORD,
OR_BYTE,
OR_WORD,
XOR_BYTE,
XOR_WORD,
NOT_BYTE,
NOT_WORD,
// increment, decrement
INC_VAR_B,
INC_VAR_UB,
INC_VAR_W,
INC_VAR_UW,
INC_VAR_F,
DEC_VAR_B,
DEC_VAR_UB,
DEC_VAR_W,
DEC_VAR_UW,
DEC_VAR_F,
INC_MEMORY, // increment direct address
DEC_MEMORY, // decrement direct address
POP_INC_MEMORY, // increment address from stack
POP_DEC_MEMORY, // decrement address from address
// comparisons
LESS_B,
LESS_UB,
LESS_W,
LESS_UW,
LESS_F,
GREATER_B,
GREATER_UB,
GREATER_W,
GREATER_UW,
GREATER_F,
LESSEQ_B,
LESSEQ_UB,
LESSEQ_W,
LESSEQ_UW,
LESSEQ_F,
GREATEREQ_B,
GREATEREQ_UB,
GREATEREQ_W,
GREATEREQ_UW,
GREATEREQ_F,
EQUAL_BYTE,
EQUAL_WORD,
EQUAL_F,
NOTEQUAL_BYTE,
NOTEQUAL_WORD,
NOTEQUAL_F,
CMP_B, // sets processor status flags based on comparison, instead of pushing a result value
CMP_UB, // sets processor status flags based on comparison, instead of pushing a result value
CMP_W, // sets processor status flags based on comparison, instead of pushing a result value
CMP_UW, // sets processor status flags based on comparison, instead of pushing a result value
// array access and simple manipulations
READ_INDEXED_VAR_BYTE,
READ_INDEXED_VAR_WORD,
READ_INDEXED_VAR_FLOAT,
WRITE_INDEXED_VAR_BYTE,
WRITE_INDEXED_VAR_WORD,
WRITE_INDEXED_VAR_FLOAT,
INC_INDEXED_VAR_B,
INC_INDEXED_VAR_UB,
INC_INDEXED_VAR_W,
INC_INDEXED_VAR_UW,
INC_INDEXED_VAR_FLOAT,
DEC_INDEXED_VAR_B,
DEC_INDEXED_VAR_UB,
DEC_INDEXED_VAR_W,
DEC_INDEXED_VAR_UW,
DEC_INDEXED_VAR_FLOAT,
// branching, without consuming a value from the stack
JUMP,
BCS, // branch if carry set
BCC, // branch if carry clear
BZ, // branch if zero flag
BNZ, // branch if not zero flag
BNEG, // branch if negative flag
BPOS, // branch if not negative flag
BVS, // branch if overflow flag
BVC, // branch if not overflow flag
// branching, based on value on the stack (which is consumed)
JZ, // branch if value is zero (byte)
JNZ, // branch if value is not zero (byte)
JZW, // branch if value is zero (word)
JNZW, // branch if value is not zero (word)
// subroutines
CALL,
RETURN,
SYSCALL,
START_PROCDEF,
END_PROCDEF,
// misc
SEC, // set carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
CLC, // clear carry status flag NOTE: is mostly fake, carry flag is not affected by any numeric operations
SEI, // set irq-disable status flag
CLI, // clear irq-disable status flag
CARRY_TO_A, // load var/register A with carry status bit
RSAVE, // save all internal registers and status flags
RSAVEX, // save just X (the evaluation stack pointer)
RRESTORE, // restore all internal registers and status flags
RRESTOREX, // restore just X (the evaluation stack pointer)
NOP, // do nothing
BREAKPOINT, // breakpoint
TERMINATE, // end the program
LINE, // track source file line number
INLINE_ASSEMBLY, // container to hold inline raw assembly code
INCLUDE_FILE // directive to include a file at this position in the memory of the program
}
val opcodesWithVarArgument = setOf(
Opcode.INC_VAR_B, Opcode.INC_VAR_W, Opcode.DEC_VAR_B, Opcode.DEC_VAR_W,
Opcode.INC_VAR_UB, Opcode.INC_VAR_UW, Opcode.DEC_VAR_UB, Opcode.DEC_VAR_UW,
Opcode.SHR_VAR_SBYTE, Opcode.SHR_VAR_UBYTE, Opcode.SHR_VAR_SWORD, Opcode.SHR_VAR_UWORD,
Opcode.SHL_VAR_BYTE, Opcode.SHL_VAR_WORD,
Opcode.ROL_VAR_BYTE, Opcode.ROL_VAR_WORD, Opcode.ROR_VAR_BYTE, Opcode.ROR_VAR_WORD,
Opcode.ROL2_VAR_BYTE, Opcode.ROL2_VAR_WORD, Opcode.ROR2_VAR_BYTE, Opcode.ROR2_VAR_WORD,
Opcode.POP_VAR_BYTE, Opcode.POP_VAR_WORD, Opcode.POP_VAR_FLOAT,
Opcode.PUSH_VAR_BYTE, Opcode.PUSH_VAR_WORD, Opcode.PUSH_VAR_FLOAT, Opcode.PUSH_ADDR_HEAPVAR,
Opcode.READ_INDEXED_VAR_BYTE, Opcode.READ_INDEXED_VAR_WORD, Opcode.READ_INDEXED_VAR_FLOAT,
Opcode.WRITE_INDEXED_VAR_BYTE, Opcode.WRITE_INDEXED_VAR_WORD, Opcode.WRITE_INDEXED_VAR_FLOAT,
Opcode.INC_INDEXED_VAR_UB, Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UW,
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UW,
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_FLOAT
)
val branchOpcodes = setOf(
Opcode.BCS, Opcode.BCC, Opcode.BZ, Opcode.BNZ,
Opcode.BNEG, Opcode.BPOS, Opcode.BVS, Opcode.BVC
)

View File

@ -1,761 +0,0 @@
package compiler.target.c64.codegen
// note: to put stuff on the stack, we use Absolute,X addressing mode which is 3 bytes / 4 cycles
// possible space optimization is to use zeropage (indirect),Y which is 2 bytes, but 5 cycles
import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.initvarsSubName
import prog8.ast.statements.ZeropageWish
import prog8.compiler.*
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import java.io.File
import java.util.*
import kotlin.math.abs
class AssemblyError(msg: String) : RuntimeException(msg)
internal fun intVal(valueInstr: Instruction) = valueInstr.arg!!.integerValue()
internal fun hexVal(valueInstr: Instruction) = valueInstr.arg!!.integerValue().toHex()
internal fun hexValPlusOne(valueInstr: Instruction) = (valueInstr.arg!!.integerValue()+1).toHex()
internal fun getFloatConst(value: RuntimeValue): String =
globalFloatConsts[value.numericValue().toDouble()]
?: throw AssemblyError("should have a global float const for number $value")
internal val globalFloatConsts = mutableMapOf<Double, String>()
internal fun signExtendA(into: String) =
"""
ora #$7f
bmi +
lda #0
+ sta $into
"""
class AsmGen(private val options: CompilationOptions, private val program: IntermediateProgram,
private val heap: HeapValues, private val zeropage: Zeropage) {
private val assemblyLines = mutableListOf<String>()
private lateinit var block: IntermediateProgram.ProgramBlock
init {
// Convert invalid label names (such as "<anon-1>") to something that's allowed.
val newblocks = mutableListOf<IntermediateProgram.ProgramBlock>()
for(block in program.blocks) {
val newvars = block.variables.map { IntermediateProgram.Variable(symname(it.scopedname, block), it.value, it.params) }.toMutableList()
val newlabels = block.labels.map { symname(it.key, block) to it.value}.toMap().toMutableMap()
val newinstructions = block.instructions.asSequence().map {
when {
it is LabelInstr -> LabelInstr(symname(it.name, block), it.asmProc)
it.opcode == Opcode.INLINE_ASSEMBLY -> it
else ->
Instruction(it.opcode, it.arg, it.arg2,
callLabel = if (it.callLabel != null) symname(it.callLabel, block) else null,
callLabel2 = if (it.callLabel2 != null) symname(it.callLabel2, block) else null)
}
}.toMutableList()
val newMempointers = block.memoryPointers.map { symname(it.key, block) to it.value }.toMap().toMutableMap()
val newblock = IntermediateProgram.ProgramBlock(
block.name,
block.address,
newinstructions,
newvars,
newMempointers,
newlabels,
force_output = block.force_output)
newblocks.add(newblock)
}
program.blocks.clear()
program.blocks.addAll(newblocks)
val newAllocatedZp = program.allocatedZeropageVariables.map { symname(it.key, null) to it.value}
program.allocatedZeropageVariables.clear()
program.allocatedZeropageVariables.putAll(newAllocatedZp)
// make a list of all const floats that are used
for(block in program.blocks) {
for(ins in block.instructions.filter{it.arg?.type== DataType.FLOAT}) {
val float = ins.arg!!.numericValue().toDouble()
if(float !in globalFloatConsts)
globalFloatConsts[float] = "prog8_const_float_${globalFloatConsts.size}"
}
}
}
fun compileToAssembly(optimize: Boolean): AssemblyProgram {
println("Generating assembly code from intermediate code... ")
assemblyLines.clear()
header()
for(b in program.blocks)
block2asm(b)
if(optimize) {
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
}
File("${program.name}.asm").printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
return AssemblyProgram(program.name)
}
private fun out(str: String, splitlines: Boolean=true) {
if(splitlines) {
for (line in str.split('\n')) {
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
assemblyLines.add(trimmed)
}
} else assemblyLines.add(str)
}
// convert a fully scoped name (defined in the given block) to a valid assembly symbol name
private fun symname(scoped: String, block: IntermediateProgram.ProgramBlock?): String {
if(' ' in scoped)
return scoped
val blockLocal: Boolean
var name = if (block!=null && scoped.startsWith("${block.name}.")) {
blockLocal = true
scoped.substring(block.name.length+1)
}
else {
blockLocal = false
scoped
}
name = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
if(name=="-")
return "-"
if(blockLocal)
name = name.replace(".", "_")
else {
val parts = name.split(".", limit=2)
if(parts.size>1)
name = "${parts[0]}.${parts[1].replace(".", "_")}"
}
return name.replace("-", "")
}
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
val b0 = "$"+flt.b0.toString(16).padStart(2, '0')
val b1 = "$"+flt.b1.toString(16).padStart(2, '0')
val b2 = "$"+flt.b2.toString(16).padStart(2, '0')
val b3 = "$"+flt.b3.toString(16).padStart(2, '0')
val b4 = "$"+flt.b4.toString(16).padStart(2, '0')
return "$b0, $b1, $b2, $b3, $b4"
}
private fun header() {
val ourName = this.javaClass.name
out("; 6502 assembly code for '${program.name}'")
out("; generated by $ourName on ${Date()}")
out("; assembler syntax is for the 64tasm cross-assembler")
out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
out("\n.cpu '6502'\n.enc 'none'\n")
if(program.loadAddress==0) // fix load address
program.loadAddress = if(options.launcher==LauncherType.BASIC)
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
when {
options.launcher == LauncherType.BASIC -> {
if (program.loadAddress != 0x0801)
throw AssemblyError("BASIC output must have load address $0801")
out("; ---- basic program with sys call ----")
out("* = ${program.loadAddress.toHex()}")
val year = Calendar.getInstance().get(Calendar.YEAR)
out(" .word (+), $year")
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.loadAddress.toHex()}\n")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.RAW -> {
out("; ---- raw assembler program ----")
out("* = ${program.loadAddress.toHex()}\n")
}
}
if(zeropage.exitProgramStrategy!=Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
// disable shift-commodore charset switching and run/stop key
out(" lda #$80")
out(" lda #$80")
out(" sta 657\t; disable charset switching")
out(" lda #239")
out(" sta 808\t; disable run/stop key")
}
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block")
for(block in program.blocks) {
val initVarsLabel = block.instructions.firstOrNull { it is LabelInstr && it.name== initvarsSubName } as? LabelInstr
if(initVarsLabel!=null)
out(" jsr ${block.name}.${initVarsLabel.name}")
}
out(" clc")
when(zeropage.exitProgramStrategy) {
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
out(" jmp main.start\t; jump to program entrypoint")
}
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
out(" jsr main.start\t; call program entrypoint")
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out("")
// the global list of all floating point constants for the whole program
for(flt in globalFloatConsts) {
val floatFill = makeFloatFill(MachineDefinition.Mflpt5.fromNumber(flt.key))
out("${flt.value}\t.byte $floatFill ; float ${flt.key}")
}
}
private fun block2asm(blk: IntermediateProgram.ProgramBlock) {
block = blk
out("\n; ---- block: '${block.name}' ----")
if(!blk.force_output)
out("${block.name}\t.proc\n")
if(block.address!=null) {
out(".cerror * > ${block.address?.toHex()}, 'block address overlaps by ', *-${block.address?.toHex()},' bytes'")
out("* = ${block.address?.toHex()}")
}
// deal with zeropage variables
for(variable in blk.variables) {
val sym = symname(blk.name+"."+variable.scopedname, null)
val zpVar = program.allocatedZeropageVariables[sym]
if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
if(variable.params.zp != ZeropageWish.NOT_IN_ZEROPAGE &&
variable.value.type in zeropage.allowedDatatypes
&& variable.value.type != DataType.FLOAT) {
try {
val address = zeropage.allocate(sym, variable.value.type, null)
out("${variable.scopedname} = $address\t; auto zp ${variable.value.type}")
// make sure we add the var to the set of zpvars for this block
program.allocatedZeropageVariables[sym] = Pair(address, variable.value.type)
} catch (x: ZeropageDepletedError) {
// leave it as it is.
}
}
}
else {
// it was already allocated on the zp
out("${variable.scopedname} = ${zpVar.first}\t; zp ${zpVar.second}")
}
}
out("\n; memdefs and kernel subroutines")
memdefs2asm(block)
out("\n; non-zeropage variables")
vardecls2asm(block)
out("")
val instructionPatternWindowSize = 8 // increase once patterns occur longer than this.
var processed = 0
for (ins in block.instructions.windowed(instructionPatternWindowSize, partialWindows = true)) {
if (processed == 0) {
processed = instr2asm(ins)
if (processed == 0) {
// the instructions are not recognised yet and can't be translated into assembly
throw CompilerException("no asm translation found for instruction pattern: $ins")
}
}
processed--
}
if(!blk.force_output)
out("\n\t.pend\n")
}
private fun memdefs2asm(block: IntermediateProgram.ProgramBlock) {
for(m in block.memoryPointers) {
out(" ${m.key} = ${m.value.first.toHex()}")
}
}
private fun vardecls2asm(block: IntermediateProgram.ProgramBlock) {
val uniqueNames = block.variables.map { it.scopedname }.toSet()
if (uniqueNames.size != block.variables.size)
throw AssemblyError("not all variables have unique names")
// these are the non-zeropage variables.
// first get all the flattened struct members, they MUST remain in order
out("; flattened struct members")
val (structMembers, normalVars) = block.variables.partition { it.params.memberOfStruct!=null }
structMembers.forEach { vardecl2asm(it.scopedname, it.value, it.params) }
// sort the other variables by type
out("; other variables sorted by type")
val sortedVars = normalVars.sortedBy { it.value.type }
for (variable in sortedVars) {
val sym = symname(block.name + "." + variable.scopedname, null)
if(sym in program.allocatedZeropageVariables)
continue // skip the ones that already belong in the zero page
vardecl2asm(variable.scopedname, variable.value, variable.params)
}
}
private fun vardecl2asm(varname: String, value: RuntimeValue, parameters: IntermediateProgram.VariableParameters) {
when (value.type) {
DataType.UBYTE -> out("$varname\t.byte 0")
DataType.BYTE -> out("$varname\t.char 0")
DataType.UWORD -> out("$varname\t.word 0")
DataType.WORD -> out("$varname\t.sint 0")
DataType.FLOAT -> out("$varname\t.byte 0,0,0,0,0 ; float")
DataType.STR, DataType.STR_S -> {
val rawStr = heap.get(value.heapId!!).str!!
val bytes = encodeStr(rawStr, value.type).map { "$" + it.toString(16).padStart(2, '0') }
out("$varname\t; ${value.type} \"${escape(rawStr).replace("\u0000", "<NULL>")}\"")
for (chunk in bytes.chunked(16))
out(" .byte " + chunk.joinToString())
}
DataType.ARRAY_UB -> {
// unsigned integer byte arraysize
val data = makeArrayFillDataUnsigned(value)
if (data.size <= 16)
out("$varname\t.byte ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .byte " + chunk.joinToString())
}
}
DataType.ARRAY_B -> {
// signed integer byte arraysize
val data = makeArrayFillDataSigned(value)
if (data.size <= 16)
out("$varname\t.char ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .char " + chunk.joinToString())
}
}
DataType.ARRAY_UW -> {
// unsigned word arraysize
val data = makeArrayFillDataUnsigned(value)
if (data.size <= 16)
out("$varname\t.word ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .word " + chunk.joinToString())
}
}
DataType.ARRAY_W -> {
// signed word arraysize
val data = makeArrayFillDataSigned(value)
if (data.size <= 16)
out("$varname\t.sint ${data.joinToString()}")
else {
out(varname)
for (chunk in data.chunked(16))
out(" .sint " + chunk.joinToString())
}
}
DataType.ARRAY_F -> {
// float arraysize
val array = heap.get(value.heapId!!).doubleArray!!
val floatFills = array.map { makeFloatFill(MachineDefinition.Mflpt5.fromNumber(it)) }
out(varname)
for (f in array.zip(floatFills))
out(" .byte ${f.second} ; float ${f.first}")
}
DataType.STRUCT -> throw AssemblyError("vars of type STRUCT should have been removed because flattened")
}
}
private fun encodeStr(str: String, dt: DataType): List<Short> {
return when(dt) {
DataType.STR -> {
val bytes = Petscii.encodePetscii(str, true)
bytes.plus(0)
}
DataType.STR_S -> {
val bytes = Petscii.encodeScreencode(str, true)
bytes.plus(0)
}
else -> throw AssemblyError("invalid str type")
}
}
private fun makeArrayFillDataUnsigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId!!).array!!
return when {
value.type== DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map { "$"+it.integer!!.toString(16).padStart(2, '0') }
value.type== DataType.ARRAY_UW -> array.map {
when {
it.integer!=null -> "$"+it.integer.toString(16).padStart(2, '0')
it.addressOf!=null -> symname(it.addressOf.scopedname!!, block)
else -> throw AssemblyError("weird type in array")
}
}
else -> throw AssemblyError("invalid arraysize type")
}
}
private fun makeArrayFillDataSigned(value: RuntimeValue): List<String> {
val array = heap.get(value.heapId!!).array!!
// note: array of signed value can never contain pointer-to type, so simply accept values as being all integers
return if (value.type == DataType.ARRAY_B || value.type == DataType.ARRAY_W) {
array.map {
if(it.integer!!>=0)
"$"+it.integer.toString(16).padStart(2, '0')
else
"-$"+abs(it.integer).toString(16).padStart(2, '0')
}
}
else throw AssemblyError("invalid arraysize type")
}
private fun instr2asm(ins: List<Instruction>): Int {
// find best patterns (matching the most of the lines, then with the smallest weight)
val fragments = findPatterns(ins).sortedByDescending { it.segmentSize }
if(fragments.isEmpty()) {
// we didn't find any matching patterns (complex multi-instruction fragments), try simple ones
val firstIns = ins[0]
val singleAsm = simpleInstr2Asm(firstIns, block)
if(singleAsm != null) {
outputAsmFragment(singleAsm)
return 1
}
return 0
}
val best = fragments[0]
outputAsmFragment(best.asm)
return best.segmentSize
}
private fun outputAsmFragment(singleAsm: String) {
if (singleAsm.isNotEmpty()) {
if(singleAsm.startsWith("@inline@"))
out(singleAsm.substring(8), false)
else {
val withNewlines = singleAsm.replace('|', '\n')
out(withNewlines)
}
}
}
private fun findPatterns(segment: List<Instruction>): List<AsmFragment> {
val opcodes = segment.map { it.opcode }
val result = mutableListOf<AsmFragment>()
// check for operations that modify a single value, by putting it on the stack (and popping it afterwards)
if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[2]==Opcode.POP_VAR_BYTE) ||
(opcodes[0]==Opcode.PUSH_VAR_WORD && opcodes[2]==Opcode.POP_VAR_WORD) ||
(opcodes[0]==Opcode.PUSH_VAR_FLOAT && opcodes[2]==Opcode.POP_VAR_FLOAT)) {
if (segment[0].callLabel == segment[2].callLabel) {
val fragment = sameVarOperation(segment[0].callLabel!!, segment[1])
if (fragment != null) {
fragment.segmentSize = 3
result.add(fragment)
}
}
}
else if((opcodes[0]==Opcode.PUSH_BYTE && opcodes[1] in setOf(Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB,
Opcode.INC_INDEXED_VAR_UW, Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_W,
Opcode.DEC_INDEXED_VAR_UW, Opcode.DEC_INDEXED_VAR_FLOAT))) {
val fragment = sameConstantIndexedVarOperation(segment[1].callLabel!!, segment[0].arg!!.integerValue(), segment[1])
if(fragment!=null) {
fragment.segmentSize=2
result.add(fragment)
}
}
else if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1] in setOf(Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB,
Opcode.INC_INDEXED_VAR_UW, Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_FLOAT,
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB, Opcode.DEC_INDEXED_VAR_W,
Opcode.DEC_INDEXED_VAR_UW, Opcode.DEC_INDEXED_VAR_FLOAT))) {
val fragment = sameIndexedVarOperation(segment[1].callLabel!!, segment[0].callLabel!!, segment[1])
if(fragment!=null) {
fragment.segmentSize=2
result.add(fragment)
}
}
else if((opcodes[0]==Opcode.PUSH_MEM_UB && opcodes[2]==Opcode.POP_MEM_BYTE) ||
(opcodes[0]==Opcode.PUSH_MEM_B && opcodes[2]==Opcode.POP_MEM_BYTE) ||
(opcodes[0]==Opcode.PUSH_MEM_UW && opcodes[2]==Opcode.POP_MEM_WORD) ||
(opcodes[0]==Opcode.PUSH_MEM_W && opcodes[2]==Opcode.POP_MEM_WORD) ||
(opcodes[0]==Opcode.PUSH_MEM_FLOAT && opcodes[2]==Opcode.POP_MEM_FLOAT)) {
if(segment[0].arg==segment[2].arg) {
val fragment = sameMemOperation(segment[0].arg!!.integerValue(), segment[1])
if(fragment!=null) {
fragment.segmentSize = 3
result.add(fragment)
}
}
}
else if((opcodes[0]==Opcode.PUSH_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_BYTE &&
opcodes[3]==Opcode.PUSH_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_BYTE) ||
(opcodes[0]==Opcode.PUSH_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_WORD &&
opcodes[3]==Opcode.PUSH_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_WORD)) {
if(segment[0].arg==segment[3].arg && segment[1].callLabel==segment[4].callLabel) {
val fragment = sameConstantIndexedVarOperation(segment[1].callLabel!!, segment[0].arg!!.integerValue(), segment[2])
if(fragment!=null){
fragment.segmentSize = 5
result.add(fragment)
}
}
}
else if((opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_BYTE &&
opcodes[3]==Opcode.PUSH_VAR_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_BYTE) ||
(opcodes[0]==Opcode.PUSH_VAR_BYTE && opcodes[1]==Opcode.READ_INDEXED_VAR_WORD &&
opcodes[3]==Opcode.PUSH_VAR_BYTE && opcodes[4]==Opcode.WRITE_INDEXED_VAR_WORD)) {
if(segment[0].callLabel==segment[3].callLabel && segment[1].callLabel==segment[4].callLabel) {
val fragment = sameIndexedVarOperation(segment[1].callLabel!!, segment[0].callLabel!!, segment[2])
if(fragment!=null){
fragment.segmentSize = 5
result.add(fragment)
}
}
}
// add any matching patterns from the big list
for(pattern in Patterns.patterns) {
if(pattern.sequence.size > segment.size || (pattern.altSequence!=null && pattern.altSequence.size > segment.size))
continue // don't accept patterns that don't fit
val opcodesList = opcodes.subList(0, pattern.sequence.size)
if(pattern.sequence == opcodesList) {
val asm = pattern.asm(segment)
if(asm!=null)
result.add(AsmFragment(asm, pattern.sequence.size))
} else if(pattern.altSequence!=null) {
val opcodesListAlt = opcodes.subList(0, pattern.altSequence.size)
if(pattern.altSequence == opcodesListAlt) {
val asm = pattern.asm(segment)
if (asm != null)
result.add(AsmFragment(asm, pattern.sequence.size))
}
}
}
return result
}
private fun sameConstantIndexedVarOperation(variable: String, index: Int, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-value / op / push-index-value / pop-into-indexed-var
return when(ins.opcode) {
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
Opcode.SHR_SBYTE -> AsmFragment(" lda $variable+$index | asl a | ror $variable+$index")
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index * 2 + 1} | asl a | ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL_BYTE -> AsmFragment(" rol $variable+$index", 8)
Opcode.ROR_BYTE -> AsmFragment(" ror $variable+$index", 8)
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL2_BYTE -> AsmFragment(" lda $variable+$index | cmp #\$80 | rol $variable+$index", 8)
Opcode.ROR2_BYTE -> AsmFragment(" lda $variable+$index | lsr a | bcc + | ora #\$80 |+ | sta $variable+$index", 10)
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2} | bcc + | inc $variable+${index * 2 + 1} |+", 20)
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2} | bcc + | lda $variable+${index * 2 + 1} | ora #\$80 | sta $variable+${index * 2 + 1} |+", 30)
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" inc $variable+$index", 2)
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" dec $variable+$index", 5)
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment(" inc $variable+${index * 2} | bne + | inc $variable+${index * 2 + 1} |+")
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment(" lda $variable+${index * 2} | bne + | dec $variable+${index * 2 + 1} |+ | dec $variable+${index * 2}")
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(
"""
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.inc_var_f
""")
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(
"""
lda #<($variable+${index * MachineDefinition.Mflpt5.MemorySize})
ldy #>($variable+${index * MachineDefinition.Mflpt5.MemorySize})
jsr c64flt.dec_var_f
""")
else -> null
}
}
private fun sameIndexedVarOperation(variable: String, indexVar: String, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-value / op / push-index-var / pop-into-indexed-var
val saveX = " stx ${MachineDefinition.C64Zeropage.SCRATCH_B1} |"
val restoreX = " | ldx ${MachineDefinition.C64Zeropage.SCRATCH_B1}"
val loadXWord: String
val loadX: String
when(indexVar) {
"X" -> {
loadX = ""
loadXWord = " txa | asl a | tax |"
}
"Y" -> {
loadX = " tya | tax |"
loadXWord = " tya | asl a | tax |"
}
"A" -> {
loadX = " tax |"
loadXWord = " asl a | tax |"
}
else -> {
// the indexvar is a real variable, not a register
loadX = " ldx $indexVar |"
loadXWord = " lda $indexVar | asl a | tax |"
}
}
return when (ins.opcode) {
Opcode.SHL_BYTE -> AsmFragment(" txa | $loadX asl $variable,x | tax", 10)
Opcode.SHR_UBYTE -> AsmFragment(" txa | $loadX lsr $variable,x | tax", 10)
Opcode.SHR_SBYTE -> AsmFragment("$saveX $loadX lda $variable,x | asl a | ror $variable,x $restoreX", 10)
Opcode.SHL_WORD -> AsmFragment("$saveX $loadXWord asl $variable,x | rol $variable+1,x $restoreX", 10)
Opcode.SHR_UWORD -> AsmFragment("$saveX $loadXWord lsr $variable+1,x | ror $variable,x $restoreX", 10)
Opcode.SHR_SWORD -> AsmFragment("$saveX $loadXWord lda $variable+1,x | asl a | ror $variable+1,x | ror $variable,x $restoreX", 10)
Opcode.ROL_BYTE -> AsmFragment(" txa | $loadX rol $variable,x | tax", 10)
Opcode.ROR_BYTE -> AsmFragment(" txa | $loadX ror $variable,x | tax", 10)
Opcode.ROL_WORD -> AsmFragment("$saveX $loadXWord rol $variable,x | rol $variable+1,x $restoreX", 10)
Opcode.ROR_WORD -> AsmFragment("$saveX $loadXWord ror $variable+1,x | ror $variable,x $restoreX", 10)
Opcode.ROL2_BYTE -> AsmFragment("$saveX $loadX lda $variable,x | cmp #\$80 | rol $variable,x $restoreX", 10)
Opcode.ROR2_BYTE -> AsmFragment("$saveX $loadX lda $variable,x | lsr a | bcc + | ora #\$80 |+ | sta $variable,x $restoreX", 10)
Opcode.ROL2_WORD -> AsmFragment(" txa | $loadXWord asl $variable,x | rol $variable+1,x | bcc + | inc $variable,x |+ | tax", 30)
Opcode.ROR2_WORD -> AsmFragment("$saveX $loadXWord lsr $variable+1,x | ror $variable,x | bcc + | lda $variable+1,x | ora #\$80 | sta $variable+1,x |+ $restoreX", 30)
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> AsmFragment(" txa | $loadX inc $variable,x | tax", 10)
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> AsmFragment(" txa | $loadX dec $variable,x | tax", 10)
Opcode.INC_INDEXED_VAR_W, Opcode.INC_INDEXED_VAR_UW -> AsmFragment("$saveX $loadXWord inc $variable,x | bne + | inc $variable+1,x |+ $restoreX", 10)
Opcode.DEC_INDEXED_VAR_W, Opcode.DEC_INDEXED_VAR_UW -> AsmFragment("$saveX $loadXWord lda $variable,x | bne + | dec $variable+1,x |+ | dec $variable,x $restoreX", 10)
Opcode.INC_INDEXED_VAR_FLOAT -> AsmFragment(" lda #<$variable | ldy #>$variable | $saveX $loadX jsr c64flt.inc_indexed_var_f $restoreX")
Opcode.DEC_INDEXED_VAR_FLOAT -> AsmFragment(" lda #<$variable | ldy #>$variable | $saveX $loadX jsr c64flt.dec_indexed_var_f $restoreX")
else -> null
}
}
private fun sameMemOperation(address: Int, ins: Instruction): AsmFragment? {
// an in place operation that consists of push-mem / op / pop-mem
val addr = address.toHex()
val addrHi = (address+1).toHex()
return when(ins.opcode) {
Opcode.SHL_BYTE -> AsmFragment(" asl $addr", 10)
Opcode.SHR_UBYTE -> AsmFragment(" lsr $addr", 10)
Opcode.SHR_SBYTE -> AsmFragment(" lda $addr | asl a | ror $addr", 10)
Opcode.SHL_WORD -> AsmFragment(" asl $addr | rol $addrHi", 10)
Opcode.SHR_UWORD -> AsmFragment(" lsr $addrHi | ror $addr", 10)
Opcode.SHR_SWORD -> AsmFragment(" lda $addrHi | asl a | ror $addrHi | ror $addr", 10)
Opcode.ROL_BYTE -> AsmFragment(" rol $addr", 10)
Opcode.ROR_BYTE -> AsmFragment(" ror $addr", 10)
Opcode.ROL_WORD -> AsmFragment(" rol $addr | rol $addrHi", 10)
Opcode.ROR_WORD -> AsmFragment(" ror $addrHi | ror $addr", 10)
Opcode.ROL2_BYTE -> AsmFragment(" lda $addr | cmp #\$80 | rol $addr", 10)
Opcode.ROR2_BYTE -> AsmFragment(" lda $addr | lsr a | bcc + | ora #\$80 |+ | sta $addr", 10)
Opcode.ROL2_WORD -> AsmFragment(" lda $addr | cmp #\$80 | rol $addr | rol $addrHi", 10)
Opcode.ROR2_WORD -> AsmFragment(" lsr $addrHi | ror $addr | bcc + | lda $addrHi | ora #$80 | sta $addrHi |+", 20)
else -> null
}
}
private fun sameVarOperation(variable: String, ins: Instruction): AsmFragment? {
// an in place operation that consists of a push-var / op / pop-var
return when(ins.opcode) {
Opcode.SHL_BYTE -> {
when (variable) {
"A" -> AsmFragment(" asl a", 10)
"X" -> AsmFragment(" txa | asl a | tax", 10)
"Y" -> AsmFragment(" tya | asl a | tay", 10)
else -> AsmFragment(" asl $variable", 10)
}
}
Opcode.SHR_UBYTE -> {
when (variable) {
"A" -> AsmFragment(" lsr a", 10)
"X" -> AsmFragment(" txa | lsr a | tax", 10)
"Y" -> AsmFragment(" tya | lsr a | tay", 10)
else -> AsmFragment(" lsr $variable", 10)
}
}
Opcode.SHR_SBYTE -> {
// arithmetic shift right (keep sign bit)
when (variable) {
"A" -> AsmFragment(" cmp #$80 | ror a", 10)
"X" -> AsmFragment(" txa | cmp #$80 | ror a | tax", 10)
"Y" -> AsmFragment(" tya | cmp #$80 | ror a | tay", 10)
else -> AsmFragment(" lda $variable | asl a | ror $variable", 10)
}
}
Opcode.SHL_WORD -> {
AsmFragment(" asl $variable | rol $variable+1", 10)
}
Opcode.SHR_UWORD -> {
AsmFragment(" lsr $variable+1 | ror $variable", 10)
}
Opcode.SHR_SWORD -> {
// arithmetic shift right (keep sign bit)
AsmFragment(" lda $variable+1 | asl a | ror $variable+1 | ror $variable", 10)
}
Opcode.ROL_BYTE -> {
when (variable) {
"A" -> AsmFragment(" rol a", 10)
"X" -> AsmFragment(" txa | rol a | tax", 10)
"Y" -> AsmFragment(" tya | rol a | tay", 10)
else -> AsmFragment(" rol $variable", 10)
}
}
Opcode.ROR_BYTE -> {
when (variable) {
"A" -> AsmFragment(" ror a", 10)
"X" -> AsmFragment(" txa | ror a | tax", 10)
"Y" -> AsmFragment(" tya | ror a | tay", 10)
else -> AsmFragment(" ror $variable", 10)
}
}
Opcode.ROL_WORD -> {
AsmFragment(" rol $variable | rol $variable+1", 10)
}
Opcode.ROR_WORD -> {
AsmFragment(" ror $variable+1 | ror $variable", 10)
}
Opcode.ROL2_BYTE -> { // 8-bit rol
when (variable) {
"A" -> AsmFragment(" cmp #\$80 | rol a", 10)
"X" -> AsmFragment(" txa | cmp #\$80 | rol a | tax", 10)
"Y" -> AsmFragment(" tya | cmp #\$80 | rol a | tay", 10)
else -> AsmFragment(" lda $variable | cmp #\$80 | rol $variable", 10)
}
}
Opcode.ROR2_BYTE -> { // 8-bit ror
when (variable) {
"A" -> AsmFragment(" lsr a | bcc + | ora #\$80 |+", 10)
"X" -> AsmFragment(" txa | lsr a | bcc + | ora #\$80 |+ | tax", 10)
"Y" -> AsmFragment(" tya | lsr a | bcc + | ora #\$80 |+ | tay", 10)
else -> AsmFragment(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable", 10)
}
}
Opcode.ROL2_WORD -> {
AsmFragment(" lda $variable | cmp #\$80 | rol $variable | rol $variable+1", 10)
}
Opcode.ROR2_WORD -> {
AsmFragment(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+", 30)
}
else -> null
}
}
private class AsmFragment(val asm: String, var segmentSize: Int=0)
}

View File

@ -1,559 +0,0 @@
package compiler.target.c64.codegen
import prog8.compiler.CompilerException
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.IntermediateProgram
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS2_HEX
import prog8.compiler.toHex
import prog8.vm.stackvm.Syscall
import prog8.vm.stackvm.syscallsForStackVm
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
private var breakpointCounter = 0
internal fun simpleInstr2Asm(ins: Instruction, block: IntermediateProgram.ProgramBlock): String? {
// a label 'instruction' is simply translated into a asm label
if(ins is LabelInstr) {
val labelresult =
if(ins.name.startsWith("${block.name}."))
ins.name.substring(block.name.length+1)
else
ins.name
return if(ins.asmProc) labelresult+"\t\t.proc" else labelresult
}
// simple opcodes that are translated directly into one or a few asm instructions
return when(ins.opcode) {
Opcode.LINE -> " ;\tsrc line: ${ins.callLabel}"
Opcode.NOP -> " nop" // shouldn't be present anymore though
Opcode.START_PROCDEF -> "" // is done as part of a label
Opcode.END_PROCDEF -> " .pend"
Opcode.TERMINATE -> " brk"
Opcode.SEC -> " sec"
Opcode.CLC -> " clc"
Opcode.SEI -> " sei"
Opcode.CLI -> " cli"
Opcode.CARRY_TO_A -> " lda #0 | adc #0"
Opcode.JUMP -> {
if(ins.callLabel!=null)
" jmp ${ins.callLabel}"
else
" jmp ${hexVal(ins)}"
}
Opcode.CALL -> {
if(ins.callLabel!=null)
" jsr ${ins.callLabel}"
else
" jsr ${hexVal(ins)}"
}
Opcode.RETURN -> " rts"
Opcode.RSAVE -> {
// save cpu status flag and all registers A, X, Y.
// see http://6502.org/tutorials/register_preservation.html
" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}"
}
Opcode.RRESTORE -> {
// restore all registers and cpu status flag
" pla | tay | pla | tax | pla | plp"
}
Opcode.RSAVEX -> " sta ${C64Zeropage.SCRATCH_REG} | txa | pha | lda ${C64Zeropage.SCRATCH_REG}"
Opcode.RRESTOREX -> " sta ${C64Zeropage.SCRATCH_REG} | pla | tax | lda ${C64Zeropage.SCRATCH_REG}"
Opcode.DISCARD_BYTE -> " inx"
Opcode.DISCARD_WORD -> " inx"
Opcode.DISCARD_FLOAT -> " inx | inx | inx"
Opcode.DUP_B -> {
" lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | dex | ;DUP_B "
}
Opcode.DUP_W -> {
" lda $ESTACK_LO_PLUS1_HEX,x | sta $ESTACK_LO_HEX,x | lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_HI_HEX,x | dex "
}
Opcode.CMP_B, Opcode.CMP_UB -> {
" inx | lda $ESTACK_LO_HEX,x | cmp #${ins.arg!!.integerValue().toHex()} | ;CMP_B "
}
Opcode.CMP_W, Opcode.CMP_UW -> {
"""
inx
lda $ESTACK_HI_HEX,x
cmp #>${ins.arg!!.integerValue().toHex()}
bne +
lda $ESTACK_LO_HEX,x
cmp #<${ins.arg.integerValue().toHex()}
; bne + not necessary?
; lda #0 not necessary?
+
"""
}
Opcode.INLINE_ASSEMBLY -> "@inline@" + (ins.callLabel2 ?: "") // All of the inline assembly is stored in the calllabel2 property. the '@inline@' is a special marker to accept it.
Opcode.INCLUDE_FILE -> {
val offset = if(ins.arg==null) "" else ", ${ins.arg.integerValue()}"
val length = if(ins.arg2==null) "" else ", ${ins.arg2.integerValue()}"
" .binary \"${ins.callLabel}\" $offset $length"
}
Opcode.SYSCALL -> {
if (ins.arg!!.numericValue() in syscallsForStackVm.map { it.callNr })
throw CompilerException("cannot translate vm syscalls to real assembly calls - use *real* subroutine calls instead. Syscall ${ins.arg.numericValue()}")
val call = Syscall.values().find { it.callNr==ins.arg.numericValue() }
when(call) {
Syscall.FUNC_SIN,
Syscall.FUNC_COS,
Syscall.FUNC_ABS,
Syscall.FUNC_TAN,
Syscall.FUNC_ATAN,
Syscall.FUNC_LN,
Syscall.FUNC_LOG2,
Syscall.FUNC_SQRT,
Syscall.FUNC_RAD,
Syscall.FUNC_DEG,
Syscall.FUNC_ROUND,
Syscall.FUNC_FLOOR,
Syscall.FUNC_CEIL,
Syscall.FUNC_RNDF,
Syscall.FUNC_ANY_F,
Syscall.FUNC_ALL_F,
Syscall.FUNC_MAX_F,
Syscall.FUNC_MIN_F,
Syscall.FUNC_SUM_F -> " jsr c64flt.${call.name.toLowerCase()}"
null -> ""
else -> " jsr prog8_lib.${call.name.toLowerCase()}"
}
}
Opcode.BREAKPOINT -> {
breakpointCounter++
"_prog8_breakpoint_$breakpointCounter\tnop"
}
Opcode.PUSH_BYTE -> {
" lda #${hexVal(ins)} | sta $ESTACK_LO_HEX,x | dex"
}
Opcode.PUSH_WORD -> {
val value = hexVal(ins)
" lda #<$value | sta $ESTACK_LO_HEX,x | lda #>$value | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.PUSH_FLOAT -> {
val floatConst = getFloatConst(ins.arg!!)
" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float"
}
Opcode.PUSH_VAR_BYTE -> {
when(ins.callLabel) {
"X" -> throw CompilerException("makes no sense to push X, it's used as a stack pointer itself. You should probably not use the X register (or only in trivial assignments)")
"A" -> " sta $ESTACK_LO_HEX,x | dex"
"Y" -> " tya | sta $ESTACK_LO_HEX,x | dex"
else -> " lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | dex"
}
}
Opcode.PUSH_VAR_WORD -> {
" lda ${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda ${ins.callLabel}+1 | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.PUSH_VAR_FLOAT -> " lda #<${ins.callLabel} | ldy #>${ins.callLabel}| jsr c64flt.push_float"
Opcode.PUSH_MEM_B, Opcode.PUSH_MEM_UB -> {
"""
lda ${hexVal(ins)}
sta $ESTACK_LO_HEX,x
dex
"""
}
Opcode.PUSH_MEM_W, Opcode.PUSH_MEM_UW -> {
"""
lda ${hexVal(ins)}
sta $ESTACK_LO_HEX,x
lda ${hexValPlusOne(ins)}
sta $ESTACK_HI_HEX,x
dex
"""
}
Opcode.PUSH_MEM_FLOAT -> {
" lda #<${hexVal(ins)} | ldy #>${hexVal(ins)}| jsr c64flt.push_float"
}
Opcode.PUSH_MEMREAD -> {
"""
lda $ESTACK_LO_PLUS1_HEX,x
sta (+) +1
lda $ESTACK_HI_PLUS1_HEX,x
sta (+) +2
+ lda 65535 ; modified
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.PUSH_REGAY_WORD -> {
" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex "
}
Opcode.PUSH_ADDR_HEAPVAR -> {
" lda #<${ins.callLabel} | sta $ESTACK_LO_HEX,x | lda #>${ins.callLabel} | sta $ESTACK_HI_HEX,x | dex"
}
Opcode.POP_REGAX_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGXY_WORD -> throw AssemblyError("cannot load X register from stack because it's used as the stack pointer itself")
Opcode.POP_REGAY_WORD -> {
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x "
}
Opcode.READ_INDEXED_VAR_BYTE -> {
"""
ldy $ESTACK_LO_PLUS1_HEX,x
lda ${ins.callLabel},y
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.READ_INDEXED_VAR_WORD -> {
"""
lda $ESTACK_LO_PLUS1_HEX,x
asl a
tay
lda ${ins.callLabel},y
sta $ESTACK_LO_PLUS1_HEX,x
lda ${ins.callLabel}+1,y
sta $ESTACK_HI_PLUS1_HEX,x
"""
}
Opcode.READ_INDEXED_VAR_FLOAT -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.push_float_from_indexed_var
"""
}
Opcode.WRITE_INDEXED_VAR_BYTE -> {
"""
inx
ldy $ESTACK_LO_HEX,x
inx
lda $ESTACK_LO_HEX,x
sta ${ins.callLabel},y
"""
}
Opcode.WRITE_INDEXED_VAR_WORD -> {
"""
inx
lda $ESTACK_LO_HEX,x
asl a
tay
inx
lda $ESTACK_LO_HEX,x
sta ${ins.callLabel},y
lda $ESTACK_HI_HEX,x
sta ${ins.callLabel}+1,y
"""
}
Opcode.WRITE_INDEXED_VAR_FLOAT -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.pop_float_to_indexed_var
"""
}
Opcode.POP_MEM_BYTE -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta ${hexVal(ins)}
"""
}
Opcode.POP_MEM_WORD -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta ${hexVal(ins)}
lda $ESTACK_HI_HEX,x
sta ${hexValPlusOne(ins)}
"""
}
Opcode.POP_MEM_FLOAT -> {
" lda ${hexVal(ins)} | ldy ${hexValPlusOne(ins)} | jsr c64flt.pop_float"
}
Opcode.POP_MEMWRITE -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
inx
lda $ESTACK_LO_HEX,x
+ sta 65535 ; modified
"""
}
Opcode.POP_VAR_BYTE -> {
when (ins.callLabel) {
"X" -> throw CompilerException("makes no sense to pop X, it's used as a stack pointer itself")
"A" -> " inx | lda $ESTACK_LO_HEX,x"
"Y" -> " inx | ldy $ESTACK_LO_HEX,x"
else -> " inx | lda $ESTACK_LO_HEX,x | sta ${ins.callLabel}"
}
}
Opcode.POP_VAR_WORD -> {
" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x | sta ${ins.callLabel} | sty ${ins.callLabel}+1"
}
Opcode.POP_VAR_FLOAT -> {
" lda #<${ins.callLabel} | ldy #>${ins.callLabel} | jsr c64flt.pop_float"
}
Opcode.INC_VAR_UB, Opcode.INC_VAR_B -> {
when (ins.callLabel) {
"A" -> " clc | adc #1"
"X" -> " inx"
"Y" -> " iny"
else -> " inc ${ins.callLabel}"
}
}
Opcode.INC_VAR_UW, Opcode.INC_VAR_W -> {
" inc ${ins.callLabel} | bne + | inc ${ins.callLabel}+1 |+"
}
Opcode.INC_VAR_F -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.inc_var_f
"""
}
Opcode.POP_INC_MEMORY -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
+ inc 65535 ; modified
"""
}
Opcode.POP_DEC_MEMORY -> {
"""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
+ dec 65535 ; modified
"""
}
Opcode.DEC_VAR_UB, Opcode.DEC_VAR_B -> {
when (ins.callLabel) {
"A" -> " sec | sbc #1"
"X" -> " dex"
"Y" -> " dey"
else -> " dec ${ins.callLabel}"
}
}
Opcode.DEC_VAR_UW, Opcode.DEC_VAR_W -> {
" lda ${ins.callLabel} | bne + | dec ${ins.callLabel}+1 |+ | dec ${ins.callLabel}"
}
Opcode.DEC_VAR_F -> {
"""
lda #<${ins.callLabel}
ldy #>${ins.callLabel}
jsr c64flt.dec_var_f
"""
}
Opcode.INC_MEMORY -> " inc ${hexVal(ins)}"
Opcode.DEC_MEMORY -> " dec ${hexVal(ins)}"
Opcode.INC_INDEXED_VAR_B, Opcode.INC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | inc ${ins.callLabel},x | pla | tax"
Opcode.DEC_INDEXED_VAR_B, Opcode.DEC_INDEXED_VAR_UB -> " inx | txa | pha | lda $ESTACK_LO_HEX,x | tax | dec ${ins.callLabel},x | pla | tax"
Opcode.NEG_B -> " jsr prog8_lib.neg_b"
Opcode.NEG_W -> " jsr prog8_lib.neg_w"
Opcode.NEG_F -> " jsr c64flt.neg_f"
Opcode.ABS_B -> " jsr prog8_lib.abs_b"
Opcode.ABS_W -> " jsr prog8_lib.abs_w"
Opcode.ABS_F -> " jsr c64flt.abs_f"
Opcode.POW_F -> " jsr c64flt.pow_f"
Opcode.INV_BYTE -> {
"""
lda $ESTACK_LO_PLUS1_HEX,x
eor #255
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.INV_WORD -> " jsr prog8_lib.inv_word"
Opcode.NOT_BYTE -> " jsr prog8_lib.not_byte"
Opcode.NOT_WORD -> " jsr prog8_lib.not_word"
Opcode.BCS -> {
val label = ins.callLabel ?: hexVal(ins)
" bcs $label"
}
Opcode.BCC -> {
val label = ins.callLabel ?: hexVal(ins)
" bcc $label"
}
Opcode.BNEG -> {
val label = ins.callLabel ?: hexVal(ins)
" bmi $label"
}
Opcode.BPOS -> {
val label = ins.callLabel ?: hexVal(ins)
" bpl $label"
}
Opcode.BVC -> {
val label = ins.callLabel ?: hexVal(ins)
" bvc $label"
}
Opcode.BVS -> {
val label = ins.callLabel ?: hexVal(ins)
" bvs $label"
}
Opcode.BZ -> {
val label = ins.callLabel ?: hexVal(ins)
" beq $label"
}
Opcode.BNZ -> {
val label = ins.callLabel ?: hexVal(ins)
" bne $label"
}
Opcode.JZ -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
beq $label
"""
}
Opcode.JZW -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
beq $label
lda $ESTACK_HI_HEX,x
beq $label
"""
}
Opcode.JNZ -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
bne $label
"""
}
Opcode.JNZW -> {
val label = ins.callLabel ?: hexVal(ins)
"""
inx
lda $ESTACK_LO_HEX,x
bne $label
lda $ESTACK_HI_HEX,x
bne $label
"""
}
Opcode.CAST_B_TO_UB -> "" // is a no-op, just carry on with the byte as-is
Opcode.CAST_UB_TO_B -> "" // is a no-op, just carry on with the byte as-is
Opcode.CAST_W_TO_UW -> "" // is a no-op, just carry on with the word as-is
Opcode.CAST_UW_TO_W -> "" // is a no-op, just carry on with the word as-is
Opcode.CAST_W_TO_UB -> "" // is a no-op, just carry on with the lsb of the word as-is
Opcode.CAST_W_TO_B -> "" // is a no-op, just carry on with the lsb of the word as-is
Opcode.CAST_UW_TO_UB -> "" // is a no-op, just carry on with the lsb of the uword as-is
Opcode.CAST_UW_TO_B -> "" // is a no-op, just carry on with the lsb of the uword as-is
Opcode.CAST_UB_TO_F -> " jsr c64flt.stack_ub2float"
Opcode.CAST_B_TO_F -> " jsr c64flt.stack_b2float"
Opcode.CAST_UW_TO_F -> " jsr c64flt.stack_uw2float"
Opcode.CAST_W_TO_F -> " jsr c64flt.stack_w2float"
Opcode.CAST_F_TO_UB -> " jsr c64flt.stack_float2ub"
Opcode.CAST_F_TO_B -> " jsr c64flt.stack_float2b"
Opcode.CAST_F_TO_UW -> " jsr c64flt.stack_float2uw"
Opcode.CAST_F_TO_W -> " jsr c64flt.stack_float2w"
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta $ESTACK_HI_PLUS1_HEX,x" // clear the msb
Opcode.CAST_B_TO_UW, Opcode.CAST_B_TO_W -> " lda $ESTACK_LO_PLUS1_HEX,x | ${signExtendA("$ESTACK_HI_PLUS1_HEX,x")}" // sign extend the lsb
Opcode.MSB -> " lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x"
Opcode.MKWORD -> " inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x "
Opcode.ADD_UB, Opcode.ADD_B -> { // TODO inline better (pattern with more opcodes)
"""
lda $ESTACK_LO_PLUS2_HEX,x
clc
adc $ESTACK_LO_PLUS1_HEX,x
inx
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.SUB_UB, Opcode.SUB_B -> { // TODO inline better (pattern with more opcodes)
"""
lda $ESTACK_LO_PLUS2_HEX,x
sec
sbc $ESTACK_LO_PLUS1_HEX,x
inx
sta $ESTACK_LO_PLUS1_HEX,x
"""
}
Opcode.ADD_W, Opcode.ADD_UW -> " jsr prog8_lib.add_w"
Opcode.SUB_W, Opcode.SUB_UW -> " jsr prog8_lib.sub_w"
Opcode.MUL_B, Opcode.MUL_UB -> " jsr prog8_lib.mul_byte"
Opcode.MUL_W, Opcode.MUL_UW -> " jsr prog8_lib.mul_word"
Opcode.MUL_F -> " jsr c64flt.mul_f"
Opcode.ADD_F -> " jsr c64flt.add_f"
Opcode.SUB_F -> " jsr c64flt.sub_f"
Opcode.DIV_F -> " jsr c64flt.div_f"
Opcode.IDIV_UB -> " jsr prog8_lib.idiv_ub"
Opcode.IDIV_B -> " jsr prog8_lib.idiv_b"
Opcode.IDIV_W -> " jsr prog8_lib.idiv_w"
Opcode.IDIV_UW -> " jsr prog8_lib.idiv_uw"
Opcode.AND_BYTE -> " jsr prog8_lib.and_b"
Opcode.OR_BYTE -> " jsr prog8_lib.or_b"
Opcode.XOR_BYTE -> " jsr prog8_lib.xor_b"
Opcode.AND_WORD -> " jsr prog8_lib.and_w"
Opcode.OR_WORD -> " jsr prog8_lib.or_w"
Opcode.XOR_WORD -> " jsr prog8_lib.xor_w"
Opcode.BITAND_BYTE -> " jsr prog8_lib.bitand_b"
Opcode.BITOR_BYTE -> " jsr prog8_lib.bitor_b"
Opcode.BITXOR_BYTE -> " jsr prog8_lib.bitxor_b"
Opcode.BITAND_WORD -> " jsr prog8_lib.bitand_w"
Opcode.BITOR_WORD -> " jsr prog8_lib.bitor_w"
Opcode.BITXOR_WORD -> " jsr prog8_lib.bitxor_w"
Opcode.REMAINDER_UB -> " jsr prog8_lib.remainder_ub"
Opcode.REMAINDER_UW -> " jsr prog8_lib.remainder_uw"
Opcode.GREATER_B -> " jsr prog8_lib.greater_b"
Opcode.GREATER_UB -> " jsr prog8_lib.greater_ub"
Opcode.GREATER_W -> " jsr prog8_lib.greater_w"
Opcode.GREATER_UW -> " jsr prog8_lib.greater_uw"
Opcode.GREATER_F -> " jsr c64flt.greater_f"
Opcode.GREATEREQ_B -> " jsr prog8_lib.greatereq_b"
Opcode.GREATEREQ_UB -> " jsr prog8_lib.greatereq_ub"
Opcode.GREATEREQ_W -> " jsr prog8_lib.greatereq_w"
Opcode.GREATEREQ_UW -> " jsr prog8_lib.greatereq_uw"
Opcode.GREATEREQ_F -> " jsr c64flt.greatereq_f"
Opcode.EQUAL_BYTE -> " jsr prog8_lib.equal_b"
Opcode.EQUAL_WORD -> " jsr prog8_lib.equal_w"
Opcode.EQUAL_F -> " jsr c64flt.equal_f"
Opcode.NOTEQUAL_BYTE -> " jsr prog8_lib.notequal_b"
Opcode.NOTEQUAL_WORD -> " jsr prog8_lib.notequal_w"
Opcode.NOTEQUAL_F -> " jsr c64flt.notequal_f"
Opcode.LESS_UB -> " jsr prog8_lib.less_ub"
Opcode.LESS_B -> " jsr prog8_lib.less_b"
Opcode.LESS_UW -> " jsr prog8_lib.less_uw"
Opcode.LESS_W -> " jsr prog8_lib.less_w"
Opcode.LESS_F -> " jsr c64flt.less_f"
Opcode.LESSEQ_UB -> " jsr prog8_lib.lesseq_ub"
Opcode.LESSEQ_B -> " jsr prog8_lib.lesseq_b"
Opcode.LESSEQ_UW -> " jsr prog8_lib.lesseq_uw"
Opcode.LESSEQ_W -> " jsr prog8_lib.lesseq_w"
Opcode.LESSEQ_F -> " jsr c64flt.lesseq_f"
Opcode.SHIFTEDL_BYTE -> " asl $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDL_WORD -> " asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x"
Opcode.SHIFTEDR_SBYTE -> " lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_UBYTE -> " lsr $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_SWORD -> " lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
Opcode.SHIFTEDR_UWORD -> " lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x"
else -> null
}
}

View File

@ -1,47 +0,0 @@
package prog8.vm.stackvm
import prog8.printSoftwareHeader
import prog8.vm.astvm.ScreenDialog
import java.awt.EventQueue
import javax.swing.Timer
import kotlin.system.exitProcess
fun main(args: Array<String>) {
stackVmMain(args)
}
fun stackVmMain(args: Array<String>) {
printSoftwareHeader("StackVM")
if(args.size != 1) {
System.err.println("requires one argument: name of stackvm sourcecode file")
exitProcess(1)
}
val program = Program.load(args.first())
val vm = StackVm(traceOutputFile = null)
val dialog = ScreenDialog("StackVM")
vm.load(program, dialog.canvas)
EventQueue.invokeLater {
dialog.pack()
dialog.isVisible = true
dialog.start()
val programTimer = Timer(10) { a ->
try {
vm.step()
} catch(bp: VmBreakpointException) {
println("Breakpoint: execution halted. Press enter to resume.")
readLine()
} catch (tx: VmTerminationException) {
println("Execution halted: ${tx.message}")
(a.source as Timer).stop()
}
}
val irqTimer = Timer(1000/60) { a -> vm.irq(a.`when`) }
programTimer.start()
irqTimer.start()
}
}

View File

@ -1,302 +0,0 @@
package prog8.vm.stackvm
import prog8.ast.antlr.unescape
import prog8.ast.base.*
import prog8.ast.expressions.AddressOf
import prog8.ast.expressions.IdentifierReference
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.intermediate.Instruction
import prog8.compiler.intermediate.LabelInstr
import prog8.compiler.intermediate.Opcode
import prog8.compiler.intermediate.opcodesWithVarArgument
import prog8.vm.RuntimeValue
import java.io.File
import java.util.*
import java.util.regex.Pattern
class Program (val name: String,
val program: MutableList<Instruction>,
val variables: Map<String, RuntimeValue>,
val memoryPointers: Map<String, Pair<Int, DataType>>,
val labels: Map<String, Int>,
val memory: Map<Int, List<RuntimeValue>>,
val heap: HeapValues)
{
init {
// add end of program marker and some sentinel instructions, to correctly connect all others
program.add(LabelInstr("____program_end", false))
program.add(Instruction(Opcode.TERMINATE))
program.add(Instruction(Opcode.NOP))
}
companion object {
fun load(filename: String): Program {
val lines = File(filename).readLines().withIndex().iterator()
val memory = mutableMapOf<Int, List<RuntimeValue>>()
val heap = HeapValues()
val program = mutableListOf<Instruction>()
val variables = mutableMapOf<String, RuntimeValue>()
val memoryPointers = mutableMapOf<String, Pair<Int, DataType>>()
val labels = mutableMapOf<String, Int>()
while(lines.hasNext()) {
val (lineNr, line) = lines.next()
if(line.startsWith(';') || line.isEmpty())
continue
else if(line=="%memory")
loadMemory(lines, memory)
else if(line=="%heap")
loadHeap(lines, heap)
else if(line.startsWith("%block "))
loadBlock(lines, heap, program, variables, memoryPointers, labels)
else throw VmExecutionException("syntax error at line ${lineNr + 1}")
}
return Program(filename, program, variables, memoryPointers, labels, memory, heap)
}
private fun loadBlock(lines: Iterator<IndexedValue<String>>,
heap: HeapValues,
program: MutableList<Instruction>,
variables: MutableMap<String, RuntimeValue>,
memoryPointers: MutableMap<String, Pair<Int, DataType>>,
labels: MutableMap<String, Int>)
{
while(true) {
val (_, line) = lines.next()
if(line.isEmpty())
continue
else if(line=="%end_block")
return
else if(line=="%variables")
loadVars(lines, variables)
else if(line=="%memorypointers")
loadMemoryPointers(lines, memoryPointers, heap)
else if(line=="%instructions") {
val (blockInstructions, blockLabels) = loadInstructions(lines, heap)
val baseIndex = program.size
program.addAll(blockInstructions)
val labelsWithIndex = blockLabels.mapValues { baseIndex+blockInstructions.indexOf(it.value) }
labels.putAll(labelsWithIndex)
}
}
}
private fun loadHeap(lines: Iterator<IndexedValue<String>>, heap: HeapValues) {
val splitpattern = Pattern.compile("\\s+")
val heapvalues = mutableListOf<Triple<Int, DataType, String>>()
while(true) {
val (_, line) = lines.next()
if (line == "%end_heap")
break
val parts = line.split(splitpattern, limit=3)
val value = Triple(parts[0].toInt(), DataType.valueOf(parts[1].toUpperCase()), parts[2])
heapvalues.add(value)
}
heapvalues.sortedBy { it.first }.forEach {
when(it.second) {
DataType.STR, DataType.STR_S -> heap.addString(it.second, unescape(it.third.substring(1, it.third.length - 1), Position("<stackvmsource>", 0, 0, 0)))
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numbers = it.third.substring(1, it.third.length-1).split(',')
val intarray = numbers.map{number->
val num=number.trim()
if(num.startsWith("&")) {
// it's AddressOf
val scopedname = num.substring(1)
val iref = IdentifierReference(scopedname.split('.'), Position("<intermediate>", 0, 0, 0))
val addrOf = AddressOf(iref, Position("<intermediate>", 0, 0, 0))
addrOf.scopedname=scopedname
IntegerOrAddressOf(null, addrOf)
} else {
IntegerOrAddressOf(num.toInt(), null)
}
}.toTypedArray()
heap.addIntegerArray(it.second, intarray)
}
DataType.ARRAY_F -> {
val numbers = it.third.substring(1, it.third.length-1).split(',')
val doublearray = numbers.map{number->number.trim().toDouble()}.toDoubleArray()
heap.addDoublesArray(doublearray)
}
in NumericDatatypes -> throw VmExecutionException("invalid heap value type ${it.second}")
else -> throw VmExecutionException("weird datatype")
}
}
}
private fun loadInstructions(lines: Iterator<IndexedValue<String>>, heap: HeapValues): Pair<MutableList<Instruction>, Map<String, Instruction>> {
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 isSameAs line
while(true) {
val (lineNr, line) = lines.next()
if(line.isEmpty())
continue
if(line=="%end_instructions")
return Pair(instructions, labels)
if(!line.startsWith(' ') && line.endsWith(':')) {
nextInstructionLabels.push(line.substring(0, line.length-1))
} else if(line.startsWith(' ')) {
val parts = line.trimStart().split(splitpattern, limit = 2)
val opcodeStr = parts[0].toUpperCase()
val opcode= Opcode.valueOf(if(opcodeStr.startsWith('_')) opcodeStr.substring(1) else opcodeStr)
val args = if(parts.size==2) parts[1] else null
val instruction = when(opcode) {
Opcode.LINE -> Instruction(opcode, null, callLabel = args)
Opcode.JUMP, Opcode.CALL, Opcode.BNEG, Opcode.BPOS,
Opcode.BZ, Opcode.BNZ, Opcode.BCS, Opcode.BCC,
Opcode.JZ, Opcode.JNZ, Opcode.JZW, Opcode.JNZW -> {
if(args!!.startsWith('$')) {
Instruction(opcode, RuntimeValue(DataType.UWORD, args.substring(1).toInt(16)))
} else {
Instruction(opcode, callLabel = args)
}
}
in opcodesWithVarArgument -> {
val withoutQuotes =
if(args!!.startsWith('"') && args.endsWith('"'))
args.substring(1, args.length-1) else args
Instruction(opcode, callLabel = withoutQuotes)
}
Opcode.SYSCALL -> {
if(args!! in syscallNames) {
val call = Syscall.valueOf(args)
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
} else {
val args2 = args.replace('.', '_')
if(args2 in syscallNames) {
val call = Syscall.valueOf(args2)
Instruction(opcode, RuntimeValue(DataType.UBYTE, call.callNr))
} else {
// the syscall is not yet implemented. emit a stub.
Instruction(Opcode.SYSCALL, RuntimeValue(DataType.UBYTE, Syscall.SYSCALLSTUB.callNr), callLabel = args2)
}
}
}
Opcode.INCLUDE_FILE -> {
val argparts = args!!.split(' ')
val filename = argparts[0]
val offset = if(argparts.size>=2 && argparts[1]!="null") getArgValue(argparts[1], heap) else null
val length = if(argparts.size>=3 && argparts[2]!="null") getArgValue(argparts[2], heap) else null
Instruction(opcode, offset, length, filename)
}
else -> {
Instruction(opcode, getArgValue(args, heap))
}
}
instructions.add(instruction)
while(nextInstructionLabels.isNotEmpty()) {
val label = nextInstructionLabels.pop()
labels[label] = instruction
}
} else throw VmExecutionException("syntax error at line ${lineNr + 1}")
}
}
private fun getArgValue(args: String?, heap: HeapValues): RuntimeValue? {
if(args==null)
return null
if(args[0]=='"' && args[args.length-1]=='"') {
throw VmExecutionException("encountered a string arg value, but all strings should already have been moved into the heap")
}
val (type, valueStr) = args.split(':')
return when(type) {
"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()
RuntimeValue(heap.get(heapId).type, heapId = heapId)
}
else -> throw VmExecutionException("invalid datatype $type")
}
}
private fun loadVars(lines: Iterator<IndexedValue<String>>,
vars: MutableMap<String, RuntimeValue>) {
val splitpattern = Pattern.compile("\\s+")
while(true) {
val (_, line) = lines.next()
if(line=="%end_variables")
return
val (name, typeStr, valueStr) = line.split(splitpattern, limit = 3)
if(valueStr[0] !='"' && ':' !in valueStr)
throw VmExecutionException("missing value type character")
val value = when(val type = DataType.valueOf(typeStr.toUpperCase())) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, valueStr.substring(3).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
DataType.BYTE -> RuntimeValue(DataType.BYTE, valueStr.substring(2).substringBefore(' ').toShort(16))// TODO process ZP and struct info?
DataType.UWORD -> RuntimeValue(DataType.UWORD, valueStr.substring(3).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
DataType.WORD -> RuntimeValue(DataType.WORD, valueStr.substring(2).substringBefore(' ').toInt(16))// TODO process ZP and struct info?
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, valueStr.substring(2).substringBefore(' ').toDouble())// TODO process ZP and struct info?
in StringDatatypes -> {
if(valueStr.startsWith('"') && valueStr.endsWith('"'))
throw VmExecutionException("encountered a var with a string value, but all string values should already have been moved into the heap")
else if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid string value, should be a heap reference")
else {
val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
RuntimeValue(type, heapId = heapId)
}
}
in ArrayDatatypes -> {
if(!valueStr.startsWith("heap:"))
throw VmExecutionException("invalid array value, should be a heap reference")
else {
val heapId = valueStr.substring(5).substringBefore(' ').toInt() // TODO process ZP and struct info?
RuntimeValue(type, heapId = heapId)
}
}
else -> throw VmExecutionException("weird datatype")
}
vars[name] = value
}
}
private fun loadMemoryPointers(lines: Iterator<IndexedValue<String>>,
pointers: MutableMap<String, Pair<Int, DataType>>,
heap: HeapValues) {
val splitpattern = Pattern.compile("\\s+")
while(true) {
val (_, line) = lines.next()
if(line=="%end_memorypointers")
return
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 = getArgValue(valueStr, heap)!!.integerValue()
pointers[name] = Pair(value, type)
}
}
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")
return memory
val address = line.substringBefore(' ').toInt(16)
val rest = line.substringAfter(' ').trim()
if(rest.startsWith('"')) {
TODO("memory init with char/string")
} else {
val valueStrings = rest.split(' ')
val values = mutableListOf<RuntimeValue>()
valueStrings.forEach {
when(it.length) {
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")
}
}
memory[address] = values
}
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -14,40 +14,36 @@ as used in many home computers from that era. It is a medium to low level progra
which aims to provide many conveniences over raw assembly code (even when using a macro assembler): which aims to provide many conveniences over raw assembly code (even when using a macro assembler):
- reduction of source code length - reduction of source code length
- easier program understanding (because it's higher level, and way more compact)
- modularity, symbol scoping, subroutines - modularity, symbol scoping, subroutines
- subroutines have enforced input- and output parameter definitions
- various data types other than just bytes (16-bit words, floats, strings) - various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string variables and string sharing - automatic variable allocations, automatic string and array variables and string sharing
- constant folding in expressions (compile-time evaluation) - subroutines with a input- and output parameter signature
- constant folding in expressions
- conditional branches - conditional branches
- when statement to provide a 'jump table' alternative to if/elseif chains - 'when' statement to provide a concise jump table alternative to if/elseif chains
- structs to group together sets of variables and manipulate them at once - structs to group together sets of variables and manipulate them at once
- automatic type conversions - floating point operations (requires the C64 Basic ROM routines for this)
- floating point operations (uses the C64 Basic ROM routines for this)
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses - abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- various code optimizations (code structure, logical and numerical expressions, unused code removal...) - various code optimizations (code structure, logical and numerical expressions, unused code removal...)
- inline assembly allows you to have full control when every cycle or byte matters - inline assembly allows you to have full control when every cycle or byte matters
- many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
Rapid edit-compile-run-debug cycle: Rapid edit-compile-run-debug cycle:
- use modern PC to work on - use modern PC to work on
- quick compilation times (around a second, and less than 0.1 seconds when using the continuous compilation mode) - quick compilation times (seconds)
- option to automatically run the program in the Vice emulator - option to automatically run the program in the Vice emulator
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them - breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly - source code labels automatically loaded in Vice emulator so it can show them in disassembly
- the compiler includes a virtual machine that can execute compiled code directy on the - virtual machine that can execute compiled code directy on the host system,
host system without having to actually convert it to assembly to run on a real 6502. without having to actually convert it to assembly to run on a real 6502
This allows for very quick experimentation and debugging
It is mainly targeted at the Commodore-64 machine at this time. It is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome. Contributions to add support for other 8-bit (or other?!) machines are welcome.
Documentation/manual Documentation/manual
-------------------- --------------------
See https://prog8.readthedocs.io/ This describes the language, but also how to build and run the compiler. See https://prog8.readthedocs.io/
Required tools Required tools
-------------- --------------

View File

@ -1,54 +1,61 @@
buildscript { buildscript {
dependencies { dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61"
} }
} }
plugins { plugins {
// id "org.jetbrains.kotlin.jvm" version $kotlinVersion // id "org.jetbrains.kotlin.jvm" version "1.3.61"
id 'application' id 'application'
id 'org.jetbrains.dokka' version "0.9.18" id 'org.jetbrains.dokka' version "0.9.18"
id 'com.github.johnrengelman.shadow' version '5.1.0' id 'com.github.johnrengelman.shadow' version '5.2.0'
id 'java' id 'java'
} }
apply plugin: "kotlin" apply plugin: "kotlin"
apply plugin: "java" apply plugin: "java"
targetCompatibility = 1.8
sourceCompatibility = 1.8
repositories { repositories {
mavenLocal() mavenLocal()
mavenCentral() mavenCentral()
jcenter() jcenter()
maven { url "https://dl.bintray.com/orangy/maven/" }
} }
sourceCompatibility = 1.8
def prog8version = rootProject.file('compiler/res/version.txt').text.trim() def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies { dependencies {
implementation project(':parser') implementation project(':parser')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" // implementation "org.jetbrains.kotlin:kotlin-reflect"
// runtime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" implementation 'org.antlr:antlr4-runtime:4.8'
runtime 'org.antlr:antlr4-runtime:4.7.2' implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
runtime project(':parser') // implementation 'net.razorvine:ksim65:1.6'
implementation project(':parser')
testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:$kotlinVersion" testImplementation "org.jetbrains.kotlin:kotlin-test-junit5"
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.3.2'
testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0' testImplementation 'org.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
} }
compileKotlin { compileKotlin {
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
verbose = true // verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference" // freeCompilerArgs += "-XXLanguage:+NewInference"
} }
} }
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
sourceSets { sourceSets {
main { main {
java { java {
@ -77,12 +84,9 @@ artifacts {
} }
// To create a fat-jar use the 'create_compiler_jar' script for now
// @todo investigate https://imperceptiblethoughts.com/shadow/introduction/
shadowJar { shadowJar {
baseName = 'prog8compiler' archiveBaseName = 'prog8compiler'
version = prog8version archiveVersion = prog8version
// minimize() // minimize()
} }

View File

@ -8,11 +8,12 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
</content> </content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" /> <orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" /> <orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
<orderEntry type="module" module-name="parser" /> <orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" /> <orderEntry type="library" name="unittest-libs" level="project" />
<orderEntry type="library" name="kotlinx-cli-jvm-0.1.0-dev-5" level="project" />
<orderEntry type="library" name="antlr-runtime-4.8" level="project" />
</component> </component>
</module> </module>

Binary file not shown.

View File

@ -954,6 +954,16 @@ func_sum_f .proc
bne - bne -
+ jmp push_fac1_as_result + jmp push_fac1_as_result
.pend .pend
sign_f .proc
jsr pop_float_fac1
jsr SIGN
sta c64.ESTACK_LO,x
dex
rts
.pend
}} }}
} ; ------ end of block c64flt } ; ------ end of block c64flt

View File

@ -15,28 +15,199 @@ c64utils {
const uword ESTACK_HI = $cf00 const uword ESTACK_HI = $cf00
; ----- utility functions ---- ; ----- number conversions to decimal strings
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A { asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
; ---- A to decimal string in Y/X/A (100s in Y, 10s in X, 1s in A) ; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{ %asm {{
ldy #$2f ldy #uword2decimal.ASCII_0_OFFSET
ldx #$3a bne uword2decimal.hex_try200
sec rts
- iny
sbc #100
bcs -
- dex
adc #10
bmi -
adc #$2f
rts
}} }}
} }
asmsub byte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A { asmsub uword2decimal (uword value @ AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- A (signed byte) to decimal string in Y/X/A (100s in Y, 10s in X, 1s in A) ; ---- convert 16 bit uword in A/Y to decimal
; note: the '-' is not part of the conversion here if it's a negative number ; output in uword2decimal.decTenThousands, decThousands, decHundreds, decTens, decOnes
; (these are terminated by a zero byte so they can be easily printed)
; also returns Y = 100's, A = 10's, X = 1's
%asm {{
;Convert 16 bit Hex to Decimal (0-65535) Rev 2
;By Omegamatrix Further optimizations by tepples
; routine from http://forums.nesdev.com/viewtopic.php?f=2&t=11341&start=15
;HexToDec99
; start in A
; end with A = 10's, decOnes (also in X)
;HexToDec255
; start in A
; end with Y = 100's, A = 10's, decOnes (also in X)
;HexToDec999
; start with A = high byte, Y = low byte
; end with Y = 100's, A = 10's, decOnes (also in X)
; requires 1 extra temp register on top of decOnes, could combine
; these two if HexToDec65535 was eliminated...
;HexToDec65535
; start with A/Y (low/high) as 16 bit value
; end with decTenThousand, decThousand, Y = 100's, A = 10's, decOnes (also in X)
; (irmen: I store Y and A in decHundreds and decTens too, so all of it can be easily printed)
ASCII_0_OFFSET = $30
temp = c64.SCRATCH_ZPB1 ; byte in zeropage
hexHigh = c64.SCRATCH_ZPWORD1 ; byte in zeropage
hexLow = c64.SCRATCH_ZPWORD1+1 ; byte in zeropage
HexToDec65535; SUBROUTINE
sty hexHigh ;3 @9
sta hexLow ;3 @12
tya
tax ;2 @14
lsr a ;2 @16
lsr a ;2 @18 integer divide 1024 (result 0-63)
cpx #$A7 ;2 @20 account for overflow of multiplying 24 from 43,000 ($A7F8) onward,
adc #1 ;2 @22 we can just round it to $A700, and the divide by 1024 is fine...
;at this point we have a number 1-65 that we have to times by 24,
;add to original sum, and Mod 1024 to get a remainder 0-999
sta temp ;3 @25
asl a ;2 @27
adc temp ;3 @30 x3
tay ;2 @32
lsr a ;2 @34
lsr a ;2 @36
lsr a ;2 @38
lsr a ;2 @40
lsr a ;2 @42
tax ;2 @44
tya ;2 @46
asl a ;2 @48
asl a ;2 @50
asl a ;2 @52
clc ;2 @54
adc hexLow ;3 @57
sta hexLow ;3 @60
txa ;2 @62
adc hexHigh ;3 @65
sta hexHigh ;3 @68
ror a ;2 @70
lsr a ;2 @72
tay ;2 @74 integer divide 1,000 (result 0-65)
lsr a ;2 @76 split the 1,000 and 10,000 digit
tax ;2 @78
lda ShiftedBcdTab,x ;4 @82
tax ;2 @84
rol a ;2 @86
and #$0F ;2 @88
ora #ASCII_0_OFFSET
sta decThousands ;3 @91
txa ;2 @93
lsr a ;2 @95
lsr a ;2 @97
lsr a ;2 @99
ora #ASCII_0_OFFSET
sta decTenThousands ;3 @102
lda hexLow ;3 @105
cpy temp ;3 @108
bmi _doSubtract ;2³ @110/111
beq _useZero ;2³ @112/113
adc #23 + 24 ;2 @114
_doSubtract
sbc #23 ;2 @116
sta hexLow ;3 @119
_useZero
lda hexHigh ;3 @122
sbc #0 ;2 @124
Start100s
and #$03 ;2 @126
tax ;2 @128 0,1,2,3
cmp #2 ;2 @130
rol a ;2 @132 0,2,5,7
ora #ASCII_0_OFFSET
tay ;2 @134 Y = Hundreds digit
lda hexLow ;3 @137
adc Mod100Tab,x ;4 @141 adding remainder of 256, 512, and 256+512 (all mod 100)
bcs hex_doSub200 ;2³ @143/144
hex_try200
cmp #200 ;2 @145
bcc hex_try100 ;2³ @147/148
hex_doSub200
iny ;2 @149
iny ;2 @151
sbc #200 ;2 @153
hex_try100
cmp #100 ;2 @155
bcc HexToDec99 ;2³ @157/158
iny ;2 @159
sbc #100 ;2 @161
HexToDec99; SUBROUTINE
lsr a ;2 @163
tax ;2 @165
lda ShiftedBcdTab,x ;4 @169
tax ;2 @171
rol a ;2 @173
and #$0F ;2 @175
ora #ASCII_0_OFFSET
sta decOnes ;3 @178
txa ;2 @180
lsr a ;2 @182
lsr a ;2 @184
lsr a ;2 @186
ora #ASCII_0_OFFSET
; irmen: load X with ones, and store Y and A too, for easy printing afterwards
sty decHundreds
sta decTens
ldx decOnes
rts ;6 @192 Y=hundreds, A = tens digit, X=ones digit
HexToDec999; SUBROUTINE
sty hexLow ;3 @9
jmp Start100s ;3 @12
Mod100Tab
.byte 0,56,12,56+12
ShiftedBcdTab
.byte $00,$01,$02,$03,$04,$08,$09,$0A,$0B,$0C
.byte $10,$11,$12,$13,$14,$18,$19,$1A,$1B,$1C
.byte $20,$21,$22,$23,$24,$28,$29,$2A,$2B,$2C
.byte $30,$31,$32,$33,$34,$38,$39,$3A,$3B,$3C
.byte $40,$41,$42,$43,$44,$48,$49,$4A,$4B,$4C
decTenThousands .byte 0
decThousands .byte 0
decHundreds .byte 0
decTens .byte 0
decOnes .byte 0
.byte 0 ; zero-terminate the decimal output string
}}
}
; ----- utility functions ----
asmsub byte2decimal (byte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
; ---- A (signed byte) to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
; note: if the number is negative, you have to deal with the '-' yourself!
%asm {{ %asm {{
cmp #0 cmp #0
bpl + bpl +
@ -48,7 +219,7 @@ asmsub byte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A {
} }
asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y { asmsub ubyte2hex (ubyte value @ A) -> ubyte @ A, ubyte @ Y {
; ---- A to hex string in AY (first hex char in A, second hex char in Y) ; ---- A to hex petscii string in AY (first hex char in A, second hex char in Y)
%asm {{ %asm {{
stx c64.SCRATCH_ZPREGX stx c64.SCRATCH_ZPREGX
pha pha
@ -69,7 +240,6 @@ _hex_digits .text "0123456789abcdef" ; can probably be reused for other stuff as
}} }}
} }
asmsub uword2hex (uword value @ AY) clobbers(A,Y) { asmsub uword2hex (uword value @ AY) clobbers(A,Y) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated) ; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%asm {{ %asm {{
@ -87,92 +257,6 @@ output .text "0000", $00 ; 0-terminated output buffer (to make printing ea
}} }}
} }
asmsub uword2bcd (uword value @ AY) clobbers(A,Y) {
; Convert an 16 bit binary value to BCD
;
; This function converts a 16 bit binary value in A/Y into a 24 bit BCD. It
; works by transferring one bit a time from the source and adding it
; into a BCD value that is being doubled on each iteration. As all the
; arithmetic is being done in BCD the result is a binary to decimal
; conversion.
%asm {{
sta c64.SCRATCH_ZPB1
sty c64.SCRATCH_ZPREG
php
pla ; read status register
and #%00000100
sta _had_irqd
sei ; disable interrupts because of bcd math
sed ; switch to decimal mode
lda #0 ; ensure the result is clear
sta bcdbuff+0
sta bcdbuff+1
sta bcdbuff+2
ldy #16 ; the number of source bits
- asl c64.SCRATCH_ZPB1 ; shift out one bit
rol c64.SCRATCH_ZPREG
lda bcdbuff+0 ; and add into result
adc bcdbuff+0
sta bcdbuff+0
lda bcdbuff+1 ; propagating any carry
adc bcdbuff+1
sta bcdbuff+1
lda bcdbuff+2 ; ... thru whole result
adc bcdbuff+2
sta bcdbuff+2
dey ; and repeat for next bit
bne -
cld ; back to binary
lda _had_irqd
bne +
cli ; enable interrupts again (only if they were enabled before)
+ rts
_had_irqd .byte 0
bcdbuff .byte 0,0,0
}}
}
asmsub uword2decimal (uword value @ AY) clobbers(A) -> ubyte @ Y {
; ---- convert 16 bit uword in A/Y into 0-terminated decimal string into memory 'uword2decimal.output'
; returns length of resulting string in Y
%asm {{
jsr uword2bcd
lda uword2bcd.bcdbuff+2
clc
adc #'0'
sta output
ldy #1
lda uword2bcd.bcdbuff+1
jsr +
lda uword2bcd.bcdbuff+0
+ pha
lsr a
lsr a
lsr a
lsr a
clc
adc #'0'
sta output,y
iny
pla
and #$0f
adc #'0'
sta output,y
iny
lda #0
sta output,y
rts
output .text "00000", $00 ; 0 terminated
}}
}
asmsub str2uword(str string @ AY) -> uword @ AY { asmsub str2uword(str string @ AY) -> uword @ AY {
; -- returns the unsigned word value of the string number argument in AY ; -- returns the unsigned word value of the string number argument in AY
; the number may NOT be preceded by a + sign and may NOT contain spaces ; the number may NOT be preceded by a + sign and may NOT contain spaces
@ -227,7 +311,6 @@ _result_times_10 ; (W*4 + W)*2
}} }}
} }
asmsub str2word(str string @ AY) -> word @ AY { asmsub str2word(str string @ AY) -> word @ AY {
; -- returns the signed word value of the string number argument in AY ; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces ; the number may be preceded by a + or - sign but may NOT contain spaces
@ -283,7 +366,6 @@ _negative .byte 0
}} }}
} }
asmsub set_irqvec_excl() clobbers(A) { asmsub set_irqvec_excl() clobbers(A) {
%asm {{ %asm {{
sei sei
@ -341,6 +423,7 @@ _irq_handler_init
dex dex
dex dex
dex dex
cld
rts rts
_irq_handler_end _irq_handler_end
@ -372,7 +455,6 @@ IRQ_SCRATCH_ZPWORD2 .word 0
}} }}
} }
asmsub restore_irqvec() { asmsub restore_irqvec() {
%asm {{ %asm {{
sei sei
@ -389,7 +471,6 @@ asmsub restore_irqvec() {
}} }}
} }
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) { asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
%asm {{ %asm {{
sei sei
@ -454,13 +535,11 @@ _raster_irq_handler
} }
} ; ------ end of block c64utils } ; ------ end of block c64utils
c64scr { c64scr {
; ---- this block contains (character) Screen and text I/O related functions ---- ; ---- this block contains (character) Screen and text I/O related functions ----
@ -480,7 +559,6 @@ asmsub clear_screen (ubyte char @ A, ubyte color @ Y) clobbers(A) {
} }
asmsub clear_screenchars (ubyte char @ A) clobbers(Y) { asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
; ---- clear the character screen with the given fill character (leaves colors) ; ---- clear the character screen with the given fill character (leaves colors)
; (assumes screen matrix is at the default address) ; (assumes screen matrix is at the default address)
@ -521,7 +599,6 @@ _loop sta c64.Colors,y
}} }}
} }
asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) { asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
; ---- scroll the whole screen 1 character to the left ; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself ; contents of the rightmost column are unchanged, you should clear/refill this yourself
@ -582,7 +659,6 @@ _scroll_screen ; scroll the screen memory
}} }}
} }
asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) { asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character to the right ; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself ; contents of the leftmost column are unchanged, you should clear/refill this yourself
@ -635,7 +711,6 @@ _scroll_screen ; scroll the screen memory
}} }}
} }
asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) { asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character up ; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself ; contents of the bottom row are unchanged, you should refill/clear this yourself
@ -688,7 +763,6 @@ _scroll_screen ; scroll the screen memory
}} }}
} }
asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) { asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
; ---- scroll the whole screen 1 character down ; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself ; contents of the top row are unchanged, you should refill/clear this yourself
@ -742,7 +816,6 @@ _scroll_screen ; scroll the screen memory
} }
asmsub print (str text @ AY) clobbers(A,Y) { asmsub print (str text @ AY) clobbers(A,Y) {
; ---- print null terminated string from A/Y ; ---- print null terminated string from A/Y
; note: the compiler contains an optimization that will replace ; note: the compiler contains an optimization that will replace
@ -761,7 +834,6 @@ asmsub print (str text @ AY) clobbers(A,Y) {
}} }}
} }
asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) { asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total) ; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{ %asm {{
@ -770,16 +842,15 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
pha pha
tya tya
jsr c64.CHROUT jsr c64.CHROUT
txa
jsr c64.CHROUT
pla pla
jsr c64.CHROUT jsr c64.CHROUT
txa
jsr c64.CHROUT
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
rts rts
}} }}
} }
asmsub print_ub (ubyte value @ A) clobbers(A,Y) { asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s ; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{ %asm {{
@ -788,15 +859,14 @@ asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
_print_byte_digits _print_byte_digits
pha pha
cpy #'0' cpy #'0'
bne _print_hundreds beq +
cpx #'0' tya
bne _print_tens
jmp _end
_print_hundreds tya
jsr c64.CHROUT jsr c64.CHROUT
_print_tens txa + pla
jsr c64.CHROUT cmp #'0'
_end pla beq +
jsr c64.CHROUT
+ txa
jsr c64.CHROUT jsr c64.CHROUT
ldx c64.SCRATCH_ZPREGX ldx c64.SCRATCH_ZPREGX
rts rts
@ -820,7 +890,6 @@ asmsub print_b (byte value @ A) clobbers(A,Y) {
}} }}
} }
asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) { asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well) ; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%asm {{ %asm {{
@ -839,7 +908,6 @@ asmsub print_ubhex (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
}} }}
} }
asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) { asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well) ; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{ %asm {{
@ -861,7 +929,6 @@ asmsub print_ubbin (ubyte value @ A, ubyte prefix @ Pc) clobbers(A,Y) {
}} }}
} }
asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) { asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well) ; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%asm {{ %asm {{
@ -874,7 +941,6 @@ asmsub print_uwbin (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
}} }}
} }
asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) { asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
; ---- print the uword in A/Y in hexadecimal form (4 digits) ; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well) ; (if Carry is set, a radix prefix '$' is printed as well)
@ -888,51 +954,44 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) clobbers(A,Y) {
}} }}
} }
asmsub print_uw0 (uword value @ AY) clobbers(A,Y) { asmsub print_uw0 (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total) ; ---- print the uword in A/Y in decimal form, with left padding 0s (5 positions total)
%asm {{ %asm {{
stx c64.SCRATCH_ZPREGX
jsr c64utils.uword2decimal jsr c64utils.uword2decimal
ldy #0 ldy #0
- lda c64utils.uword2decimal.output,y - lda c64utils.uword2decimal.decTenThousands,y
beq +
jsr c64.CHROUT jsr c64.CHROUT
iny iny
cpy #5
bne - bne -
+ ldx c64.SCRATCH_ZPREGX
rts rts
}} }}
} }
asmsub print_uw (uword value @ AY) clobbers(A,Y) { asmsub print_uw (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s ; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{ %asm {{
stx c64.SCRATCH_ZPREGX
jsr c64utils.uword2decimal jsr c64utils.uword2decimal
ldy #0 ldy #0
lda c64utils.uword2decimal.output - lda c64utils.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0' cmp #'0'
bne _pr_decimal bne _gotdigit
iny
lda c64utils.uword2decimal.output+1
cmp #'0'
bne _pr_decimal
iny
lda c64utils.uword2decimal.output+2
cmp #'0'
bne _pr_decimal
iny
lda c64utils.uword2decimal.output+3
cmp #'0'
bne _pr_decimal
iny iny
bne -
_pr_decimal _gotdigit
lda c64utils.uword2decimal.output,y
jsr c64.CHROUT jsr c64.CHROUT
iny iny
cpy #5 lda c64utils.uword2decimal.decTenThousands,y
bcc _pr_decimal bne _gotdigit
rts rts
_allzero
lda #'0'
jmp c64.CHROUT
}} }}
} }

View File

@ -643,3 +643,40 @@ mul_word_40 .proc
sta c64.ESTACK_HI+1,x sta c64.ESTACK_HI+1,x
rts rts
.pend .pend
sign_b .proc
lda c64.ESTACK_LO+1,x
beq _sign_zero
bmi _sign_neg
_sign_pos lda #1
sta c64.ESTACK_LO+1,x
rts
_sign_neg lda #-1
_sign_zero sta c64.ESTACK_LO+1,x
rts
.pend
sign_ub .proc
lda c64.ESTACK_LO+1,x
beq sign_b._sign_zero
bne sign_b._sign_pos
.pend
sign_w .proc
lda c64.ESTACK_HI+1,x
bmi sign_b._sign_neg
beq sign_ub
bne sign_b._sign_pos
.pend
sign_uw .proc
lda c64.ESTACK_HI+1,x
beq _sign_possibly_zero
_sign_pos lda #1
sta c64.ESTACK_LO+1,x
rts
_sign_possibly_zero lda c64.ESTACK_LO+1,x
bne _sign_pos
sta c64.ESTACK_LO+1,x
rts
.pend

View File

@ -35,7 +35,7 @@ init_system .proc
rts rts
.pend .pend
read_byte_from_address .proc read_byte_from_address .proc
; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged) ; -- read the byte from the memory address on the top of the stack, return in A (stack remains unchanged)
lda c64.ESTACK_LO+1,x lda c64.ESTACK_LO+1,x
@ -45,7 +45,7 @@ read_byte_from_address .proc
+ lda $ffff ; modified + lda $ffff ; modified
rts rts
.pend .pend
add_a_to_zpword .proc add_a_to_zpword .proc
; -- add ubyte in A to the uword in c64.SCRATCH_ZPWORD1 ; -- add ubyte in A to the uword in c64.SCRATCH_ZPWORD1
@ -716,7 +716,7 @@ func_sin8 .proc
lda _sinecos8,y lda _sinecos8,y
sta c64.ESTACK_LO+1,x sta c64.ESTACK_LO+1,x
rts rts
_sinecos8 .char 127 * sin(range(256+64) * rad(360.0/256.0)) _sinecos8 .char trunc(127.0 * sin(range(256+64) * rad(360.0/256.0)))
.pend .pend
func_sin8u .proc func_sin8u .proc
@ -724,7 +724,7 @@ func_sin8u .proc
lda _sinecos8u,y lda _sinecos8u,y
sta c64.ESTACK_LO+1,x sta c64.ESTACK_LO+1,x
rts rts
_sinecos8u .byte 128 + 127.5 * sin(range(256+64) * rad(360.0/256.0)) _sinecos8u .byte trunc(128.0 + 127.5 * sin(range(256+64) * rad(360.0/256.0)))
.pend .pend
func_sin16 .proc func_sin16 .proc
@ -735,7 +735,7 @@ func_sin16 .proc
sta c64.ESTACK_HI+1,x sta c64.ESTACK_HI+1,x
rts rts
_ := 32767 * sin(range(256+64) * rad(360.0/256.0)) _ := trunc(32767.0 * sin(range(256+64) * rad(360.0/256.0)))
_sinecos8lo .byte <_ _sinecos8lo .byte <_
_sinecos8hi .byte >_ _sinecos8hi .byte >_
.pend .pend
@ -748,7 +748,7 @@ func_sin16u .proc
sta c64.ESTACK_HI+1,x sta c64.ESTACK_HI+1,x
rts rts
_ := 32768 + 32767.5 * sin(range(256+64) * rad(360.0/256.0)) _ := trunc(32768.0 + 32767.5 * sin(range(256+64) * rad(360.0/256.0)))
_sinecos8ulo .byte <_ _sinecos8ulo .byte <_
_sinecos8uhi .byte >_ _sinecos8uhi .byte >_
.pend .pend
@ -851,11 +851,12 @@ func_all_w .proc
bne + bne +
iny iny
lda (c64.SCRATCH_ZPWORD1),y lda (c64.SCRATCH_ZPWORD1),y
bne + bne ++
lda #0 lda #0
sta c64.ESTACK_LO+1,x sta c64.ESTACK_LO+1,x
rts rts
+ iny + iny
+ iny
_cmp_mod cpy #255 ; modified _cmp_mod cpy #255 ; modified
bne - bne -
lda #1 lda #1
@ -1383,3 +1384,378 @@ _mod2b lda #0 ; self-modified
_done rts _done rts
.pend .pend
sort_ub .proc
; 8bit unsigned sort
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
; input: address of array to sort in c64.SCRATCH_ZPWORD1, length in c64.SCRATCH_ZPB1
; first, put pointer BEFORE array
lda c64.SCRATCH_ZPWORD1
bne +
dec c64.SCRATCH_ZPWORD1+1
+ dec c64.SCRATCH_ZPWORD1
_sortloop ldy c64.SCRATCH_ZPB1 ;start of subroutine sort
lda (c64.SCRATCH_ZPWORD1),y ;last value in (what is left of) sequence to be sorted
sta c64.SCRATCH_ZPREG ;save value. will be over-written by largest number
jmp _l2
_l1 dey
beq _l3
lda (c64.SCRATCH_ZPWORD1),y
cmp c64.SCRATCH_ZPWORD2+1
bcc _l1
_l2 sty c64.SCRATCH_ZPWORD2 ;index of potentially largest value
sta c64.SCRATCH_ZPWORD2+1 ;potentially largest value
jmp _l1
_l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
lda c64.SCRATCH_ZPWORD2+1 ;the largest value
sta (c64.SCRATCH_ZPWORD1),y ;put largest value in place
ldy c64.SCRATCH_ZPWORD2 ;index of free space
lda c64.SCRATCH_ZPREG ;the over-written value
sta (c64.SCRATCH_ZPWORD1),y ;put the over-written value in the free space
dec c64.SCRATCH_ZPB1 ;end of the shorter sequence still left
bne _sortloop ;start working with the shorter sequence
rts
.pend
sort_b .proc
; 8bit signed sort
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
; input: address of array to sort in c64.SCRATCH_ZPWORD1, length in c64.SCRATCH_ZPB1
; first, put pointer BEFORE array
lda c64.SCRATCH_ZPWORD1
bne +
dec c64.SCRATCH_ZPWORD1+1
+ dec c64.SCRATCH_ZPWORD1
_sortloop ldy c64.SCRATCH_ZPB1 ;start of subroutine sort
lda (c64.SCRATCH_ZPWORD1),y ;last value in (what is left of) sequence to be sorted
sta c64.SCRATCH_ZPREG ;save value. will be over-written by largest number
jmp _l2
_l1 dey
beq _l3
lda (c64.SCRATCH_ZPWORD1),y
cmp c64.SCRATCH_ZPWORD2+1
bmi _l1
_l2 sty c64.SCRATCH_ZPWORD2 ;index of potentially largest value
sta c64.SCRATCH_ZPWORD2+1 ;potentially largest value
jmp _l1
_l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
lda c64.SCRATCH_ZPWORD2+1 ;the largest value
sta (c64.SCRATCH_ZPWORD1),y ;put largest value in place
ldy c64.SCRATCH_ZPWORD2 ;index of free space
lda c64.SCRATCH_ZPREG ;the over-written value
sta (c64.SCRATCH_ZPWORD1),y ;put the over-written value in the free space
dec c64.SCRATCH_ZPB1 ;end of the shorter sequence still left
bne _sortloop ;start working with the shorter sequence
rts
.pend
sort_uw .proc
; 16bit unsigned sort
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
; input: address of array to sort in c64.SCRATCH_ZPWORD1, length in c64.SCRATCH_ZPB1
; first: subtract 2 of the pointer
asl c64.SCRATCH_ZPB1 ; *2 because words
lda c64.SCRATCH_ZPWORD1
sec
sbc #2
sta c64.SCRATCH_ZPWORD1
bcs _sort_loop
dec c64.SCRATCH_ZPWORD1+1
_sort_loop ldy c64.SCRATCH_ZPB1 ;start of subroutine sort
lda (c64.SCRATCH_ZPWORD1),y ;last value in (what is left of) sequence to be sorted
sta _work3 ;save value. will be over-written by largest number
iny
lda (c64.SCRATCH_ZPWORD1),y
sta _work3+1
dey
jmp _l2
_l1 dey
dey
beq _l3
iny
lda (c64.SCRATCH_ZPWORD1),y
dey
cmp c64.SCRATCH_ZPWORD2+1
bne +
lda (c64.SCRATCH_ZPWORD1),y
cmp c64.SCRATCH_ZPWORD2
+ bcc _l1
_l2 sty _work1 ;index of potentially largest value
lda (c64.SCRATCH_ZPWORD1),y
sta c64.SCRATCH_ZPWORD2 ;potentially largest value
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.SCRATCH_ZPWORD2+1
dey
jmp _l1
_l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
lda c64.SCRATCH_ZPWORD2 ;the largest value
sta (c64.SCRATCH_ZPWORD1),y ;put largest value in place
iny
lda c64.SCRATCH_ZPWORD2+1
sta (c64.SCRATCH_ZPWORD1),y
ldy _work1 ;index of free space
lda _work3 ;the over-written value
sta (c64.SCRATCH_ZPWORD1),y ;put the over-written value in the free space
iny
lda _work3+1
sta (c64.SCRATCH_ZPWORD1),y
dey
dec c64.SCRATCH_ZPB1 ;end of the shorter sequence still left
dec c64.SCRATCH_ZPB1
bne _sort_loop ;start working with the shorter sequence
rts
_work1 .byte 0
_work3 .word 0
.pend
sort_w .proc
; 16bit signed sort
; sorting subroutine coded by mats rosengren (mats.rosengren@esa.int)
; input: address of array to sort in c64.SCRATCH_ZPWORD1, length in c64.SCRATCH_ZPB1
; first: subtract 2 of the pointer
asl c64.SCRATCH_ZPB1 ; *2 because words
lda c64.SCRATCH_ZPWORD1
sec
sbc #2
sta c64.SCRATCH_ZPWORD1
bcs _sort_loop
dec c64.SCRATCH_ZPWORD1+1
_sort_loop ldy c64.SCRATCH_ZPB1 ;start of subroutine sort
lda (c64.SCRATCH_ZPWORD1),y ;last value in (what is left of) sequence to be sorted
sta _work3 ;save value. will be over-written by largest number
iny
lda (c64.SCRATCH_ZPWORD1),y
sta _work3+1
dey
jmp _l2
_l1 dey
dey
beq _l3
lda (c64.SCRATCH_ZPWORD1),y
cmp c64.SCRATCH_ZPWORD2
iny
lda (c64.SCRATCH_ZPWORD1),y
dey
sbc c64.SCRATCH_ZPWORD2+1
bvc +
eor #$80
+ bmi _l1
_l2 sty _work1 ;index of potentially largest value
lda (c64.SCRATCH_ZPWORD1),y
sta c64.SCRATCH_ZPWORD2 ;potentially largest value
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.SCRATCH_ZPWORD2+1
dey
jmp _l1
_l3 ldy c64.SCRATCH_ZPB1 ;where the largest value shall be put
lda c64.SCRATCH_ZPWORD2 ;the largest value
sta (c64.SCRATCH_ZPWORD1),y ;put largest value in place
iny
lda c64.SCRATCH_ZPWORD2+1
sta (c64.SCRATCH_ZPWORD1),y
ldy _work1 ;index of free space
lda _work3 ;the over-written value
sta (c64.SCRATCH_ZPWORD1),y ;put the over-written value in the free space
iny
lda _work3+1
sta (c64.SCRATCH_ZPWORD1),y
dey
dec c64.SCRATCH_ZPB1 ;end of the shorter sequence still left
dec c64.SCRATCH_ZPB1
bne _sort_loop ;start working with the shorter sequence
rts
_work1 .byte 0
_work3 .word 0
.pend
reverse_b .proc
; --- reverse an array of bytes (in-place)
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
_left_index = c64.SCRATCH_ZPWORD2
_right_index = c64.SCRATCH_ZPWORD2+1
pha
sec
sbc #1
sta _left_index
lda #0
sta _right_index
pla
lsr a
tay
_loop sty c64.SCRATCH_ZPREG
ldy _left_index
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _right_index
sta (c64.SCRATCH_ZPWORD1),y
inc _right_index
dec _left_index
ldy c64.SCRATCH_ZPREG
dey
bne _loop
rts
.pend
reverse_w .proc
; --- reverse an array of words (in-place)
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
_left_index = c64.SCRATCH_ZPWORD2
_right_index = c64.SCRATCH_ZPWORD2+1
pha
asl a ; *2 because words
sec
sbc #2
sta _left_index
lda #0
sta _right_index
pla
lsr a
pha
tay
; first reverse the lsbs
_loop_lo sty c64.SCRATCH_ZPREG
ldy _left_index
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _right_index
sta (c64.SCRATCH_ZPWORD1),y
inc _right_index
inc _right_index
dec _left_index
dec _left_index
ldy c64.SCRATCH_ZPREG
dey
bne _loop_lo
; now reverse the msbs
dec _right_index
inc _left_index
inc _left_index
inc _left_index
pla
tay
_loop_hi sty c64.SCRATCH_ZPREG
ldy _left_index
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _right_index
sta (c64.SCRATCH_ZPWORD1),y
dec _right_index
dec _right_index
inc _left_index
inc _left_index
ldy c64.SCRATCH_ZPREG
dey
bne _loop_hi
rts
.pend
ror2_mem_ub .proc
; -- in-place 8-bit ror of byte at memory location on stack
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
lsr a
bcc +
ora #$80
+ sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol2_mem_ub .proc
; -- in-place 8-bit rol of byte at memory location on stack
;" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}"
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
cmp #$80
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsl_array_b .proc
.warn "lsl_array_b" ; TODO
.pend
lsl_array_w .proc
.warn "lsl_array_w" ; TODO
.pend
lsr_array_ub .proc
.warn "lsr_array_ub" ; TODO
.pend
lsr_array_b .proc
.warn "lsr_array_b" ; TODO
.pend
lsr_array_uw .proc
.warn "lsr_array_uw" ; TODO
.pend
lsr_array_w .proc
.warn "lsr_array_w" ; TODO
.pend
rol_array_ub .proc
.warn "rol_array_ub" ; TODO
.pend
rol_array_uw .proc
.warn "rol_array_uw" ; TODO
.pend
rol2_array_ub .proc
.warn "rol2_array_ub" ; TODO
.pend
rol2_array_uw .proc
.warn "rol2_array_uw" ; TODO
.pend
ror_array_ub .proc
.warn "ror_array_ub" ; TODO
.pend
ror_array_uw .proc
.warn "ror_array_uw" ; TODO
.pend
ror2_array_ub .proc
.warn "ror2_array_ub" ; TODO
.pend
ror2_array_uw .proc
.warn "ror2_array_uw" ; TODO
.pend

View File

@ -1 +1 @@
1.50 1.70

View File

@ -1,24 +1,25 @@
package prog8 package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.compiler.CompilationResult import prog8.compiler.*
import prog8.compiler.compileProgram import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AsmGen
import prog8.parser.ParsingFailedError import prog8.parser.ParsingFailedError
import prog8.vm.astvm.AstVm import prog8.vm.astvm.AstVm
import java.io.IOException
import java.nio.file.FileSystems import java.nio.file.FileSystems
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardWatchEventKinds import java.nio.file.StandardWatchEventKinds
import java.util.* import java.time.LocalDateTime
import kotlin.system.exitProcess import kotlin.system.exitProcess
fun main(args: Array<String>) { fun main(args: Array<String>) {
printSoftwareHeader("compiler") printSoftwareHeader("compiler")
if (args.isEmpty())
usage()
compileMain(args) compileMain(args)
} }
@ -29,58 +30,68 @@ internal fun printSoftwareHeader(what: String) {
} }
fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
private fun compileMain(args: Array<String>) { private fun compileMain(args: Array<String>) {
var emulatorToStart = "" val cli = CommandLineInterface("prog8compiler")
var moduleFile = "" val startEmulator by cli.flagArgument("-emu", "auto-start the Vice C-64 emulator after successful compilation")
var writeAssembly = true val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
var optimize = true val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
var optimizeInlining = true val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
var launchAstVm = false val launchSimulator by cli.flagArgument("-sim", "launch the builtin execution simulator after compilation")
var watchMode = false val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
for (arg in args) { val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently only 'c64' (C64 6502 assembly) available", "c64")
if(arg=="-emu") val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
emulatorToStart = "x64"
else if(arg=="-emu2") try {
emulatorToStart = "x64sc" cli.parse(args)
else if(arg=="-noasm") } catch (e: Exception) {
writeAssembly = false exitProcess(1)
else if(arg=="-noopt")
optimize = false
else if(arg=="-nooptinline")
optimizeInlining = false
else if(arg=="-avm")
launchAstVm = true
else if(arg=="-watch")
watchMode = true
else if(!arg.startsWith("-"))
moduleFile = arg
else
usage()
} }
if(watchMode) { when(compilationTarget) {
if(moduleFile.isBlank()) "c64" -> {
usage() with(CompilationTarget) {
name = "c64"
machine = C64MachineDefinition
encodeString = { str -> Petscii.encodePetscii(str, true) }
decodeString = { bytes -> Petscii.decodePetscii(bytes, true) }
asmGenerator = ::AsmGen
}
}
else -> {
System.err.println("invalid compilation target")
exitProcess(1)
}
}
val outputPath = pathFrom(outputDir)
if(!outputPath.toFile().isDirectory) {
System.err.println("Output path doesn't exist")
exitProcess(1)
}
if(watchMode && moduleFiles.size<=1) {
val watchservice = FileSystems.getDefault().newWatchService() val watchservice = FileSystems.getDefault().newWatchService()
while(true) { while(true) {
val filepath = Paths.get(moduleFile).normalize() val filepath = pathFrom(moduleFiles.single()).normalize()
println("Continuous watch mode active. Main module: $filepath") println("Continuous watch mode active. Main module: $filepath")
try { try {
val compilationResult = compileProgram(filepath, optimize, optimizeInlining, writeAssembly) val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
println("Imported files (now watching:)") println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) { for (importedFile in compilationResult.importedFiles) {
print(" ") print(" ")
println(importedFile) println(importedFile)
importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY) importedFile.parent.register(watchservice, StandardWatchEventKinds.ENTRY_MODIFY)
} }
println("${Date()}: Waiting for file changes.") println("[${LocalDateTime.now().withNano(0)}] Waiting for file changes.")
val event = watchservice.take() val event = watchservice.take()
for(changed in event.pollEvents()) { for(changed in event.pollEvents()) {
val changedPath = changed.context() as Path val changedPath = changed.context() as Path
println(" change detected: ${changedPath}") println(" change detected: $changedPath")
} }
event.reset() event.reset()
println("\u001b[H\u001b[2J") // clear the screen println("\u001b[H\u001b[2J") // clear the screen
@ -90,52 +101,51 @@ private fun compileMain(args: Array<String>) {
} }
} else { } else {
if(moduleFile.isBlank()) for(filepathRaw in moduleFiles) {
usage() val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
val filepath = Paths.get(moduleFile).normalize() try {
val compilationResult: CompilationResult compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
if(!compilationResult.success)
try { exitProcess(1)
compilationResult = compileProgram(filepath, optimize, optimizeInlining, writeAssembly) } catch (x: ParsingFailedError) {
if(!compilationResult.success)
exitProcess(1) exitProcess(1)
} catch (x: ParsingFailedError) { } catch (x: AstException) {
exitProcess(1) exitProcess(1)
} catch (x: AstException) { }
exitProcess(1)
}
if (launchAstVm) { if (launchSimulator) {
println("\nLaunching AST-based vm...") // val c64 = razorvine.c64emu.C64Machine("C64 emulator launched from Prog8 compiler")
val vm = AstVm(compilationResult.programAst) // c64.cpu.addBreakpoint(0xea31) { cpu, address ->
vm.run() // println("zz")
} // Cpu6502.BreakpointResultAction()
// }
// c64.start()
println("\nLaunching AST-based simulator...")
val vm = AstVm(compilationResult.programAst, compilationTarget)
vm.run()
}
if (emulatorToStart.isNotEmpty()) { if (startEmulator) {
if (compilationResult.programName.isEmpty()) if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.") println("\nCan't start emulator because no program was assembled.")
else { else if(startEmulator) {
println("\nStarting C-64 emulator $emulatorToStart...") for(emulator in listOf("x64sc", "x64")) {
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list", println("\nStarting C-64 emulator $emulator...")
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg") val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
val process = ProcessBuilder(cmdline).inheritIO().start() "-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
process.waitFor() val processb = ProcessBuilder(cmdline).inheritIO()
val process: Process
try {
process=processb.start()
} catch(x: IOException) {
continue // try the next emulator executable
}
process.waitFor()
break
}
}
} }
} }
} }
} }
private fun usage() {
System.err.println("Missing argument(s):")
System.err.println(" [-noasm] don't create assembly code")
System.err.println(" [-noopt] don't perform any optimizations")
System.err.println(" [-nooptinline] don't perform subroutine inlining optimizations")
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(" [-avm] launch the prog8 ast-based virtual machine after compilation")
System.err.println(" [-watch] continuous compilation mode (watches for file changes)")
System.err.println(" modulefile main module file to compile")
exitProcess(1)
}

View File

@ -3,7 +3,6 @@ package prog8.ast
import prog8.ast.antlr.escape import prog8.ast.antlr.escape
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes import prog8.ast.base.NumericDatatypes
import prog8.ast.base.StringDatatypes
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
@ -79,7 +78,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun datatypeString(dt: DataType): String { private fun datatypeString(dt: DataType): String {
return when(dt) { return when(dt) {
in NumericDatatypes -> dt.toString().toLowerCase() in NumericDatatypes -> dt.toString().toLowerCase()
in StringDatatypes -> dt.toString().toLowerCase() DataType.STR -> dt.toString().toLowerCase()
DataType.ARRAY_UB -> "ubyte[" DataType.ARRAY_UB -> "ubyte["
DataType.ARRAY_B -> "byte[" DataType.ARRAY_B -> "byte["
DataType.ARRAY_UW -> "uword[" DataType.ARRAY_UW -> "uword["
@ -132,7 +131,7 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) { for(param in subroutine.parameters.zip(subroutine.asmParameterRegisters)) {
val reg = val reg =
when { when {
true==param.second.stack -> "stack" param.second.stack -> "stack"
param.second.registerOrPair!=null -> param.second.registerOrPair.toString() param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
param.second.statusflag!=null -> param.second.statusflag.toString() param.second.statusflag!=null -> param.second.statusflag.toString()
else -> "?????1" else -> "?????1"
@ -197,9 +196,9 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun printout(call: IFunctionCall) { private fun printout(call: IFunctionCall) {
call.target.accept(this) call.target.accept(this)
output("(") output("(")
for(arg in call.arglist) { for(arg in call.args) {
arg.accept(this) arg.accept(this)
if(arg!==call.arglist.last()) if(arg!==call.args.last())
output(", ") output(", ")
} }
output(")") output(")")
@ -256,15 +255,12 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(numLiteral.number.toString()) output(numLiteral.number.toString())
} }
override fun visit(refLiteral: ReferenceLiteralValue) { override fun visit(string: StringLiteralValue) {
when { output("\"${escape(string.value)}\"")
refLiteral.isString -> output("\"${escape(refLiteral.str!!)}\"") }
refLiteral.isArray -> {
if(refLiteral.array!=null) { override fun visit(array: ArrayLiteralValue) {
outputListMembers(refLiteral.array.asSequence(), '[', ']') outputListMembers(array.value.asSequence(), '[', ']')
}
}
}
} }
private fun outputListMembers(array: Sequence<Expression>, openchar: Char, closechar: Char) { private fun outputListMembers(array: Sequence<Expression>, openchar: Char, closechar: Char) {
@ -318,13 +314,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
override fun visit(forLoop: ForLoop) { override fun visit(forLoop: ForLoop) {
output("for ") output("for ")
if(forLoop.decltype!=null) {
output(datatypeString(forLoop.decltype))
if (forLoop.zeropage==ZeropageWish.REQUIRE_ZEROPAGE || forLoop.zeropage==ZeropageWish.PREFER_ZEROPAGE)
output(" @zp ")
else
output(" ")
}
if(forLoop.loopRegister!=null) if(forLoop.loopRegister!=null)
output(forLoop.loopRegister.toString()) output(forLoop.loopRegister.toString())
else else

View File

@ -4,7 +4,6 @@ import prog8.ast.base.*
import prog8.ast.expressions.Expression import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import java.nio.file.Path import java.nio.file.Path
@ -38,7 +37,7 @@ interface Node {
interface IFunctionCall { interface IFunctionCall {
var target: IdentifierReference var target: IdentifierReference
var arglist: MutableList<Expression> var args: MutableList<Expression>
} }
interface INameScope { interface INameScope {
@ -161,12 +160,15 @@ interface INameScope {
} }
} }
interface IAssignable {
// just a tag for now
}
/*********** Everything starts from here, the Program; zero or more modules *************/ /*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String, val modules: MutableList<Module>) { class Program(val name: String, val modules: MutableList<Module>) {
val namespace = GlobalNamespace(modules) val namespace = GlobalNamespace(modules)
val heap = HeapValues()
val definedLoadAddress: Int val definedLoadAddress: Int
get() = modules.first().loadAddress get() = modules.first().loadAddress
@ -174,7 +176,7 @@ class Program(val name: String, val modules: MutableList<Module>) {
var actualLoadAddress: Int = 0 var actualLoadAddress: Int = 0
fun entrypoint(): Subroutine? { fun entrypoint(): Subroutine? {
val mainBlocks = modules.flatMap { it.statements }.filter { b -> b is Block && b.name=="main" }.map { it as Block } val mainBlocks = allBlocks().filter { it.name=="main" }
if(mainBlocks.size > 1) if(mainBlocks.size > 1)
throw FatalAstException("more than one 'main' block") throw FatalAstException("more than one 'main' block")
return if(mainBlocks.isEmpty()) { return if(mainBlocks.isEmpty()) {
@ -240,9 +242,8 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
} }
} }
} }
// lookup something from the module.
val stmt = localContext.definingModule().lookup(scopedName, localContext) return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
return when (stmt) {
is Label, is VarDecl, is Block, is Subroutine -> stmt is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null null -> null
else -> throw NameError("wrong identifier target: $stmt", stmt.position) else -> throw NameError("wrong identifier target: $stmt", stmt.position)

View File

@ -7,7 +7,7 @@ import prog8.ast.Module
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.CompilationTarget
import prog8.parser.CustomLexer import prog8.parser.CustomLexer
import prog8.parser.prog8Parser import prog8.parser.prog8Parser
import java.io.CharConversionException import java.io.CharConversionException
@ -264,11 +264,12 @@ private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement { private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
val void = this.VOID() != null
val location = scoped_identifier().toAst() val location = scoped_identifier().toAst()
return if(expression_list() == null) return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), toPosition()) FunctionCallStatement(location, mutableListOf(), void, toPosition())
else else
FunctionCallStatement(location, expression_list().toAst().toMutableList(), toPosition()) FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
} }
@ -429,19 +430,20 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
else -> throw FatalAstException("invalid datatype for numeric literal") else -> throw FatalAstException("invalid datatype for numeric literal")
} }
litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition()) litval.floatliteral()!=null -> NumericLiteralValue(DataType.FLOAT, litval.floatliteral().toAst(), litval.toPosition())
litval.stringliteral()!=null -> ReferenceLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), position = litval.toPosition()) litval.stringliteral()!=null -> StringLiteralValue(unescape(litval.stringliteral().text, litval.toPosition()), litval.toPosition())
litval.charliteral()!=null -> { litval.charliteral()!=null -> {
try { try {
NumericLiteralValue(DataType.UBYTE, Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], litval.toPosition()) NumericLiteralValue(DataType.UBYTE, CompilationTarget.encodeString(
unescape(litval.charliteral().text, litval.toPosition()))[0], litval.toPosition())
} catch (ce: CharConversionException) { } catch (ce: CharConversionException) {
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition()) throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
} }
} }
litval.arrayliteral()!=null -> { litval.arrayliteral()!=null -> {
val array = litval.arrayliteral()?.toAst() val array = litval.arrayliteral().toAst()
// the actual type of the arraysize can not yet be determined here (missing namespace & heap) // the actual type of the arraysize can not yet be determined here (missing namespace & heap)
// the ConstantFold takes care of that and converts the type if needed. // the ConstantFold takes care of that and converts the type if needed.
ReferenceLiteralValue(DataType.ARRAY_UB, array = array, position = litval.toPosition()) ArrayLiteralValue(DataType.ARRAY_UB, array, position = litval.toPosition())
} }
litval.structliteral()!=null -> { litval.structliteral()!=null -> {
val values = litval.structliteral().expression().map { it.toAst() } val values = litval.structliteral().expression().map { it.toAst() }
@ -552,8 +554,6 @@ private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf
private fun prog8Parser.ForloopContext.toAst(): ForLoop { private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst() val loopregister = register()?.toAst()
val datatype = datatype()?.toAst()
val zeropage = if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE
val loopvar = identifier()?.toAst() val loopvar = identifier()?.toAst()
val iterable = expression()!!.toAst() val iterable = expression()!!.toAst()
val scope = val scope =
@ -561,7 +561,7 @@ private fun prog8Parser.ForloopContext.toAst(): ForLoop {
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition()) AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
else else
AnonymousScope(statement_block().toAst(), statement_block().toPosition()) AnonymousScope(statement_block().toAst(), statement_block().toPosition())
return ForLoop(loopregister, datatype, zeropage, loopvar, iterable, scope, toPosition()) return ForLoop(loopregister, loopvar, iterable, scope, toPosition())
} }
@ -596,10 +596,10 @@ private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
private fun prog8Parser.When_choiceContext.toAst(): WhenChoice { private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
val values = expression_list()?.toAst() val values = expression_list()?.toAst()
val stmt = statement()?.toAst() val stmt = statement()?.toAst()
val stmt_block = statement_block()?.toAst()?.toMutableList() ?: mutableListOf() val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
if(stmt!=null) if(stmt!=null)
stmt_block.add(stmt) stmtBlock.add(stmt)
val scope = AnonymousScope(stmt_block, toPosition()) val scope = AnonymousScope(stmtBlock, toPosition())
return WhenChoice(values, scope, toPosition()) return WhenChoice(values, scope, toPosition())
} }

View File

@ -1,7 +1,8 @@
package prog8.ast.base package prog8.ast.base
import prog8.ast.Node import prog8.ast.Node
import prog8.compiler.target.c64.MachineDefinition import prog8.compiler.target.CompilationTarget
/**************************** AST Data classes ****************************/ /**************************** AST Data classes ****************************/
@ -12,7 +13,6 @@ enum class DataType {
WORD, // pass by value WORD, // pass by value
FLOAT, // pass by value FLOAT, // pass by value
STR, // pass by reference STR, // pass by reference
STR_S, // pass by reference
ARRAY_UB, // pass by reference ARRAY_UB, // pass by reference
ARRAY_B, // pass by reference ARRAY_B, // pass by reference
ARRAY_UW, // pass by reference ARRAY_UW, // pass by reference
@ -31,8 +31,7 @@ enum class DataType {
UWORD -> targetType in setOf(UWORD, FLOAT) UWORD -> targetType in setOf(UWORD, FLOAT)
WORD -> targetType in setOf(WORD, FLOAT) WORD -> targetType in setOf(WORD, FLOAT)
FLOAT -> targetType == FLOAT FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType==STR_S STR -> targetType == STR
STR_S -> targetType == STR || targetType==STR_S
in ArrayDatatypes -> targetType == this in ArrayDatatypes -> targetType == this
else -> false else -> false
} }
@ -58,7 +57,7 @@ enum class DataType {
return when(this) { return when(this) {
in ByteDatatypes -> 1 in ByteDatatypes -> 1
in WordDatatypes -> 2 in WordDatatypes -> 2
FLOAT -> MachineDefinition.Mflpt5.MemorySize FLOAT -> CompilationTarget.machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> 2 in PassByReferenceDatatypes -> 2
else -> -9999999 else -> -9999999
} }
@ -112,10 +111,9 @@ val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = setOf(DataType.UWORD, DataType.WORD) val WordDatatypes = setOf(DataType.UWORD, DataType.WORD)
val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD) val IntegerDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD)
val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT) val NumericDatatypes = setOf(DataType.UBYTE, DataType.BYTE, DataType.UWORD, DataType.WORD, DataType.FLOAT)
val StringDatatypes = setOf(DataType.STR, DataType.STR_S)
val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F) val ArrayDatatypes = setOf(DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_F)
val IterableDatatypes = setOf( val IterableDatatypes = setOf(
DataType.STR, DataType.STR_S, DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F) DataType.ARRAY_F)
@ -123,7 +121,6 @@ val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT) val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
val ArrayElementTypes = mapOf( val ArrayElementTypes = mapOf(
DataType.STR to DataType.UBYTE, DataType.STR to DataType.UBYTE,
DataType.STR_S to DataType.UBYTE,
DataType.ARRAY_B to DataType.BYTE, DataType.ARRAY_B to DataType.BYTE,
DataType.ARRAY_UB to DataType.UBYTE, DataType.ARRAY_UB to DataType.UBYTE,
DataType.ARRAY_W to DataType.WORD, DataType.ARRAY_W to DataType.WORD,

View File

@ -5,7 +5,7 @@ import prog8.parser.ParsingFailedError
fun printErrors(errors: List<Any>, moduleName: String) { fun printErrors(errors: List<Any>, moduleName: String) {
val reportedMessages = mutableSetOf<String>() val reportedMessages = mutableSetOf<String>()
print("\u001b[91m") // bright red System.err.print("\u001b[91m") // bright red
errors.forEach { errors.forEach {
val msg = it.toString() val msg = it.toString()
if(msg !in reportedMessages) { if(msg !in reportedMessages) {
@ -13,7 +13,7 @@ fun printErrors(errors: List<Any>, moduleName: String) {
reportedMessages.add(msg) reportedMessages.add(msg)
} }
} }
print("\u001b[0m") // reset color System.err.print("\u001b[0m") // reset color
if(reportedMessages.isNotEmpty()) if(reportedMessages.isNotEmpty())
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.") throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
} }

View File

@ -10,13 +10,13 @@ class SyntaxError(override var message: String, val position: Position) : AstExc
override fun toString() = "$position Syntax error: $message" override fun toString() = "$position Syntax error: $message"
} }
class NameError(override var message: String, val position: Position) : AstException(message) { open class NameError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Name error: $message" override fun toString() = "$position Name error: $message"
} }
open class ExpressionError(message: String, val position: Position) : AstException(message) { class ExpressionError(message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Error: $message" override fun toString() = "$position Error: $message"
} }
class UndefinedSymbolError(symbol: IdentifierReference) class UndefinedSymbolError(symbol: IdentifierReference)
: ExpressionError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position) : NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)

View File

@ -4,7 +4,6 @@ import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.processing.* import prog8.ast.processing.*
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.target.c64.codegen2.AnonymousScopeVarsCleanup
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
@ -40,6 +39,11 @@ internal fun Program.reorderStatements() {
checker.visit(this) checker.visit(this)
} }
internal fun Program.addTypecasts() {
val caster = TypecastsAdder(this)
caster.visit(this)
}
internal fun Module.checkImportedValid() { internal fun Module.checkImportedValid() {
val checker = ImportedModuleDirectiveRemover() val checker = ImportedModuleDirectiveRemover()
checker.visit(this) checker.visit(this)

View File

@ -1,9 +1,6 @@
package prog8.ast.expressions package prog8.ast.expressions
import prog8.ast.IFunctionCall import prog8.ast.*
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.antlr.escape import prog8.ast.antlr.escape
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
@ -12,12 +9,11 @@ import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.compiler.HeapValues import prog8.compiler.target.CompilationTarget
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType import prog8.functions.builtinFunctionReturnType
import java.util.Objects
import kotlin.math.abs import kotlin.math.abs
@ -28,8 +24,8 @@ sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue? abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstModifyingVisitor): Expression abstract fun accept(visitor: IAstModifyingVisitor): Expression
abstract fun accept(visitor: IAstVisitor) abstract fun accept(visitor: IAstVisitor)
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this here and move it into CallGraph instead abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
abstract fun inferType(program: Program): DataType? abstract fun inferType(program: Program): InferredTypes.InferredType
infix fun isSameAs(other: Expression): Boolean { infix fun isSameAs(other: Expression): Boolean {
if(this===other) if(this===other)
@ -67,7 +63,27 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name) override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? = expression.inferType(program) override fun inferType(program: Program): InferredTypes.InferredType {
val inferred = expression.inferType(program)
return when(operator) {
"+" -> inferred
"~", "not" -> {
when(inferred.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
in WordDatatypes -> InferredTypes.knownFor(DataType.UWORD)
else -> inferred
}
}
"-" -> {
when(inferred.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> InferredTypes.knownFor(DataType.BYTE)
in WordDatatypes -> InferredTypes.knownFor(DataType.WORD)
else -> inferred
}
}
else -> throw FatalAstException("weird prefix expression operator")
}
}
override fun toString(): String { override fun toString(): String {
return "Prefix($operator $expression)" return "Prefix($operator $expression)"
@ -93,15 +109,22 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name) override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program) val leftDt = left.inferType(program)
val rightDt = right.inferType(program) val rightDt = right.inferType(program)
return when (operator) { return when (operator) {
"+", "-", "*", "**", "%", "/" -> if (leftDt == null || rightDt == null) null else { "+", "-", "*", "**", "%", "/" -> {
try { if (!leftDt.isKnown || !rightDt.isKnown)
commonDatatype(leftDt, rightDt, null, null).first InferredTypes.unknown()
} catch (x: FatalAstException) { else {
null try {
InferredTypes.knownFor(commonDatatype(
leftDt.typeOrElse(DataType.BYTE),
rightDt.typeOrElse(DataType.BYTE),
null, null).first)
} catch (x: FatalAstException) {
InferredTypes.unknown()
}
} }
} }
"&" -> leftDt "&" -> leftDt
@ -110,7 +133,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
"and", "or", "xor", "and", "or", "xor",
"<", ">", "<", ">",
"<=", ">=", "<=", ">=",
"==", "!=" -> DataType.UBYTE "==", "!=" -> InferredTypes.knownFor(DataType.UBYTE)
"<<", ">>" -> leftDt "<<", ">>" -> leftDt
else -> throw FatalAstException("resulting datatype check for invalid operator $operator") else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
} }
@ -177,7 +200,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
class ArrayIndexedExpression(var identifier: IdentifierReference, class ArrayIndexedExpression(var identifier: IdentifierReference,
val arrayspec: ArrayIndex, val arrayspec: ArrayIndex,
override val position: Position) : Expression() { override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -190,17 +213,16 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name) override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): InferredTypes.InferredType {
val target = identifier.targetStatement(program.namespace) val target = identifier.targetStatement(program.namespace)
if (target is VarDecl) { if (target is VarDecl) {
return when (target.datatype) { return when (target.datatype) {
in NumericDatatypes -> null DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in StringDatatypes -> DataType.UBYTE in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(target.datatype))
in ArrayDatatypes -> ArrayElementTypes[target.datatype] else -> InferredTypes.unknown()
else -> throw FatalAstException("invalid dt")
} }
} }
return null return InferredTypes.unknown()
} }
override fun toString(): String { override fun toString(): String {
@ -219,7 +241,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name) override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? = type override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? { override fun constValue(program: Program): NumericLiteralValue? {
val cv = expression.constValue(program) ?: return null val cv = expression.constValue(program) ?: return null
return cv.cast(type) return cv.cast(type)
@ -240,15 +262,14 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
identifier.parent=this identifier.parent=this
} }
var scopedname: String? = null // will be set in a later state by the compiler // TODO get rid of this??
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifiers(vararg name: String) = false override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program) = DataType.UWORD override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UWORD)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
} }
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression() { class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -259,7 +280,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = false override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program): DataType? = DataType.UBYTE override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
override fun constValue(program: Program): NumericLiteralValue? = null override fun constValue(program: Program): NumericLiteralValue? = null
override fun toString(): String { override fun toString(): String {
@ -301,7 +322,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
} }
} }
val asBooleanValue: Boolean = number!=0 val asBooleanValue: Boolean = number.toDouble() != 0.0
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
@ -315,9 +336,9 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
override fun toString(): String = "NumericLiteral(${type.name}:$number)" override fun toString(): String = "NumericLiteral(${type.name}:$number)"
override fun inferType(program: Program) = type override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun hashCode(): Int = type.hashCode() * 31 xor number.hashCode() override fun hashCode(): Int = Objects.hash(type, number)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if(other==null || other !is NumericLiteralValue) if(other==null || other !is NumericLiteralValue)
@ -327,7 +348,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble()) operator fun compareTo(other: NumericLiteralValue): Int = number.toDouble().compareTo(other.number.toDouble())
fun cast(targettype: DataType): NumericLiteralValue? { fun cast(targettype: DataType): NumericLiteralValue {
if(type==targettype) if(type==targettype)
return this return this
val numval = number.toDouble() val numval = number.toDouble()
@ -382,7 +403,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
} }
else -> {} else -> {}
} }
return null // invalid type conversion from $this to $targettype throw ExpressionError("can't cast $type into $targettype", position)
} }
} }
@ -399,148 +420,88 @@ class StructLiteralValue(var values: List<Expression>,
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) } override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
override fun inferType(program: Program) = DataType.STRUCT override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
override fun toString(): String { override fun toString(): String {
return "struct{ ${values.joinToString(", ")} }" return "struct{ ${values.joinToString(", ")} }"
} }
} }
class ReferenceLiteralValue(val type: DataType, // only reference types allowed here private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
val str: String? = null,
val array: Array<Expression>? = null, class StringLiteralValue(val value: String,
// actually, at the moment, we don't have struct literals in the language override val position: Position) : Expression() {
initHeapId: Int? =null,
override val position: Position) : Expression() {
override lateinit var parent: Node override lateinit var parent: Node
override fun referencesIdentifiers(vararg name: String) = array?.any { it.referencesIdentifiers(*name) } ?: false val heapId = ++heapIdSequence
val isString = type in StringDatatypes
val isArray = type in ArrayDatatypes
var heapId = initHeapId
private set
init {
when(type){
in StringDatatypes ->
if(str==null) throw FatalAstException("literal value missing strvalue/heapId")
in ArrayDatatypes ->
if(array==null) throw FatalAstException("literal value missing arrayvalue/heapId")
else -> throw FatalAstException("invalid type $type")
}
if(array==null && str==null)
throw FatalAstException("literal ref value without actual value")
}
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
array?.forEach {it.linkParents(this)}
} }
override fun referencesIdentifiers(vararg name: String) = false
override fun constValue(program: Program): NumericLiteralValue? { override fun constValue(program: Program): NumericLiteralValue? = null
// note that we can't handle arrays that only contain constant numbers here anymore
// so they're not treated as constants anymore
return null
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String = "'${escape(value)}'"
override fun toString(): String { override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STR)
val valueStr = when(type) { operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
in StringDatatypes -> "'${escape(str!!)}'" override fun hashCode(): Int = value.hashCode()
in ArrayDatatypes -> "$array"
else -> throw FatalAstException("weird ref type")
}
return "RefValueLit($type, $valueStr)"
}
override fun inferType(program: Program) = type
override fun hashCode(): Int {
val sh = str?.hashCode() ?: 0x00014567
val ah = array?.hashCode() ?: 0x11119876
return sh * 31 xor ah xor type.hashCode()
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if(other==null || other !is ReferenceLiteralValue) if(other==null || other !is StringLiteralValue)
return false return false
if(isArray && other.isArray) return value==other.value
return array!!.contentEquals(other.array!!) && heapId==other.heapId }
if(isString && other.isString) }
return str==other.str && heapId==other.heapId
if(type!=other.type) class ArrayLiteralValue(val type: DataType, // only array types
val value: Array<Expression>,
override val position: Position) : Expression() {
override lateinit var parent: Node
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) {
this.parent = parent
value.forEach {it.linkParents(this)}
}
override fun referencesIdentifiers(vararg name: String) = value.any { it.referencesIdentifiers(*name) }
override fun constValue(program: Program): NumericLiteralValue? = null
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String = "$value"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
operator fun compareTo(other: ArrayLiteralValue): Int = throw ExpressionError("cannot order compare arrays", position)
override fun hashCode(): Int = Objects.hash(value, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is ArrayLiteralValue)
return false return false
return type==other.type && value.contentEquals(other.value)
return compareTo(other) == 0
} }
operator fun compareTo(other: ReferenceLiteralValue): Int { fun cast(targettype: DataType): ArrayLiteralValue? {
throw ExpressionError("cannot order compare type $type with ${other.type}", other.position)
}
fun cast(targettype: DataType): ReferenceLiteralValue? {
if(type==targettype) if(type==targettype)
return this return this
when(type) { if(targettype in ArrayDatatypes) {
in StringDatatypes -> { val elementType = ArrayElementTypes.getValue(targettype)
if(targettype in StringDatatypes) val castArray = value.map{
return ReferenceLiteralValue(targettype, str, position = position) val num = it as? NumericLiteralValue
} if(num==null) {
in ArrayDatatypes -> { // an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted
if(targettype in ArrayDatatypes) { if (elementType != DataType.UWORD || it !is AddressOf)
val elementType = ArrayElementTypes.getValue(targettype) return null
val castArray = array!!.map{ it
val num = it as? NumericLiteralValue } else {
if(num==null) { try {
// an array of UWORDs could possibly also contain AddressOfs num.cast(elementType)
if (elementType != DataType.UWORD || it !is AddressOf) } catch(x: ExpressionError) {
throw FatalAstException("weird array element $it") return null
it }
} else {
num.cast(elementType)!!
}
}.toTypedArray()
return ReferenceLiteralValue(targettype, null, array=castArray, position = position)
} }
} }.toTypedArray()
else -> {} return ArrayLiteralValue(targettype, castArray, position = position)
} }
return null // invalid type conversion from $this to $targettype return null // invalid type conversion from $this to $targettype
} }
fun addToHeap(heap: HeapValues) {
if (heapId != null) return
if (str != null) {
heapId = heap.addString(type, str)
}
else if (array!=null) {
if(array.any {it is AddressOf }) {
val intArrayWithAddressOfs = array.map {
when (it) {
is AddressOf -> IntegerOrAddressOf(null, it)
is NumericLiteralValue -> IntegerOrAddressOf(it.number.toInt(), null)
else -> throw FatalAstException("invalid datatype in array")
}
}
heapId = heap.addIntegerArray(type, intArrayWithAddressOfs.toTypedArray())
} else {
val valuesInArray = array.map { (it as? NumericLiteralValue)?.number }
if(null !in valuesInArray) {
heapId = if (type == DataType.ARRAY_F) {
val doubleArray = valuesInArray.map { it!!.toDouble() }.toDoubleArray()
heap.addDoublesArray(doubleArray)
} else {
val integerArray = valuesInArray.map { it!!.toInt() }
heap.addIntegerArray(type, integerArray.map { IntegerOrAddressOf(it, null) }.toTypedArray())
}
}
}
}
}
} }
class RangeExpr(var from: Expression, class RangeExpr(var from: Expression,
@ -560,18 +521,17 @@ class RangeExpr(var from: Expression,
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name) override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program) val fromDt=from.inferType(program)
val toDt=to.inferType(program) val toDt=to.inferType(program)
return when { return when {
fromDt==null || toDt==null -> null !fromDt.isKnown || !toDt.isKnown -> InferredTypes.unknown()
fromDt== DataType.UBYTE && toDt== DataType.UBYTE -> DataType.ARRAY_UB fromDt istype DataType.UBYTE && toDt istype DataType.UBYTE -> InferredTypes.knownFor(DataType.ARRAY_UB)
fromDt== DataType.UWORD && toDt== DataType.UWORD -> DataType.ARRAY_UW fromDt istype DataType.UWORD && toDt istype DataType.UWORD -> InferredTypes.knownFor(DataType.ARRAY_UW)
fromDt== DataType.STR && toDt== DataType.STR -> DataType.STR fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
fromDt== DataType.STR_S && toDt== DataType.STR_S -> DataType.STR_S fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W)
fromDt== DataType.WORD || toDt== DataType.WORD -> DataType.ARRAY_W fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B)
fromDt== DataType.BYTE || toDt== DataType.BYTE -> DataType.ARRAY_B else -> InferredTypes.knownFor(DataType.ARRAY_UB)
else -> DataType.ARRAY_UB
} }
} }
override fun toString(): String { override fun toString(): String {
@ -589,12 +549,12 @@ class RangeExpr(var from: Expression,
fun toConstantIntegerRange(): IntProgression? { fun toConstantIntegerRange(): IntProgression? {
val fromVal: Int val fromVal: Int
val toVal: Int val toVal: Int
val fromRlv = from as? ReferenceLiteralValue val fromString = from as? StringLiteralValue
val toRlv = to as? ReferenceLiteralValue val toString = to as? StringLiteralValue
if(fromRlv!=null && fromRlv.isString && toRlv!=null && toRlv.isString) { if(fromString!=null && toString!=null ) {
// string range -> int range over petscii values // string range -> int range over petscii values
fromVal = Petscii.encodePetscii(fromRlv.str!!, true)[0].toInt() fromVal = CompilationTarget.encodeString(fromString.value)[0].toInt()
toVal = Petscii.encodePetscii(toRlv.str!!, true)[0].toInt() toVal = CompilationTarget.encodeString(toString.value)[0].toInt()
} else { } else {
val fromLv = from as? NumericLiteralValue val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue val toLv = to as? NumericLiteralValue
@ -605,22 +565,26 @@ class RangeExpr(var from: Expression,
toVal = toLv.number.toInt() toVal = toLv.number.toInt()
} }
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1 val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
return when { return makeRange(fromVal, toVal, stepVal)
fromVal <= toVal -> when { }
stepVal <= 0 -> IntRange.EMPTY }
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
} return when {
else -> when { fromVal <= toVal -> when {
stepVal >= 0 -> IntRange.EMPTY stepVal <= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal stepVal == 1 -> fromVal..toVal
else -> fromVal downTo toVal step abs(stepVal) else -> fromVal..toVal step stepVal
} }
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
} }
} }
} }
class RegisterExpr(val register: Register, override val position: Position) : Expression() { class RegisterExpr(val register: Register, override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
@ -635,10 +599,10 @@ class RegisterExpr(val register: Register, override val position: Position) : Ex
return "RegisterExpr(register=$register, pos=$position)" return "RegisterExpr(register=$register, pos=$position)"
} }
override fun inferType(program: Program) = DataType.UBYTE override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
} }
data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression() { data class IdentifierReference(val nameInSource: List<String>, override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node override lateinit var parent: Node
fun targetStatement(namespace: INameScope) = fun targetStatement(namespace: INameScope) =
@ -672,12 +636,12 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name // @todo is this correct all the time? override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): InferredTypes.InferredType {
val targetStmt = targetStatement(program.namespace) val targetStmt = targetStatement(program.namespace)
if(targetStmt is VarDecl) { if(targetStmt is VarDecl) {
return targetStmt.datatype return InferredTypes.knownFor(targetStmt.datatype)
} else { } else {
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position") throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
} }
@ -690,28 +654,22 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value") val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
return when (value) { return when (value) {
is IdentifierReference -> value.heapId(namespace) is IdentifierReference -> value.heapId(namespace)
is ReferenceLiteralValue -> value.heapId ?: throw FatalAstException("refLv is not on the heap: $value") is StringLiteralValue -> value.heapId
is ArrayLiteralValue -> value.heapId
else -> throw FatalAstException("requires a reference value") else -> throw FatalAstException("requires a reference value")
} }
} }
fun withPrefixedName(nameprefix: String): IdentifierReference {
val prefixed = nameInSource.dropLast(1) + listOf(nameprefix+nameInSource.last())
val new = IdentifierReference(prefixed, position)
new.parent = parent
return new
}
} }
class FunctionCall(override var target: IdentifierReference, class FunctionCall(override var target: IdentifierReference,
override var arglist: MutableList<Expression>, override var args: MutableList<Expression>,
override val position: Position) : Expression(), IFunctionCall { override val position: Position) : Expression(), IFunctionCall {
override lateinit var parent: Node override lateinit var parent: Node
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
target.linkParents(this) target.linkParents(this)
arglist.forEach { it.linkParents(this) } args.forEach { it.linkParents(this) }
} }
override fun constValue(program: Program) = constValue(program, true) override fun constValue(program: Program) = constValue(program, true)
@ -726,14 +684,14 @@ class FunctionCall(override var target: IdentifierReference,
if(func!=null) { if(func!=null) {
val exprfunc = func.constExpressionFunc val exprfunc = func.constExpressionFunc
if(exprfunc!=null) if(exprfunc!=null)
resultValue = exprfunc(arglist, position, program) resultValue = exprfunc(args, position, program)
else if(func.returntype==null) else if(func.returntype==null)
throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position) throw ExpressionError("builtin function ${target.nameInSource[0]} can't be used here because it doesn't return a value", position)
} }
if(withDatatypeCheck) { if(withDatatypeCheck) {
val resultDt = this.inferType(program) val resultDt = this.inferType(program)
if(resultValue==null || resultDt == resultValue.type) if(resultValue==null || resultDt istype resultValue.type)
return resultValue return resultValue
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position") throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position")
} else { } else {
@ -752,29 +710,29 @@ class FunctionCall(override var target: IdentifierReference,
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this) override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || arglist.any{it.referencesIdentifiers(*name)} override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || args.any{it.referencesIdentifiers(*name)}
override fun inferType(program: Program): DataType? { override fun inferType(program: Program): InferredTypes.InferredType {
val constVal = constValue(program ,false) val constVal = constValue(program ,false)
if(constVal!=null) if(constVal!=null)
return constVal.type return InferredTypes.knownFor(constVal.type)
val stmt = target.targetStatement(program.namespace) ?: return null val stmt = target.targetStatement(program.namespace) ?: return InferredTypes.unknown()
when (stmt) { when (stmt) {
is BuiltinFunctionStatementPlaceholder -> { is BuiltinFunctionStatementPlaceholder -> {
if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" || if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_irqd" ||
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") { target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
return null // these have no return value return InferredTypes.void() // these have no return value
} }
return builtinFunctionReturnType(target.nameInSource[0], this.arglist, program) return builtinFunctionReturnType(target.nameInSource[0], this.args, program)
} }
is Subroutine -> { is Subroutine -> {
if(stmt.returntypes.isEmpty()) if(stmt.returntypes.isEmpty())
return null // no return value return InferredTypes.void() // no return value
if(stmt.returntypes.size==1) if(stmt.returntypes.size==1)
return stmt.returntypes[0] return InferredTypes.knownFor(stmt.returntypes[0])
return null // has multiple return types... so not a single resulting datatype possible return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
} }
else -> return null else -> return InferredTypes.unknown()
} }
} }
} }

View File

@ -0,0 +1,60 @@
package prog8.ast.expressions
import java.util.Objects
import prog8.ast.base.DataType
object InferredTypes {
class InferredType private constructor(val isUnknown: Boolean, val isVoid: Boolean, private var datatype: DataType?) {
init {
require(!(datatype!=null && (isUnknown || isVoid))) { "invalid combination of args" }
}
val isKnown = datatype!=null
fun typeOrElse(alternative: DataType) = if(isUnknown || isVoid) alternative else datatype!!
infix fun istype(type: DataType): Boolean = if(isUnknown || isVoid) false else this.datatype==type
companion object {
fun unknown() = InferredType(isUnknown = true, isVoid = false, datatype = null)
fun void() = InferredType(isUnknown = false, isVoid = true, datatype = null)
fun known(type: DataType) = InferredType(isUnknown = false, isVoid = false, datatype = type)
}
override fun equals(other: Any?): Boolean {
if(other !is InferredType)
return false
return isVoid==other.isVoid && datatype==other.datatype
}
override fun toString(): String {
return when {
datatype!=null -> datatype.toString()
isVoid -> "<void>"
else -> "<unknown>"
}
}
override fun hashCode(): Int = Objects.hash(isVoid, datatype)
}
private val unknownInstance = InferredType.unknown()
private val voidInstance = InferredType.void()
private val knownInstances = mapOf(
DataType.UBYTE to InferredType.known(DataType.UBYTE),
DataType.BYTE to InferredType.known(DataType.BYTE),
DataType.UWORD to InferredType.known(DataType.UWORD),
DataType.WORD to InferredType.known(DataType.WORD),
DataType.FLOAT to InferredType.known(DataType.FLOAT),
DataType.STR to InferredType.known(DataType.STR),
DataType.ARRAY_UB to InferredType.known(DataType.ARRAY_UB),
DataType.ARRAY_B to InferredType.known(DataType.ARRAY_B),
DataType.ARRAY_UW to InferredType.known(DataType.ARRAY_UW),
DataType.ARRAY_W to InferredType.known(DataType.ARRAY_W),
DataType.ARRAY_F to InferredType.known(DataType.ARRAY_F),
DataType.STRUCT to InferredType.known(DataType.STRUCT)
)
fun void() = voidInstance
fun unknown() = unknownInstance
fun knownFor(type: DataType) = knownInstances.getValue(type)
}

View File

@ -1,9 +1,8 @@
package prog8.compiler.target.c64.codegen2 package prog8.ast.processing
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.AstException import prog8.ast.base.AstException
import prog8.ast.base.NameError import prog8.ast.base.NameError
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.AnonymousScope import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Statement import prog8.ast.statements.Statement
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
@ -21,7 +20,7 @@ class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
super.visit(program) super.visit(program)
for((scope, decls) in varsToMove) { for((scope, decls) in varsToMove) {
val sub = scope.definingSubroutine()!! val sub = scope.definingSubroutine()!!
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associate { it.name to it } val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false var conflicts = false
decls.forEach { decls.forEach {
val existing = existingVariables[it.name] val existing = existingVariables[it.name]

View File

@ -1,5 +1,6 @@
package prog8.ast.processing package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope import prog8.ast.INameScope
import prog8.ast.Module import prog8.ast.Module
import prog8.ast.Program import prog8.ast.Program
@ -7,9 +8,7 @@ import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.HeapValues import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import java.io.File import java.io.File
@ -98,14 +97,18 @@ internal class AstChecker(private val program: Program,
} }
if(expectedReturnValues.size==1 && returnStmt.value!=null) { if(expectedReturnValues.size==1 && returnStmt.value!=null) {
val valueDt = returnStmt.value!!.inferType(program) val valueDt = returnStmt.value!!.inferType(program)
if(expectedReturnValues[0]!=valueDt) if(!valueDt.isKnown) {
checkResult.add(ExpressionError("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position)) checkResult.add(ExpressionError("return value type mismatch", returnStmt.value!!.position))
} else {
if (expectedReturnValues[0] != valueDt.typeOrElse(DataType.STRUCT))
checkResult.add(ExpressionError("type $valueDt of return value doesn't match subroutine's return type", returnStmt.value!!.position))
}
} }
super.visit(returnStmt) super.visit(returnStmt)
} }
override fun visit(ifStatement: IfStatement) { override fun visit(ifStatement: IfStatement) {
if(ifStatement.condition.inferType(program) !in IntegerDatatypes) if(ifStatement.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
checkResult.add(ExpressionError("condition value should be an integer type", ifStatement.condition.position)) checkResult.add(ExpressionError("condition value should be an integer type", ifStatement.condition.position))
super.visit(ifStatement) super.visit(ifStatement)
} }
@ -114,13 +117,13 @@ internal class AstChecker(private val program: Program,
if(forLoop.body.containsNoCodeNorVars()) if(forLoop.body.containsNoCodeNorVars())
printWarning("for loop body is empty", forLoop.position) printWarning("for loop body is empty", forLoop.position)
val iterableDt = forLoop.iterable.inferType(program) val iterableDt = forLoop.iterable.inferType(program).typeOrElse(DataType.BYTE)
if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) { if(iterableDt !in IterableDatatypes && forLoop.iterable !is RangeExpr) {
checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position)) checkResult.add(ExpressionError("can only loop over an iterable type", forLoop.position))
} else { } else {
if (forLoop.loopRegister != null) { if (forLoop.loopRegister != null) {
// loop register // loop register
if (iterableDt != DataType.ARRAY_UB && iterableDt != DataType.ARRAY_B && iterableDt !in StringDatatypes) if (iterableDt != DataType.ARRAY_UB && iterableDt != DataType.ARRAY_B && iterableDt != DataType.STR)
checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position)) checkResult.add(ExpressionError("register can only loop over bytes", forLoop.position))
if(forLoop.loopRegister!=Register.A) if(forLoop.loopRegister!=Register.A)
checkResult.add(ExpressionError("it's only possible to use A as a loop register", forLoop.position)) checkResult.add(ExpressionError("it's only possible to use A as a loop register", forLoop.position))
@ -132,11 +135,11 @@ internal class AstChecker(private val program: Program,
} else { } else {
when (loopvar.datatype) { when (loopvar.datatype) {
DataType.UBYTE -> { DataType.UBYTE -> {
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt !in StringDatatypes) if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.ARRAY_UB && iterableDt != DataType.STR)
checkResult.add(ExpressionError("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position)) checkResult.add(ExpressionError("ubyte loop variable can only loop over unsigned bytes or strings", forLoop.position))
} }
DataType.UWORD -> { DataType.UWORD -> {
if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt !in StringDatatypes && if(iterableDt!= DataType.UBYTE && iterableDt!= DataType.UWORD && iterableDt != DataType.STR &&
iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW) iterableDt != DataType.ARRAY_UB && iterableDt!= DataType.ARRAY_UW)
checkResult.add(ExpressionError("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position)) checkResult.add(ExpressionError("uword loop variable can only loop over unsigned bytes, words or strings", forLoop.position))
} }
@ -239,7 +242,7 @@ internal class AstChecker(private val program: Program,
} }
else if(param.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) { else if(param.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD if (param.first.type != DataType.UWORD && param.first.type != DataType.WORD
&& param.first.type !in StringDatatypes && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT) && param.first.type != DataType.STR && param.first.type !in ArrayDatatypes && param.first.type != DataType.FLOAT)
err("parameter '${param.first.name}' should be (u)word/address") err("parameter '${param.first.name}' should be (u)word/address")
} }
else if(param.second.statusflag!=null) { else if(param.second.statusflag!=null) {
@ -254,7 +257,7 @@ internal class AstChecker(private val program: Program,
} }
else if(ret.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) { else if(ret.second.registerOrPair in setOf(RegisterOrPair.AX, RegisterOrPair.AY, RegisterOrPair.XY)) {
if (ret.first.value != DataType.UWORD && ret.first.value != DataType.WORD if (ret.first.value != DataType.UWORD && ret.first.value != DataType.WORD
&& ret.first.value !in StringDatatypes && ret.first.value !in ArrayDatatypes && ret.first.value != DataType.FLOAT) && ret.first.value != DataType.STR && ret.first.value !in ArrayDatatypes && ret.first.value != DataType.FLOAT)
err("return value #${ret.first.index + 1} should be (u)word/address") err("return value #${ret.first.index + 1} should be (u)word/address")
} }
else if(ret.second.statusflag!=null) { else if(ret.second.statusflag!=null) {
@ -317,14 +320,11 @@ internal class AstChecker(private val program: Program,
err("carry parameter has to come last") err("carry parameter has to come last")
} else { } else {
// TODO: non-asm subroutines can only take numeric arguments for now. (not strings and arrays) Maybe this can be improved now that we have '&' ? // Pass-by-reference datatypes can not occur as parameters to a subroutine directly
// the way string params are treated is almost okay (their address is passed) but the receiving subroutine treats it as an integer rather than referring back to the original string. // Instead, their reference (address) should be passed (as an UWORD).
// the way array params are treated is buggy; it thinks the subroutine needs a byte parameter in place of a byte[] ... // The language has no typed pointers at this time.
// This is not easy to fix because strings and arrays are treated a bit simplistic (a "virtual" pointer to the value on the heap) if(subroutine.parameters.any{it.type in PassByReferenceDatatypes }) {
// while passing them as subroutine parameters would require a "real" pointer OR copying the VALUE to the subroutine's parameter variable (which is very inefficient). err("Pass-by-reference types (str, array) cannot occur as a parameter type directly. Instead, use an uword for their address, or access the variable from the outer scope directly.")
// For now, don't pass strings and arrays as parameters and instead create the workaround as suggested in the error message below.
if(!subroutine.parameters.all{it.type in NumericDatatypes }) {
err("Non-asm subroutine can only take numerical parameters (no str/array types) for now. Workaround (for nested subroutine): access the variable from the outer scope directly.")
} }
} }
} }
@ -332,7 +332,7 @@ internal class AstChecker(private val program: Program,
override fun visit(repeatLoop: RepeatLoop) { override fun visit(repeatLoop: RepeatLoop) {
if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y")) if(repeatLoop.untilCondition.referencesIdentifiers("A", "X", "Y"))
printWarning("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position) printWarning("using a register in the loop condition is risky (it could get clobbered)", repeatLoop.untilCondition.position)
if(repeatLoop.untilCondition.inferType(program) !in IntegerDatatypes) if(repeatLoop.untilCondition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
checkResult.add(ExpressionError("condition value should be an integer type", repeatLoop.untilCondition.position)) checkResult.add(ExpressionError("condition value should be an integer type", repeatLoop.untilCondition.position))
super.visit(repeatLoop) super.visit(repeatLoop)
} }
@ -340,7 +340,7 @@ internal class AstChecker(private val program: Program,
override fun visit(whileLoop: WhileLoop) { override fun visit(whileLoop: WhileLoop) {
if(whileLoop.condition.referencesIdentifiers("A", "X", "Y")) if(whileLoop.condition.referencesIdentifiers("A", "X", "Y"))
printWarning("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position) printWarning("using a register in the loop condition is risky (it could get clobbered)", whileLoop.condition.position)
if(whileLoop.condition.inferType(program) !in IntegerDatatypes) if(whileLoop.condition.inferType(program).typeOrElse(DataType.STRUCT) !in IntegerDatatypes)
checkResult.add(ExpressionError("condition value should be an integer type", whileLoop.condition.position)) checkResult.add(ExpressionError("condition value should be an integer type", whileLoop.condition.position))
super.visit(whileLoop) super.visit(whileLoop)
} }
@ -354,7 +354,8 @@ internal class AstChecker(private val program: Program,
if(stmt.returntypes.size>1) if(stmt.returntypes.size>1)
checkResult.add(ExpressionError("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position)) checkResult.add(ExpressionError("It's not possible to store the multiple results of this asmsub call; you should use a small block of custom inline assembly for this.", assignment.value.position))
else { else {
if(stmt.returntypes.single()!=assignment.target.inferType(program, assignment)) { val idt = assignment.target.inferType(program, assignment)
if(!idt.isKnown || stmt.returntypes.single()!=idt.typeOrElse(DataType.BYTE)) {
checkResult.add(ExpressionError("return type mismatch", assignment.value.position)) checkResult.add(ExpressionError("return type mismatch", assignment.value.position))
} }
} }
@ -391,7 +392,7 @@ internal class AstChecker(private val program: Program,
val targetSymbol = program.namespace.lookup(targetName, assignment) val targetSymbol = program.namespace.lookup(targetName, assignment)
when (targetSymbol) { when (targetSymbol) {
null -> { null -> {
checkResult.add(ExpressionError("undefined symbol: ${targetName.joinToString(".")}", assignment.position)) checkResult.add(UndefinedSymbolError(targetIdentifier))
return return
} }
!is VarDecl -> { !is VarDecl -> {
@ -406,6 +407,9 @@ internal class AstChecker(private val program: Program,
} }
} }
} }
val targetDt = assignTarget.inferType(program, assignment).typeOrElse(DataType.STR)
if(targetDt in IterableDatatypes)
checkResult.add(SyntaxError("cannot assign to a string or array type", assignTarget.position))
if (assignment is Assignment) { if (assignment is Assignment) {
@ -413,13 +417,13 @@ internal class AstChecker(private val program: Program,
throw FatalAstException("augmented assignment should have been converted into normal assignment") throw FatalAstException("augmented assignment should have been converted into normal assignment")
val targetDatatype = assignTarget.inferType(program, assignment) val targetDatatype = assignTarget.inferType(program, assignment)
if (targetDatatype != null) { if (targetDatatype.isKnown) {
val constVal = assignment.value.constValue(program) val constVal = assignment.value.constValue(program)
if (constVal != null) { if (constVal != null) {
checkValueTypeAndRange(targetDatatype, constVal) checkValueTypeAndRange(targetDatatype.typeOrElse(DataType.BYTE), constVal)
} else { } else {
val sourceDatatype: DataType? = assignment.value.inferType(program) val sourceDatatype = assignment.value.inferType(program)
if (sourceDatatype == null) { if (!sourceDatatype.isKnown) {
if (assignment.value is FunctionCall) { if (assignment.value is FunctionCall) {
val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace) val targetStmt = (assignment.value as FunctionCall).target.targetStatement(program.namespace)
if (targetStmt != null) if (targetStmt != null)
@ -427,7 +431,8 @@ internal class AstChecker(private val program: Program,
} else } else
checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position)) checkResult.add(ExpressionError("assignment value is invalid or has no proper datatype", assignment.value.position))
} else { } else {
checkAssignmentCompatible(targetDatatype, assignTarget, sourceDatatype, assignment.value, assignment.position) checkAssignmentCompatible(targetDatatype.typeOrElse(DataType.BYTE), assignTarget,
sourceDatatype.typeOrElse(DataType.BYTE), assignment.value, assignment.position)
} }
} }
} }
@ -439,11 +444,9 @@ internal class AstChecker(private val program: Program,
if(variable==null) if(variable==null)
checkResult.add(ExpressionError("pointer-of operand must be the name of a heap variable", addressOf.position)) checkResult.add(ExpressionError("pointer-of operand must be the name of a heap variable", addressOf.position))
else { else {
if(variable.datatype !in ArrayDatatypes && variable.datatype !in StringDatatypes && variable.datatype!=DataType.STRUCT) if(variable.datatype !in ArrayDatatypes && variable.datatype != DataType.STR && variable.datatype!=DataType.STRUCT)
checkResult.add(ExpressionError("invalid pointer-of operand type", addressOf.position)) checkResult.add(ExpressionError("invalid pointer-of operand type", addressOf.position))
} }
if(addressOf.scopedname==null)
throw FatalAstException("the scopedname of AddressOf should have been set by now $addressOf")
super.visit(addressOf) super.visit(addressOf)
} }
@ -501,9 +504,9 @@ internal class AstChecker(private val program: Program,
decl.datatype in NumericDatatypes -> { decl.datatype in NumericDatatypes -> {
// initialize numeric var with value zero by default. // initialize numeric var with value zero by default.
val litVal = val litVal =
when { when (decl.datatype) {
decl.datatype in ByteDatatypes -> NumericLiteralValue(decl.datatype, 0, decl.position) in ByteDatatypes -> NumericLiteralValue(decl.datatype, 0, decl.position)
decl.datatype in WordDatatypes -> NumericLiteralValue(decl.datatype, 0, decl.position) in WordDatatypes -> NumericLiteralValue(decl.datatype, 0, decl.position)
else -> NumericLiteralValue(decl.datatype, 0.0, decl.position) else -> NumericLiteralValue(decl.datatype, 0.0, decl.position)
} }
litVal.parent = decl litVal.parent = decl
@ -528,14 +531,12 @@ internal class AstChecker(private val program: Program,
} }
when(decl.value) { when(decl.value) {
is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value") is RangeExpr -> throw FatalAstException("range expression should have been converted to a true array value")
is ReferenceLiteralValue -> { is StringLiteralValue -> {
val arraySpec = decl.arraysize ?: ( checkValueTypeAndRangeString(decl.datatype, decl.value as StringLiteralValue)
if((decl.value as ReferenceLiteralValue).isArray) }
ArrayIndex.forArray(decl.value as ReferenceLiteralValue, program.heap) is ArrayLiteralValue -> {
else val arraySpec = decl.arraysize ?: ArrayIndex.forArray(decl.value as ArrayLiteralValue)
ArrayIndex(NumericLiteralValue.optimalInteger(-2, decl.position), decl.position) checkValueTypeAndRangeArray(decl.datatype, decl.struct, arraySpec, decl.value as ArrayLiteralValue)
)
checkValueTypeAndRange(decl.datatype, decl.struct, arraySpec, decl.value as ReferenceLiteralValue, program.heap)
} }
is NumericLiteralValue -> { is NumericLiteralValue -> {
checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue) checkValueTypeAndRange(decl.datatype, decl.value as NumericLiteralValue)
@ -684,35 +685,24 @@ internal class AstChecker(private val program: Program,
checkResult.add(NameError("included file not found: $filename", directive.position)) checkResult.add(NameError("included file not found: $filename", directive.position))
} }
override fun visit(refLiteral: ReferenceLiteralValue) { override fun visit(array: ArrayLiteralValue) {
if(!compilerOptions.floats && refLiteral.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) { if(!compilerOptions.floats && array.type in setOf(DataType.FLOAT, DataType.ARRAY_F)) {
checkResult.add(SyntaxError("floating point used, but that is not enabled via options", refLiteral.position)) checkResult.add(SyntaxError("floating point used, but that is not enabled via options", array.position))
} }
val arrayspec = val arrayspec = ArrayIndex.forArray(array)
if(refLiteral.isArray) checkValueTypeAndRangeArray(array.type, null, arrayspec, array)
ArrayIndex.forArray(refLiteral, program.heap)
else
ArrayIndex(NumericLiteralValue.optimalInteger(-3, refLiteral.position), refLiteral.position)
checkValueTypeAndRange(refLiteral.type, null, arrayspec, refLiteral, program.heap)
super.visit(refLiteral) super.visit(array)
}
when(refLiteral.type) { override fun visit(string: StringLiteralValue) {
in StringDatatypes -> { checkValueTypeAndRangeString(DataType.STR, string)
if(refLiteral.heapId==null) super.visit(string)
throw FatalAstException("string should have been moved to heap at ${refLiteral.position}")
}
in ArrayDatatypes -> {
if(refLiteral.heapId==null)
throw FatalAstException("array should have been moved to heap at ${refLiteral.position}")
}
else -> {}
}
} }
override fun visit(expr: PrefixExpression) { override fun visit(expr: PrefixExpression) {
if(expr.operator=="-") { if(expr.operator=="-") {
val dt = expr.inferType(program) val dt = expr.inferType(program).typeOrElse(DataType.STRUCT)
if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) { if (dt != DataType.BYTE && dt != DataType.WORD && dt != DataType.FLOAT) {
checkResult.add(ExpressionError("can only take negative of a signed number type", expr.position)) checkResult.add(ExpressionError("can only take negative of a signed number type", expr.position))
} }
@ -721,8 +711,13 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(expr: BinaryExpression) { override fun visit(expr: BinaryExpression) {
val leftDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program) val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown) {
throw FatalAstException("can't determine datatype of both expression operands $expr")
}
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
when(expr.operator){ when(expr.operator){
"/", "%" -> { "/", "%" -> {
@ -816,24 +811,24 @@ internal class AstChecker(private val program: Program,
val targetStatement = checkFunctionOrLabelExists(functionCall.target, stmtOfExpression) val targetStatement = checkFunctionOrLabelExists(functionCall.target, stmtOfExpression)
if(targetStatement!=null) if(targetStatement!=null)
checkFunctionCall(targetStatement, functionCall.arglist, functionCall.position) checkFunctionCall(targetStatement, functionCall.args, functionCall.position)
super.visit(functionCall) super.visit(functionCall)
} }
override fun visit(functionCallStatement: FunctionCallStatement) { override fun visit(functionCallStatement: FunctionCallStatement) {
val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement) val targetStatement = checkFunctionOrLabelExists(functionCallStatement.target, functionCallStatement)
if(targetStatement!=null) if(targetStatement!=null)
checkFunctionCall(targetStatement, functionCallStatement.arglist, functionCallStatement.position) checkFunctionCall(targetStatement, functionCallStatement.args, functionCallStatement.position)
if(targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) { if(!functionCallStatement.void && targetStatement is Subroutine && targetStatement.returntypes.isNotEmpty()) {
if(targetStatement.returntypes.size==1) if(targetStatement.returntypes.size==1)
printWarning("result value of subroutine call is discarded", functionCallStatement.position) printWarning("result value of subroutine call is discarded (use void?)", functionCallStatement.position)
else else
printWarning("result values of subroutine call are discarded", functionCallStatement.position) printWarning("result values of subroutine call are discarded (use void?)", functionCallStatement.position)
} }
if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap")) { if(functionCallStatement.target.nameInSource.last() in setOf("lsl", "lsr", "rol", "ror", "rol2", "ror2", "swap", "sort", "reverse")) {
// in-place modification, can't be done on literals // in-place modification, can't be done on literals
if(functionCallStatement.arglist.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) { if(functionCallStatement.args.any { it !is IdentifierReference && it !is RegisterExpr && it !is ArrayIndexedExpression && it !is DirectMemoryRead }) {
checkResult.add(ExpressionError("can't use that as argument to a in-place modifying function", functionCallStatement.position)) checkResult.add(ExpressionError("can't use that as argument to a in-place modifying function", functionCallStatement.position))
} }
} }
@ -850,35 +845,50 @@ internal class AstChecker(private val program: Program,
if(args.size!=func.parameters.size) if(args.size!=func.parameters.size)
checkResult.add(SyntaxError("invalid number of arguments", position)) checkResult.add(SyntaxError("invalid number of arguments", position))
else { else {
val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for (arg in args.withIndex().zip(func.parameters)) { for (arg in args.withIndex().zip(func.parameters)) {
val argDt=arg.first.value.inferType(program) val argDt=arg.first.value.inferType(program)
if(argDt!=null && !(argDt isAssignableTo arg.second.possibleDatatypes)) { if (argDt.isKnown
&& !(argDt.typeOrElse(DataType.STRUCT) isAssignableTo arg.second.possibleDatatypes)
&& (argDt.typeOrElse(DataType.STRUCT) != DataType.UWORD || arg.second.possibleDatatypes.intersect(paramTypesForAddressOf).isEmpty())) {
checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position)) checkResult.add(ExpressionError("builtin function '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.possibleDatatypes}", position))
} }
} }
if(target.name=="swap") { if(target.name=="swap") {
// swap() is a bit weird because this one is translated into a operations directly, instead of being a function call // swap() is a bit weird because this one is translated into a operations directly, instead of being a function call
val dt1 = args[0].inferType(program)!! val dt1 = args[0].inferType(program)
val dt2 = args[1].inferType(program)!! val dt2 = args[1].inferType(program)
if (dt1 != dt2) if (dt1 != dt2)
checkResult.add(ExpressionError("swap requires 2 args of identical type", position)) checkResult.add(ExpressionError("swap requires 2 args of identical type", position))
else if (args[0].constValue(program) != null || args[1].constValue(program) != null) else if (args[0].constValue(program) != null || args[1].constValue(program) != null)
checkResult.add(ExpressionError("swap requires 2 variables, not constant value(s)", position)) checkResult.add(ExpressionError("swap requires 2 variables, not constant value(s)", position))
else if(args[0] isSameAs args[1]) else if(args[0] isSameAs args[1])
checkResult.add(ExpressionError("swap should have 2 different args", position)) checkResult.add(ExpressionError("swap should have 2 different args", position))
else if(dt1 !in NumericDatatypes) else if(dt1.typeOrElse(DataType.STRUCT) !in NumericDatatypes)
checkResult.add(ExpressionError("swap requires args of numerical type", position)) checkResult.add(ExpressionError("swap requires args of numerical type", position))
} }
else if(target.name=="all" || target.name=="any") {
if((args[0] as? AddressOf)?.identifier?.targetVarDecl(program.namespace)?.datatype == DataType.STR) {
checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position))
}
if(args[0].inferType(program).typeOrElse(DataType.STR) == DataType.STR) {
checkResult.add(ExpressionError("any/all on a string is useless (is always true unless the string is empty)", position))
}
}
} }
} else if(target is Subroutine) { } else if(target is Subroutine) {
if(args.size!=target.parameters.size) if(args.size!=target.parameters.size)
checkResult.add(SyntaxError("invalid number of arguments", position)) checkResult.add(SyntaxError("invalid number of arguments", position))
else { else {
for (arg in args.withIndex().zip(target.parameters)) { for (arg in args.withIndex().zip(target.parameters)) {
val argDt = arg.first.value.inferType(program) val argIDt = arg.first.value.inferType(program)
if(argDt!=null && !(argDt isAssignableTo arg.second.type)) { if(!argIDt.isKnown) {
return
}
val argDt=argIDt.typeOrElse(DataType.STRUCT)
if(!(argDt isAssignableTo arg.second.type)) {
// for asm subroutines having STR param it's okay to provide a UWORD (address value) // for asm subroutines having STR param it's okay to provide a UWORD (address value)
if(!(target.isAsmSubroutine && arg.second.type in StringDatatypes && argDt == DataType.UWORD)) if(!(target.isAsmSubroutine && arg.second.type == DataType.STR && argDt == DataType.UWORD))
checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position)) checkResult.add(ExpressionError("subroutine '${target.name}' argument ${arg.first.index + 1} has invalid type $argDt, expected ${arg.second.type}", position))
} }
@ -911,7 +921,7 @@ internal class AstChecker(private val program: Program,
val targetName = postIncrDecr.target.identifier!!.nameInSource val targetName = postIncrDecr.target.identifier!!.nameInSource
val target = program.namespace.lookup(targetName, postIncrDecr) val target = program.namespace.lookup(targetName, postIncrDecr)
if(target==null) { if(target==null) {
checkResult.add(SyntaxError("undefined symbol: ${targetName.joinToString(".")}", postIncrDecr.position)) checkResult.add(UndefinedSymbolError(postIncrDecr.target.identifier!!))
} else { } else {
if(target !is VarDecl || target.type== VarDeclType.CONST) { if(target !is VarDecl || target.type== VarDeclType.CONST) {
checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position)) checkResult.add(SyntaxError("can only increment or decrement a variable", postIncrDecr.position))
@ -922,16 +932,15 @@ internal class AstChecker(private val program: Program,
} else if(postIncrDecr.target.arrayindexed != null) { } else if(postIncrDecr.target.arrayindexed != null) {
val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace) val target = postIncrDecr.target.arrayindexed?.identifier?.targetStatement(program.namespace)
if(target==null) { if(target==null) {
checkResult.add(SyntaxError("undefined symbol", postIncrDecr.position)) checkResult.add(NameError("undefined symbol", postIncrDecr.position))
} }
else { else {
val dt = (target as VarDecl).datatype val dt = (target as VarDecl).datatype
if(dt !in NumericDatatypes && dt !in ArrayDatatypes) if(dt !in NumericDatatypes && dt !in ArrayDatatypes)
checkResult.add(SyntaxError("can only increment or decrement a byte/float/word", postIncrDecr.position)) checkResult.add(SyntaxError("can only increment or decrement a byte/float/word", postIncrDecr.position))
} }
} else if(postIncrDecr.target.memoryAddress != null) {
// a memory location can always be ++/--
} }
// else if(postIncrDecr.target.memoryAddress != null) { } // a memory location can always be ++/--
super.visit(postIncrDecr) super.visit(postIncrDecr)
} }
@ -946,11 +955,10 @@ internal class AstChecker(private val program: Program,
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt() val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
if(index!=null && (index<0 || index>=arraysize)) if(index!=null && (index<0 || index>=arraysize))
checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position)) checkResult.add(ExpressionError("array index out of bounds", arrayIndexedExpression.arrayspec.position))
} else if(target.datatype in StringDatatypes) { } else if(target.datatype == DataType.STR) {
if(target.value is ReferenceLiteralValue) { if(target.value is StringLiteralValue) {
// check string lengths for non-memory mapped strings // check string lengths for non-memory mapped strings
val heapId = (target.value as ReferenceLiteralValue).heapId!! val stringLen = (target.value as StringLiteralValue).value.length
val stringLen = program.heap.get(heapId).str!!.length
val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt() val index = (arrayIndexedExpression.arrayspec.index as? NumericLiteralValue)?.number?.toInt()
if (index != null && (index < 0 || index >= stringLen)) if (index != null && (index < 0 || index >= stringLen))
checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position)) checkResult.add(ExpressionError("index out of bounds", arrayIndexedExpression.arrayspec.position))
@ -960,7 +968,7 @@ internal class AstChecker(private val program: Program,
checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position)) checkResult.add(SyntaxError("indexing requires a variable to act upon", arrayIndexedExpression.position))
// check index value 0..255 // check index value 0..255
val dtx = arrayIndexedExpression.arrayspec.index.inferType(program) val dtx = arrayIndexedExpression.arrayspec.index.inferType(program).typeOrElse(DataType.STRUCT)
if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE) if(dtx!= DataType.UBYTE && dtx!= DataType.BYTE)
checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position)) checkResult.add(SyntaxError("array indexing is limited to byte size 0..255", arrayIndexedExpression.position))
@ -968,7 +976,7 @@ internal class AstChecker(private val program: Program,
} }
override fun visit(whenStatement: WhenStatement) { override fun visit(whenStatement: WhenStatement) {
val conditionType = whenStatement.condition.inferType(program) val conditionType = whenStatement.condition.inferType(program).typeOrElse(DataType.STRUCT)
if(conditionType !in IntegerDatatypes) if(conditionType !in IntegerDatatypes)
checkResult.add(SyntaxError("when condition must be an integer value", whenStatement.position)) checkResult.add(SyntaxError("when condition must be an integer value", whenStatement.position))
val choiceValues = whenStatement.choiceValues(program) val choiceValues = whenStatement.choiceValues(program)
@ -987,12 +995,14 @@ internal class AstChecker(private val program: Program,
val whenStmt = whenChoice.parent as WhenStatement val whenStmt = whenChoice.parent as WhenStatement
if(whenChoice.values!=null) { if(whenChoice.values!=null) {
val conditionType = whenStmt.condition.inferType(program) val conditionType = whenStmt.condition.inferType(program)
if(!conditionType.isKnown)
throw FatalAstException("can't determine when choice datatype $whenChoice")
val constvalues = whenChoice.values!!.map { it.constValue(program) } val constvalues = whenChoice.values!!.map { it.constValue(program) }
for(constvalue in constvalues) { for(constvalue in constvalues) {
when { when {
constvalue == null -> checkResult.add(SyntaxError("choice value must be a constant", whenChoice.position)) constvalue == null -> checkResult.add(SyntaxError("choice value must be a constant", whenChoice.position))
constvalue.type !in IntegerDatatypes -> checkResult.add(SyntaxError("choice value must be a byte or word", whenChoice.position)) constvalue.type !in IntegerDatatypes -> checkResult.add(SyntaxError("choice value must be a byte or word", whenChoice.position))
constvalue.type != conditionType -> checkResult.add(SyntaxError("choice value datatype differs from condition value", whenChoice.position)) constvalue.type != conditionType.typeOrElse(DataType.STRUCT) -> checkResult.add(SyntaxError("choice value datatype differs from condition value", whenChoice.position))
} }
} }
} else { } else {
@ -1028,26 +1038,33 @@ internal class AstChecker(private val program: Program,
return null return null
} }
private fun checkValueTypeAndRange(targetDt: DataType, struct: StructDecl?, private fun checkValueTypeAndRangeString(targetDt: DataType, value: StringLiteralValue) : Boolean {
arrayspec: ArrayIndex, value: ReferenceLiteralValue, heap: HeapValues) : Boolean { return if (targetDt == DataType.STR) {
if (value.value.length > 255) {
checkResult.add(ExpressionError("string length must be 0-255", value.position))
false
}
else
true
}
else false
}
private fun checkValueTypeAndRangeArray(targetDt: DataType, struct: StructDecl?,
arrayspec: ArrayIndex, value: ArrayLiteralValue) : Boolean {
fun err(msg: String) : Boolean { fun err(msg: String) : Boolean {
checkResult.add(ExpressionError(msg, value.position)) checkResult.add(ExpressionError(msg, value.position))
return false return false
} }
when (targetDt) { when (targetDt) {
in StringDatatypes -> { DataType.STR -> return err("string value expected")
if(!value.isString)
return err("string value expected")
if (value.str!!.length > 255)
return err("string length must be 0-255")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> { DataType.ARRAY_UB, DataType.ARRAY_B -> {
// value may be either a single byte, or a byte arraysize (of all constant values), or a range // value may be either a single byte, or a byte arraysize (of all constant values), or a range
if(value.type==targetDt) { if(value.type==targetDt) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySpecSize = arrayspec.size() val arraySpecSize = arrayspec.size()
val arraySize = value.array?.size ?: heap.get(value.heapId!!).arraysize val arraySize = value.value.size
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>256) if(arraySpecSize<1 || arraySpecSize>256)
return err("byte array length must be 1-256") return err("byte array length must be 1-256")
@ -1069,7 +1086,7 @@ internal class AstChecker(private val program: Program,
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySpecSize = arrayspec.size() val arraySpecSize = arrayspec.size()
val arraySize = value.array?.size ?: heap.get(value.heapId!!).arraysize val arraySize = value.value.size
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize<1 || arraySpecSize>128) if(arraySpecSize<1 || arraySpecSize>128)
return err("word array length must be 1-128") return err("word array length must be 1-128")
@ -1090,7 +1107,7 @@ internal class AstChecker(private val program: Program,
if(value.type==targetDt) { if(value.type==targetDt) {
if(!checkArrayValues(value, targetDt)) if(!checkArrayValues(value, targetDt))
return false return false
val arraySize = value.array?.size ?: heap.get(value.heapId!!).doubleArray!!.size val arraySize = value.value.size
val arraySpecSize = arrayspec.size() val arraySpecSize = arrayspec.size()
if(arraySpecSize!=null && arraySpecSize>0) { if(arraySpecSize!=null && arraySpecSize>0) {
if(arraySpecSize < 1 || arraySpecSize>51) if(arraySpecSize < 1 || arraySpecSize>51)
@ -1105,11 +1122,8 @@ internal class AstChecker(private val program: Program,
return err("invalid float array size, must be 1-51") return err("invalid float array size, must be 1-51")
// check if the floating point values are all within range // check if the floating point values are all within range
val doubles = if(value.array!=null) val doubles = value.value.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray()
value.array.map {it.constValue(program)?.number!!.toDouble()}.toDoubleArray() if(doubles.any { it < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || it > CompilationTarget.machine.FLOAT_MAX_POSITIVE })
else
heap.get(value.heapId!!).doubleArray!!
if(doubles.any { it < FLOAT_MAX_NEGATIVE || it> FLOAT_MAX_POSITIVE })
return err("floating point value overflow") return err("floating point value overflow")
return true return true
} }
@ -1117,12 +1131,12 @@ internal class AstChecker(private val program: Program,
} }
DataType.STRUCT -> { DataType.STRUCT -> {
if(value.type in ArrayDatatypes) { if(value.type in ArrayDatatypes) {
if(value.array!!.size != struct!!.numberOfElements) if(value.value.size != struct!!.numberOfElements)
return err("number of values is not the same as the number of members in the struct") return err("number of values is not the same as the number of members in the struct")
for(elt in value.array.zip(struct.statements)) { for(elt in value.value.zip(struct.statements)) {
val vardecl = elt.second as VarDecl val vardecl = elt.second as VarDecl
val valuetype = elt.first.inferType(program)!! val valuetype = elt.first.inferType(program)
if (!(valuetype isAssignableTo vardecl.datatype)) { if (!valuetype.isKnown || !(valuetype.typeOrElse(DataType.STRUCT) isAssignableTo vardecl.datatype)) {
checkResult.add(ExpressionError("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position)) checkResult.add(ExpressionError("invalid struct member init value type $valuetype, expected ${vardecl.datatype}", elt.first.position))
return false return false
} }
@ -1133,7 +1147,6 @@ internal class AstChecker(private val program: Program,
} }
else -> return false else -> return false
} }
return true
} }
private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteralValue) : Boolean { private fun checkValueTypeAndRange(targetDt: DataType, value: NumericLiteralValue) : Boolean {
@ -1180,54 +1193,36 @@ internal class AstChecker(private val program: Program,
return true return true
} }
private fun checkArrayValues(value: ReferenceLiteralValue, type: DataType): Boolean { private fun checkArrayValues(value: ArrayLiteralValue, type: DataType): Boolean {
if(value.isArray && value.heapId==null) { val array = value.value.map {
// hmm weird, array literal that hasn't been moved to the heap yet? when (it) {
val array = value.array!!.map { it.constValue(program)!! } is NumericLiteralValue -> it.number.toInt()
val correct: Boolean is AddressOf -> it.identifier.heapId(program.namespace)
when(type) { is TypecastExpression -> {
DataType.ARRAY_UB -> { val constVal = it.expression.constValue(program)
correct=array.all { it.type==DataType.UBYTE && it.number.toInt() in 0..255 } constVal?.cast(it.type)?.number?.toInt() ?: -9999999
} }
DataType.ARRAY_B -> { else -> -9999999
correct=array.all { it.type==DataType.BYTE && it.number.toInt() in -128..127 }
}
DataType.ARRAY_UW -> {
correct=array.all { it.type==DataType.UWORD && it.number.toInt() in 0..65535 }
}
DataType.ARRAY_W -> {
correct=array.all { it.type==DataType.WORD && it.number.toInt() 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!!)
if(array.type !in ArrayDatatypes || (array.array==null && array.doubleArray==null))
throw FatalAstException("should have an array in the heapvar $array")
val correct: Boolean val correct: Boolean
when(type) { when (type) {
DataType.ARRAY_UB -> { DataType.ARRAY_UB -> {
correct= array.array?.all { it.integer!=null && it.integer in 0..255 } ?: false correct = array.all { it in 0..255 }
} }
DataType.ARRAY_B -> { DataType.ARRAY_B -> {
correct=array.array?.all { it.integer!=null && it.integer in -128..127 } ?: false correct = array.all { it in -128..127 }
} }
DataType.ARRAY_UW -> { DataType.ARRAY_UW -> {
correct=array.array?.all { (it.integer!=null && it.integer in 0..65535) || it.addressOf!=null} ?: false correct = array.all { (it in 0..65535) }
} }
DataType.ARRAY_W -> { DataType.ARRAY_W -> {
correct=array.array?.all { it.integer!=null && it.integer in -32768..32767 } ?: false correct = array.all { it in -32768..32767 }
} }
DataType.ARRAY_F -> correct = array.doubleArray!=null DataType.ARRAY_F -> correct = true
else -> throw AstException("invalid array type $type") else -> throw AstException("invalid array type $type")
} }
if(!correct) if (!correct)
checkResult.add(ExpressionError("array value out of range for type $type", value.position)) checkResult.add(ExpressionError("array value out of range for type $type", value.position))
return correct return correct
} }
@ -1248,7 +1243,6 @@ internal class AstChecker(private val program: Program,
DataType.UWORD -> sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.UWORD DataType.UWORD -> sourceDatatype== DataType.UBYTE || sourceDatatype== DataType.UWORD
DataType.FLOAT -> sourceDatatype in NumericDatatypes DataType.FLOAT -> sourceDatatype in NumericDatatypes
DataType.STR -> sourceDatatype== DataType.STR DataType.STR -> sourceDatatype== DataType.STR
DataType.STR_S -> sourceDatatype== DataType.STR_S
DataType.STRUCT -> { DataType.STRUCT -> {
if(sourceDatatype==DataType.STRUCT) { if(sourceDatatype==DataType.STRUCT) {
val structLv = sourceValue as StructLiteralValue val structLv = sourceValue as StructLiteralValue

View File

@ -7,8 +7,7 @@ import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.HeapValues import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.AssemblyProgram
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
@ -51,7 +50,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(functionCall: FunctionCall): Expression { override fun visit(functionCall: FunctionCall): Expression {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") { if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte" // lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position) val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
typecast.linkParents(functionCall.parent) typecast.linkParents(functionCall.parent)
return super.visit(typecast) return super.visit(typecast)
} }
@ -67,8 +66,8 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
// the builtin functions can't be redefined // the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position)) checkResult.add(NameError("builtin function cannot be redefined", decl.position))
if(decl.name in AssemblyProgram.opcodeNames) if(decl.name in CompilationTarget.machine.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol", decl.position)) checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${decl.name}'", decl.position))
// is it a struct variable? then define all its struct members as mangled names, // is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well. // and include the original decl as well.
@ -104,8 +103,8 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
} }
override fun visit(subroutine: Subroutine): Statement { override fun visit(subroutine: Subroutine): Statement {
if(subroutine.name in AssemblyProgram.opcodeNames) { if(subroutine.name in CompilationTarget.machine.opcodeNames) {
checkResult.add(NameError("can't use a cpu opcode name as a symbol", subroutine.position)) checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position))
} else if(subroutine.name in BuiltinFunctions) { } else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined // the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position)) checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
@ -144,8 +143,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
// NOTE: // NOTE:
// - numeric types BYTE and WORD and FLOAT are passed by value; // - numeric types BYTE and WORD and FLOAT are passed by value;
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter) // - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
// - do NOT do this is the statement can be transformed into an asm subroutine later! if(subroutine.asmAddress==null) {
if(subroutine.asmAddress==null && !subroutine.canBeAsmSubroutine) {
if(subroutine.asmParameterRegisters.isEmpty()) { if(subroutine.asmParameterRegisters.isEmpty()) {
subroutine.parameters subroutine.parameters
.filter { it.name !in namesInSub } .filter { it.name !in namesInSub }
@ -157,13 +155,17 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
} }
} }
} }
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position))
}
} }
return super.visit(subroutine) return super.visit(subroutine)
} }
override fun visit(label: Label): Statement { override fun visit(label: Label): Statement {
if(label.name in AssemblyProgram.opcodeNames) if(label.name in CompilationTarget.machine.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol", label.position)) checkResult.add(NameError("can't use a cpu opcode name as a symbol: '${label.name}'", label.position))
if(label.name in BuiltinFunctions) { if(label.name in BuiltinFunctions) {
// the builtin functions can't be redefined // the builtin functions can't be redefined
@ -182,28 +184,11 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
// For loops that loop over an interable variable (instead of a range of numbers) get an // For loops that loop over an interable variable (instead of a range of numbers) get an
// additional interation count variable in their scope. // additional interation count variable in their scope.
if(forLoop.loopRegister!=null) { if(forLoop.loopRegister!=null) {
if(forLoop.decltype!=null)
checkResult.add(SyntaxError("register loop variables have a fixed implicit datatype", forLoop.position))
if(forLoop.loopRegister == Register.X) if(forLoop.loopRegister == Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position) printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
} else { } else {
val loopVar = forLoop.loopVar val loopVar = forLoop.loopVar
if (loopVar != null) { if (loopVar != null) {
val varName = loopVar.nameInSource.last()
if (forLoop.decltype != null) {
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(loopVar.nameInSource, forLoop.body.statements.first())
if (existing == null) {
// create the local scoped for loop variable itself
val vardecl = VarDecl(VarDeclType.VAR, forLoop.decltype, forLoop.zeropage, null, varName, null, null,
isArray = false, autogeneratedDontRemove = true, position = loopVar.position)
vardecl.linkParents(forLoop.body)
forLoop.body.statements.add(0, vardecl)
loopVar.parent = forLoop.body // loopvar 'is defined in the body'
} else if(existing.parent!==forLoop && existing.parent.parent!==forLoop) {
checkResult.add(NameError("for loop var was already defined at ${existing.position}", loopVar.position))
}
}
val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "") val validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
val loopvarName = "prog8_loopvar_$validName" val loopvarName = "prog8_loopvar_$validName"
if (forLoop.iterable !is RangeExpr) { if (forLoop.iterable !is RangeExpr) {
@ -234,90 +219,86 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
val subroutine = returnStmt.definingSubroutine()!! val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size!=1) if(subroutine.returntypes.size!=1)
return returnStmt // mismatch in number of return values, error will be printed later. return returnStmt // mismatch in number of return values, error will be printed later.
val newValue: Expression
val lval = returnStmt.value as? NumericLiteralValue val lval = returnStmt.value as? NumericLiteralValue
if(lval!=null) { returnStmt.value = lval?.cast(subroutine.returntypes.single()) ?: returnStmt.value!!
val adjusted = lval.cast(subroutine.returntypes.single())
newValue = if(adjusted!=null && adjusted !== lval) adjusted else lval
} else {
newValue = returnStmt.value!!
}
returnStmt.value = newValue
} }
return super.visit(returnStmt) return super.visit(returnStmt)
} }
override fun visit(refLiteral: ReferenceLiteralValue): Expression { override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val litval = super.visit(refLiteral) val array = super.visit(arrayLiteral)
if(litval is ReferenceLiteralValue) { if(array is ArrayLiteralValue) {
val vardecl = litval.parent as? VarDecl val vardecl = array.parent as? VarDecl
if (litval.isString) { return if(vardecl!=null)
// intern the string; move it into the heap fixupArrayEltDatatypesFromVardecl(array, vardecl)
if (litval.str!!.length !in 1..255) else {
checkResult.add(ExpressionError("string literal length must be between 1 and 255", litval.position)) // fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
else { // (we don't know the desired datatype here exactly so we guess)
litval.addToHeap(program.heap) val datatype = determineArrayDt(array.value)
} val litval2 = array.cast(datatype)
return if(vardecl!=null) if(litval2!=null) {
litval litval2.parent = array.parent
else
makeIdentifierFromRefLv(litval) // replace the literal string by a identifier reference.
} else if (litval.isArray) {
if (vardecl!=null) {
return fixupArrayDatatype(litval, vardecl, program.heap)
} else {
// fix the datatype of the array (also on the heap) to the 'biggest' datatype in the array
// (we don't know the desired datatype here exactly so we guess)
val datatype = determineArrayDt(litval.array!!) ?: return litval
val litval2 = litval.cast(datatype)!!
litval2.parent = litval.parent
// finally, replace the literal array by a identifier reference. // finally, replace the literal array by a identifier reference.
return makeIdentifierFromRefLv(litval2) makeIdentifierFromRefLv(litval2)
} } else array
} }
} }
return array
return litval
} }
private fun determineArrayDt(array: Array<Expression>): DataType? { override fun visit(stringLiteral: StringLiteralValue): Expression {
val datatypesInArray = array.mapNotNull { it.inferType(program) } val string = super.visit(stringLiteral)
if(datatypesInArray.isEmpty()) if(string is StringLiteralValue) {
return null val vardecl = string.parent as? VarDecl
if(DataType.FLOAT in datatypesInArray) // intern the string; move it into the heap
return DataType.ARRAY_F if (string.value.length !in 1..255)
if(DataType.WORD in datatypesInArray) checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position))
return DataType.ARRAY_W return if (vardecl != null)
if(DataType.UWORD in datatypesInArray) string
return DataType.ARRAY_UW else
if(DataType.BYTE in datatypesInArray) makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
return DataType.ARRAY_B }
if(DataType.UBYTE in datatypesInArray) return string
return DataType.ARRAY_UB
return null
} }
private fun makeIdentifierFromRefLv(refLiteral: ReferenceLiteralValue): IdentifierReference { private fun determineArrayDt(array: Array<Expression>): DataType {
val datatypesInArray = array.map { it.inferType(program) }
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when {
DataType.FLOAT in dts -> DataType.ARRAY_F
DataType.WORD in dts -> DataType.ARRAY_W
DataType.UWORD in dts -> DataType.ARRAY_UW
DataType.BYTE in dts -> DataType.ARRAY_B
DataType.UBYTE in dts -> DataType.ARRAY_UB
else -> throw IllegalArgumentException("can't determine type of array")
}
}
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable // a referencetype literal value that's not declared as a variable
// we need to introduce an auto-generated variable for this to be able to refer to the value // we need to introduce an auto-generated variable for this to be able to refer to the value
// note: if the var references the same literal value, it is not yet de-duplicated here. // note: if the var references the same literal value, it is not yet de-duplicated here.
refLiteral.addToHeap(program.heap) val scope = array.definingScope()
val scope = refLiteral.definingScope() val variable = VarDecl.createAuto(array)
var variable = VarDecl.createAuto(refLiteral, program.heap) return replaceWithIdentifier(variable, scope, array.parent)
val existing = scope.lookup(listOf(variable.name), refLiteral)
variable = addVarDecl(scope, variable)
// replace the reference literal by a identifier reference
val identifier = IdentifierReference(listOf(variable.name), variable.position)
identifier.parent = refLiteral.parent
return identifier
} }
override fun visit(addressOf: AddressOf): Expression { private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
// register the scoped name of the referenced identifier // a referencetype literal value that's not declared as a variable
val variable= addressOf.identifier.targetVarDecl(program.namespace) ?: return addressOf // we need to introduce an auto-generated variable for this to be able to refer to the value
addressOf.scopedname = variable.scopedname // note: if the var references the same literal value, it is not yet de-duplicated here.
return super.visit(addressOf) val scope = string.definingScope()
val variable = VarDecl.createAuto(string)
return replaceWithIdentifier(variable, scope, string.parent)
}
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
val variable1 = addVarDecl(scope, variable)
// replace the reference literal by a identifier reference
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
identifier.parent = parent
return identifier
} }
override fun visit(structDecl: StructDecl): Statement { override fun visit(structDecl: StructDecl): Statement {
@ -332,33 +313,26 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(expr: BinaryExpression): Expression { override fun visit(expr: BinaryExpression): Expression {
return when { return when {
expr.left is ReferenceLiteralValue -> expr.left is StringLiteralValue ->
processBinaryExprWithReferenceVal(expr.left as ReferenceLiteralValue, expr.right, expr) processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
expr.right is ReferenceLiteralValue -> expr.right is StringLiteralValue ->
processBinaryExprWithReferenceVal(expr.right as ReferenceLiteralValue, expr.left, expr) processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
else -> super.visit(expr) else -> super.visit(expr)
} }
} }
private fun processBinaryExprWithReferenceVal(refLv: ReferenceLiteralValue, operand: Expression, expr: BinaryExpression): Expression { private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
// expressions on strings or arrays val constvalue = operand.constValue(program)
if(refLv.isString) { if(constvalue!=null) {
val constvalue = operand.constValue(program) if (expr.operator == "*") {
if(constvalue!=null) { // repeat a string a number of times
if (expr.operator == "*") { return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), expr.position)
// repeat a string a number of times
return ReferenceLiteralValue(refLv.inferType(program),
refLv.str!!.repeat(constvalue.number.toInt()), null, null, expr.position)
}
}
if(expr.operator == "+" && operand is ReferenceLiteralValue) {
if (operand.isString) {
// concatenate two strings
return ReferenceLiteralValue(refLv.inferType(program),
"${refLv.str}${operand.str}", null, null, expr.position)
}
} }
} }
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
return StringLiteralValue("${string.value}${operand.value}", expr.position)
}
return expr return expr
} }
@ -377,19 +351,45 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
} }
internal fun fixupArrayDatatype(array: ReferenceLiteralValue, vardecl: VarDecl, heap: HeapValues): ReferenceLiteralValue { internal fun fixupArrayEltDatatypes(array: ArrayLiteralValue, program: Program): ArrayLiteralValue {
if(array.heapId!=null) { val dts = array.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
val arrayDt = array.type if(dts.any { it !in NumericDatatypes }) {
if(arrayDt!=vardecl.datatype) { return array
// fix the datatype of the array (also on the heap) to match the vardecl }
val litval2 = array.cast(vardecl.datatype)!! val dt = when {
vardecl.value = litval2 DataType.FLOAT in dts -> DataType.ARRAY_F
litval2.linkParents(vardecl) DataType.WORD in dts -> DataType.ARRAY_W
litval2.addToHeap(heap) // TODO is the previous array discarded from the resulting asm code? DataType.UWORD in dts -> DataType.ARRAY_UW
return litval2 DataType.BYTE in dts -> DataType.ARRAY_B
else -> DataType.ARRAY_UB
}
if(dt==array.type)
return array
// convert values and array type
val elementType = ArrayElementTypes.getValue(dt)
val allNumerics = array.value.all { it is NumericLiteralValue }
if(allNumerics) {
val values = array.value.map { (it as NumericLiteralValue).cast(elementType) as Expression }.toTypedArray()
val array2 = ArrayLiteralValue(dt, values, array.position)
array2.linkParents(array.parent)
return array2
}
return array
}
internal fun fixupArrayEltDatatypesFromVardecl(array: ArrayLiteralValue, vardecl: VarDecl): ArrayLiteralValue {
val arrayDt = array.type
if(arrayDt!=vardecl.datatype) {
// fix the datatype of the array (also on the heap) to match the vardecl
val cast = array.cast(vardecl.datatype)
if (cast != null) {
vardecl.value = cast
cast.linkParents(vardecl)
return cast
} }
} else { // can't be casted yet, attempt again later
array.addToHeap(heap)
} }
return array return array
} }

View File

@ -52,7 +52,7 @@ interface IAstModifyingVisitor {
functionCall.target = newtarget functionCall.target = newtarget
else else
throw FatalAstException("cannot change class of function call target") throw FatalAstException("cannot change class of function call target")
functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList() functionCall.args = functionCall.args.map { it.accept(this) }.toMutableList()
return functionCall return functionCall
} }
@ -62,7 +62,7 @@ interface IAstModifyingVisitor {
functionCallStatement.target = newtarget functionCallStatement.target = newtarget
else else
throw FatalAstException("cannot change class of function call target") throw FatalAstException("cannot change class of function call target")
functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList() functionCallStatement.args = functionCallStatement.args.map { it.accept(this) }.toMutableList()
return functionCallStatement return functionCallStatement
} }
@ -110,14 +110,16 @@ interface IAstModifyingVisitor {
return literalValue return literalValue
} }
fun visit(refLiteral: ReferenceLiteralValue): Expression { fun visit(stringLiteral: StringLiteralValue): Expression {
if(refLiteral.array!=null) { return stringLiteral
for(av in refLiteral.array.withIndex()) { }
val newvalue = av.value.accept(this)
refLiteral.array[av.index] = newvalue fun visit(arrayLiteral: ArrayLiteralValue): Expression {
} for(av in arrayLiteral.value.withIndex()) {
val newvalue = av.value.accept(this)
arrayLiteral.value[av.index] = newvalue
} }
return refLiteral return arrayLiteral
} }
fun visit(assignment: Assignment): Statement { fun visit(assignment: Assignment): Statement {
@ -140,8 +142,7 @@ interface IAstModifyingVisitor {
} }
fun visit(forLoop: ForLoop): Statement { fun visit(forLoop: ForLoop): Statement {
val newloopvar = forLoop.loopVar?.accept(this) when(val newloopvar = forLoop.loopVar?.accept(this)) {
when(newloopvar) {
is IdentifierReference -> forLoop.loopVar = newloopvar is IdentifierReference -> forLoop.loopVar = newloopvar
null -> forLoop.loopVar = null null -> forLoop.loopVar = null
else -> throw FatalAstException("can't change class of loopvar") else -> throw FatalAstException("can't change class of loopvar")
@ -177,8 +178,7 @@ interface IAstModifyingVisitor {
} }
fun visit(assignTarget: AssignTarget): AssignTarget { fun visit(assignTarget: AssignTarget): AssignTarget {
val ident = assignTarget.identifier?.accept(this) when (val ident = assignTarget.identifier?.accept(this)) {
when (ident) {
is IdentifierReference -> assignTarget.identifier = ident is IdentifierReference -> assignTarget.identifier = ident
null -> assignTarget.identifier = null null -> assignTarget.identifier = null
else -> throw FatalAstException("can't change class of assign target identifier") else -> throw FatalAstException("can't change class of assign target identifier")

View File

@ -41,12 +41,12 @@ interface IAstVisitor {
fun visit(functionCall: FunctionCall) { fun visit(functionCall: FunctionCall) {
functionCall.target.accept(this) functionCall.target.accept(this)
functionCall.arglist.forEach { it.accept(this) } functionCall.args.forEach { it.accept(this) }
} }
fun visit(functionCallStatement: FunctionCallStatement) { fun visit(functionCallStatement: FunctionCallStatement) {
functionCallStatement.target.accept(this) functionCallStatement.target.accept(this)
functionCallStatement.arglist.forEach { it.accept(this) } functionCallStatement.args.forEach { it.accept(this) }
} }
fun visit(identifier: IdentifierReference) { fun visit(identifier: IdentifierReference) {
@ -79,8 +79,11 @@ interface IAstVisitor {
fun visit(numLiteral: NumericLiteralValue) { fun visit(numLiteral: NumericLiteralValue) {
} }
fun visit(refLiteral: ReferenceLiteralValue) { fun visit(string: StringLiteralValue) {
refLiteral.array?.let { it.forEach { v->v.accept(this) }} }
fun visit(array: ArrayLiteralValue) {
array.value.forEach { v->v.accept(this) }
} }
fun visit(assignment: Assignment) { fun visit(assignment: Assignment) {

View File

@ -4,19 +4,17 @@ import prog8.ast.*
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName import prog8.ast.base.initvarsSubName
import prog8.ast.base.printWarning
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> { private fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program: Program): List<Assignment> {
val identifier = structAssignment.target.identifier!! val identifier = structAssignment.target.identifier!!
val identifierName = identifier.nameInSource.single() val identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!! val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!! val struct = targetVar.struct!!
when { when (structAssignment.value) {
structAssignment.value is IdentifierReference -> { is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!! val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null) if (sourceVar.struct == null)
throw FatalAstException("can only assign arrays or structs to structs") throw FatalAstException("can only assign arrays or structs to structs")
@ -41,7 +39,7 @@ fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program:
assign assign
} }
} }
structAssignment.value is StructLiteralValue -> { is StructLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here") throw IllegalArgumentException("not going to flatten a structLv assignment here")
} }
else -> throw FatalAstException("strange struct value") else -> throw FatalAstException("strange struct value")
@ -61,13 +59,13 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
// - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives. // - the 'start' subroutine in the 'main' block will be moved to the top immediately following the directives.
// - all other subroutines will be moved to the end of their block. // - all other subroutines will be moved to the end of their block.
// - sorts the choices in when statement. // - sorts the choices in when statement.
//
// Also, makes sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
// (this includes function call arguments)
private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option") private val directivesToMove = setOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address", "%option")
private val addReturns = mutableListOf<Pair<INameScope, Int>>()
override fun visit(module: Module) { override fun visit(module: Module) {
addReturns.clear()
super.visit(module) super.visit(module)
val (blocks, other) = module.statements.partition { it is Block } val (blocks, other) = module.statements.partition { it is Block }
@ -95,6 +93,13 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove} val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
module.statements.removeAll(directives) module.statements.removeAll(directives)
module.statements.addAll(0, directives) module.statements.addAll(0, directives)
for(pos in addReturns) {
println(pos)
val returnStmt = Return(null, pos.first.position)
returnStmt.linkParents(pos.first as Node)
pos.first.statements.add(pos.second, returnStmt)
}
} }
override fun visit(block: Block): Statement { override fun visit(block: Block): Statement {
@ -164,6 +169,19 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
override fun visit(subroutine: Subroutine): Statement { override fun visit(subroutine: Subroutine): Statement {
super.visit(subroutine) super.visit(subroutine)
val scope = subroutine.definingScope()
if(scope is Subroutine) {
for(stmt in scope.statements.withIndex()) {
if(stmt.index>0 && stmt.value===subroutine) {
val precedingStmt = scope.statements[stmt.index-1]
if(precedingStmt !is Jump && precedingStmt !is Subroutine) {
// insert a return statement before a nested subroutine, to avoid falling trough inside the subroutine
addReturns.add(Pair(scope, stmt.index))
}
}
}
}
val varDecls = subroutine.statements.filterIsInstance<VarDecl>() val varDecls = subroutine.statements.filterIsInstance<VarDecl>()
subroutine.statements.removeAll(varDecls) subroutine.statements.removeAll(varDecls)
subroutine.statements.addAll(0, varDecls) subroutine.statements.addAll(0, varDecls)
@ -186,64 +204,34 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return subroutine return subroutine
} }
override fun visit(expr: BinaryExpression): Expression {
val expr2 = super.visit(expr)
if(expr2 !is BinaryExpression)
return expr2
val leftDt = expr2.left.inferType(program)
val rightDt = expr2.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) = BinaryExpression.commonDatatype(leftDt, rightDt, expr2.left, expr2.right)
if(toFix!=null) {
when {
toFix===expr2.left -> {
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
expr2.left.linkParents(expr2)
}
toFix===expr2.right -> {
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
expr2.right.linkParents(expr2)
}
else -> throw FatalAstException("confused binary expression side")
}
}
}
return expr2
}
override fun visit(assignment: Assignment): Statement { override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment) val assg = super.visit(assignment)
if(assg !is Assignment) if(assg !is Assignment)
return assg return assg
// see if a typecast is needed to convert the value's type into the proper target type // see if a typecast is needed to convert the value's type into the proper target type
val valuetype = assg.value.inferType(program) val valueItype = assg.value.inferType(program)
val targettype = assg.target.inferType(program, assg) val targetItype = assg.target.inferType(program, assg)
if(targettype!=null && valuetype!=null) {
if(valuetype!=targettype) { if(targetItype.isKnown && valueItype.isKnown) {
if (valuetype isAssignableTo targettype) { val targettype = targetItype.typeOrElse(DataType.STRUCT)
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position) val valuetype = valueItype.typeOrElse(DataType.STRUCT)
assg.value.linkParents(assg)
// struct assignments will be flattened (if it's not a struct literal)
if (valuetype == DataType.STRUCT && targettype == DataType.STRUCT) {
if (assg.value is StructLiteralValue)
return assg // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed)
val assignments = flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
return if (assignments.isEmpty()) {
// something went wrong (probably incompatible struct types)
// we'll get an error later from the AstChecker
assg
} else {
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
scope.linkParents(assg.parent)
scope
} }
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
// struct assignments will be flattened (if it's not a struct literal)
if(valuetype==DataType.STRUCT && targettype==DataType.STRUCT) {
if(assg.value is StructLiteralValue)
return assg // do NOT flatten it at this point!! (the compiler will take care if it, later, if needed)
val assignments = flattenStructAssignmentFromIdentifier(assg, program) // 'structvar1 = structvar2'
return if(assignments.isEmpty()) {
// something went wrong (probably incompatible struct types)
// we'll get an error later from the AstChecker
assg
} else {
val scope = AnonymousScope(assignments.toMutableList(), assg.position)
scope.linkParents(assg.parent)
scope
} }
} }
@ -267,134 +255,4 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
return assg return assg
} }
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
return super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall): Expression {
checkFunctionCallArguments(functionCall, functionCall.definingScope())
return super.visit(functionCall)
}
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
// see if a typecast is needed to convert the arguments into the required parameter's type
when(val sub = call.target.targetStatement(scope)) {
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 -> {
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}")
}
}
override fun visit(typecast: TypecastExpression): Expression {
// 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.visit(typecast)
}
override fun visit(memread: DirectMemoryRead): Expression {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt!=DataType.UWORD) {
val literaladdr = memread.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)!!
} else {
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
memread.addressExpression.parent = memread
}
}
return super.visit(memread)
}
override fun visit(memwrite: DirectMemoryWrite) {
val dt = memwrite.addressExpression.inferType(program)
if(dt!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)!!
} else {
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
memwrite.addressExpression.parent = memwrite
}
}
super.visit(memwrite)
}
override fun visit(structLv: StructLiteralValue): Expression {
val litval = super.visit(structLv)
if(litval !is StructLiteralValue)
return litval
val decl = litval.parent as? VarDecl
if(decl != null) {
val struct = decl.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
} else {
val assign = litval.parent as? Assignment
if (assign != null) {
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
if(decl2 != null) {
val struct = decl2.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
}
}
}
return litval
}
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
structLv.values = struct.statements.zip(structLv.values).map {
val memberDt = (it.first as VarDecl).datatype
val valueDt = it.second.inferType(program)
if (valueDt != memberDt)
TypecastExpression(it.second, memberDt, true, it.second.position)
else
it.second
}
}
} }

View File

@ -0,0 +1,198 @@
package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.printWarning
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor {
// Make 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)
override fun visit(expr: BinaryExpression): Expression {
val expr2 = super.visit(expr)
if(expr2 !is BinaryExpression)
return expr2
val leftDt = expr2.left.inferType(program)
val rightDt = expr2.right.inferType(program)
if(leftDt.isKnown && rightDt.isKnown && leftDt!=rightDt) {
// determine common datatype and add typecast as required to make left and right equal types
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr2.left, expr2.right)
if(toFix!=null) {
when {
toFix===expr2.left -> {
expr2.left = TypecastExpression(expr2.left, commonDt, true, expr2.left.position)
expr2.left.linkParents(expr2)
}
toFix===expr2.right -> {
expr2.right = TypecastExpression(expr2.right, commonDt, true, expr2.right.position)
expr2.right.linkParents(expr2)
}
else -> throw FatalAstException("confused binary expression side")
}
}
}
return expr2
}
override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment)
if(assg !is Assignment)
return assg
// see if a typecast is needed to convert the value's type into the proper target type
val valueItype = assg.value.inferType(program)
val targetItype = assg.target.inferType(program, assg)
if(targetItype.isKnown && valueItype.isKnown) {
val targettype = targetItype.typeOrElse(DataType.STRUCT)
val valuetype = valueItype.typeOrElse(DataType.STRUCT)
if (valuetype != targettype) {
if (valuetype isAssignableTo targettype) {
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
assg.value.linkParents(assg)
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
return assg
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
return super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall): Expression {
checkFunctionCallArguments(functionCall, functionCall.definingScope())
return super.visit(functionCall)
}
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
// see if a typecast is needed to convert the arguments into the required parameter's type
when(val sub = call.target.targetStatement(scope)) {
is Subroutine -> {
for(arg in sub.parameters.zip(call.args.withIndex())) {
val argItype = arg.second.value.inferType(program)
if(argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
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.args[arg.second.index] = typecasted
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
}
}
is BuiltinFunctionStatementPlaceholder -> {
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.args.withIndex())) {
val argItype = arg.second.value.inferType(program)
if (argItype.isKnown) {
val argtype = argItype.typeOrElse(DataType.STRUCT)
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.args[arg.second.index] = typecasted
break
}
}
}
}
}
}
null -> {}
else -> throw FatalAstException("call to something weird $sub ${call.target}")
}
}
override fun visit(typecast: TypecastExpression): Expression {
// 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.visit(typecast)
}
override fun visit(memread: DirectMemoryRead): Expression {
// make sure the memory address is an uword
val dt = memread.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val literaladdr = memread.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memread.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memread.addressExpression = TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
memread.addressExpression.parent = memread
}
}
return super.visit(memread)
}
override fun visit(memwrite: DirectMemoryWrite) {
val dt = memwrite.addressExpression.inferType(program)
if(dt.isKnown && dt.typeOrElse(DataType.UWORD)!=DataType.UWORD) {
val literaladdr = memwrite.addressExpression as? NumericLiteralValue
if(literaladdr!=null) {
memwrite.addressExpression = literaladdr.cast(DataType.UWORD)
} else {
memwrite.addressExpression = TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
memwrite.addressExpression.parent = memwrite
}
}
super.visit(memwrite)
}
override fun visit(structLv: StructLiteralValue): Expression {
val litval = super.visit(structLv)
if(litval !is StructLiteralValue)
return litval
val decl = litval.parent as? VarDecl
if(decl != null) {
val struct = decl.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
} else {
val assign = litval.parent as? Assignment
if (assign != null) {
val decl2 = assign.target.identifier?.targetVarDecl(program.namespace)
if(decl2 != null) {
val struct = decl2.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
}
}
}
return litval
}
private fun addTypecastsIfNeeded(structLv: StructLiteralValue, struct: StructDecl) {
structLv.values = struct.statements.zip(structLv.values).map {
val memberDt = (it.first as VarDecl).datatype
val valueDt = it.second.inferType(program)
if (valueDt.typeOrElse(memberDt) != memberDt)
TypecastExpression(it.second, memberDt, true, it.second.position)
else
it.second
}
}
}

View File

@ -42,10 +42,9 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
if(decl.isArray && decl.value==null) { if(decl.isArray && decl.value==null) {
// array datatype without initialization value, add list of zeros // array datatype without initialization value, add list of zeros
val arraysize = decl.arraysize!!.size()!! val arraysize = decl.arraysize!!.size()!!
val array = ReferenceLiteralValue(decl.datatype, null, val array = ArrayLiteralValue(decl.datatype,
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) }, Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
null, decl.position) decl.position)
array.addToHeap(program.heap)
decl.value = array decl.value = array
} }
@ -57,10 +56,8 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
addVarDecl(scope, decl.asDefaultValueDecl(null)) addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!! val declvalue = decl.value!!
val value = val value =
if(declvalue is NumericLiteralValue) { if(declvalue is NumericLiteralValue)
val converted = declvalue.cast(decl.datatype) declvalue.cast(decl.datatype)
converted ?: declvalue
}
else else
declvalue declvalue
val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".") val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".")
@ -81,11 +78,11 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
parentStatement = parentStatement.parent parentStatement = parentStatement.parent
val targetStatement = functionCall.target.targetSubroutine(program.namespace) val targetStatement = functionCall.target.targetSubroutine(program.namespace)
if(targetStatement!=null) { if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, parentStatement) addAddressOfExprIfNeeded(targetStatement, functionCall.args, parentStatement)
} else { } else {
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")] val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
if(builtinFunc!=null) if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.arglist, parentStatement) addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.args, parentStatement)
} }
return functionCall return functionCall
} }
@ -93,11 +90,11 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
override fun visit(functionCallStatement: FunctionCallStatement): Statement { override fun visit(functionCallStatement: FunctionCallStatement): Statement {
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace) val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
if(targetStatement!=null) { if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement) addAddressOfExprIfNeeded(targetStatement, functionCallStatement.args, functionCallStatement)
} else { } else {
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")] val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
if(builtinFunc!=null) if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.arglist, functionCallStatement) addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.args, functionCallStatement)
} }
return functionCallStatement return functionCallStatement
} }
@ -105,47 +102,43 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<Expression>, parent: Statement) { private fun addAddressOfExprIfNeeded(subroutine: Subroutine, arglist: MutableList<Expression>, parent: Statement) {
// functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead. // functions that accept UWORD and are given an array type, or string, will receive the AddressOf (memory location) of that value instead.
for(argparam in subroutine.parameters.withIndex().zip(arglist)) { for(argparam in subroutine.parameters.withIndex().zip(arglist)) {
if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type in StringDatatypes) { if(argparam.first.value.type==DataType.UWORD || argparam.first.value.type == DataType.STR) {
if(argparam.second is AddressOf) if(argparam.second is AddressOf)
continue continue
val idref = argparam.second as? IdentifierReference val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? ReferenceLiteralValue val strvalue = argparam.second as? StringLiteralValue
if(idref!=null) { if(idref!=null) {
val variable = idref.targetVarDecl(program.namespace) val variable = idref.targetVarDecl(program.namespace)
if(variable!=null && (variable.datatype in StringDatatypes || variable.datatype in ArrayDatatypes)) { if(variable!=null && variable.datatype in IterableDatatypes) {
val pointerExpr = AddressOf(idref, idref.position) val pointerExpr = AddressOf(idref, idref.position)
pointerExpr.scopedname = parent.makeScopedName(idref.nameInSource.single())
pointerExpr.linkParents(arglist[argparam.first.index].parent) pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr arglist[argparam.first.index] = pointerExpr
} }
} }
else if(strvalue!=null) { else if(strvalue!=null) {
if(strvalue.isString) { // add a vardecl so that the autovar can be resolved in later lookups
// add a vardecl so that the autovar can be resolved in later lookups val variable = VarDecl.createAuto(strvalue)
val variable = VarDecl.createAuto(strvalue, program.heap) addVarDecl(strvalue.definingScope(), variable)
addVarDecl(strvalue.definingScope(), variable) // replace the argument with &autovar
// replace the argument with &autovar val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position) val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position) pointerExpr.linkParents(arglist[argparam.first.index].parent)
pointerExpr.scopedname = parent.makeScopedName(variable.name) arglist[argparam.first.index] = pointerExpr
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
} }
} }
} }
} }
private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) { private fun addAddressOfExprIfNeededForBuiltinFuncs(signature: FunctionSignature, args: MutableList<Expression>, parent: Statement) {
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for(arg in args.withIndex().zip(signature.parameters)) { for(arg in args.withIndex().zip(signature.parameters)) {
val argvalue = arg.first.value val argvalue = arg.first.value
val argDt = argvalue.inferType(program) val argDt = argvalue.inferType(program)
if(DataType.UWORD in arg.second.possibleDatatypes && argDt in PassByReferenceDatatypes) { if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argvalue !is IdentifierReference) if(argvalue !is IdentifierReference)
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue") throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
val addrOf = AddressOf(argvalue, argvalue.position) val addrOf = AddressOf(argvalue, argvalue.position)
args[arg.first.index] = addrOf args[arg.first.index] = addrOf
addrOf.scopedname = parent.makeScopedName(argvalue.nameInSource.single())
addrOf.linkParents(parent) addrOf.linkParents(parent)
} }
} }

View File

@ -5,7 +5,6 @@ import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.compiler.HeapValues
sealed class Statement : Node { sealed class Statement : Node {
@ -38,7 +37,6 @@ sealed class Statement : Node {
} }
} }
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() { class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
override var parent: Node = ParentSentinel override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {} override fun linkParents(parent: Node) {}
@ -48,10 +46,8 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
override val expensiveToInline = false override val expensiveToInline = false
} }
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean) data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
class Block(override val name: String, class Block(override val name: String,
val address: Int?, val address: Int?,
override var statements: MutableList<Statement>, override var statements: MutableList<Statement>,
@ -111,8 +107,6 @@ data class Label(val name: String, override val position: Position) : Statement(
override fun toString(): String { override fun toString(): String {
return "Label(name=$name, pos=$position)" return "Label(name=$name, pos=$position)"
} }
val scopedname: String by lazy { makeScopedName(name) }
} }
open class Return(var value: Expression?, override val position: Position) : Statement() { open class Return(var value: Expression?, override val position: Position) : Statement() {
@ -197,20 +191,18 @@ class VarDecl(val type: VarDeclType,
companion object { companion object {
private var autoHeapValueSequenceNumber = 0 private var autoHeapValueSequenceNumber = 0
fun createAuto(refLv: ReferenceLiteralValue, heap: HeapValues): VarDecl { fun createAuto(string: StringLiteralValue): VarDecl {
if(refLv.heapId==null)
throw FatalAstException("can only create autovar for a ref lv that has a heapid $refLv")
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}" val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
return if(refLv.isArray) { return VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string,
val declaredType = ArrayElementTypes.getValue(refLv.type) isArray = false, autogeneratedDontRemove = true, position = string.position)
val arraysize = ArrayIndex.forArray(refLv, heap) }
VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, refLv,
isArray = true, autogeneratedDontRemove = true, position = refLv.position) fun createAuto(array: ArrayLiteralValue): VarDecl {
} else { val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
VarDecl(VarDeclType.VAR, refLv.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, refLv, val declaredType = ArrayElementTypes.getValue(array.type)
isArray = false, autogeneratedDontRemove = true, position = refLv.position) val arraysize = ArrayIndex.forArray(array)
} return VarDecl(VarDeclType.VAR, declaredType, ZeropageWish.NOT_IN_ZEROPAGE, arraysize, autoVarName, null, array,
isArray = true, autogeneratedDontRemove = true, position = array.position)
} }
} }
@ -284,12 +276,6 @@ class VarDecl(val type: VarDeclType,
structHasBeenFlattened = true structHasBeenFlattened = true
return result return result
} }
fun withPrefixedName(nameprefix: String): Statement {
val new = VarDecl(type, declaredDatatype, zeropage, arraysize, nameprefix+name, structName, value, isArray, autogeneratedDontRemove, position)
new.parent = parent
return new
}
} }
class ArrayIndex(var index: Expression, override val position: Position) : Node { class ArrayIndex(var index: Expression, override val position: Position) : Node {
@ -301,9 +287,8 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
} }
companion object { companion object {
fun forArray(v: ReferenceLiteralValue, heap: HeapValues): ArrayIndex { fun forArray(v: ArrayLiteralValue): ArrayIndex {
val arraySize = v.array?.size ?: heap.get(v.heapId!!).arraysize return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
return ArrayIndex(NumericLiteralValue.optimalNumeric(arraySize, v.position), v.position)
} }
} }
@ -375,25 +360,23 @@ data class AssignTarget(val register: Register?,
} }
} }
fun inferType(program: Program, stmt: Statement): DataType? { fun inferType(program: Program, stmt: Statement): InferredTypes.InferredType {
if(register!=null) if(register!=null)
return DataType.UBYTE return InferredTypes.knownFor(DataType.UBYTE)
if(identifier!=null) { if(identifier!=null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return null val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return symbol.datatype if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
} }
if(arrayindexed!=null) { if(arrayindexed!=null) {
val dt = arrayindexed!!.inferType(program) return arrayindexed!!.inferType(program)
if(dt!=null)
return dt
} }
if(memoryAddress!=null) if(memoryAddress!=null)
return DataType.UBYTE return InferredTypes.knownFor(DataType.UBYTE)
return null return InferredTypes.unknown()
} }
infix fun isSameAs(value: Expression): Boolean { infix fun isSameAs(value: Expression): Boolean {
@ -489,16 +472,17 @@ class Jump(val address: Int?,
} }
class FunctionCallStatement(override var target: IdentifierReference, class FunctionCallStatement(override var target: IdentifierReference,
override var arglist: MutableList<Expression>, override var args: MutableList<Expression>,
val void: Boolean,
override val position: Position) : Statement(), IFunctionCall { override val position: Position) : Statement(), IFunctionCall {
override lateinit var parent: Node override lateinit var parent: Node
override val expensiveToInline override val expensiveToInline
get() = arglist.any { it !is NumericLiteralValue } get() = args.any { it !is NumericLiteralValue }
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent = parent this.parent = parent
target.linkParents(this) target.linkParents(this)
arglist.forEach { it.linkParents(this) } args.forEach { it.linkParents(this) }
} }
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@ -608,40 +592,6 @@ class Subroutine(override val name: String,
.filter { it is InlineAssembly } .filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly } .map { (it as InlineAssembly).assembly }
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it } .count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
val canBeAsmSubroutine =false // TODO disabled for now, see below about problem with converting to asm subroutine
// !isAsmSubroutine
// && ((parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE, DataType.WORD, DataType.UWORD))
// || (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE }))
fun intoAsmSubroutine(): Subroutine {
// TODO turn subroutine into asm calling convention. Requires rethinking of how parameters are handled (conflicts with local vardefs now, see AstIdentifierChecker...)
return this // TODO
// println("TO ASM $this") // TODO
// val paramregs = if (parameters.size == 1 && parameters[0].type in setOf(DataType.BYTE, DataType.UBYTE))
// listOf(RegisterOrStatusflag(RegisterOrPair.Y, null, null))
// else if (parameters.size == 1 && parameters[0].type in setOf(DataType.WORD, DataType.UWORD))
// listOf(RegisterOrStatusflag(RegisterOrPair.AY, null, null))
// else if (parameters.size == 2 && parameters.map { it.type }.all { it == DataType.BYTE || it == DataType.UBYTE })
// listOf(RegisterOrStatusflag(RegisterOrPair.A, null, null), RegisterOrStatusflag(RegisterOrPair.Y, null, null))
// else throw FatalAstException("cannot convert subroutine to asm parameters")
//
// val asmsub=Subroutine(
// name,
// parameters,
// returntypes,
// paramregs,
// emptyList(),
// emptySet(),
// null,
// true,
// statements,
// position
// )
// asmsub.linkParents(parent)
// return asmsub
}
} }
open class SubroutineParameter(val name: String, open class SubroutineParameter(val name: String,
@ -692,8 +642,6 @@ class BranchStatement(var condition: BranchCondition,
} }
class ForLoop(val loopRegister: Register?, class ForLoop(val loopRegister: Register?,
val decltype: DataType?,
val zeropage: ZeropageWish,
var loopVar: IdentifierReference?, var loopVar: IdentifierReference?,
var iterable: Expression, var iterable: Expression,
var body: AnonymousScope, var body: AnonymousScope,
@ -703,7 +651,7 @@ class ForLoop(val loopRegister: Register?,
override fun linkParents(parent: Node) { override fun linkParents(parent: Node) {
this.parent=parent this.parent=parent
loopVar?.linkParents(if(decltype==null) this else body) loopVar?.linkParents(this)
iterable.linkParents(this) iterable.linkParents(this)
body.linkParents(this) body.linkParents(this)
} }
@ -837,4 +785,3 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
fun accept(visitor: IAstVisitor) = visitor.visit(this) fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this) fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
} }

View File

@ -0,0 +1,3 @@
package prog8.compiler
internal class AssemblyError(msg: String) : RuntimeException(msg)

View File

@ -1,13 +1,8 @@
package prog8.compiler package prog8.compiler
import prog8.ast.base.ArrayDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.StringDatatypes
import prog8.ast.expressions.AddressOf
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.nio.file.Path import java.nio.file.Path
import java.util.*
import kotlin.math.abs import kotlin.math.abs
enum class OutputType { enum class OutputType {
@ -28,8 +23,6 @@ enum class ZeropageType {
DONTUSE DONTUSE
} }
data class IntegerOrAddressOf(val integer: Int?, val addressOf: AddressOf?)
data class CompilationOptions(val output: OutputType, data class CompilationOptions(val output: OutputType,
val launcher: LauncherType, val launcher: LauncherType,
val zeropage: ZeropageType, val zeropage: ZeropageType,
@ -73,83 +66,3 @@ fun loadAsmIncludeFile(filename: String, source: Path): String {
internal fun tryGetEmbeddedResource(name: String): InputStream? { internal fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name") return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
} }
class HeapValues {
data class HeapValue(val type: DataType, val str: String?, val array: Array<IntegerOrAddressOf>?, val doubleArray: DoubleArray?) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as HeapValue
return type==other.type && str==other.str && Arrays.equals(array, other.array) && Arrays.equals(doubleArray, other.doubleArray)
}
override fun hashCode(): Int {
var result = type.hashCode()
result = 31 * result + (str?.hashCode() ?: 0)
result = 31 * result + (array?.let { Arrays.hashCode(it) } ?: 0)
result = 31 * result + (doubleArray?.let { Arrays.hashCode(it) } ?: 0)
return result
}
val arraysize: Int = array?.size ?: doubleArray?.size ?: 0
}
private val heap = mutableMapOf<Int, HeapValue>()
private var heapId = 1
fun size(): Int = heap.size
fun addString(type: DataType, str: String): Int {
if (str.length > 255)
throw IllegalArgumentException("string length must be 0-255")
// 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()
if(existing!=null)
return existing
val newId = heapId++
heap[newId] = value
return newId
}
fun addIntegerArray(type: DataType, array: Array<IntegerOrAddressOf>): Int {
// arrays are never shared, don't check for existing
if(type !in ArrayDatatypes)
throw CompilerException("wrong array type")
val newId = heapId++
heap[newId] = HeapValue(type, null, array, null)
return newId
}
fun addDoublesArray(darray: DoubleArray): Int {
// arrays are never shared, don't check for existing
val newId = heapId++
heap[newId] = HeapValue(DataType.ARRAY_F, null, null, darray)
return newId
}
fun update(heapId: Int, str: String) {
val oldVal = heap[heapId] ?: throw IllegalArgumentException("heapId not found in heap")
if(oldVal.type in StringDatatypes) {
if (oldVal.str!!.length != str.length)
throw IllegalArgumentException("heap string length mismatch")
heap[heapId] = oldVal.copy(str = str)
}
else throw IllegalArgumentException("heap data type mismatch")
}
fun update(heapId: Int, heapval: HeapValue) {
if(heapId !in heap)
throw IllegalArgumentException("heapId not found in heap")
heap[heapId] = heapval
}
fun get(heapId: Int): HeapValue {
return heap[heapId] ?:
throw IllegalArgumentException("heapId $heapId not found in heap")
}
fun allEntries() = heap.entries
}

View File

@ -4,8 +4,7 @@ import prog8.ast.AstToSourceCode
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.compiler.target.c64.MachineDefinition import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.codegen2.AsmGen2
import prog8.optimizer.constantFold import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions import prog8.optimizer.simplifyExpressions
@ -24,8 +23,9 @@ class CompilationResult(val success: Boolean,
fun compileProgram(filepath: Path, fun compileProgram(filepath: Path,
optimize: Boolean, optimizeInlining: Boolean, optimize: Boolean,
writeAssembly: Boolean): CompilationResult { writeAssembly: Boolean,
outputDir: Path): CompilationResult {
lateinit var programAst: Program lateinit var programAst: Program
var programName: String? = null var programName: String? = null
@ -69,7 +69,8 @@ fun compileProgram(filepath: Path,
//println(" time2: $time2") //println(" time2: $time2")
val time3 = measureTimeMillis { val time3 = measureTimeMillis {
programAst.removeNopsFlattenAnonScopes() programAst.removeNopsFlattenAnonScopes()
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later programAst.reorderStatements()
programAst.addTypecasts()
} }
//println(" time3: $time3") //println(" time3: $time3")
val time4 = measureTimeMillis { val time4 = measureTimeMillis {
@ -84,23 +85,24 @@ fun compileProgram(filepath: Path,
while (true) { while (true) {
// keep optimizing expressions and statements until no more steps remain // keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions() val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements(optimizeInlining) val optsDone2 = programAst.optimizeStatements()
if (optsDone1 + optsDone2 == 0) if (optsDone1 + optsDone2 == 0)
break break
} }
} }
programAst.addTypecasts()
programAst.removeNopsFlattenAnonScopes() programAst.removeNopsFlattenAnonScopes()
programAst.checkValid(compilerOptions) // check if final tree is valid programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls programAst.checkRecursion() // check if there are recursive subroutine calls
printAst(programAst) // printAst(programAst)
if(writeAssembly) { if(writeAssembly) {
// asm generation directly from the Ast, no need for intermediate code // asm generation directly from the Ast, no need for intermediate code
val zeropage = MachineDefinition.C64Zeropage(compilerOptions) val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
programAst.anonscopeVarsCleanup() programAst.anonscopeVarsCleanup()
val assembly = AsmGen2(programAst, compilerOptions, zeropage).compileToAssembly(optimize) val assembly = CompilationTarget.asmGenerator(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions) assembly.assemble(compilerOptions)
programName = assembly.name programName = assembly.name
} }

View File

@ -13,7 +13,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
val allowedDatatypes = NumericDatatypes val allowedDatatypes = NumericDatatypes
fun available() = free.size fun available() = if(options.zeropage==ZeropageType.DONTUSE) 0 else free.size
fun allocate(scopedname: String, datatype: DataType, position: Position?): Int { fun allocate(scopedname: String, datatype: DataType, position: Position?): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"} assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}

View File

@ -0,0 +1,16 @@
package prog8.compiler.target
import prog8.ast.Program
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import java.nio.file.Path
internal interface CompilationTarget {
companion object {
lateinit var name: String
lateinit var machine: IMachineDefinition
lateinit var encodeString: (str: String) -> List<Short>
lateinit var decodeString: (bytes: List<Short>) -> String
lateinit var asmGenerator: (Program, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
}
}

View File

@ -0,0 +1,12 @@
package prog8.compiler.target
import prog8.compiler.CompilationOptions
internal interface IAssemblyGenerator {
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
}
internal interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions)
}

View File

@ -0,0 +1,15 @@
package prog8.compiler.target
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
interface IMachineDefinition {
val FLOAT_MAX_NEGATIVE: Double
val FLOAT_MAX_POSITIVE: Double
val FLOAT_MEM_SIZE: Int
val opcodeNames: Set<String>
fun getZeropage(compilerOptions: CompilationOptions): Zeropage
}

View File

@ -2,46 +2,35 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType import prog8.compiler.OutputType
import java.io.File import prog8.compiler.target.IAssemblyProgram
import java.nio.file.Path
import kotlin.system.exitProcess import kotlin.system.exitProcess
class AssemblyProgram(val name: String) { class AssemblyProgram(override val name: String, outputDir: Path): IAssemblyProgram {
private val assemblyFile = "$name.asm" private val assemblyFile = outputDir.resolve("$name.asm")
private val viceMonListFile = "$name.vice-mon-list" private val prgFile = outputDir.resolve("$name.prg")
private val binFile = outputDir.resolve("$name.bin")
private val viceMonListFile = outputDir.resolve("$name.vice-mon-list")
companion object { override fun assemble(options: CompilationOptions) {
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
}
fun assemble(options: CompilationOptions) {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps // add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch", val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--long-branch",
"-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch", "-Wall", "-Wno-strict-bool", "-Wno-shadow", "-Werror", "-Wno-error=long-branch",
"--dump-labels", "--vice-labels", "-l", viceMonListFile, "--no-monitor") "--dump-labels", "--vice-labels", "-l", viceMonListFile.toString(), "--no-monitor")
val outFile = when(options.output) { val outFile = when(options.output) {
OutputType.PRG -> { OutputType.PRG -> {
command.add("--cbm-prg") command.add("--cbm-prg")
println("\nCreating C-64 prg.") println("\nCreating C-64 prg.")
"$name.prg" prgFile
} }
OutputType.RAW -> { OutputType.RAW -> {
command.add("--nostart") command.add("--nostart")
println("\nCreating raw binary.") println("\nCreating raw binary.")
"$name.bin" binFile
} }
} }
command.addAll(listOf("--output", outFile, assemblyFile)) command.addAll(listOf("--output", outFile.toString(), assemblyFile.toString()))
val proc = ProcessBuilder(command).inheritIO().start() val proc = ProcessBuilder(command).inheritIO().start()
val result = proc.waitFor() val result = proc.waitFor()
@ -57,7 +46,7 @@ class AssemblyProgram(val name: String) {
// builds list of breakpoints, appends to monitor list file // builds list of breakpoints, appends to monitor list file
val breakpoints = mutableListOf<String>() val breakpoints = mutableListOf<String>()
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that"s generated for them
for(line in File(viceMonListFile).readLines()) { for(line in viceMonListFile.toFile().readLines()) {
val match = pattern.matchEntire(line) val match = pattern.matchEntire(line)
if(match!=null) if(match!=null)
breakpoints.add("break \$" + match.groupValues[1]) breakpoints.add("break \$" + match.groupValues[1])
@ -66,6 +55,6 @@ class AssemblyProgram(val name: String) {
breakpoints.add(0, "; vice monitor breakpoint list now follows") breakpoints.add(0, "; vice monitor breakpoint list now follows")
breakpoints.add(1, "; $num breakpoints have been defined") breakpoints.add(1, "; $num breakpoints have been defined")
breakpoints.add(2, "del") breakpoints.add(2, "del")
File(viceMonListFile).appendText(breakpoints.joinToString("\n")+"\n") viceMonListFile.toFile().appendText(breakpoints.joinToString("\n")+"\n")
} }
} }

View File

@ -4,18 +4,19 @@ import prog8.compiler.CompilationOptions
import prog8.compiler.CompilerException import prog8.compiler.CompilerException
import prog8.compiler.Zeropage import prog8.compiler.Zeropage
import prog8.compiler.ZeropageType import prog8.compiler.ZeropageType
import prog8.compiler.target.IMachineDefinition
import java.awt.Color import java.awt.Color
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import javax.imageio.ImageIO import javax.imageio.ImageIO
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.pow import kotlin.math.pow
object MachineDefinition { object C64MachineDefinition: IMachineDefinition {
// 5-byte cbm MFLPT format limitations: // 5-byte cbm MFLPT format limitations:
const val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255 override val FLOAT_MAX_POSITIVE = 1.7014118345e+38 // bytes: 255,127,255,255,255
const val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255 override val FLOAT_MAX_NEGATIVE = -1.7014118345e+38 // bytes: 255,255,255,255,255
override val FLOAT_MEM_SIZE = 5
const val BASIC_LOAD_ADDRESS = 0x0801 const val BASIC_LOAD_ADDRESS = 0x0801
const val RAW_LOAD_ADDRESS = 0xc000 const val RAW_LOAD_ADDRESS = 0xc000
@ -30,6 +31,19 @@ object MachineDefinition {
const val ESTACK_HI_PLUS1_HEX = "\$cf01" const val ESTACK_HI_PLUS1_HEX = "\$cf01"
const val ESTACK_HI_PLUS2_HEX = "\$cf02" const val ESTACK_HI_PLUS2_HEX = "\$cf02"
override fun getZeropage(compilerOptions: CompilationOptions) = C64Zeropage(compilerOptions)
// 6502 opcodes (including aliases and illegal opcodes), these cannot be used as variable or label names
override val opcodeNames = setOf("adc", "ahx", "alr", "anc", "and", "ane", "arr", "asl", "asr", "axs", "bcc", "bcs",
"beq", "bge", "bit", "blt", "bmi", "bne", "bpl", "brk", "bvc", "bvs", "clc",
"cld", "cli", "clv", "cmp", "cpx", "cpy", "dcm", "dcp", "dec", "dex", "dey",
"eor", "gcc", "gcs", "geq", "gge", "glt", "gmi", "gne", "gpl", "gvc", "gvs",
"inc", "ins", "inx", "iny", "isb", "isc", "jam", "jmp", "jsr", "lae", "las",
"lax", "lda", "lds", "ldx", "ldy", "lsr", "lxa", "nop", "ora", "pha", "php",
"pla", "plp", "rla", "rol", "ror", "rra", "rti", "rts", "sax", "sbc", "sbx",
"sec", "sed", "sei", "sha", "shl", "shr", "shs", "shx", "shy", "slo", "sre",
"sta", "stx", "sty", "tas", "tax", "tay", "tsx", "txa", "txs", "tya", "xaa")
class C64Zeropage(options: CompilationOptions) : Zeropage(options) { class C64Zeropage(options: CompilationOptions) : Zeropage(options) {
@ -110,8 +124,6 @@ object MachineDefinition {
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) { data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
companion object { companion object {
const val MemorySize = 5
val zero = Mflpt5(0, 0, 0, 0, 0) val zero = Mflpt5(0, 0, 0, 0, 0)
fun fromNumber(num: Number): Mflpt5 { fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format // see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
@ -232,7 +244,6 @@ object MachineDefinition {
return bcopy return bcopy
} }
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/ val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white Color(0xFFFFFF), // 1 = white

View File

@ -1058,7 +1058,7 @@ object Petscii {
0.toShort() 0.toShort()
else { else {
val case = if (lowercase) "lower" else "upper" val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}case Petscii character for '$it'") throw CharConversionException("no ${case}case Petscii character for '$it' (${it.toShort()})")
} }
} }
} }
@ -1076,7 +1076,7 @@ object Petscii {
0.toShort() 0.toShort()
else { else {
val case = if (lowercase) "lower" else "upper" val case = if (lowercase) "lower" else "upper"
throw CharConversionException("no ${case}Screencode character for '$it'") throw CharConversionException("no ${case}Screencode character for '$it' (${it.toShort()})")
} }
} }
} }

View File

@ -0,0 +1,871 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.*
import prog8.compiler.target.IAssemblyGenerator
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.Petscii
import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature
import java.math.RoundingMode
import java.nio.file.Path
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.ArrayDeque
import kotlin.math.absoluteValue
internal class AsmGen(private val program: Program,
private val zeropage: Zeropage,
private val options: CompilationOptions,
private val outputDir: Path): IAssemblyGenerator {
private val assemblyLines = mutableListOf<String>()
private val globalFloatConsts = mutableMapOf<Double, String>() // all float values in the entire program (value -> varname)
private val allocatedZeropageVariables = mutableMapOf<String, Pair<Int, DataType>>()
private val breakpointLabels = mutableListOf<String>()
private val builtinFunctionsAsmGen = BuiltinFunctionsAsmGen(program, this)
private val forloopsAsmGen = ForLoopsAsmGen(program, this)
private val postincrdecrAsmGen = PostIncrDecrAsmGen(program, this)
private val functioncallAsmGen = FunctionCallAsmGen(program, this)
private val assignmentAsmGen = AssignmentAsmGen(program, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
internal val loopEndLabels = ArrayDeque<String>()
internal val loopContinueLabels = ArrayDeque<String>()
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
assemblyLines.clear()
loopEndLabels.clear()
loopContinueLabels.clear()
println("Generating assembly code... ")
header()
val allBlocks = program.allBlocks()
if(allBlocks.first().name != "main")
throw AssemblyError("first block should be 'main'")
for(b in program.allBlocks())
block2asm(b)
footer()
if(optimize) {
var optimizationsDone = 1
while (optimizationsDone > 0) {
optimizationsDone = optimizeAssembly(assemblyLines)
}
}
val outputFile = outputDir.resolve("${program.name}.asm").toFile()
outputFile.printWriter().use {
for (line in assemblyLines) { it.println(line) }
}
return AssemblyProgram(program.name, outputDir)
}
private fun header() {
val ourName = this.javaClass.name
out("; 6502 assembly code for '${program.name}'")
out("; generated by $ourName on ${LocalDateTime.now().withNano(0)}")
out("; assembler syntax is for the 64tasm cross-assembler")
out("; output options: output=${options.output} launcher=${options.launcher} zp=${options.zeropage}")
out("\n.cpu '6502'\n.enc 'none'\n")
program.actualLoadAddress = program.definedLoadAddress
if (program.actualLoadAddress == 0) // fix load address
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
C64MachineDefinition.BASIC_LOAD_ADDRESS else C64MachineDefinition.RAW_LOAD_ADDRESS
when {
options.launcher == LauncherType.BASIC -> {
if (program.actualLoadAddress != 0x0801)
throw AssemblyError("BASIC output must have load address $0801")
out("; ---- basic program with sys call ----")
out("* = ${program.actualLoadAddress.toHex()}")
val year = LocalDate.now().year
out(" .word (+), $year")
out(" .null $9e, format(' %d ', _prog8_entrypoint), $3a, $8f, ' prog8 by idj'")
out("+\t.word 0")
out("_prog8_entrypoint\t; assembly code starts here\n")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.RAW -> {
out("; ---- raw assembler program ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
}
}
if (zeropage.exitProgramStrategy != Zeropage.ExitProgramStrategy.CLEAN_EXIT) {
// disable shift-commodore charset switching and run/stop key
out(" lda #$80")
out(" lda #$80")
out(" sta 657\t; disable charset switching")
out(" lda #239")
out(" sta 808\t; disable run/stop key")
}
out(" ldx #\$ff\t; init estack pointer")
out(" ; initialize the variables in each block")
for (block in program.allBlocks()) {
val initVarsSub = block.statements.singleOrNull { it is Subroutine && it.name == initvarsSubName }
if(initVarsSub!=null)
out(" jsr ${block.name}.$initvarsSubName")
}
out(" clc")
when (zeropage.exitProgramStrategy) {
Zeropage.ExitProgramStrategy.CLEAN_EXIT -> {
out(" jmp main.start\t; jump to program entrypoint")
}
Zeropage.ExitProgramStrategy.SYSTEM_RESET -> {
out(" jsr main.start\t; call program entrypoint")
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out("")
}
private fun footer() {
// the global list of all floating point constants for the whole program
out("; global float constants")
for (flt in globalFloatConsts) {
val mflpt5 = C64MachineDefinition.Mflpt5.fromNumber(flt.key)
val floatFill = makeFloatFill(mflpt5)
val floatvalue = flt.key
out("${flt.value}\t.byte $floatFill ; float $floatvalue")
}
}
private fun block2asm(block: Block) {
out("\n\n; ---- block: '${block.name}' ----")
out("${block.name}\t" + (if("force_output" in block.options()) ".block\n" else ".proc\n"))
if(block.address!=null) {
out(".cerror * > ${block.address.toHex()}, 'block address overlaps by ', *-${block.address.toHex()},' bytes'")
out("* = ${block.address.toHex()}")
}
outputSourceLine(block)
zeropagevars2asm(block.statements)
memdefs2asm(block.statements)
vardecls2asm(block.statements)
out("\n; subroutines in this block")
// first translate regular statements, and then put the subroutines at the end.
val (subroutine, stmts) = block.statements.partition { it is Subroutine }
stmts.forEach { translate(it) }
subroutine.forEach { translateSubroutine(it as Subroutine) }
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
}
private var generatedLabelSequenceNumber: Int = 0
internal fun makeLabel(postfix: String): String {
generatedLabelSequenceNumber++
return "_prog8_label_${generatedLabelSequenceNumber}_$postfix"
}
private fun outputSourceLine(node: Node) {
out(" ;\tsrc line: ${node.position.file}:${node.position.line}")
}
internal fun out(str: String, splitlines: Boolean = true) {
val fragment = (if(" | " in str) str.replace("|", "\n") else str).trim('\n')
if (splitlines) {
for (line in fragment.split('\n')) {
val trimmed = if (line.startsWith(' ')) "\t" + line.trim() else line.trim()
// trimmed = trimmed.replace(Regex("^\\+\\s+"), "+\t") // sanitize local label indentation
assemblyLines.add(trimmed)
}
} else assemblyLines.add(fragment)
}
private fun makeFloatFill(flt: C64MachineDefinition.Mflpt5): String {
val b0 = "$" + flt.b0.toString(16).padStart(2, '0')
val b1 = "$" + flt.b1.toString(16).padStart(2, '0')
val b2 = "$" + flt.b2.toString(16).padStart(2, '0')
val b3 = "$" + flt.b3.toString(16).padStart(2, '0')
val b4 = "$" + flt.b4.toString(16).padStart(2, '0')
return "$b0, $b1, $b2, $b3, $b4"
}
private fun petscii(str: String): List<Short> {
val bytes = Petscii.encodePetscii(str, true)
return bytes.plus(0)
}
private fun zeropagevars2asm(statements: List<Statement>) {
out("; vars allocated on zeropage")
val variables = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
for(variable in variables) {
// should NOT allocate subroutine parameters on the zero page
val fullName = variable.scopedname
val zpVar = allocatedZeropageVariables[fullName]
if(zpVar==null) {
// This var is not on the ZP yet. Attempt to move it there (if it's not a float, those take up too much space)
if(variable.zeropage != ZeropageWish.NOT_IN_ZEROPAGE &&
variable.datatype in zeropage.allowedDatatypes
&& variable.datatype != DataType.FLOAT
&& options.zeropage != ZeropageType.DONTUSE) {
try {
val address = zeropage.allocate(fullName, variable.datatype, null)
out("${variable.name} = $address\t; auto zp ${variable.datatype}")
// make sure we add the var to the set of zpvars for this block
allocatedZeropageVariables[fullName] = Pair(address, variable.datatype)
} catch (x: ZeropageDepletedError) {
// leave it as it is.
}
}
}
}
}
private fun vardecl2asm(decl: VarDecl) {
when (decl.datatype) {
DataType.UBYTE -> out("${decl.name}\t.byte 0")
DataType.BYTE -> out("${decl.name}\t.char 0")
DataType.UWORD -> out("${decl.name}\t.word 0")
DataType.WORD -> out("${decl.name}\t.sint 0")
DataType.FLOAT -> out("${decl.name}\t.byte 0,0,0,0,0 ; float")
DataType.STRUCT -> {} // is flattened
DataType.STR -> {
val string = (decl.value as StringLiteralValue).value
outputStringvar(decl, petscii(string))
}
DataType.ARRAY_UB -> {
val data = makeArrayFillDataUnsigned(decl)
if (data.size <= 16)
out("${decl.name}\t.byte ${data.joinToString()}")
else {
out(decl.name)
for (chunk in data.chunked(16))
out(" .byte " + chunk.joinToString())
}
}
DataType.ARRAY_B -> {
val data = makeArrayFillDataSigned(decl)
if (data.size <= 16)
out("${decl.name}\t.char ${data.joinToString()}")
else {
out(decl.name)
for (chunk in data.chunked(16))
out(" .char " + chunk.joinToString())
}
}
DataType.ARRAY_UW -> {
val data = makeArrayFillDataUnsigned(decl)
if (data.size <= 16)
out("${decl.name}\t.word ${data.joinToString()}")
else {
out(decl.name)
for (chunk in data.chunked(16))
out(" .word " + chunk.joinToString())
}
}
DataType.ARRAY_W -> {
val data = makeArrayFillDataSigned(decl)
if (data.size <= 16)
out("${decl.name}\t.sint ${data.joinToString()}")
else {
out(decl.name)
for (chunk in data.chunked(16))
out(" .sint " + chunk.joinToString())
}
}
DataType.ARRAY_F -> {
val array = (decl.value as ArrayLiteralValue).value
val floatFills = array.map {
val number = (it as NumericLiteralValue).number
makeFloatFill(C64MachineDefinition.Mflpt5.fromNumber(number))
}
out(decl.name)
for (f in array.zip(floatFills))
out(" .byte ${f.second} ; float ${f.first}")
}
}
}
private fun memdefs2asm(statements: List<Statement>) {
out("\n; memdefs and kernel subroutines")
val memvars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.MEMORY || it.type==VarDeclType.CONST }
for(m in memvars) {
out(" ${m.name} = ${(m.value as NumericLiteralValue).number.toHex()}")
}
val asmSubs = statements.filterIsInstance<Subroutine>().filter { it.isAsmSubroutine }
for(sub in asmSubs) {
if(sub.asmAddress!=null) {
if(sub.statements.isNotEmpty())
throw AssemblyError("kernel subroutine cannot have statements")
out(" ${sub.name} = ${sub.asmAddress.toHex()}")
}
}
}
private fun vardecls2asm(statements: List<Statement>) {
out("\n; non-zeropage variables")
val vars = statements.filterIsInstance<VarDecl>().filter { it.type==VarDeclType.VAR }
// first output the flattened struct member variables *in order*
// after that, the other variables sorted by their datatype
val (structMembers, normalVars) = vars.partition { it.struct!=null }
structMembers.forEach { vardecl2asm(it) }
// special treatment for string types: merge strings that are identical
val encodedstringVars = normalVars
.filter {it.datatype == DataType.STR }
.map { it to petscii((it.value as StringLiteralValue).value) }
.groupBy({it.second}, {it.first})
for((encoded, variables) in encodedstringVars) {
variables.dropLast(1).forEach { out(it.name) }
val lastvar = variables.last()
outputStringvar(lastvar, encoded)
}
// non-string variables
normalVars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
if(it.scopedname !in allocatedZeropageVariables)
vardecl2asm(it)
}
}
private fun outputStringvar(lastvar: VarDecl, encoded: List<Short>) {
val string = (lastvar.value as StringLiteralValue).value
out("${lastvar.name}\t; ${lastvar.datatype} \"${escape(string).replace("\u0000", "<NULL>")}\"")
val outputBytes = encoded.map { "$" + it.toString(16).padStart(2, '0') }
for (chunk in outputBytes.chunked(16))
out(" .byte " + chunk.joinToString())
}
private fun makeArrayFillDataUnsigned(decl: VarDecl): List<String> {
val array = (decl.value as ArrayLiteralValue).value
return when (decl.datatype) {
DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteralValue).number.toInt()
"$"+number.toString(16).padStart(2, '0')
}
DataType.ARRAY_UW -> array.map {
if(it is NumericLiteralValue) {
"$" + it.number.toInt().toString(16).padStart(4, '0')
} else {
(it as AddressOf).identifier.nameInSource.joinToString(".")
}
}
else -> throw AssemblyError("invalid arraysize type")
}
}
private fun makeArrayFillDataSigned(decl: VarDecl): List<String> {
val array = (decl.value as ArrayLiteralValue).value
return when {
decl.datatype == DataType.ARRAY_UB ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.toString(16).padStart(2, '0')
"$$hexnum"
}
decl.datatype == DataType.ARRAY_B ->
// byte array can never contain pointer-to types, so treat values as all integers
array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(2, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
decl.datatype== DataType.ARRAY_UW -> array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.toString(16).padStart(4, '0')
"$$hexnum"
}
decl.datatype== DataType.ARRAY_W -> array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
"$$hexnum"
else
"-$$hexnum"
}
else -> throw AssemblyError("invalid arraysize type ${decl.datatype}")
}
}
internal fun getFloatConst(number: Double): String {
// try to match the ROM float constants to save memory
val mflpt5 = C64MachineDefinition.Mflpt5.fromNumber(number)
val floatbytes = shortArrayOf(mflpt5.b0, mflpt5.b1, mflpt5.b2, mflpt5.b3, mflpt5.b4)
when {
floatbytes.contentEquals(shortArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_ZERO"
floatbytes.contentEquals(shortArrayOf(0x82, 0x49, 0x0f, 0xda, 0xa1)) -> return "c64flt.FL_PIVAL"
floatbytes.contentEquals(shortArrayOf(0x90, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_N32768"
floatbytes.contentEquals(shortArrayOf(0x81, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FONE"
floatbytes.contentEquals(shortArrayOf(0x80, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRHLF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x35, 0x04, 0xf3, 0x34)) -> return "c64flt.FL_SQRTWO"
floatbytes.contentEquals(shortArrayOf(0x80, 0x80, 0x00, 0x00, 0x00)) -> return "c64flt.FL_NEGHLF"
floatbytes.contentEquals(shortArrayOf(0x80, 0x31, 0x72, 0x17, 0xf8)) -> return "c64flt.FL_LOG2"
floatbytes.contentEquals(shortArrayOf(0x84, 0x20, 0x00, 0x00, 0x00)) -> return "c64flt.FL_TENC"
floatbytes.contentEquals(shortArrayOf(0x9e, 0x6e, 0x6b, 0x28, 0x00)) -> return "c64flt.FL_NZMIL"
floatbytes.contentEquals(shortArrayOf(0x80, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FHALF"
floatbytes.contentEquals(shortArrayOf(0x81, 0x38, 0xaa, 0x3b, 0x29)) -> return "c64flt.FL_LOGEB2"
floatbytes.contentEquals(shortArrayOf(0x81, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_PIHALF"
floatbytes.contentEquals(shortArrayOf(0x83, 0x49, 0x0f, 0xda, 0xa2)) -> return "c64flt.FL_TWOPI"
floatbytes.contentEquals(shortArrayOf(0x7f, 0x00, 0x00, 0x00, 0x00)) -> return "c64flt.FL_FR4"
else -> {
// attempt to correct for a few rounding issues
when (number.toBigDecimal().setScale(10, RoundingMode.HALF_DOWN).toDouble()) {
3.1415926536 -> return "c64flt.FL_PIVAL"
1.4142135624 -> return "c64flt.FL_SQRTWO"
0.7071067812 -> return "c64flt.FL_SQRHLF"
0.6931471806 -> return "c64flt.FL_LOG2"
else -> {}
}
// no ROM float const for this value, create our own
val name = globalFloatConsts[number]
if(name!=null)
return name
val newName = "prog8_float_const_${globalFloatConsts.size}"
globalFloatConsts[number] = newName
return newName
}
}
}
internal fun signExtendAtoMsb(destination: String) =
"""
ora #$7f
bmi +
lda #0
+ sta $destination
"""
internal fun asmIdentifierName(identifier: IdentifierReference): String {
val name = if(identifier.memberOfStruct(program.namespace)!=null) {
identifier.targetVarDecl(program.namespace)!!.name
} else {
identifier.nameInSource.joinToString(".")
}
return fixNameSymbols(name)
}
internal fun fixNameSymbols(name: String) = name.replace("<", "prog8_").replace(">", "") // take care of the autogenerated invalid (anon) label names
private fun branchInstruction(condition: BranchCondition, complement: Boolean) =
if(complement) {
when (condition) {
BranchCondition.CS -> "bcc"
BranchCondition.CC -> "bcs"
BranchCondition.EQ, BranchCondition.Z -> "beq"
BranchCondition.NE, BranchCondition.NZ -> "bne"
BranchCondition.VS -> "bvc"
BranchCondition.VC -> "bvs"
BranchCondition.MI, BranchCondition.NEG -> "bmi"
BranchCondition.PL, BranchCondition.POS -> "bpl"
}
} else {
when (condition) {
BranchCondition.CS -> "bcs"
BranchCondition.CC -> "bcc"
BranchCondition.EQ, BranchCondition.Z -> "beq"
BranchCondition.NE, BranchCondition.NZ -> "bne"
BranchCondition.VS -> "bvs"
BranchCondition.VC -> "bvc"
BranchCondition.MI, BranchCondition.NEG -> "bmi"
BranchCondition.PL, BranchCondition.POS -> "bpl"
}
}
internal fun readAndPushArrayvalueWithIndexA(arrayDt: DataType, variable: IdentifierReference) {
val variablename = asmIdentifierName(variable)
when (arrayDt) {
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B ->
out(" tay | lda $variablename,y | sta $ESTACK_LO_HEX,x | dex")
DataType.ARRAY_UW, DataType.ARRAY_W ->
out(" asl a | tay | lda $variablename,y | sta $ESTACK_LO_HEX,x | lda $variablename+1,y | sta $ESTACK_HI_HEX,x | dex")
DataType.ARRAY_F ->
// index * 5 is done in the subroutine that's called
out("""
sta $ESTACK_LO_HEX,x
dex
lda #<$variablename
ldy #>$variablename
jsr c64flt.push_float_from_indexed_var
""")
else ->
throw AssemblyError("weird array type")
}
}
internal fun saveRegister(register: Register) {
when(register) {
Register.A -> out(" pha")
Register.X -> out(" txa | pha")
Register.Y -> out(" tya | pha")
}
}
internal fun restoreRegister(register: Register) {
when(register) {
Register.A -> out(" pla")
Register.X -> out(" pla | tax")
Register.Y -> out(" pla | tay")
}
}
private fun translateSubroutine(sub: Subroutine) {
out("")
outputSourceLine(sub)
if(sub.isAsmSubroutine) {
if(sub.asmAddress!=null)
return // already done at the memvars section
// asmsub with most likely just an inline asm in it
out("${sub.name}\t.proc")
sub.statements.forEach{ translate(it) }
out(" .pend\n")
} else {
// regular subroutine
out("${sub.name}\t.proc")
zeropagevars2asm(sub.statements)
memdefs2asm(sub.statements)
out("; statements")
sub.statements.forEach{ translate(it) }
out("; variables")
vardecls2asm(sub.statements)
out(" .pend\n")
}
}
internal fun translate(stmt: Statement) {
outputSourceLine(stmt)
when(stmt) {
is VarDecl, is StructDecl, is NopStatement -> {}
is Directive -> translate(stmt)
is Return -> translate(stmt)
is Subroutine -> translateSubroutine(stmt)
is InlineAssembly -> translate(stmt)
is FunctionCallStatement -> {
val functionName = stmt.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
if(builtinFunc!=null) {
builtinFunctionsAsmGen.translateFunctioncallStatement(stmt, builtinFunc)
} else {
functioncallAsmGen.translateFunctionCall(stmt)
// discard any results from the stack:
val sub = stmt.target.targetSubroutine(program.namespace)!!
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for((t, reg) in returns) {
if(reg.stack) {
if (t in IntegerDatatypes || t in PassByReferenceDatatypes) out(" inx")
else if (t == DataType.FLOAT) out(" inx | inx | inx")
}
}
}
}
is Assignment -> assignmentAsmGen.translate(stmt)
is Jump -> translate(stmt)
is PostIncrDecr -> postincrdecrAsmGen.translate(stmt)
is Label -> translate(stmt)
is BranchStatement -> translate(stmt)
is IfStatement -> translate(stmt)
is ForLoop -> forloopsAsmGen.translate(stmt)
is Continue -> out(" jmp ${loopContinueLabels.peek()}")
is Break -> out(" jmp ${loopEndLabels.peek()}")
is WhileLoop -> translate(stmt)
is RepeatLoop -> translate(stmt)
is WhenStatement -> translate(stmt)
is BuiltinFunctionStatementPlaceholder -> throw AssemblyError("builtin function should not have placeholder anymore?")
is AnonymousScope -> translate(stmt)
is Block -> throw AssemblyError("block should have been handled elsewhere")
}
}
private fun translate(stmt: IfStatement) {
expressionsAsmGen.translateExpression(stmt.condition)
translateTestStack(stmt.condition.inferType(program).typeOrElse(DataType.STRUCT))
val elseLabel = makeLabel("if_else")
val endLabel = makeLabel("if_end")
out(" beq $elseLabel")
translate(stmt.truepart)
out(" jmp $endLabel")
out(elseLabel)
translate(stmt.elsepart)
out(endLabel)
}
private fun translateTestStack(dataType: DataType) {
when(dataType) {
in ByteDatatypes -> out(" inx | lda $ESTACK_LO_HEX,x")
in WordDatatypes -> out(" inx | lda $ESTACK_LO_HEX,x | ora $ESTACK_HI_HEX,x")
DataType.FLOAT -> throw AssemblyError("conditional value should be an integer (boolean)")
else -> throw AssemblyError("non-numerical dt")
}
}
private fun translate(stmt: WhileLoop) {
val whileLabel = makeLabel("while")
val endLabel = makeLabel("whileend")
loopEndLabels.push(endLabel)
loopContinueLabels.push(whileLabel)
out(whileLabel)
// TODO optimize for the simple cases, can we avoid stack use?
expressionsAsmGen.translateExpression(stmt.condition)
val conditionDt = stmt.condition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" inx | lda $ESTACK_LO_HEX,x | beq $endLabel")
} else {
out("""
inx
lda $ESTACK_LO_HEX,x
bne +
lda $ESTACK_HI_HEX,x
beq $endLabel
+ """)
}
translate(stmt.body)
out(" jmp $whileLabel")
out(endLabel)
loopEndLabels.pop()
loopContinueLabels.pop()
}
private fun translate(stmt: RepeatLoop) {
val repeatLabel = makeLabel("repeat")
val endLabel = makeLabel("repeatend")
loopEndLabels.push(endLabel)
loopContinueLabels.push(repeatLabel)
out(repeatLabel)
// TODO optimize this for the simple cases, can we avoid stack use?
translate(stmt.body)
expressionsAsmGen.translateExpression(stmt.untilCondition)
val conditionDt = stmt.untilCondition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" inx | lda $ESTACK_LO_HEX,x | beq $repeatLabel")
} else {
out("""
inx
lda $ESTACK_LO_HEX,x
bne +
lda $ESTACK_HI_HEX,x
beq $repeatLabel
+ """)
}
out(endLabel)
loopEndLabels.pop()
loopContinueLabels.pop()
}
private fun translate(stmt: WhenStatement) {
expressionsAsmGen.translateExpression(stmt.condition)
val endLabel = makeLabel("choice_end")
val choiceBlocks = mutableListOf<Pair<String, AnonymousScope>>()
val conditionDt = stmt.condition.inferType(program)
if(!conditionDt.isKnown)
throw AssemblyError("unknown condition dt")
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes)
out(" inx | lda $ESTACK_LO_HEX,x")
else
out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
for(choice in stmt.choices) {
val choiceLabel = makeLabel("choice")
if(choice.values==null) {
// the else choice
translate(choice.statements)
out(" jmp $endLabel")
} else {
choiceBlocks.add(Pair(choiceLabel, choice.statements))
for (cv in choice.values!!) {
val value = (cv as NumericLiteralValue).number.toInt()
if(conditionDt.typeOrElse(DataType.BYTE) in ByteDatatypes) {
out(" cmp #${value.toHex()} | beq $choiceLabel")
} else {
out("""
cmp #<${value.toHex()}
bne +
cpy #>${value.toHex()}
beq $choiceLabel
+
""")
}
}
}
}
for(choiceBlock in choiceBlocks) {
out(choiceBlock.first)
translate(choiceBlock.second)
out(" jmp $endLabel")
}
out(endLabel)
}
private fun translate(stmt: Label) {
out(stmt.name)
}
private fun translate(scope: AnonymousScope) {
// note: the variables defined in an anonymous scope have been moved to their defining subroutine's scope
scope.statements.forEach{ translate(it) }
}
private fun translate(stmt: BranchStatement) {
if(stmt.truepart.containsNoCodeNorVars() && stmt.elsepart.containsCodeOrVars())
throw AssemblyError("only else part contains code, shoud have been switched already")
val jump = stmt.truepart.statements.first() as? Jump
if(jump!=null) {
// branch with only a jump
val instruction = branchInstruction(stmt.condition, false)
out(" $instruction ${getJumpTarget(jump)}")
translate(stmt.elsepart)
} else {
if(stmt.elsepart.containsNoCodeNorVars()) {
val instruction = branchInstruction(stmt.condition, true)
val elseLabel = makeLabel("branch_else")
out(" $instruction $elseLabel")
translate(stmt.truepart)
out(elseLabel)
} else {
val instruction = branchInstruction(stmt.condition, false)
val trueLabel = makeLabel("branch_true")
val endLabel = makeLabel("branch_end")
out(" $instruction $trueLabel")
translate(stmt.elsepart)
out(" jmp $endLabel")
out(trueLabel)
translate(stmt.truepart)
out(endLabel)
}
}
}
private fun translate(stmt: Directive) {
when(stmt.directive) {
"%asminclude" -> {
val sourcecode = loadAsmIncludeFile(stmt.args[0].str!!, stmt.definingModule().source)
val scopeprefix = stmt.args[1].str ?: ""
if(!scopeprefix.isBlank())
out("$scopeprefix\t.proc")
assemblyLines.add(sourcecode.trimEnd().trimStart('\n'))
if(!scopeprefix.isBlank())
out(" .pend\n")
}
"%asmbinary" -> {
val offset = if(stmt.args.size>1) ", ${stmt.args[1].int}" else ""
val length = if(stmt.args.size>2) ", ${stmt.args[2].int}" else ""
out(" .binary \"${stmt.args[0].str}\" $offset $length")
}
"%breakpoint" -> {
val label = "_prog8_breakpoint_${breakpointLabels.size+1}"
breakpointLabels.add(label)
out("$label\tnop")
}
}
}
private fun translate(jmp: Jump) {
out(" jmp ${getJumpTarget(jmp)}")
}
private fun getJumpTarget(jmp: Jump): String {
return when {
jmp.identifier!=null -> asmIdentifierName(jmp.identifier)
jmp.generatedLabel!=null -> jmp.generatedLabel
jmp.address!=null -> jmp.address.toHex()
else -> "????"
}
}
private fun translate(ret: Return) {
ret.value?.let { expressionsAsmGen.translateExpression(it) }
out(" rts")
}
private fun translate(asm: InlineAssembly) {
val assembly = asm.assembly.trimEnd().trimStart('\n')
assemblyLines.add(assembly)
}
internal fun translateArrayIndexIntoA(expr: ArrayIndexedExpression) {
when (val index = expr.arrayspec.index) {
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
is RegisterExpr -> {
when (index.register) {
Register.A -> {}
Register.X -> out(" txa")
Register.Y -> out(" tya")
}
}
is IdentifierReference -> {
val indexName = asmIdentifierName(index)
out(" lda $indexName")
}
else -> {
expressionsAsmGen.translateExpression(index)
out(" inx | lda $ESTACK_LO_HEX,x")
}
}
}
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FunctionSignature) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
internal fun translateFunctionCall(functionCall: FunctionCall) =
functioncallAsmGen.translateFunctionCall(functionCall)
internal fun assignFromEvalResult(target: AssignTarget) =
assignmentAsmGen.assignFromEvalResult(target)
fun assignFromByteConstant(target: AssignTarget, value: Short) =
assignmentAsmGen.assignFromByteConstant(target, value)
fun assignFromWordConstant(target: AssignTarget, value: Int) =
assignmentAsmGen.assignFromWordConstant(target, value)
fun assignFromFloatConstant(target: AssignTarget, value: Double) =
assignmentAsmGen.assignFromFloatConstant(target, value)
fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromByteVariable(target, variable)
fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromWordVariable(target, variable)
fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) =
assignmentAsmGen.assignFromFloatVariable(target, variable)
fun assignFromRegister(target: AssignTarget, register: Register) =
assignmentAsmGen.assignFromRegister(target, register)
fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) =
assignmentAsmGen.assignFromMemoryByte(target, address, identifier)
}

View File

@ -1,7 +1,7 @@
package prog8.compiler.target.c64.codegen2 package prog8.compiler.target.c64.codegen
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations // note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
@ -54,7 +54,7 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
numberOfOptimizations++ numberOfOptimizations++
} }
// TODO more assembly optimizations? // TODO more assembly optimizations
return numberOfOptimizations return numberOfOptimizations
} }
@ -101,7 +101,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats) // optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check... // the float one is the one that requires 2*7=14 lines of code to check...
// @todo a better place to do this is in the Compiler instead and work on opcodes, and never even create the inefficient asm... // @todo a better place to do this is in the Compiler instead and transform the Ast, or the AsmGen, and never even create the inefficient asm in the first place...
val removeLines = mutableListOf<Int>() val removeLines = mutableListOf<Int>()
for (pair in linesByFourteen) { for (pair in linesByFourteen) {

View File

@ -0,0 +1,750 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.DirectMemoryWrite
import prog8.ast.statements.VarDecl
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
internal class AssignmentAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(assign: Assignment) {
if(assign.aug_op!=null)
throw AssemblyError("aug-op assignments should have been transformed to normal ones")
when(assign.value) {
is NumericLiteralValue -> {
val numVal = assign.value as NumericLiteralValue
when(numVal.type) {
DataType.UBYTE, DataType.BYTE -> assignFromByteConstant(assign.target, numVal.number.toShort())
DataType.UWORD, DataType.WORD -> assignFromWordConstant(assign.target, numVal.number.toInt())
DataType.FLOAT -> assignFromFloatConstant(assign.target, numVal.number.toDouble())
else -> throw AssemblyError("weird numval type")
}
}
is RegisterExpr -> {
assignFromRegister(assign.target, (assign.value as RegisterExpr).register)
}
is IdentifierReference -> {
val type = assign.target.inferType(program, assign).typeOrElse(DataType.STRUCT)
when(type) {
DataType.UBYTE, DataType.BYTE -> assignFromByteVariable(assign.target, assign.value as IdentifierReference)
DataType.UWORD, DataType.WORD -> assignFromWordVariable(assign.target, assign.value as IdentifierReference)
DataType.FLOAT -> assignFromFloatVariable(assign.target, assign.value as IdentifierReference)
else -> throw AssemblyError("unsupported assignment target type $type")
}
}
is AddressOf -> {
val identifier = (assign.value as AddressOf).identifier
assignFromAddressOf(assign.target, identifier)
}
is DirectMemoryRead -> {
val read = (assign.value as DirectMemoryRead)
when(read.addressExpression) {
is NumericLiteralValue -> {
val address = (read.addressExpression as NumericLiteralValue).number.toInt()
assignFromMemoryByte(assign.target, address, null)
}
is IdentifierReference -> {
assignFromMemoryByte(assign.target, null, read.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(read.addressExpression)
TODO("read memory byte from result and put that in ${assign.target}")
}
}
}
is PrefixExpression -> {
// TODO optimize common cases
asmgen.translateExpression(assign.value as PrefixExpression)
assignFromEvalResult(assign.target)
}
is BinaryExpression -> {
// TODO optimize common cases
asmgen.translateExpression(assign.value as BinaryExpression)
assignFromEvalResult(assign.target)
}
is ArrayIndexedExpression -> {
// TODO optimize common cases
val arrayExpr = assign.value as ArrayIndexedExpression
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
val index = arrayExpr.arrayspec.index
if(index is NumericLiteralValue) {
// constant array index value
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
val indexValue = index.number.toInt() * ArrayElementTypes.getValue(arrayDt).memorySize()
when (arrayDt) {
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B ->
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | dex")
DataType.ARRAY_UW, DataType.ARRAY_W ->
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | lda $arrayVarName+$indexValue+1 | sta $ESTACK_HI_HEX,x | dex")
DataType.ARRAY_F ->
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
else ->
throw AssemblyError("weird array type")
}
} else {
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
}
assignFromEvalResult(assign.target)
}
is TypecastExpression -> {
val cast = assign.value as TypecastExpression
val sourceType = cast.expression.inferType(program)
val targetType = assign.target.inferType(program, assign)
if(sourceType.isKnown && targetType.isKnown &&
(sourceType.typeOrElse(DataType.STRUCT) in ByteDatatypes && targetType.typeOrElse(DataType.STRUCT) in ByteDatatypes) ||
(sourceType.typeOrElse(DataType.STRUCT) in WordDatatypes && targetType.typeOrElse(DataType.STRUCT) in WordDatatypes)) {
// no need for a type cast
assign.value = cast.expression
translate(assign)
} else {
asmgen.translateExpression(assign.value as TypecastExpression)
assignFromEvalResult(assign.target)
}
}
is FunctionCall -> {
asmgen.translateExpression(assign.value as FunctionCall)
assignFromEvalResult(assign.target)
}
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
}
}
internal fun assignFromEvalResult(target: AssignTarget) {
val targetIdent = target.identifier
when {
target.register!=null -> {
if(target.register== Register.X)
throw AssemblyError("can't pop into X register - use variable instead")
asmgen.out(" inx | ld${target.register.name.toLowerCase()} $ESTACK_LO_HEX,x ")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
val targetDt = targetIdent.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $targetName")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta $targetName
lda $ESTACK_HI_HEX,x
sta $targetName+1
""")
}
DataType.FLOAT -> {
asmgen.out("""
lda #<$targetName
ldy #>$targetName
jsr c64flt.pop_float
""")
}
else -> throw AssemblyError("weird target variable type $targetDt")
}
}
target.memoryAddress!=null -> {
asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
target.arrayindexed!=null -> {
val arrayDt = target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!.datatype
val arrayVarName = asmgen.asmIdentifierName(target.arrayindexed!!.identifier)
asmgen.translateExpression(target.arrayindexed!!.arrayspec.index)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
popAndWriteArrayvalueWithIndexA(arrayDt, arrayVarName)
}
else -> throw AssemblyError("weird assignment target $target")
}
}
internal fun assignFromAddressOf(target: AssignTarget, name: IdentifierReference) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
val struct = name.memberOfStruct(program.namespace)
val sourceName = if(struct!=null) {
// take the address of the first struct member instead
val decl = name.targetVarDecl(program.namespace)!!
val firstStructMember = struct.nameOfFirstMember()
// find the flattened var that belongs to this first struct member
val firstVarName = listOf(decl.name, firstStructMember)
val firstVar = name.definingScope().lookup(firstVarName, name) as VarDecl
firstVar.name
} else {
asmgen.fixNameSymbols(name.nameInSource.joinToString ("."))
}
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda #<$sourceName
ldy #>$sourceName
sta $targetName
sty $targetName+1
""")
}
target.memoryAddress!=null -> {
TODO("assign address $sourceName to memory word $target")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign address $sourceName to array $targetName [ $index ]")
}
else -> TODO("assign address $sourceName to $target")
}
}
internal fun assignFromWordVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $sourceName
ldy $sourceName+1
sta $targetName
sty $targetName+1
""")
}
target.memoryAddress!=null -> {
TODO("assign wordvar $sourceName to memory ${target.memoryAddress}")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
asmgen.out(" lda $sourceName | sta $ESTACK_LO_HEX,x | lda $sourceName+1 | sta $ESTACK_HI_HEX,x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
}
else -> TODO("assign wordvar to $target")
}
}
internal fun assignFromFloatVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $sourceName
sta $targetName
lda $sourceName+1
sta $targetName+1
lda $sourceName+2
sta $targetName+2
lda $sourceName+3
sta $targetName+3
lda $sourceName+4
sta $targetName+4
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
asmgen.out(" lda #<$sourceName | ldy #>$sourceName | jsr c64flt.push_float")
asmgen.translateExpression(index)
asmgen.out(" lda #<$targetName | ldy #>$targetName | jsr c64flt.pop_float_to_indexed_var")
}
else -> TODO("assign floatvar to $target")
}
}
internal fun assignFromByteVariable(target: AssignTarget, variable: IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(variable)
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
target.register!=null -> {
asmgen.out(" ld${target.register.name.toLowerCase()} $sourceName")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $sourceName
sta $targetName
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
asmgen.out(" lda $sourceName | sta $ESTACK_LO_HEX,x | dex")
asmgen.translateExpression(index)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
popAndWriteArrayvalueWithIndexA(arrayDt, targetName)
}
target.memoryAddress != null -> {
val addressExpr = target.memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
when {
addressLv != null -> asmgen.out(" lda $sourceName | sta ${addressLv.number.toHex()}")
addressExpr is IdentifierReference -> {
val targetName = asmgen.asmIdentifierName(addressExpr)
asmgen.out(" lda $sourceName | sta $targetName")
}
else -> {
asmgen.translateExpression(addressExpr)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
ldy $ESTACK_HI_HEX,x
sta (+) +1
sty (+) +2
lda $sourceName
+ sta ${65535.toHex()} ; modified
""")
}
}
}
else -> TODO("assign bytevar to $target")
}
}
internal fun assignFromRegister(target: AssignTarget, register: Register) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out(" st${register.name.toLowerCase()} $targetName")
}
target.register!=null -> {
when(register) {
Register.A -> when(target.register) {
Register.A -> {}
Register.X -> asmgen.out(" tax")
Register.Y -> asmgen.out(" tay")
}
Register.X -> when(target.register) {
Register.A -> asmgen.out(" txa")
Register.X -> {}
Register.Y -> asmgen.out(" txy")
}
Register.Y -> when(target.register) {
Register.A -> asmgen.out(" tya")
Register.X -> asmgen.out(" tyx")
Register.Y -> {}
}
}
}
target.memoryAddress!=null -> {
storeRegisterInMemoryAddress(register, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
when (index) {
is NumericLiteralValue -> {
val memindex = index.number.toInt()
when(register) {
Register.A -> asmgen.out(" sta $targetName+$memindex")
Register.X -> asmgen.out(" stx $targetName+$memindex")
Register.Y -> asmgen.out(" sty $targetName+$memindex")
}
}
is RegisterExpr -> {
when(register) {
Register.A -> asmgen.out(" sta ${C64Zeropage.SCRATCH_B1}")
Register.X -> asmgen.out(" stx ${C64Zeropage.SCRATCH_B1}")
Register.Y -> asmgen.out(" sty ${C64Zeropage.SCRATCH_B1}")
}
when(index.register) {
Register.A -> {}
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
}
asmgen.out("""
tay
lda ${C64Zeropage.SCRATCH_B1}
sta $targetName,y
""")
}
is IdentifierReference -> {
when(register) {
Register.A -> asmgen.out(" sta ${C64Zeropage.SCRATCH_B1}")
Register.X -> asmgen.out(" stx ${C64Zeropage.SCRATCH_B1}")
Register.Y -> asmgen.out(" sty ${C64Zeropage.SCRATCH_B1}")
}
asmgen.out("""
lda ${asmgen.asmIdentifierName(index)}
tay
lda ${C64Zeropage.SCRATCH_B1}
sta $targetName,y
""")
}
else -> {
asmgen.saveRegister(register)
asmgen.translateExpression(index)
asmgen.restoreRegister(register)
when(register) {
Register.A -> asmgen.out(" sta ${C64Zeropage.SCRATCH_B1}")
Register.X -> asmgen.out(" stx ${C64Zeropage.SCRATCH_B1}")
Register.Y -> asmgen.out(" sty ${C64Zeropage.SCRATCH_B1}")
}
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
tay
lda ${C64Zeropage.SCRATCH_B1}
sta $targetName,y
""")
}
}
}
else -> TODO("assign register $register to $target")
}
}
private fun storeRegisterInMemoryAddress(register: Register, memoryAddress: DirectMemoryWrite) {
val addressExpr = memoryAddress.addressExpression
val addressLv = addressExpr as? NumericLiteralValue
val registerName = register.name.toLowerCase()
when {
addressLv != null -> asmgen.out(" st$registerName ${addressLv.number.toHex()}")
addressExpr is IdentifierReference -> {
val targetName = asmgen.asmIdentifierName(addressExpr)
when(register) {
Register.A -> asmgen.out("""
ldy $targetName
sty ${C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y
""")
Register.X -> asmgen.out("""
txa
ldy $targetName
sty ${C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y
""")
Register.Y -> asmgen.out("""
tya
ldy $targetName
sty ${C64Zeropage.SCRATCH_W1}
ldy $targetName+1
sty ${C64Zeropage.SCRATCH_W1+1}
ldy #0
sta (${C64Zeropage.SCRATCH_W1}),y
""")
}
}
else -> {
asmgen.saveRegister(register)
asmgen.translateExpression(addressExpr)
asmgen.restoreRegister(register)
when (register) {
Register.A -> asmgen.out(" tay")
Register.X -> throw AssemblyError("can't use X register here")
Register.Y -> {}
}
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) +1
lda $ESTACK_HI_HEX,x
sta (+) +2
+ sty ${65535.toHex()} ; modified
""")
}
}
}
internal fun assignFromWordConstant(target: AssignTarget, word: Int) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
if(word ushr 8 == word and 255) {
// lsb=msb
asmgen.out("""
lda #${(word and 255).toHex()}
sta $targetName
sta $targetName+1
""")
} else {
asmgen.out("""
lda #<${word.toHex()}
ldy #>${word.toHex()}
sta $targetName
sty $targetName+1
""")
}
}
target.memoryAddress!=null -> {
TODO("assign word $word to memory ${target.memoryAddress}")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
// TODO optimize common cases
asmgen.translateExpression(index)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
asl a
tay
lda #<${word.toHex()}
sta $targetName,y
lda #>${word.toHex()}
sta $targetName+1,y
""")
}
else -> TODO("assign word $word to $target")
}
}
internal fun assignFromByteConstant(target: AssignTarget, byte: Short) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
when {
target.register!=null -> {
asmgen.out(" ld${target.register.name.toLowerCase()} #${byte.toHex()}")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out(" lda #${byte.toHex()} | sta $targetName ")
}
target.memoryAddress!=null -> {
asmgen.out(" ldy #${byte.toHex()}")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
// TODO optimize common cases
asmgen.translateExpression(index)
asmgen.out("""
inx
ldy $ESTACK_LO_HEX,x
lda #${byte.toHex()}
sta $targetName,y
""")
}
else -> TODO("assign byte $byte to $target")
}
}
internal fun assignFromFloatConstant(target: AssignTarget, float: Double) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
if(float==0.0) {
// optimized case for float zero
when {
targetIdent != null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda #0
sta $targetName
sta $targetName+1
sta $targetName+2
sta $targetName+3
sta $targetName+4
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * C64MachineDefinition.FLOAT_MEM_SIZE
asmgen.out("""
lda #0
sta $targetName+$indexValue
sta $targetName+$indexValue+1
sta $targetName+$indexValue+2
sta $targetName+$indexValue+3
sta $targetName+$indexValue+4
""")
} else {
asmgen.translateExpression(index)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
asl a
asl a
clc
adc $ESTACK_LO_HEX,x
tay
lda #0
sta $targetName,y
sta $targetName+1,y
sta $targetName+2,y
sta $targetName+3,y
sta $targetName+4,y
""") // TODO use a subroutine for this
}
}
else -> TODO("assign float 0.0 to $target")
}
} else {
// non-zero value
val constFloat = asmgen.getFloatConst(float)
when {
targetIdent != null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda $constFloat
sta $targetName
lda $constFloat+1
sta $targetName+1
lda $constFloat+2
sta $targetName+2
lda $constFloat+3
sta $targetName+3
lda $constFloat+4
sta $targetName+4
""")
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val arrayVarName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
if(index is NumericLiteralValue) {
val indexValue = index.number.toInt() * C64MachineDefinition.FLOAT_MEM_SIZE
asmgen.out("""
lda $constFloat
sta $arrayVarName+$indexValue
lda $constFloat+1
sta $arrayVarName+$indexValue+1
lda $constFloat+2
sta $arrayVarName+$indexValue+2
lda $constFloat+3
sta $arrayVarName+$indexValue+3
lda $constFloat+4
sta $arrayVarName+$indexValue+4
""")
} else {
asmgen.translateArrayIndexIntoA(targetArrayIdx)
asmgen.out("""
sta ${C64Zeropage.SCRATCH_REG}
asl a
asl a
clc
adc ${C64Zeropage.SCRATCH_REG}
tay
lda $constFloat
sta $arrayVarName,y
lda $constFloat+1
sta $arrayVarName+1,y
lda $constFloat+2
sta $arrayVarName+2,y
lda $constFloat+3
sta $arrayVarName+3,y
lda $constFloat+4
sta $arrayVarName+4,y
""") // TODO use a subroutine for this
}
}
else -> TODO("assign float $float to $target")
}
}
}
internal fun assignFromMemoryByte(target: AssignTarget, address: Int?, identifier: IdentifierReference?) {
val targetIdent = target.identifier
val targetArrayIdx = target.arrayindexed
if(address!=null) {
when {
target.register!=null -> {
asmgen.out(" ld${target.register.name.toLowerCase()} ${address.toHex()}")
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
lda ${address.toHex()}
sta $targetName
""")
}
target.memoryAddress!=null -> {
asmgen.out(" ldy ${address.toHex()}")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign memory byte at $address to array $targetName [ $index ]")
}
else -> TODO("assign memory byte $target")
}
}
else if(identifier!=null) {
val sourceName = asmgen.asmIdentifierName(identifier)
when {
target.register!=null -> {
asmgen.out("""
ldy #0
lda ($sourceName),y
""")
when(target.register){
Register.A -> {}
Register.X -> asmgen.out(" tax")
Register.Y -> asmgen.out(" tay")
}
}
targetIdent!=null -> {
val targetName = asmgen.asmIdentifierName(targetIdent)
asmgen.out("""
ldy #0
lda ($sourceName),y
sta $targetName
""")
}
target.memoryAddress!=null -> {
asmgen.out(" ldy $sourceName")
storeRegisterInMemoryAddress(Register.Y, target.memoryAddress)
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val targetName = asmgen.asmIdentifierName(targetArrayIdx.identifier)
TODO("assign memory byte $sourceName to array $targetName [ $index ]")
}
else -> TODO("assign memory byte $target")
}
}
}
private fun popAndWriteArrayvalueWithIndexA(arrayDt: DataType, variablename: String) {
when (arrayDt) {
DataType.STR, DataType.ARRAY_UB, DataType.ARRAY_B ->
asmgen.out(" tay | inx | lda $ESTACK_LO_HEX,x | sta $variablename,y")
DataType.ARRAY_UW, DataType.ARRAY_W ->
asmgen.out(" asl a | tay | inx | lda $ESTACK_LO_HEX,x | sta $variablename,y | lda $ESTACK_HI_HEX,x | sta $variablename+1,y")
DataType.ARRAY_F ->
// index * 5 is done in the subroutine that's called
asmgen.out("""
sta $ESTACK_LO_HEX,x
dex
lda #<$variablename
ldy #>$variablename
jsr c64flt.pop_float_to_indexed_var
""")
else ->
throw AssemblyError("weird array type")
}
}
}

View File

@ -0,0 +1,603 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.Register
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.FunctionCallStatement
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import prog8.functions.FunctionSignature
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) {
translateFunctioncall(fcall, func, false)
}
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FunctionSignature) {
translateFunctioncall(fcall, func, true)
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) {
val functionName = fcall.target.nameInSource.last()
if (discardResult) {
if (func.pure)
return // can just ignore the whole function call altogether
else if (func.returntype != null)
throw AssemblyError("discarding result of non-pure function $fcall")
}
when (functionName) {
"msb" -> {
val arg = fcall.args.single()
if (arg.inferType(program).typeOrElse(DataType.STRUCT) !in WordDatatypes)
throw AssemblyError("msb required word argument")
if (arg is NumericLiteralValue)
throw AssemblyError("should have been const-folded")
if (arg is IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(arg)
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
} else {
asmgen.translateExpression(arg)
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x")
}
}
"mkword" -> {
translateFunctionArguments(fcall.args, func)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x")
}
"abs" -> {
translateFunctionArguments(fcall.args, func)
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
else -> throw AssemblyError("weird type")
}
}
"swap" -> {
val first = fcall.args[0]
val second = fcall.args[1]
asmgen.translateExpression(first)
asmgen.translateExpression(second)
// pop in reverse order
val firstTarget = AssignTarget.fromExpr(first)
val secondTarget = AssignTarget.fromExpr(second)
asmgen.assignFromEvalResult(firstTarget)
asmgen.assignFromEvalResult(secondTarget)
}
"strlen" -> {
outputPushAddressOfIdentifier(fcall.args[0])
asmgen.out(" jsr prog8_lib.func_strlen")
}
"min", "max", "sum" -> {
outputPushAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_ub")
DataType.ARRAY_B -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW -> asmgen.out(" jsr prog8_lib.func_${functionName}_uw")
DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
"any", "all" -> {
outputPushAddressAndLenghtOfArray(fcall.args[0])
val dt = fcall.args.single().inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR -> asmgen.out(" jsr prog8_lib.func_${functionName}_b")
DataType.ARRAY_UW, DataType.ARRAY_W -> asmgen.out(" jsr prog8_lib.func_${functionName}_w")
DataType.ARRAY_F -> asmgen.out(" jsr c64flt.func_${functionName}_f")
else -> throw AssemblyError("weird type $dt")
}
}
"sgn" -> {
translateFunctionArguments(fcall.args, func)
val dt = fcall.args.single().inferType(program)
when(dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> asmgen.out(" jsr math.sign_ub")
DataType.BYTE -> asmgen.out(" jsr math.sign_b")
DataType.UWORD -> asmgen.out(" jsr math.sign_uw")
DataType.WORD -> asmgen.out(" jsr math.sign_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.sign_f")
else -> throw AssemblyError("weird type $dt")
}
}
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
"rdnf" -> {
translateFunctionArguments(fcall.args, func)
asmgen.out(" jsr c64flt.func_$functionName")
}
"lsl" -> {
// in-place
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
in ByteDatatypes -> {
when (what) {
is RegisterExpr -> {
when (what.register) {
Register.A -> asmgen.out(" asl a")
Register.X -> asmgen.out(" txa | asl a | tax")
Register.Y -> asmgen.out(" tya | asl a | tay")
}
}
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" asl ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
+ asl 0 ; modified
""")
}
}
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsl_array_b")
}
else -> throw AssemblyError("weird type")
}
}
in WordDatatypes -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsl_array_w")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" asl $variable | rol $variable+1")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
"lsr" -> {
// in-place
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when (what) {
is RegisterExpr -> {
when (what.register) {
Register.A -> asmgen.out(" lsr a")
Register.X -> asmgen.out(" txa | lsr a | tax")
Register.Y -> asmgen.out(" tya | lsr a | tay")
}
}
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" lsr ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
+ lsr 0 ; modified
""")
}
}
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_ub")
}
else -> throw AssemblyError("weird type")
}
}
DataType.BYTE -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_b")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable | asl a | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lsr $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.WORD -> {
when (what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.lsr_array_w")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable+1 | asl a | ror $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
"rol" -> {
// in-place
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when(what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.rol_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" rol ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
+ rol 0 ; modified
""")
}
}
is RegisterExpr -> {
when(what.register) {
Register.A -> asmgen.out(" rol a")
Register.X -> asmgen.out(" txa | rol a | tax")
Register.Y -> asmgen.out(" tya | rol a | tay")
}
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" rol $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when(what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.rol_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" rol $variable | rol $variable+1")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
"rol2" -> {
// in-place
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when(what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.rol2_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" lda ${number.toHex()} | cmp #\$80 | rol a | sta ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out(" jsr prog8_lib.rol2_mem_ub")
}
}
is RegisterExpr -> {
when(what.register) {
Register.A -> asmgen.out(" cmp #\$80 | rol a ")
Register.X -> asmgen.out(" txa | cmp #\$80 | rol a | tax")
Register.Y -> asmgen.out(" tya | cmp #\$80 | rol a | tay")
}
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable | cmp #\$80 | rol a | sta $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when(what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.rol2_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" asl $variable | rol $variable+1 | bcc + | inc $variable |+ ")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
"ror" -> {
// in-place
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when(what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.ror_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" ror ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
sta (+) + 1
lda $ESTACK_HI_HEX,x
sta (+) + 2
+ ror 0 ; modified
""") }
}
is RegisterExpr -> {
when(what.register) {
Register.A -> asmgen.out(" ror a")
Register.X -> asmgen.out(" txa | ror a | tax")
Register.Y -> asmgen.out(" tya | ror a | tay")
}
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when(what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.ror_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" ror $variable+1 | ror $variable")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
"ror2" -> {
// in-place
val what = fcall.args.single()
val dt = what.inferType(program)
when (dt.typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when(what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.ror2_array_ub")
}
is DirectMemoryRead -> {
if (what.addressExpression is NumericLiteralValue) {
val number = (what.addressExpression as NumericLiteralValue).number
asmgen.out(" lda ${number.toHex()} | lsr a | bcc + | ora #\$80 |+ | sta ${number.toHex()}")
} else {
asmgen.translateExpression(what.addressExpression)
asmgen.out(" jsr prog8_lib.ror2_mem_ub")
}
}
is RegisterExpr -> {
when(what.register) {
Register.A -> asmgen.out(" lsr a | bcc + | ora #\$80 |+ ")
Register.X -> asmgen.out(" txa | lsr a | bcc + | ora #\$80 |+ tax ")
Register.Y -> asmgen.out(" tya | lsr a | bcc + | ora #\$80 |+ tay ")
}
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lda $variable | lsr a | bcc + | ora #\$80 |+ | sta $variable")
}
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when(what) {
is ArrayIndexedExpression -> {
asmgen.translateExpression(what.identifier)
asmgen.translateExpression(what.arrayspec.index)
asmgen.out(" jsr prog8_lib.ror2_array_uw")
}
is IdentifierReference -> {
val variable = asmgen.asmIdentifierName(what)
asmgen.out(" lsr $variable+1 | ror $variable | bcc + | lda $variable+1 | ora #\$80 | sta $variable+1 |+ ")
}
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("weird type")
}
}
"sort" -> {
val variable = fcall.args.single()
if(variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!!
val varName = asmgen.asmIdentifierName(variable)
val numElements = decl.arraysize!!.size()
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1+1}
lda #$numElements
sta ${C64Zeropage.SCRATCH_B1}
""")
asmgen.out(if(decl.datatype==DataType.ARRAY_UB) " jsr prog8_lib.sort_ub" else " jsr prog8_lib.sort_b")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1+1}
lda #$numElements
sta ${C64Zeropage.SCRATCH_B1}
""")
asmgen.out(if(decl.datatype==DataType.ARRAY_UW) " jsr prog8_lib.sort_uw" else " jsr prog8_lib.sort_w")
}
DataType.ARRAY_F -> TODO("sort floats (consider another solution if possible - this will be very slow, if ever implemented)")
else -> throw AssemblyError("weird type")
}
}
else
throw AssemblyError("weird type")
}
"reverse" -> {
val variable = fcall.args.single()
if (variable is IdentifierReference) {
val decl = variable.targetVarDecl(program.namespace)!!
val varName = asmgen.asmIdentifierName(variable)
val numElements = decl.arraysize!!.size()
when (decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1 + 1}
lda #$numElements
jsr prog8_lib.reverse_b
""")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1 + 1}
lda #$numElements
jsr prog8_lib.reverse_w
""")
}
DataType.ARRAY_F -> TODO("reverse floats (consider another solution if possible - this will be quite slow, if ever implemented)")
else -> throw AssemblyError("weird type")
}
}
}
"rsave" -> {
// save cpu status flag and all registers A, X, Y.
// see http://6502.org/tutorials/register_preservation.html
asmgen.out(" php | sta ${C64Zeropage.SCRATCH_REG} | pha | txa | pha | tya | pha | lda ${C64Zeropage.SCRATCH_REG}")
}
"rrestore" -> {
// restore all registers and cpu status flag
asmgen.out(" pla | tay | pla | tax | pla | plp")
}
else -> {
translateFunctionArguments(fcall.args, func)
asmgen.out(" jsr prog8_lib.func_$functionName")
}
}
}
private fun outputPushAddressAndLenghtOfArray(arg: Expression) {
arg as IdentifierReference
val identifierName = asmgen.asmIdentifierName(arg)
val size = arg.targetVarDecl(program.namespace)!!.arraysize!!.size()!!
asmgen.out("""
lda #<$identifierName
sta $ESTACK_LO_HEX,x
lda #>$identifierName
sta $ESTACK_HI_HEX,x
dex
lda #$size
sta $ESTACK_LO_HEX,x
dex
""")
}
private fun outputPushAddressOfIdentifier(arg: Expression) {
val identifierName = asmgen.asmIdentifierName(arg as IdentifierReference)
asmgen.out("""
lda #<$identifierName
sta $ESTACK_LO_HEX,x
lda #>$identifierName
sta $ESTACK_HI_HEX,x
dex
""")
}
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FunctionSignature) {
args.forEach {
asmgen.translateExpression(it)
}
}
}

View File

@ -0,0 +1,438 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS2_HEX
import prog8.functions.BuiltinFunctions
import kotlin.math.absoluteValue
internal class ExpressionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateExpression(expression: Expression) {
when(expression) {
is PrefixExpression -> translateExpression(expression)
is BinaryExpression -> translateExpression(expression)
is ArrayIndexedExpression -> translatePushFromArray(expression)
is TypecastExpression -> translateExpression(expression)
is AddressOf -> translateExpression(expression)
is DirectMemoryRead -> translateExpression(expression)
is NumericLiteralValue -> translateExpression(expression)
is RegisterExpr -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCall -> translateExpression(expression)
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
is StructLiteralValue -> throw AssemblyError("struct literal value assignment should have been flattened")
is RangeExpr -> throw AssemblyError("range expression should have been changed into array values")
}
}
private fun translateExpression(expression: FunctionCall) {
val functionName = expression.target.nameInSource.last()
val builtinFunc = BuiltinFunctions[functionName]
if (builtinFunc != null) {
asmgen.translateFunctioncallExpression(expression, builtinFunc)
} else {
asmgen.translateFunctionCall(expression)
val sub = expression.target.targetSubroutine(program.namespace)!!
val returns = sub.returntypes.zip(sub.asmReturnvaluesRegisters)
for ((_, reg) in returns) {
if (!reg.stack) {
// result value in cpu or status registers, put it on the stack
if (reg.registerOrPair != null) {
when (reg.registerOrPair) {
RegisterOrPair.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
RegisterOrPair.AY -> asmgen.out(" sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable")
}
}
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
}
}
}
}
private fun translateExpression(expr: TypecastExpression) {
translateExpression(expr.expression)
when(expr.expression.inferType(program).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE -> {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> asmgen.out(" lda #0 | sta $ESTACK_HI_PLUS1_HEX,x")
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_ub2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.BYTE -> {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | ${asmgen.signExtendAtoMsb("$ESTACK_HI_PLUS1_HEX,x")}")
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_b2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.UWORD -> {
when(expr.type) {
DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_uw2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.WORD -> {
when(expr.type) {
DataType.BYTE, DataType.UBYTE -> {}
DataType.WORD, DataType.UWORD -> {}
DataType.FLOAT -> asmgen.out(" jsr c64flt.stack_w2float")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
DataType.FLOAT -> {
when(expr.type) {
DataType.UBYTE -> asmgen.out(" jsr c64flt.stack_float2uw")
DataType.BYTE -> asmgen.out(" jsr c64flt.stack_float2w")
DataType.UWORD -> asmgen.out(" jsr c64flt.stack_float2uw")
DataType.WORD -> asmgen.out(" jsr c64flt.stack_float2w")
DataType.FLOAT -> {}
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast to a pass-by-reference datatype")
else -> throw AssemblyError("weird type")
}
}
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else")
else -> throw AssemblyError("weird type")
}
}
private fun translateExpression(expr: AddressOf) {
val name = asmgen.asmIdentifierName(expr.identifier)
asmgen.out(" lda #<$name | sta $ESTACK_LO_HEX,x | lda #>$name | sta $ESTACK_HI_HEX,x | dex")
}
private fun translateExpression(expr: DirectMemoryRead) {
when(expr.addressExpression) {
is NumericLiteralValue -> {
val address = (expr.addressExpression as NumericLiteralValue).number.toInt()
asmgen.out(" lda ${address.toHex()} | sta $ESTACK_LO_HEX,x | dex")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference)
asmgen.out(" lda $sourceName | sta $ESTACK_LO_HEX,x | dex")
}
else -> {
translateExpression(expr.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address")
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
}
}
}
private fun translateExpression(expr: NumericLiteralValue) {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> asmgen.out(" lda #${expr.number.toHex()} | sta $ESTACK_LO_HEX,x | dex")
DataType.UWORD, DataType.WORD -> asmgen.out("""
lda #<${expr.number.toHex()}
sta $ESTACK_LO_HEX,x
lda #>${expr.number.toHex()}
sta $ESTACK_HI_HEX,x
dex
""")
DataType.FLOAT -> {
val floatConst = asmgen.getFloatConst(expr.number.toDouble())
asmgen.out(" lda #<$floatConst | ldy #>$floatConst | jsr c64flt.push_float")
}
else -> throw AssemblyError("weird type")
}
}
private fun translateExpression(expr: RegisterExpr) {
when(expr.register) {
Register.A -> asmgen.out(" sta $ESTACK_LO_HEX,x | dex")
Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register")
Register.Y -> asmgen.out(" tya | sta $ESTACK_LO_HEX,x | dex")
}
}
private fun translateExpression(expr: IdentifierReference) {
val varname = asmgen.asmIdentifierName(expr)
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" lda $varname | sta $ESTACK_LO_HEX,x | dex")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out(" lda $varname | sta $ESTACK_LO_HEX,x | lda $varname+1 | sta $ESTACK_HI_HEX,x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$varname | ldy #>$varname| jsr c64flt.push_float")
}
in IterableDatatypes -> {
asmgen.out(" lda #<$varname | sta $ESTACK_LO_HEX,x | lda #>$varname | sta $ESTACK_HI_HEX,x | dex")
}
else -> throw AssemblyError("stack push weird variable type $expr")
}
}
private val optimizedByteMultiplications = setOf(3,5,6,7,9,10,11,12,13,14,15,20,25,40)
private val optimizedWordMultiplications = setOf(3,5,6,7,9,10,12,15,20,25,40)
private val powersOfTwo = setOf(0,1,2,4,8,16,32,64,128,256)
private fun translateExpression(expr: BinaryExpression) {
val leftIDt = expr.left.inferType(program)
val rightIDt = expr.right.inferType(program)
if(!leftIDt.isKnown || !rightIDt.isKnown)
throw AssemblyError("can't infer type of both expression operands")
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
// see if we can apply some optimized routines
when(expr.operator) {
">>" -> {
// bit-shifts are always by a constant number (for now)
translateExpression(expr.left)
val amount = expr.right.constValue(program)!!.number.toInt()
when (leftDt) {
DataType.UBYTE -> repeat(amount) { asmgen.out(" lsr $ESTACK_LO_PLUS1_HEX,x") }
DataType.BYTE -> repeat(amount) { asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x") }
DataType.UWORD -> repeat(amount) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
DataType.WORD -> repeat(amount) { asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | asl a | ror $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
else -> throw AssemblyError("weird type")
}
return
}
"<<" -> {
// bit-shifts are always by a constant number (for now)
translateExpression(expr.left)
val amount = expr.right.constValue(program)!!.number.toInt()
if (leftDt in ByteDatatypes)
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x") }
else
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
return
}
"*" -> {
val value = expr.right.constValue(program)
if(value!=null) {
if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt()
if(amount in powersOfTwo)
printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount")
when(rightDt) {
DataType.UBYTE -> {
if(amount in optimizedByteMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr math.mul_byte_$amount")
return
}
}
DataType.BYTE -> {
if(amount in optimizedByteMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr math.mul_byte_$amount")
return
}
if(amount.absoluteValue in optimizedByteMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr prog8_lib.neg_b | jsr math.mul_byte_${amount.absoluteValue}")
return
}
}
DataType.UWORD -> {
if(amount in optimizedWordMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr math.mul_word_$amount")
return
}
}
DataType.WORD -> {
if(amount in optimizedWordMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr math.mul_word_$amount")
return
}
if(amount.absoluteValue in optimizedWordMultiplications) {
translateExpression(expr.left)
asmgen.out(" jsr prog8_lib.neg_w | jsr math.mul_word_${amount.absoluteValue}")
return
}
}
else -> {}
}
}
}
}
}
// the general, non-optimized cases
translateExpression(expr.left)
translateExpression(expr.right)
if(leftDt!=rightDt)
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical") // is this strictly required always?
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
DataType.FLOAT -> translateBinaryOperatorFloats(expr.operator)
else -> throw AssemblyError("non-numerical datatype")
}
}
private fun translateExpression(expr: PrefixExpression) {
translateExpression(expr.expression)
val type = expr.inferType(program).typeOrElse(DataType.STRUCT)
when(expr.operator) {
"+" -> {}
"-" -> {
when(type) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.neg_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.neg_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.neg_f")
else -> throw AssemblyError("weird type")
}
}
"~" -> {
when(type) {
in ByteDatatypes ->
asmgen.out("""
lda $ESTACK_LO_PLUS1_HEX,x
eor #255
sta $ESTACK_LO_PLUS1_HEX,x
""")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
else -> throw AssemblyError("weird type")
}
}
"not" -> {
when(type) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.not_byte")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.not_word")
else -> throw AssemblyError("weird type")
}
}
else -> throw AssemblyError("invalid prefix operator ${expr.operator}")
}
}
private fun translatePushFromArray(arrayExpr: ArrayIndexedExpression) {
// assume *reading* from an array
val index = arrayExpr.arrayspec.index
val arrayDt = arrayExpr.identifier.targetVarDecl(program.namespace)!!.datatype
val arrayVarName = asmgen.asmIdentifierName(arrayExpr.identifier)
if(index is NumericLiteralValue) {
val elementDt = ArrayElementTypes.getValue(arrayDt)
val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | dex")
}
in WordDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | lda $arrayVarName+$indexValue+1 | sta $ESTACK_HI_HEX,x | dex")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$arrayVarName+$indexValue | ldy #>$arrayVarName+$indexValue | jsr c64flt.push_float")
}
else -> throw AssemblyError("weird type")
}
} else {
asmgen.translateArrayIndexIntoA(arrayExpr)
asmgen.readAndPushArrayvalueWithIndexA(arrayDt, arrayExpr.identifier)
}
}
private fun translateBinaryOperatorBytes(operator: String, types: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
"*" -> asmgen.out(" jsr prog8_lib.mul_byte") // the optimized routines should have been checked earlier
"/" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.idiv_ub" else " jsr prog8_lib.idiv_b")
"%" -> {
if(types==DataType.BYTE)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" jsr prog8_lib.remainder_ub")
}
"+" -> asmgen.out("""
lda $ESTACK_LO_PLUS2_HEX,x
clc
adc $ESTACK_LO_PLUS1_HEX,x
inx
sta $ESTACK_LO_PLUS1_HEX,x
""")
"-" -> asmgen.out("""
lda $ESTACK_LO_PLUS2_HEX,x
sec
sbc $ESTACK_LO_PLUS1_HEX,x
inx
sta $ESTACK_LO_PLUS1_HEX,x
""")
"<<", ">>" -> throw AssemblyError("bit-shifts not via stack")
"<" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.less_ub" else " jsr prog8_lib.less_b")
">" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greater_ub" else " jsr prog8_lib.greater_b")
"<=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.lesseq_ub" else " jsr prog8_lib.lesseq_b")
">=" -> asmgen.out(if(types==DataType.UBYTE) " jsr prog8_lib.greatereq_ub" else " jsr prog8_lib.greatereq_b")
"==" -> asmgen.out(" jsr prog8_lib.equal_b")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_b")
"&" -> asmgen.out(" jsr prog8_lib.bitand_b")
"^" -> asmgen.out(" jsr prog8_lib.bitxor_b")
"|" -> asmgen.out(" jsr prog8_lib.bitor_b")
"and" -> asmgen.out(" jsr prog8_lib.and_b")
"or" -> asmgen.out(" jsr prog8_lib.or_b")
"xor" -> asmgen.out(" jsr prog8_lib.xor_b")
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateBinaryOperatorWords(operator: String, types: DataType) {
when(operator) {
"**" -> throw AssemblyError("** operator requires floats")
"*" -> asmgen.out(" jsr prog8_lib.mul_word")
"/" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.idiv_uw" else " jsr prog8_lib.idiv_w")
"%" -> {
if(types==DataType.WORD)
throw AssemblyError("remainder of signed integers is not properly defined/implemented, use unsigned instead")
asmgen.out(" jsr prog8_lib.remainder_uw")
}
"+" -> asmgen.out(" jsr prog8_lib.add_w")
"-" -> asmgen.out(" jsr prog8_lib.sub_w")
"<<" -> throw AssemblyError("<< should not operate via stack")
">>" -> throw AssemblyError(">> should not operate via stack")
"<" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.less_uw" else " jsr prog8_lib.less_w")
">" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.greater_uw" else " jsr prog8_lib.greater_w")
"<=" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.lesseq_uw" else " jsr prog8_lib.lesseq_w")
">=" -> asmgen.out(if(types==DataType.UWORD) " jsr prog8_lib.greatereq_uw" else " jsr prog8_lib.greatereq_w")
"==" -> asmgen.out(" jsr prog8_lib.equal_w")
"!=" -> asmgen.out(" jsr prog8_lib.notequal_w")
"&" -> asmgen.out(" jsr prog8_lib.bitand_w")
"^" -> asmgen.out(" jsr prog8_lib.bitxor_w")
"|" -> asmgen.out(" jsr prog8_lib.bitor_w")
"and" -> asmgen.out(" jsr prog8_lib.and_w")
"or" -> asmgen.out(" jsr prog8_lib.or_w")
"xor" -> asmgen.out(" jsr prog8_lib.xor_w")
else -> throw AssemblyError("invalid operator $operator")
}
}
private fun translateBinaryOperatorFloats(operator: String) {
when(operator) {
"**" -> asmgen.out(" jsr c64flt.pow_f")
"*" -> asmgen.out(" jsr c64flt.mul_f")
"/" -> asmgen.out(" jsr c64flt.div_f")
"+" -> asmgen.out(" jsr c64flt.add_f")
"-" -> asmgen.out(" jsr c64flt.sub_f")
"<" -> asmgen.out(" jsr c64flt.less_f")
">" -> asmgen.out(" jsr c64flt.greater_f")
"<=" -> asmgen.out(" jsr c64flt.lesseq_f")
">=" -> asmgen.out(" jsr c64flt.greatereq_f")
"==" -> asmgen.out(" jsr c64flt.equal_f")
"!=" -> asmgen.out(" jsr c64flt.notequal_f")
"%", "<<", ">>", "&", "^", "|", "and", "or", "xor" -> throw AssemblyError("requires integer datatype")
else -> throw AssemblyError("invalid operator $operator")
}
}
}

View File

@ -0,0 +1,700 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.Register
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import kotlin.math.absoluteValue
// todo choose more efficient comparisons to avoid needless lda's
// todo optimize common case step == 2 / -2
internal class ForLoopsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(stmt: ForLoop) {
val iterableDt = stmt.iterable.inferType(program)
if(!iterableDt.isKnown)
throw AssemblyError("can't determine iterable dt")
when(stmt.iterable) {
is RangeExpr -> {
val range = (stmt.iterable as RangeExpr).toConstantIntegerRange()
if(range==null) {
translateForOverNonconstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as RangeExpr)
} else {
translateForOverConstRange(stmt, iterableDt.typeOrElse(DataType.STRUCT), range)
}
}
is IdentifierReference -> {
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
}
else -> throw AssemblyError("can't iterate over ${stmt.iterable}")
}
}
private fun translateForOverNonconstRange(stmt: ForLoop, iterableDt: DataType, range: RangeExpr) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopContinueLabels.push(continueLabel)
val stepsize=range.step.constValue(program)!!.number.toInt()
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
if (stepsize==1 || stepsize==-1) {
// bytes, step 1 or -1
val incdec = if(stepsize==1) "inc" else "dec"
if (stmt.loopRegister != null) {
// loop register over range
if(stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
asmgen.translateExpression(range.to)
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${ESTACK_LO_HEX},x
sta $loopLabel+1
$loopLabel lda #0 ; modified""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $loopLabel+1
cmp $ESTACK_LO_PLUS1_HEX,x
beq $endLabel
$incdec $loopLabel+1
jmp $loopLabel
$endLabel inx""")
} else {
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
asmgen.translateExpression(range.to)
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${ESTACK_LO_HEX},x
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
beq $endLabel
$incdec $varname
jmp $loopLabel
$endLabel inx""")
}
}
else {
// bytes, step >= 2 or <= -2
if (stmt.loopRegister != null) {
// loop register over range
if(stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
asmgen.translateExpression(range.to)
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${ESTACK_LO_HEX},x
sta $loopLabel+1
$loopLabel lda #0 ; modified""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $loopLabel+1""")
if(stepsize>0) {
asmgen.out("""
clc
adc #$stepsize
sta $loopLabel+1
cmp $ESTACK_LO_PLUS1_HEX,x
bcc $loopLabel
beq $loopLabel""")
} else {
asmgen.out("""
sec
sbc #${stepsize.absoluteValue}
sta $loopLabel+1
cmp $ESTACK_LO_PLUS1_HEX,x
bcs $loopLabel""")
}
asmgen.out("""
$endLabel inx""")
} else {
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
asmgen.translateExpression(range.to)
asmgen.translateExpression(range.from)
asmgen.out("""
inx
lda ${ESTACK_LO_HEX},x
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $varname""")
if(stepsize>0) {
asmgen.out("""
clc
adc #$stepsize
sta $varname
cmp $ESTACK_LO_PLUS1_HEX,x
bcc $loopLabel
beq $loopLabel""")
} else {
asmgen.out("""
sec
sbc #${stepsize.absoluteValue}
sta $varname
cmp $ESTACK_LO_PLUS1_HEX,x
bcs $loopLabel""")
}
asmgen.out("""
$endLabel inx""")
}
}
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
when {
// words, step 1 or -1
stepsize == 1 || stepsize == -1 -> {
asmgen.translateExpression(range.to)
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
null, range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
asmgen.out("""
lda $varname+1
cmp $ESTACK_HI_PLUS1_HEX,x
bne +
lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
beq $endLabel""")
if(stepsize==1) {
asmgen.out("""
+ inc $varname
bne +
inc $varname+1
""")
} else {
asmgen.out("""
+ lda $varname
bne +
dec $varname+1
+ dec $varname""")
}
asmgen.out("""
+ jmp $loopLabel
$endLabel inx""")
}
stepsize > 0 -> {
// (u)words, step >= 2
asmgen.translateExpression(range.to)
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
null, range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
if (iterableDt == DataType.ARRAY_UW) {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
sta $varname
lda $varname+1
adc #>$stepsize
sta $varname+1
lda $ESTACK_HI_PLUS1_HEX,x
cmp $varname+1
bcc $endLabel
bne $loopLabel
lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
bcc $endLabel
bcs $loopLabel
$endLabel inx""")
} else {
asmgen.out("""
lda $varname
clc
adc #<$stepsize
sta $varname
lda $varname+1
adc #>$stepsize
sta $varname+1
lda $ESTACK_LO_PLUS1_HEX,x
cmp $varname
lda $ESTACK_HI_PLUS1_HEX,x
sbc $varname+1
bvc +
eor #$80
+ bpl $loopLabel
$endLabel inx""")
}
}
else -> {
// (u)words, step <= -2
asmgen.translateExpression(range.to)
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
val assignLoopvar = Assignment(AssignTarget(null, stmt.loopVar, null, null, stmt.loopVar!!.position),
null, range.from, range.position)
assignLoopvar.linkParents(stmt)
asmgen.translate(assignLoopvar)
asmgen.out(loopLabel)
asmgen.translate(stmt.body)
if(iterableDt==DataType.ARRAY_UW) {
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
sta $varname
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
cmp $ESTACK_HI_PLUS1_HEX,x
bcc $endLabel
bne $loopLabel
lda $varname
cmp $ESTACK_LO_PLUS1_HEX,x
bcs $loopLabel
$endLabel inx""")
} else {
asmgen.out("""
lda $varname
sec
sbc #<${stepsize.absoluteValue}
sta $varname
pha
lda $varname+1
sbc #>${stepsize.absoluteValue}
sta $varname+1
pla
cmp $ESTACK_LO_PLUS1_HEX,x
lda $varname+1
sbc $ESTACK_HI_PLUS1_HEX,x
bvc +
eor #$80
+ bpl $loopLabel
$endLabel inx""")
}
}
}
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopContinueLabels.pop()
}
private fun translateForOverIterableVar(stmt: ForLoop, iterableDt: DataType, ident: IdentifierReference) {
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopContinueLabels.push(continueLabel)
val iterableName = asmgen.asmIdentifierName(ident)
val decl = ident.targetVarDecl(program.namespace)!!
when(iterableDt) {
DataType.STR -> {
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
sta $loopLabel+1
sty $loopLabel+2
$loopLabel lda ${65535.toHex()} ; modified
beq $endLabel""")
if(stmt.loopVar!=null)
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel inc $loopLabel+1
bne $loopLabel
inc $loopLabel+2
bne $loopLabel
$endLabel""")
}
DataType.ARRAY_UB, DataType.ARRAY_B -> {
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
val length = decl.arraysize!!.size()!!
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
val counterLabel = asmgen.makeLabel("for_counter")
val modifiedLabel = asmgen.makeLabel("for_modified")
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
sta $modifiedLabel+1
sty $modifiedLabel+2
ldy #0
$loopLabel sty $counterLabel
$modifiedLabel lda ${65535.toHex()},y ; modified""")
if(stmt.loopVar!=null)
asmgen.out(" sta ${asmgen.asmIdentifierName(stmt.loopVar!!)}")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel ldy $counterLabel
iny
cpy #${length and 255}
beq $endLabel
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
// TODO: optimize loop code when the length of the array is < 256, don't need a separate counter in such cases
val length = decl.arraysize!!.size()!! * 2
if(stmt.loopRegister!=null)
throw AssemblyError("can't use register to loop over words")
val counterLabel = asmgen.makeLabel("for_counter")
val modifiedLabel = asmgen.makeLabel("for_modified")
val modifiedLabel2 = asmgen.makeLabel("for_modified2")
val loopvarName = asmgen.asmIdentifierName(stmt.loopVar!!)
asmgen.out("""
lda #<$iterableName
ldy #>$iterableName
sta $modifiedLabel+1
sty $modifiedLabel+2
lda #<$iterableName+1
ldy #>$iterableName+1
sta $modifiedLabel2+1
sty $modifiedLabel2+2
ldy #0
$loopLabel sty $counterLabel
$modifiedLabel lda ${65535.toHex()},y ; modified
sta $loopvarName
$modifiedLabel2 lda ${65535.toHex()},y ; modified
sta $loopvarName+1""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel ldy $counterLabel
iny
iny
cpy #${length and 255}
beq $endLabel
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
DataType.ARRAY_F -> {
throw AssemblyError("for loop with floating point variables is not supported")
}
else -> throw AssemblyError("can't iterate over $iterableDt")
}
asmgen.loopEndLabels.pop()
asmgen.loopContinueLabels.pop()
}
private fun translateForOverConstRange(stmt: ForLoop, iterableDt: DataType, range: IntProgression) {
// TODO: optimize loop code when the range is < 256 iterations, don't need a separate counter in such cases
if (range.isEmpty())
throw AssemblyError("empty range")
val loopLabel = asmgen.makeLabel("for_loop")
val endLabel = asmgen.makeLabel("for_end")
val continueLabel = asmgen.makeLabel("for_continue")
asmgen.loopEndLabels.push(endLabel)
asmgen.loopContinueLabels.push(continueLabel)
when(iterableDt) {
DataType.ARRAY_B, DataType.ARRAY_UB -> {
val counterLabel = asmgen.makeLabel("for_counter")
if(stmt.loopRegister!=null) {
// loop register over range
if(stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
when {
range.step==1 -> {
// step = 1
asmgen.out("""
lda #${range.first}
sta $loopLabel+1
lda #${range.last-range.first+1 and 255}
sta $counterLabel
$loopLabel lda #0 ; modified""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
inc $loopLabel+1
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step==-1 -> {
// step = -1
asmgen.out("""
lda #${range.first}
sta $loopLabel+1
lda #${range.first-range.last+1 and 255}
sta $counterLabel
$loopLabel lda #0 ; modified """)
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $loopLabel+1
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step >= 2 -> {
// step >= 2
asmgen.out("""
lda #${(range.last-range.first) / range.step + 1}
sta $counterLabel
lda #${range.first}
$loopLabel pha""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel pla
dec $counterLabel
beq $endLabel
clc
adc #${range.step}
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
else -> {
// step <= -2
asmgen.out("""
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
sta $counterLabel
lda #${range.first}
$loopLabel pha""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel pla
dec $counterLabel
beq $endLabel
sec
sbc #${range.step.absoluteValue}
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
}
} else {
// loop over byte range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
when {
range.step==1 -> {
// step = 1
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.last-range.first+1 and 255}
sta $counterLabel
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
inc $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step==-1 -> {
// step = -1
asmgen.out("""
lda #${range.first}
sta $varname
lda #${range.first-range.last+1 and 255}
sta $counterLabel
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
dec $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
range.step >= 2 -> {
// step >= 2
asmgen.out("""
lda #${(range.last-range.first) / range.step + 1}
sta $counterLabel
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
lda $varname
clc
adc #${range.step}
sta $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
else -> {
// step <= -2
asmgen.out("""
lda #${(range.first-range.last) / range.step.absoluteValue + 1}
sta $counterLabel
lda #${range.first}
sta $varname
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel dec $counterLabel
beq $endLabel
lda $varname
sec
sbc #${range.step.absoluteValue}
sta $varname
jmp $loopLabel
$counterLabel .byte 0
$endLabel""")
}
}
}
}
DataType.ARRAY_W, DataType.ARRAY_UW -> {
// loop over word range via loopvar
val varname = asmgen.asmIdentifierName(stmt.loopVar!!)
when {
range.step == 1 -> {
// word, step = 1
val lastValue = range.last+1
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel inc $varname
bne +
inc $varname+1
+ lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
}
range.step == -1 -> {
// word, step = 1
val lastValue = range.last-1
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel lda $varname
bne +
dec $varname+1
+ dec $varname
lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
}
range.step >= 2 -> {
// word, step >= 2
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
val lastValue = range.last+range.step
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel clc
lda $varname
adc #<${range.step}
sta $varname
lda $varname+1
adc #>${range.step}
sta $varname+1
lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
}
else -> {
// step <= -2
// note: range.last has already been adjusted by kotlin itself to actually be the last value of the sequence
val lastValue = range.last+range.step
asmgen.out("""
lda #<${range.first}
ldy #>${range.first}
sta $varname
sty $varname+1
$loopLabel""")
asmgen.translate(stmt.body)
asmgen.out("""
$continueLabel sec
lda $varname
sbc #<${range.step.absoluteValue}
sta $varname
lda $varname+1
sbc #>${range.step.absoluteValue}
sta $varname+1
lda $varname
cmp #<$lastValue
bne +
lda $varname+1
cmp #>$lastValue
beq $endLabel
+ jmp $loopLabel
$endLabel""")
}
}
}
else -> throw AssemblyError("range expression can only be byte or word")
}
asmgen.loopEndLabels.pop()
asmgen.loopContinueLabels.pop()
}
}

View File

@ -0,0 +1,238 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
internal class FunctionCallAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctionCall(stmt: IFunctionCall) {
// output the code to setup the parameters and perform the actual call
// does NOT output the code to deal with the result values!
val sub = stmt.target.targetSubroutine(program.namespace) ?: throw AssemblyError("undefined subroutine ${stmt.target}")
if(Register.X in sub.asmClobbers)
asmgen.out(" stx c64.SCRATCH_ZPREGX") // we only save X for now (required! is the eval stack pointer), screw A and Y...
val subName = asmgen.asmIdentifierName(stmt.target)
if(stmt.args.isNotEmpty()) {
for(arg in sub.parameters.withIndex().zip(stmt.args)) {
translateFuncArguments(arg.first, arg.second, sub)
}
}
asmgen.out(" jsr $subName")
if(Register.X in sub.asmClobbers)
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
}
private fun translateFuncArguments(parameter: IndexedValue<SubroutineParameter>, value: Expression, sub: Subroutine) {
val sourceIDt = value.inferType(program)
if(!sourceIDt.isKnown)
throw AssemblyError("arg type unknown")
val sourceDt = sourceIDt.typeOrElse(DataType.STRUCT)
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) {
// pass parameter via a variable
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
target.linkParents(value.parent)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
when(parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteConstant(target, value.number.toShort())
in WordDatatypes -> asmgen.assignFromWordConstant(target, value.number.toInt())
DataType.FLOAT -> asmgen.assignFromFloatConstant(target, value.number.toDouble())
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
else -> throw AssemblyError("weird parameter datatype")
}
}
is IdentifierReference -> {
// optimize when the argument is a variable
when (parameter.value.type) {
in ByteDatatypes -> asmgen.assignFromByteVariable(target, value)
in WordDatatypes -> asmgen.assignFromWordVariable(target, value)
DataType.FLOAT -> asmgen.assignFromFloatVariable(target, value)
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as arguments?")
else -> throw AssemblyError("weird parameter datatype")
}
}
is RegisterExpr -> {
asmgen.assignFromRegister(target, value.register)
}
is DirectMemoryRead -> {
when(value.addressExpression) {
is NumericLiteralValue -> {
val address = (value.addressExpression as NumericLiteralValue).number.toInt()
asmgen.assignFromMemoryByte(target, address, null)
}
is IdentifierReference -> {
asmgen.assignFromMemoryByte(target, null, value.addressExpression as IdentifierReference)
}
else -> {
asmgen.translateExpression(value.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address | inx")
asmgen.assignFromRegister(target, Register.A)
}
}
}
else -> {
asmgen.translateExpression(value)
asmgen.assignFromEvalResult(target)
}
}
} else {
// pass parameter via a register parameter
val paramRegister = sub.asmParameterRegisters[parameter.index]
val statusflag = paramRegister.statusflag
val register = paramRegister.registerOrPair
val stack = paramRegister.stack
when {
stack -> {
// push arg onto the stack
// note: argument order is reversed (first argument will be deepest on the stack)
asmgen.translateExpression(value)
}
statusflag!=null -> {
if (statusflag == Statusflag.Pc) {
// this param needs to be set last, right before the jsr
// for now, this is already enforced on the subroutine definition by the Ast Checker
when(value) {
is NumericLiteralValue -> {
val carrySet = value.number.toInt() != 0
asmgen.out(if(carrySet) " sec" else " clc")
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
asmgen.out("""
lda $sourceName
beq +
sec
bcs ++
+ clc
+
""")
}
is RegisterExpr -> {
when(value.register) {
Register.A -> asmgen.out(" cmp #0")
Register.X -> asmgen.out(" txa")
Register.Y -> asmgen.out(" tya")
}
asmgen.out("""
beq +
sec
bcs ++
+ clc
+
""")
}
else -> {
asmgen.translateExpression(value)
asmgen.out("""
inx
lda $ESTACK_LO_HEX,x
beq +
sec
bcs ++
+ clc
+
""")
}
}
}
else throw AssemblyError("can only use Carry as status flag parameter")
}
register!=null && register.name.length==1 -> {
when (value) {
is NumericLiteralValue -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteConstant(target, value.number.toShort())
}
is IdentifierReference -> {
val target = AssignTarget(Register.valueOf(register.name), null, null, null, sub.position)
target.linkParents(value.parent)
asmgen.assignFromByteVariable(target, value)
}
else -> {
asmgen.translateExpression(value)
when(register) {
RegisterOrPair.A -> asmgen.out(" inx | lda $ESTACK_LO_HEX,x")
RegisterOrPair.X -> throw AssemblyError("can't pop into X register - use a variable instead")
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
else -> throw AssemblyError("cannot assign to register pair")
}
}
}
}
register!=null && register.name.length==2 -> {
// register pair as a 16-bit value (only possible for subroutine parameters)
when (value) {
is NumericLiteralValue -> {
// optimize when the argument is a constant literal
val hex = value.number.toHex()
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$hex | ldx #>$hex")
RegisterOrPair.AY -> asmgen.out(" lda #<$hex | ldy #>$hex")
RegisterOrPair.XY -> asmgen.out(" ldx #<$hex | ldy #>$hex")
else -> {}
}
}
is AddressOf -> {
// optimize when the argument is an address of something
val sourceName = asmgen.asmIdentifierName(value.identifier)
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda #<$sourceName | ldx #>$sourceName")
RegisterOrPair.AY -> asmgen.out(" lda #<$sourceName | ldy #>$sourceName")
RegisterOrPair.XY -> asmgen.out(" ldx #<$sourceName | ldy #>$sourceName")
else -> {}
}
}
is IdentifierReference -> {
val sourceName = asmgen.asmIdentifierName(value)
when (register) {
RegisterOrPair.AX -> asmgen.out(" lda $sourceName | ldx $sourceName+1")
RegisterOrPair.AY -> asmgen.out(" lda $sourceName | ldy $sourceName+1")
RegisterOrPair.XY -> asmgen.out(" ldx $sourceName | ldy $sourceName+1")
else -> {}
}
}
else -> {
asmgen.translateExpression(value)
if (register == RegisterOrPair.AX || register == RegisterOrPair.XY)
throw AssemblyError("can't use X register here - use a variable")
else if (register == RegisterOrPair.AY)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
}
}
}
}
}
}
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
if(argType==DataType.STR && paramType==DataType.UWORD)
return true
if(argType==DataType.UWORD && paramType == DataType.STR)
return true
return false
}
}

View File

@ -0,0 +1,150 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RegisterExpr
import prog8.ast.statements.PostIncrDecr
import prog8.compiler.toHex
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
internal class PostIncrDecrAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translate(stmt: PostIncrDecr) {
val incr = stmt.operator=="++"
val targetIdent = stmt.target.identifier
val targetMemory = stmt.target.memoryAddress
val targetArrayIdx = stmt.target.arrayindexed
val targetRegister = stmt.target.register
when {
targetRegister!=null -> {
when(targetRegister) {
Register.A -> {
if(incr)
asmgen.out(" clc | adc #1 ")
else
asmgen.out(" sec | sbc #1 ")
}
Register.X -> {
if(incr) asmgen.out(" inx") else asmgen.out(" dex")
}
Register.Y -> {
if(incr) asmgen.out(" iny") else asmgen.out(" dey")
}
}
}
targetIdent!=null -> {
val what = asmgen.asmIdentifierName(targetIdent)
val dt = stmt.target.inferType(program, stmt).typeOrElse(DataType.STRUCT)
when (dt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what" else " dec $what")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $what | bne + | inc $what+1 |+")
else
asmgen.out("""
lda $what
bne +
dec $what+1
+ dec $what
""")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$what | ldy #>$what")
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
targetMemory!=null -> {
when (val addressExpr = targetMemory.addressExpression) {
is NumericLiteralValue -> {
val what = addressExpr.number.toHex()
asmgen.out(if(incr) " inc $what" else " dec $what")
}
is IdentifierReference -> {
val what = asmgen.asmIdentifierName(addressExpr)
asmgen.out(if(incr) " inc $what" else " dec $what")
}
else -> throw AssemblyError("weird target type $targetMemory")
}
}
targetArrayIdx!=null -> {
val index = targetArrayIdx.arrayspec.index
val what = asmgen.asmIdentifierName(targetArrayIdx.identifier)
val arrayDt = targetArrayIdx.identifier.inferType(program).typeOrElse(DataType.STRUCT)
val elementDt = ArrayElementTypes.getValue(arrayDt)
when(index) {
is NumericLiteralValue -> {
val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> asmgen.out(if (incr) " inc $what+$indexValue" else " dec $what+$indexValue")
in WordDatatypes -> {
if(incr)
asmgen.out(" inc $what+$indexValue | bne + | inc $what+$indexValue+1 |+")
else
asmgen.out("""
lda $what+$indexValue
bne +
dec $what+$indexValue+1
+ dec $what+$indexValue
""")
}
DataType.FLOAT -> {
asmgen.out(" lda #<$what+$indexValue | ldy #>$what+$indexValue")
asmgen.out(if(incr) " jsr c64flt.inc_var_f" else " jsr c64flt.dec_var_f")
}
else -> throw AssemblyError("need numeric type")
}
}
is RegisterExpr -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
is IdentifierReference -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
else -> {
// TODO optimize common cases
asmgen.translateArrayIndexIntoA(targetArrayIdx)
incrDecrArrayvalueWithIndexA(incr, arrayDt, what)
}
}
}
else -> throw AssemblyError("weird target type ${stmt.target}")
}
}
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
asmgen.out(" stx ${C64Zeropage.SCRATCH_REG_X} | tax")
when(arrayDt) {
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
if(incr)
asmgen.out(" inc $arrayVarName,x | bne + | inc $arrayVarName+1,x |+")
else
asmgen.out("""
lda $arrayVarName,x
bne +
dec $arrayVarName+1,x
+ dec $arrayVarName
""")
}
DataType.ARRAY_F -> {
asmgen.out(" lda #<$arrayVarName | ldy #>$arrayVarName")
asmgen.out(if(incr) " jsr c64flt.inc_indexed_var_f" else " jsr c64flt.dec_indexed_var_f")
}
else -> throw AssemblyError("weird array dt")
}
asmgen.out(" ldx ${C64Zeropage.SCRATCH_REG_X}")
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,252 +0,0 @@
package prog8.compiler.target.c64.codegen2
import prog8.ast.IFunctionCall
import prog8.ast.Program
import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.Register
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.FunctionCallStatement
import prog8.compiler.CompilationOptions
import prog8.compiler.Zeropage
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.toHex
import prog8.functions.FunctionSignature
internal class BuiltinFunctionsAsmGen(private val program: Program,
private val options: CompilationOptions,
private val zeropage: Zeropage,
private val asmgen: AsmGen2) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FunctionSignature) {
translateFunctioncall(fcall, func, false)
}
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FunctionSignature) {
translateFunctioncall(fcall, func, true)
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FunctionSignature, discardResult: Boolean) {
val functionName = fcall.target.nameInSource.last()
if(discardResult) {
if(func.pure)
return // can just ignore the whole function call altogether
else if(func.returntype!=null)
throw AssemblyError("discarding result of non-pure function $fcall")
}
when(functionName) {
"msb" -> {
val arg = fcall.arglist.single()
if(arg.inferType(program) !in WordDatatypes)
throw AssemblyError("msb required word argument")
if(arg is NumericLiteralValue)
throw AssemblyError("should have been const-folded")
if(arg is IdentifierReference) {
val sourceName = asmgen.asmIdentifierName(arg)
asmgen.out(" lda $sourceName+1 | sta $ESTACK_LO_HEX,x | dex")
} else {
asmgen.translateExpression(arg)
asmgen.out(" lda $ESTACK_HI_PLUS1_HEX,x | sta $ESTACK_LO_PLUS1_HEX,x")
}
}
"mkword" -> {
translateFunctionArguments(fcall.arglist, func)
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | sta $ESTACK_HI_PLUS1_HEX,x")
}
"abs" -> {
translateFunctionArguments(fcall.arglist, func)
val dt = fcall.arglist.single().inferType(program)!!
when (dt) {
in ByteDatatypes -> asmgen.out(" jsr prog8_lib.abs_b")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.abs_w")
DataType.FLOAT -> asmgen.out(" jsr c64flt.abs_f")
else -> throw AssemblyError("weird type")
}
}
"swap" -> {
val first = fcall.arglist[0]
val second = fcall.arglist[1]
asmgen.translateExpression(first)
asmgen.translateExpression(second)
// pop in reverse order
val firstTarget = AssignTarget.fromExpr(first)
val secondTarget = AssignTarget.fromExpr(second)
asmgen.assignFromEvalResult(firstTarget)
asmgen.assignFromEvalResult(secondTarget)
}
// TODO: any(f), all(f), max(f), min(f), sum(f)
"sin", "cos", "tan", "atan",
"ln", "log2", "sqrt", "rad",
"deg", "round", "floor", "ceil",
"rdnf" -> {
translateFunctionArguments(fcall.arglist, func)
asmgen.out(" jsr c64flt.func_$functionName")
}
/*
TODO this was the old code for bit rotations:
Opcode.SHL_BYTE -> AsmFragment(" asl $variable+$index", 8)
Opcode.SHR_UBYTE -> AsmFragment(" lsr $variable+$index", 8)
Opcode.SHR_SBYTE -> AsmFragment(" lda $variable+$index | asl a | ror $variable+$index")
Opcode.SHL_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.SHR_UWORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.SHR_SWORD -> AsmFragment(" lda $variable+${index * 2 + 1} | asl a | ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL_BYTE -> AsmFragment(" rol $variable+$index", 8)
Opcode.ROR_BYTE -> AsmFragment(" ror $variable+$index", 8)
Opcode.ROL_WORD -> AsmFragment(" rol $variable+${index * 2 + 1} | rol $variable+${index * 2}", 8)
Opcode.ROR_WORD -> AsmFragment(" ror $variable+${index * 2 + 1} | ror $variable+${index * 2}", 8)
Opcode.ROL2_BYTE -> AsmFragment(" lda $variable+$index | cmp #\$80 | rol $variable+$index", 8)
Opcode.ROR2_BYTE -> AsmFragment(" lda $variable+$index | lsr a | bcc + | ora #\$80 |+ | sta $variable+$index", 10)
Opcode.ROL2_WORD -> AsmFragment(" asl $variable+${index * 2 + 1} | rol $variable+${index * 2} | bcc + | inc $variable+${index * 2 + 1} |+", 20)
Opcode.ROR2_WORD -> AsmFragment(" lsr $variable+${index * 2 + 1} | ror $variable+${index * 2} | bcc + | lda $variable+${index * 2 + 1} | ora #\$80 | sta $variable+${index * 2 + 1} |+", 30)
*/
"lsl" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
in ByteDatatypes -> {
when(what) {
is RegisterExpr -> {
when(what.register) {
Register.A -> asmgen.out(" asl a")
Register.X -> asmgen.out(" txa | asl a | tax")
Register.Y -> asmgen.out(" tya | asl a | tay")
}
}
is IdentifierReference -> asmgen.out(" asl ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if(what.addressExpression is NumericLiteralValue) {
asmgen.out(" asl ${(what.addressExpression as NumericLiteralValue).number.toHex()}")
} else {
TODO("lsl memory byte $what")
}
}
is ArrayIndexedExpression -> {
TODO("lsl byte array $what")
}
else -> throw AssemblyError("weird type")
}
}
in WordDatatypes -> {
TODO("lsl word $what")
}
else -> throw AssemblyError("weird type")
}
}
"lsr" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
DataType.UBYTE -> {
when(what) {
is RegisterExpr -> {
when(what.register) {
Register.A -> asmgen.out(" lsr a")
Register.X -> asmgen.out(" txa | lsr a | tax")
Register.Y -> asmgen.out(" tya | lsr a | tay")
}
}
is IdentifierReference -> asmgen.out(" lsr ${asmgen.asmIdentifierName(what)}")
is DirectMemoryRead -> {
if(what.addressExpression is NumericLiteralValue) {
asmgen.out(" lsr ${(what.addressExpression as NumericLiteralValue).number.toHex()}")
} else {
TODO("lsr memory byte $what")
}
}
is ArrayIndexedExpression -> {
TODO("lsr byte array $what")
}
else -> throw AssemblyError("weird type")
}
}
DataType.BYTE -> {
TODO("lsr sbyte $what")
}
DataType.UWORD -> {
TODO("lsr sword $what")
}
DataType.WORD -> {
TODO("lsr word $what")
}
else -> throw AssemblyError("weird type")
}
}
"rol" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
DataType.UBYTE -> {
TODO("rol ubyte")
}
DataType.UWORD -> {
TODO("rol uword")
}
else -> throw AssemblyError("weird type")
}
}
"rol2" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
DataType.UBYTE -> {
TODO("rol2 ubyte")
}
DataType.UWORD -> {
TODO("rol2 uword")
}
else -> throw AssemblyError("weird type")
}
}
"ror" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
DataType.UBYTE -> {
TODO("ror ubyte")
}
DataType.UWORD -> {
TODO("ror uword")
}
else -> throw AssemblyError("weird type")
}
}
"ror2" -> {
// in-place
val what = fcall.arglist.single()
val dt = what.inferType(program)!!
when(dt) {
DataType.UBYTE -> {
TODO("ror2 ubyte")
}
DataType.UWORD -> {
TODO("ror2 uword")
}
else -> throw AssemblyError("weird type")
}
}
else -> {
translateFunctionArguments(fcall.arglist, func)
asmgen.out(" jsr prog8_lib.func_$functionName")
}
}
}
private fun translateFunctionArguments(args: MutableList<Expression>, signature: FunctionSignature) {
args.forEach {
asmgen.translateExpression(it)
}
}
}

View File

@ -27,13 +27,16 @@ val BuiltinFunctions = mapOf(
"ror2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null), "ror2" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null), "lsl" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null), "lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"sort" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
"reverse" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
// these few have a return value depending on the argument(s): // these few have a return value depending on the argument(s):
"max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, _ -> collectionArgNeverConst(a, p) }, // type depends on args "max" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, _ -> collectionArgNeverConst(a, p) }, // type depends on args "min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, _ -> collectionArgNeverConst(a, p) }, // type depends on args "sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument "abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length "len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
// normal functions follow: // normal functions follow:
"sgn" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) }, "sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ), "sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ), "sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
@ -52,12 +55,11 @@ val BuiltinFunctions = mapOf(
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) }, "sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) }, "rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) }, "deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"avg" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.FLOAT) { a, p, _ -> collectionArgNeverConst(a, p) },
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) }, "round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) }, "floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) }, "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, _ -> collectionArgNeverConst(a, p) }, "any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, _ -> collectionArgNeverConst(a, p) }, "all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }}, "lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, 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}}, "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( "mkword" to FunctionSignature(true, listOf(
@ -75,63 +77,52 @@ val BuiltinFunctions = mapOf(
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE), "read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null), "swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
"memcopy" to FunctionSignature(false, listOf( "memcopy" to FunctionSignature(false, listOf(
BuiltinFunctionParam("from", IterableDatatypes + setOf(DataType.UWORD)), BuiltinFunctionParam("from", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("to", IterableDatatypes + setOf(DataType.UWORD)), BuiltinFunctionParam("to", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null), BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FunctionSignature(false, listOf( "memset" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)), BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)), BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null), BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FunctionSignature(false, listOf( "memsetw" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)), BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)), BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null), BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", StringDatatypes)), DataType.UBYTE, ::builtinStrlen), "strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen)
"vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null),
"vm_write_char" to FunctionSignature(false, listOf(BuiltinFunctionParam("char", setOf(DataType.UBYTE))), null),
"vm_write_str" to FunctionSignature(false, listOf(BuiltinFunctionParam("string", StringDatatypes)), null),
"vm_input_str" to FunctionSignature(false, listOf(BuiltinFunctionParam("intovar", StringDatatypes)), null),
"vm_gfx_clearscr" to FunctionSignature(false, listOf(BuiltinFunctionParam("color", setOf(DataType.UBYTE))), null),
"vm_gfx_pixel" to FunctionSignature(false, listOf(
BuiltinFunctionParam("x", IntegerDatatypes),
BuiltinFunctionParam("y", IntegerDatatypes),
BuiltinFunctionParam("color", IntegerDatatypes)), null),
"vm_gfx_line" to FunctionSignature(false, listOf(
BuiltinFunctionParam("x1", IntegerDatatypes),
BuiltinFunctionParam("y1", IntegerDatatypes),
BuiltinFunctionParam("x2", IntegerDatatypes),
BuiltinFunctionParam("y2", IntegerDatatypes),
BuiltinFunctionParam("color", IntegerDatatypes)), null),
"vm_gfx_text" to FunctionSignature(false, listOf(
BuiltinFunctionParam("x", IntegerDatatypes),
BuiltinFunctionParam("y", IntegerDatatypes),
BuiltinFunctionParam("color", IntegerDatatypes),
BuiltinFunctionParam("text", StringDatatypes)),
null)
) )
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): DataType? { fun builtinMin(array: List<Number>): Number = array.minBy { it.toDouble() }!!
fun builtinSum(array: List<Number>): Number = array.sumByDouble { it.toDouble() }
fun builtinAny(array: List<Number>): Number = if(array.any { it.toDouble()!=0.0 }) 1 else 0
fun builtinAll(array: List<Number>): Number = if(array.all { it.toDouble()!=0.0 }) 1 else 0
fun builtinFunctionReturnType(function: String, args: List<Expression>, program: Program): InferredTypes.InferredType {
fun datatypeFromIterableArg(arglist: Expression): DataType { fun datatypeFromIterableArg(arglist: Expression): DataType {
if(arglist is ReferenceLiteralValue) { if(arglist is ArrayLiteralValue) {
if(arglist.type== DataType.ARRAY_UB || arglist.type== DataType.ARRAY_UW || arglist.type== DataType.ARRAY_F) { val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
val dt = arglist.array!!.map {it.inferType(program)} if(dt.any { it !in NumericDatatypes }) {
if(dt.any { it!= DataType.UBYTE && it!= DataType.UWORD && it!= DataType.FLOAT}) { throw FatalAstException("fuction $function only accepts array of numeric values")
throw FatalAstException("fuction $function only accepts arraysize of numeric values")
}
if(dt.any { it== DataType.FLOAT }) return DataType.FLOAT
if(dt.any { it== DataType.UWORD }) return DataType.UWORD
return DataType.UBYTE
} }
if(DataType.FLOAT in dt) return DataType.FLOAT
if(DataType.UWORD in dt) return DataType.UWORD
if(DataType.WORD in dt) return DataType.WORD
if(DataType.BYTE in dt) return DataType.BYTE
return DataType.UBYTE
} }
if(arglist is IdentifierReference) { if(arglist is IdentifierReference) {
return when(val dt = arglist.inferType(program)) { val idt = arglist.inferType(program)
in NumericDatatypes -> dt!! if(!idt.isKnown)
in StringDatatypes -> dt!! throw FatalAstException("couldn't determine type of iterable $arglist")
in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!) return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
DataType.STR, in NumericDatatypes -> dt
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
else -> throw FatalAstException("function '$function' requires one argument which is an iterable") else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
} }
} }
@ -140,43 +131,43 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
val func = BuiltinFunctions.getValue(function) val func = BuiltinFunctions.getValue(function)
if(func.returntype!=null) if(func.returntype!=null)
return func.returntype return InferredTypes.knownFor(func.returntype)
// function has return values, but the return type depends on the arguments // function has return values, but the return type depends on the arguments
return when (function) { return when (function) {
"abs" -> { "abs" -> {
val dt = args.single().inferType(program) val dt = args.single().inferType(program)
if(dt in NumericDatatypes) if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
return dt return dt
else else
throw FatalAstException("weird datatype passed to abs $dt") throw FatalAstException("weird datatype passed to abs $dt")
} }
"max", "min" -> { "max", "min" -> {
when(val dt = datatypeFromIterableArg(args.single())) { when(val dt = datatypeFromIterableArg(args.single())) {
in NumericDatatypes -> dt DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in StringDatatypes -> DataType.UBYTE in NumericDatatypes -> InferredTypes.knownFor(dt)
in ArrayDatatypes -> ArrayElementTypes.getValue(dt) in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
else -> null else -> InferredTypes.unknown()
} }
} }
"sum" -> { "sum" -> {
when(datatypeFromIterableArg(args.single())) { when(datatypeFromIterableArg(args.single())) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD DataType.UBYTE, DataType.UWORD -> InferredTypes.knownFor(DataType.UWORD)
DataType.BYTE, DataType.WORD -> DataType.WORD DataType.BYTE, DataType.WORD -> InferredTypes.knownFor(DataType.WORD)
DataType.FLOAT -> DataType.FLOAT DataType.FLOAT -> InferredTypes.knownFor(DataType.FLOAT)
DataType.ARRAY_UB, DataType.ARRAY_UW -> DataType.UWORD DataType.ARRAY_UB, DataType.ARRAY_UW -> InferredTypes.knownFor(DataType.UWORD)
DataType.ARRAY_B, DataType.ARRAY_W -> DataType.WORD DataType.ARRAY_B, DataType.ARRAY_W -> InferredTypes.knownFor(DataType.WORD)
DataType.ARRAY_F -> DataType.FLOAT DataType.ARRAY_F -> InferredTypes.knownFor(DataType.FLOAT)
in StringDatatypes -> DataType.UWORD DataType.STR -> InferredTypes.knownFor(DataType.UWORD)
else -> null else -> InferredTypes.unknown()
} }
} }
"len" -> { "len" -> {
// a length can be >255 so in that case, the result is an UWORD instead of an UBYTE // a length can be >255 so in that case, the result is an UWORD instead of an UBYTE
// but to avoid a lot of code duplication we simply assume UWORD in all cases for now // but to avoid a lot of code duplication we simply assume UWORD in all cases for now
return DataType.UWORD return InferredTypes.knownFor(DataType.UWORD)
} }
else -> return null else -> return InferredTypes.unknown()
} }
} }
@ -211,12 +202,16 @@ private fun oneIntArgOutputInt(args: List<Expression>, position: Position, progr
return numericLiteral(function(integer).toInt(), args[0].position) return numericLiteral(function(integer).toInt(), args[0].position)
} }
private fun collectionArgNeverConst(args: List<Expression>, position: Position): NumericLiteralValue { private fun collectionArg(args: List<Expression>, position: Position, program: Program, function: (arg: List<Number>)->Number): NumericLiteralValue {
if(args.size!=1) if(args.size!=1)
throw SyntaxError("builtin function requires one non-scalar argument", position) throw SyntaxError("builtin function requires one non-scalar argument", position)
// max/min/sum etc only work on arrays and these are never considered to be const for these functions val array= args[0] as? ArrayLiteralValue ?: throw NotConstArgumentException()
throw NotConstArgumentException() val constElements = array.value.map{it.constValue(program)?.number}
if(constElements.contains(null))
throw NotConstArgumentException()
return NumericLiteralValue.optimalNumeric(function(constElements.mapNotNull { it }), args[0].position)
} }
private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue { private fun builtinAbs(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
@ -236,7 +231,7 @@ private fun builtinStrlen(args: List<Expression>, position: Position, program: P
if (args.size != 1) if (args.size != 1)
throw SyntaxError("strlen requires one argument", position) throw SyntaxError("strlen requires one argument", position)
val argument = args[0].constValue(program) ?: throw NotConstArgumentException() val argument = args[0].constValue(program) ?: throw NotConstArgumentException()
if(argument.type !in StringDatatypes) if(argument.type != DataType.STR)
throw SyntaxError("strlen must have string argument", position) throw SyntaxError("strlen must have string argument", position)
throw NotConstArgumentException() // this function is not considering the string argument a constant throw NotConstArgumentException() // this function is not considering the string argument a constant
@ -254,6 +249,8 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
var arraySize = directMemVar?.arraysize?.size() var arraySize = directMemVar?.arraysize?.size()
if(arraySize != null) if(arraySize != null)
return NumericLiteralValue.optimalInteger(arraySize, position) return NumericLiteralValue.optimalInteger(arraySize, position)
if(args[0] is ArrayLiteralValue)
return NumericLiteralValue.optimalInteger((args[0] as ArrayLiteralValue).value.size, position)
if(args[0] !is IdentifierReference) if(args[0] !is IdentifierReference)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position) throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!! val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
@ -271,11 +268,11 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
throw CompilerException("array length exceeds byte limit ${target.position}") throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position) NumericLiteralValue.optimalInteger(arraySize, args[0].position)
} }
in StringDatatypes -> { DataType.STR -> {
val refLv = target.value as ReferenceLiteralValue val refLv = target.value as StringLiteralValue
if(refLv.str!!.length>255) if(refLv.value.length>255)
throw CompilerException("string length exceeds byte limit ${refLv.position}") throw CompilerException("string length exceeds byte limit ${refLv.position}")
NumericLiteralValue.optimalInteger(refLv.str.length, args[0].position) NumericLiteralValue.optimalInteger(refLv.value.length, args[0].position)
} }
in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position) in NumericDatatypes -> throw SyntaxError("len of weird argument ${args[0]}", position)
else -> throw CompilerException("weird datatype") else -> throw CompilerException("weird datatype")
@ -356,6 +353,13 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position) return NumericLiteralValue(DataType.UWORD, (32768.0 + 32767.5 * cos(rad)).toInt(), position)
} }
private fun builtinSgn(args: List<Expression>, position: Position, program: Program): NumericLiteralValue {
if (args.size != 1)
throw SyntaxError("sgn requires one argument", position)
val constval = args[0].constValue(program) ?: throw NotConstArgumentException()
return NumericLiteralValue(DataType.BYTE, constval.number.toDouble().sign.toShort(), position)
}
private fun numericLiteral(value: Number, position: Position): NumericLiteralValue { private fun numericLiteral(value: Number, position: Position): NumericLiteralValue {
val floatNum=value.toDouble() val floatNum=value.toDouble()
val tweakedValue: Number = val tweakedValue: Number =

View File

@ -54,15 +54,15 @@ class ConstExprEvaluator {
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-bitxor $right" val error = "cannot compute $left locical-bitxor $right"
return when { return when (left.type) {
left.type in IntegerDatatypes -> when { in IntegerDatatypes -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position) in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 0), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position) DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toDouble() != 0.0), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.type == DataType.FLOAT -> when { DataType.FLOAT -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position) in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 0), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position) DataType.FLOAT -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toDouble() != 0.0), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
@ -71,15 +71,15 @@ class ConstExprEvaluator {
private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-or $right" val error = "cannot compute $left locical-or $right"
return when { return when (left.type) {
left.type in IntegerDatatypes -> when { in IntegerDatatypes -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position) in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position) DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.type == DataType.FLOAT -> when { DataType.FLOAT -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position) in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position) DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
@ -88,15 +88,15 @@ class ConstExprEvaluator {
private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-and $right" val error = "cannot compute $left locical-and $right"
return when { return when (left.type) {
left.type in IntegerDatatypes -> when { in IntegerDatatypes -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position) in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position) DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.type == DataType.FLOAT -> when { DataType.FLOAT -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position) in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 0, left.position)
right.type == DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position) DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
@ -144,15 +144,15 @@ class ConstExprEvaluator {
private fun power(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun power(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot calculate $left ** $right" val error = "cannot calculate $left ** $right"
return when { return when (left.type) {
left.type in IntegerDatatypes -> when { in IntegerDatatypes -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position) in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.type == DataType.FLOAT -> when { DataType.FLOAT -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position) in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toDouble()), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
@ -161,15 +161,15 @@ class ConstExprEvaluator {
private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot add $left and $right" val error = "cannot add $left and $right"
return when { return when (left.type) {
left.type in IntegerDatatypes -> when { in IntegerDatatypes -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.type == DataType.FLOAT -> when { DataType.FLOAT -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
@ -178,15 +178,15 @@ class ConstExprEvaluator {
private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot subtract $left and $right" val error = "cannot subtract $left and $right"
return when { return when (left.type) {
left.type in IntegerDatatypes -> when { in IntegerDatatypes -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.type == DataType.FLOAT -> when { DataType.FLOAT -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
@ -195,15 +195,15 @@ class ConstExprEvaluator {
private fun multiply(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun multiply(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot multiply ${left.type} and ${right.type}" val error = "cannot multiply ${left.type} and ${right.type}"
return when { return when (left.type) {
left.type in IntegerDatatypes -> when { in IntegerDatatypes -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.type == DataType.FLOAT -> when { DataType.FLOAT -> when (right.type) {
right.type in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position) in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), left.position)
right.type == DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position) DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
@ -215,25 +215,25 @@ class ConstExprEvaluator {
private fun divide(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun divide(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot divide $left by $right" val error = "cannot divide $left by $right"
return when { return when (left.type) {
left.type in IntegerDatatypes -> when { in IntegerDatatypes -> when (right.type) {
right.type in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt() val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteralValue.optimalNumeric(result, left.position) NumericLiteralValue.optimalNumeric(result, left.position)
} }
right.type == DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position) if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position) NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.type == DataType.FLOAT -> when { DataType.FLOAT -> when (right.type) {
right.type in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position) NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toInt(), left.position)
} }
right.type == DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position) if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position) NumericLiteralValue(DataType.FLOAT, left.number.toDouble() / right.number.toDouble(), left.position)
} }
@ -245,24 +245,24 @@ class ConstExprEvaluator {
private fun remainder(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue { private fun remainder(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute remainder of $left by $right" val error = "cannot compute remainder of $left by $right"
return when { return when (left.type) {
left.type in IntegerDatatypes -> when { in IntegerDatatypes -> when (right.type) {
right.type in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position) NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble() % right.number.toInt().toDouble(), left.position)
} }
right.type == DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position) if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position) NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
} }
else -> throw ExpressionError(error, left.position) else -> throw ExpressionError(error, left.position)
} }
left.type == DataType.FLOAT -> when { DataType.FLOAT -> when (right.type) {
right.type in IntegerDatatypes -> { in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position) if(right.number.toInt()==0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position) NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toInt(), left.position)
} }
right.type == DataType.FLOAT -> { DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position) if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position) NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
} }

View File

@ -5,10 +5,10 @@ import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.fixupArrayDatatype import prog8.ast.processing.fixupArrayEltDatatypesFromVardecl
import prog8.ast.processing.fixupArrayEltDatatypes
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import kotlin.math.floor import kotlin.math.floor
@ -38,9 +38,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
if(decl.isArray){ if(decl.isArray){
if(decl.arraysize==null) { if(decl.arraysize==null) {
// for arrays that have no size specifier (or a non-constant one) attempt to deduce the size // for arrays that have no size specifier (or a non-constant one) attempt to deduce the size
val arrayval = (decl.value as? ReferenceLiteralValue)?.array val arrayval = decl.value as? ArrayLiteralValue
if(arrayval!=null) { if(arrayval!=null) {
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.size, decl.position), decl.position) decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position)
optimizationsDone++ optimizationsDone++
} }
} }
@ -64,12 +64,6 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
return super.visit(decl) return super.visit(decl)
} }
} }
in StringDatatypes -> {
// nothing to do for strings
}
DataType.STRUCT -> {
// struct defintions don't have anything else in them
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> { DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numericLv = decl.value as? NumericLiteralValue val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr val rangeExpr = decl.value as? RangeExpr
@ -80,15 +74,15 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!)) errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
val constRange = rangeExpr.toConstantIntegerRange() val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) { if(constRange!=null) {
val eltType = rangeExpr.inferType(program)!! val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
if(eltType in ByteDatatypes) { if(eltType in ByteDatatypes) {
decl.value = ReferenceLiteralValue(decl.datatype, decl.value = ArrayLiteralValue(decl.datatype,
array = constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) } constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
.toTypedArray(), position = decl.value!!.position) position = decl.value!!.position)
} else { } else {
decl.value = ReferenceLiteralValue(decl.datatype, decl.value = ArrayLiteralValue(decl.datatype,
array = constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) } constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
.toTypedArray(), position = decl.value!!.position) position = decl.value!!.position)
} }
decl.value!!.linkParents(decl) decl.value!!.linkParents(decl)
optimizationsDone++ optimizationsDone++
@ -122,8 +116,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} }
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray() val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
val refValue = ReferenceLiteralValue(decl.datatype, array = array, position = numericLv.position) val refValue = ArrayLiteralValue(decl.datatype, array, position = numericLv.position)
refValue.addToHeap(program.heap)
decl.value = refValue decl.value = refValue
refValue.parent=decl refValue.parent=decl
optimizationsDone++ optimizationsDone++
@ -139,13 +132,12 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} else { } else {
// arraysize initializer is a single int, and we know the size. // arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble() val fillvalue = litval.number.toDouble()
if (fillvalue < FLOAT_MAX_NEGATIVE || fillvalue > FLOAT_MAX_POSITIVE) if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("float value overflow", litval.position)) errors.add(ExpressionError("float value overflow", litval.position))
else { else {
// create the array itself, filled with the fillvalue. // create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray() val array = Array(size) {fillvalue}.map { NumericLiteralValue(DataType.FLOAT, it, litval.position) as Expression}.toTypedArray()
val refValue = ReferenceLiteralValue(DataType.ARRAY_F, array = array, position = litval.position) val refValue = ArrayLiteralValue(DataType.ARRAY_F, array, position = litval.position)
refValue.addToHeap(program.heap)
decl.value = refValue decl.value = refValue
refValue.parent=decl refValue.parent=decl
optimizationsDone++ optimizationsDone++
@ -155,6 +147,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} }
else -> { else -> {
// nothing to do for this type // nothing to do for this type
// this includes strings and structs
} }
} }
} }
@ -166,15 +159,24 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
* replace identifiers that refer to const value, with the value itself (if it's a simple type) * replace identifiers that refer to const value, with the value itself (if it's a simple type)
*/ */
override fun visit(identifier: IdentifierReference): Expression { override fun visit(identifier: IdentifierReference): Expression {
// don't replace when it's an assignment target or loop variable
if(identifier.parent is AssignTarget)
return identifier
var forloop = identifier.parent as? ForLoop
if(forloop==null)
forloop = identifier.parent.parent as? ForLoop
if(forloop!=null && identifier===forloop.loopVar)
return identifier
return try { return try {
val cval = identifier.constValue(program) ?: return identifier val cval = identifier.constValue(program) ?: return identifier
return when { return when (cval.type) {
cval.type in NumericDatatypes -> { in NumericDatatypes -> {
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position) val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
copy.parent = identifier.parent copy.parent = identifier.parent
copy copy
} }
cval.type in PassByReferenceDatatypes -> TODO("ref type $identifier") in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> identifier else -> identifier
} }
} catch (ax: AstException) { } catch (ax: AstException) {
@ -184,9 +186,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} }
override fun visit(functionCall: FunctionCall): Expression { override fun visit(functionCall: FunctionCall): Expression {
super.visit(functionCall)
typeCastConstArguments(functionCall)
return try { return try {
super.visit(functionCall)
typeCastConstArguments(functionCall)
functionCall.constValue(program) ?: functionCall functionCall.constValue(program) ?: functionCall
} catch (ax: AstException) { } catch (ax: AstException) {
addError(ax) addError(ax)
@ -205,15 +207,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()] val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
if(builtinFunction!=null) { if(builtinFunction!=null) {
// match the arguments of a builtin function signature. // match the arguments of a builtin function signature.
for(arg in functionCall.arglist.withIndex().zip(builtinFunction.parameters)) { for(arg in functionCall.args.withIndex().zip(builtinFunction.parameters)) {
val possibleDts = arg.second.possibleDatatypes val possibleDts = arg.second.possibleDatatypes
val argConst = arg.first.value.constValue(program) val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type !in possibleDts) { if(argConst!=null && argConst.type !in possibleDts) {
val convertedValue = argConst.cast(possibleDts.first()) val convertedValue = argConst.cast(possibleDts.first())
if(convertedValue!=null) { functionCall.args[arg.first.index] = convertedValue
functionCall.arglist[arg.first.index] = convertedValue optimizationsDone++
optimizationsDone++
}
} }
} }
return return
@ -223,15 +223,13 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val subroutine = functionCall.target.targetSubroutine(program.namespace) val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) { if(subroutine!=null) {
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter // if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
for(arg in functionCall.arglist.withIndex().zip(subroutine.parameters)) { for(arg in functionCall.args.withIndex().zip(subroutine.parameters)) {
val expectedDt = arg.second.type val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(program) val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) { if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.cast(expectedDt) val convertedValue = argConst.cast(expectedDt)
if(convertedValue!=null) { functionCall.args[arg.first.index] = convertedValue
functionCall.arglist[arg.first.index] = convertedValue optimizationsDone++
optimizationsDone++
}
} }
} }
} }
@ -259,27 +257,27 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val subexpr = prefixExpr.expression val subexpr = prefixExpr.expression
if (subexpr is NumericLiteralValue) { if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true) // accept prefixed literal values (such as -3, not true)
return when { return when (prefixExpr.operator) {
prefixExpr.operator == "+" -> subexpr "+" -> subexpr
prefixExpr.operator == "-" -> when { "-" -> when (subexpr.type) {
subexpr.type in IntegerDatatypes -> { in IntegerDatatypes -> {
optimizationsDone++ optimizationsDone++
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position) NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
} }
subexpr.type == DataType.FLOAT -> { DataType.FLOAT -> {
optimizationsDone++ optimizationsDone++
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position) NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position)
} }
else -> throw ExpressionError("can only take negative of int or float", subexpr.position) else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
} }
prefixExpr.operator == "~" -> when { "~" -> when (subexpr.type) {
subexpr.type in IntegerDatatypes -> { in IntegerDatatypes -> {
optimizationsDone++ optimizationsDone++
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position) NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
} }
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position) else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
} }
prefixExpr.operator == "not" -> { "not" -> {
optimizationsDone++ optimizationsDone++
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position) NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
} }
@ -314,8 +312,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
return try { return try {
super.visit(expr) super.visit(expr)
if(expr.left is ReferenceLiteralValue || expr.right is ReferenceLiteralValue) if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
TODO("binexpr with reference litval") || expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
throw FatalAstException("binexpr with reference litval instead of numeric")
val leftconst = expr.left.constValue(program) val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program) val rightconst = expr.right.constValue(program)
@ -359,7 +358,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
subleftIsConst: Boolean, subleftIsConst: Boolean,
subrightIsConst: Boolean): Expression subrightIsConst: Boolean): Expression
{ {
// @todo this implements only a small set of possible reorderings for now // todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) { if(expr.operator==subExpr.operator) {
// both operators are the isSameAs. // both operators are the isSameAs.
// If + or *, we can simply swap the const of expr and Var in subexpr. // If + or *, we can simply swap the const of expr and Var in subexpr.
@ -547,68 +546,100 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
override fun visit(forLoop: ForLoop): Statement { override fun visit(forLoop: ForLoop): Statement {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr { fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
val newFrom = rangeFrom.cast(targetDt) val newFrom: NumericLiteralValue
val newTo = rangeTo.cast(targetDt) val newTo: NumericLiteralValue
if (newFrom != null && newTo != null) { try {
val newStep: Expression = newFrom = rangeFrom.cast(targetDt)
if (stepLiteral != null) (stepLiteral.cast(targetDt) ?: stepLiteral) else range.step newTo = rangeTo.cast(targetDt)
return RangeExpr(newFrom, newTo, newStep, range.position) } catch (x: ExpressionError) {
return range
}
val newStep: Expression = try {
stepLiteral?.cast(targetDt)?: range.step
} catch(ee: ExpressionError) {
range.step
}
return RangeExpr(newFrom, newTo, newStep, range.position)
}
val forLoop2 = super.visit(forLoop) as ForLoop
// check if we need to adjust an array literal to the loop variable's datatype
val array = forLoop2.iterable as? ArrayLiteralValue
if(array!=null) {
val loopvarDt: DataType = when {
forLoop.loopVar!=null -> forLoop.loopVar!!.inferType(program).typeOrElse(DataType.UBYTE)
forLoop.loopRegister!=null -> DataType.UBYTE
else -> throw FatalAstException("weird for loop")
}
val arrayType = when(loopvarDt) {
DataType.UBYTE -> DataType.ARRAY_UB
DataType.BYTE -> DataType.ARRAY_B
DataType.UWORD -> DataType.ARRAY_UW
DataType.WORD -> DataType.ARRAY_W
DataType.FLOAT -> DataType.ARRAY_F
else -> throw FatalAstException("invalid array elt type")
}
val array2 = array.cast(arrayType)
if(array2!=null && array2!==array) {
forLoop2.iterable = array2
array2.linkParents(forLoop2)
} }
return range
} }
// adjust the datatype of a range expression in for loops to the loop variable. // adjust the datatype of a range expression in for loops to the loop variable.
val resultStmt = super.visit(forLoop) as ForLoop val iterableRange = forLoop2.iterable as? RangeExpr ?: return forLoop2
val iterableRange = resultStmt.iterable as? RangeExpr ?: return resultStmt
val rangeFrom = iterableRange.from as? NumericLiteralValue val rangeFrom = iterableRange.from as? NumericLiteralValue
val rangeTo = iterableRange.to as? NumericLiteralValue val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return resultStmt if(rangeFrom==null || rangeTo==null) return forLoop2
val loopvar = resultStmt.loopVar?.targetVarDecl(program.namespace) val loopvar = forLoop2.loopVar?.targetVarDecl(program.namespace)
if(loopvar!=null) { if(loopvar!=null) {
val stepLiteral = iterableRange.step as? NumericLiteralValue val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) { when(loopvar.datatype) {
DataType.UBYTE -> { DataType.UBYTE -> {
if(rangeFrom.type!= DataType.UBYTE) { if(rangeFrom.type!= DataType.UBYTE) {
// attempt to translate the iterable into ubyte values // attempt to translate the iterable into ubyte values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
} }
} }
DataType.BYTE -> { DataType.BYTE -> {
if(rangeFrom.type!= DataType.BYTE) { if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values // attempt to translate the iterable into byte values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
} }
} }
DataType.UWORD -> { DataType.UWORD -> {
if(rangeFrom.type!= DataType.UWORD) { if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values // attempt to translate the iterable into uword values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
} }
} }
DataType.WORD -> { DataType.WORD -> {
if(rangeFrom.type!= DataType.WORD) { if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values // attempt to translate the iterable into word values
resultStmt.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange) forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
} }
} }
else -> throw FatalAstException("invalid loopvar datatype $loopvar") else -> throw FatalAstException("invalid loopvar datatype $loopvar")
} }
} }
return resultStmt return forLoop2
} }
override fun visit(refLiteral: ReferenceLiteralValue): Expression { override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val litval = super.visit(refLiteral) val array = super.visit(arrayLiteral)
if(litval is ReferenceLiteralValue) { if(array is ArrayLiteralValue) {
if (litval.isArray) { val vardecl = array.parent as? VarDecl
val vardecl = litval.parent as? VarDecl return if (vardecl!=null) {
if (vardecl!=null) { fixupArrayEltDatatypesFromVardecl(array, vardecl)
return fixupArrayDatatype(litval, vardecl, program.heap) } else {
} // it's not an array associated with a vardecl, attempt to guess the data type from the array values
fixupArrayEltDatatypes(array, program)
} }
} }
return litval return array
} }
override fun visit(assignment: Assignment): Statement { override fun visit(assignment: Assignment): Statement {
@ -616,7 +647,10 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val lv = assignment.value as? NumericLiteralValue val lv = assignment.value as? NumericLiteralValue
if(lv!=null) { if(lv!=null) {
// see if we can promote/convert a literal value to the required datatype // see if we can promote/convert a literal value to the required datatype
when(assignment.target.inferType(program, assignment)) { val idt = assignment.target.inferType(program, assignment)
if(!idt.isKnown)
return assignment
when(idt.typeOrElse(DataType.STRUCT)) {
DataType.UWORD -> { DataType.UWORD -> {
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535, // we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
if(lv.type== DataType.UBYTE) if(lv.type== DataType.UBYTE)

View File

@ -27,8 +27,8 @@ internal fun Program.constantFold() {
} }
internal fun Program.optimizeStatements(optimizeInlining: Boolean): Int { internal fun Program.optimizeStatements(): Int {
val optimizer = StatementOptimizer(this, optimizeInlining) val optimizer = StatementOptimizer(this)
optimizer.visit(this) optimizer.visit(this)
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration

View File

@ -1,19 +1,17 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.Program import prog8.ast.Program
import prog8.ast.base.AstException import prog8.ast.base.*
import prog8.ast.base.DataType
import prog8.ast.base.IntegerDatatypes
import prog8.ast.base.NumericDatatypes
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.Assignment import prog8.ast.statements.Assignment
import prog8.ast.statements.Statement import prog8.ast.statements.Statement
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.log2 import kotlin.math.log2
import kotlin.math.pow
/* /*
todo advanced expression optimization: common (sub) expression elimination (turn common expressions into single subroutine call + introduce variable to hold it) todo add more expression optimizations
Also see https://egorbo.com/peephole-optimizations.html Also see https://egorbo.com/peephole-optimizations.html
@ -43,7 +41,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val literal = tc.expression as? NumericLiteralValue val literal = tc.expression as? NumericLiteralValue
if(literal!=null) { if(literal!=null) {
val newLiteral = literal.cast(tc.type) val newLiteral = literal.cast(tc.type)
if(newLiteral!=null && newLiteral!==literal) { if(newLiteral!==literal) {
optimizationsDone++ optimizationsDone++
return newLiteral return newLiteral
} }
@ -136,9 +134,14 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position) val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position) val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val leftDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program) val rightIDt = expr.right.inferType(program)
if (leftDt != null && rightDt != null && leftDt != rightDt) { if(!leftIDt.isKnown || !rightIDt.isKnown)
throw FatalAstException("can't determine datatype of both expression operands $expr")
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
if (leftDt != rightDt) {
// try to convert a datatype into the other (where ddd // try to convert a datatype into the other (where ddd
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) { if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
optimizationsDone++ optimizationsDone++
@ -226,7 +229,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.right val x = expr.right
val y = determineY(x, leftBinExpr) val y = determineY(x, leftBinExpr)
if(y!=null) { if(y!=null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt!!, 1, y.position), y.position) val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
return BinaryExpression(x, "*", yPlus1, x.position) return BinaryExpression(x, "*", yPlus1, x.position)
} }
} else { } else {
@ -235,7 +238,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.right val x = expr.right
val y = determineY(x, leftBinExpr) val y = determineY(x, leftBinExpr)
if(y!=null) { if(y!=null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt!!, 1, y.position), y.position) val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
return BinaryExpression(x, "*", yMinus1, x.position) return BinaryExpression(x, "*", yMinus1, x.position)
} }
} }
@ -338,6 +341,8 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
"-" -> return optimizeSub(expr, leftVal, rightVal) "-" -> return optimizeSub(expr, leftVal, rightVal)
"**" -> return optimizePower(expr, leftVal, rightVal) "**" -> return optimizePower(expr, leftVal, rightVal)
"%" -> return optimizeRemainder(expr, leftVal, rightVal) "%" -> return optimizeRemainder(expr, leftVal, rightVal)
">>" -> return optimizeShiftRight(expr, rightVal)
"<<" -> return optimizeShiftLeft(expr, rightVal)
} }
return expr return expr
} }
@ -590,7 +595,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
"%" -> { "%" -> {
if (cv == 1.0) { if (cv == 1.0) {
optimizationsDone++ optimizationsDone++
return NumericLiteralValue(expr.inferType(program)!!, 0, expr.position) return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) { } else if (cv == 2.0) {
optimizationsDone++ optimizationsDone++
expr.operator = "&" expr.operator = "&"
@ -603,17 +608,22 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
} }
private val powersOfTwo = (1 .. 16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression { private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null) if(leftVal==null && rightVal==null)
return expr return expr
// cannot shuffle assiciativity with division! // cannot shuffle assiciativity with division!
if(rightVal!=null) { if(rightVal!=null) {
// right value is a constant, see if we can optimize // right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal val rightConst: NumericLiteralValue = rightVal
val cv = rightConst.number.toDouble() val cv = rightConst.number.toDouble()
val leftDt = expr.left.inferType(program) val leftIDt = expr.left.inferType(program)
if(!leftIDt.isKnown)
return expr
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
when(cv) { when(cv) {
-1.0 -> { -1.0 -> {
// '/' -> -left // '/' -> -left
@ -629,7 +639,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr.left return expr.left
} }
} }
2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0 -> { in powersOfTwo -> {
if(leftDt in IntegerDatatypes) { if(leftDt in IntegerDatatypes) {
// divided by a power of two => shift right // divided by a power of two => shift right
optimizationsDone++ optimizationsDone++
@ -637,7 +647,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
-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 -> { in negativePowersOfTwo -> {
if(leftDt in IntegerDatatypes) { if(leftDt in IntegerDatatypes) {
// divided by a negative power of two => negate, then shift right // divided by a negative power of two => negate, then shift right
optimizationsDone++ optimizationsDone++
@ -700,16 +710,16 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
optimizationsDone++ optimizationsDone++
return expr.left return expr.left
} }
2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0 -> { in powersOfTwo -> {
if(leftValue.inferType(program) in IntegerDatatypes) { if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a power of two => shift left // times a power of two => shift left
optimizationsDone++ optimizationsDone++
val numshifts = log2(cv).toInt() val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position) return BinaryExpression(expr.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
} }
} }
-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 -> { in negativePowersOfTwo -> {
if(leftValue.inferType(program) in IntegerDatatypes) { if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a negative power of two => negate, then shift left // times a negative power of two => negate, then shift left
optimizationsDone++ optimizationsDone++
val numshifts = log2(-cv).toInt() val numshifts = log2(-cv).toInt()
@ -722,4 +732,97 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
return expr return expr
} }
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
if(amountLv==null)
return expr
val amount=amountLv.number.toInt()
if(amount==0) {
optimizationsDone++
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
if(amount>=8) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if(amount>=16) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
else if(amount>=8) {
optimizationsDone++
val lsb=TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
if(amount==8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position)
}
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position)
}
}
else -> {}
}
return expr
}
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
if(amountLv==null)
return expr
val amount=amountLv.number.toInt()
if(amount==0) {
optimizationsDone++
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE -> {
if(amount>=8) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
}
DataType.BYTE -> {
if(amount>8) {
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position)
return expr
}
}
DataType.UWORD -> {
if(amount>=16) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
else if(amount>=8) {
optimizationsDone++
val msb=FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if(amount==8)
return msb
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
}
}
DataType.WORD -> {
if(amount>16) {
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
return expr
} else if(amount>=8) {
optimizationsDone++
val msbAsByte = TypecastExpression(
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
DataType.BYTE,
true, expr.position)
if(amount==8)
return msbAsByte
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
}
}
else -> {}
}
return expr
}
} }

View File

@ -1,99 +1,42 @@
package prog8.optimizer package prog8.optimizer
import prog8.ast.* import prog8.ast.INameScope
import prog8.ast.Module
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.* import prog8.ast.base.*
import prog8.ast.expressions.* import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions import prog8.functions.BuiltinFunctions
import kotlin.math.floor 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: remove unreachable code?
todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to) + print warning about this TODO: proper inlining of tiny subroutines (correctly renaming/relocating all variables in them and refs to those as well, or restrict to subs without variables?)
*/ */
internal class StatementOptimizer(private val program: Program, private val optimizeInlining: Boolean) : IAstModifyingVisitor { internal class StatementOptimizer(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0 var optimizationsDone: Int = 0
private set private set
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure } private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
private val callgraph = CallGraph(program) private val callgraph = CallGraph(program)
private var generatedLabelSequenceNumber = 0 private val vardeclsToRemove = mutableListOf<VarDecl>()
override fun visit(program: Program) { override fun visit(program: Program) {
removeUnusedCode(callgraph) removeUnusedCode(callgraph)
if(optimizeInlining) {
inlineSubroutines(callgraph)
}
super.visit(program) super.visit(program)
}
private fun inlineSubroutines(callgraph: CallGraph) { for(decl in vardeclsToRemove) {
val entrypoint = program.entrypoint() decl.definingScope().remove(decl)
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 Statement || 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 haveNewEndLabel = false
var endLabelUsed = false
var endlabel = inlined.statements.last() as? Label
if(endlabel==null) {
endlabel = makeLabel("_prog8_auto_sub_end", inlined.statements.last().position)
endlabel.parent = inlined
haveNewEndLabel = true
}
val returns = inlined.statements.withIndex().filter { iv -> iv.value is Return }.map { iv -> Pair(iv.index, iv.value as Return)}
for(returnIdx in returns) {
val jump = Jump(null, IdentifierReference(listOf(endlabel.name), returnIdx.second.position), null, returnIdx.second.position)
inlined.statements[returnIdx.first] = jump
endLabelUsed = true
}
if(endLabelUsed && haveNewEndLabel)
inlined.statements.add(endlabel)
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) { private fun removeUnusedCode(callgraph: CallGraph) {
// remove all subroutines that aren't called, or are empty // remove all subroutines that aren't called, or are empty
val removeSubroutines = mutableSetOf<Subroutine>() val removeSubroutines = mutableSetOf<Subroutine>()
@ -167,18 +110,6 @@ internal class StatementOptimizer(private val program: Program, private val opti
linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)} linesToRemove.reversed().forEach{subroutine.statements.removeAt(it)}
} }
if(subroutine.canBeAsmSubroutine) {
optimizationsDone++
return subroutine.intoAsmSubroutine() // TODO this doesn't work yet due to parameter vardecl issue
// TODO fix parameter passing so this also works:
// asmsub aa(byte arg @ Y) -> clobbers() -> () {
// byte local = arg ; @todo fix 'undefined symbol arg' by some sort of alias name for the parameter
// A=44
// }
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) { if(subroutine !in callgraph.usedSymbols && !forceOutput) {
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position) printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++ optimizationsDone++
@ -204,7 +135,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
// removes 'duplicate' assignments that assign the isSameAs target // removes 'duplicate' assignments that assign the isSameAs target
val linesToRemove = mutableListOf<Int>() val linesToRemove = mutableListOf<Int>()
var previousAssignmentLine: Int? = null var previousAssignmentLine: Int? = null
for (i in 0 until statements.size) { for (i in statements.indices) {
val stmt = statements[i] as? Assignment val stmt = statements[i] as? Assignment
if (stmt != null && stmt.value is NumericLiteralValue) { if (stmt != null && stmt.value is NumericLiteralValue) {
if (previousAssignmentLine == null) { if (previousAssignmentLine == null) {
@ -238,24 +169,34 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") || if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) { functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters // printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
val stringVar = functionCallStatement.arglist.single() as? IdentifierReference val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference?
stringVar = if(arg is AddressOf) {
arg.identifier
} else {
arg as? IdentifierReference
}
if(stringVar!=null) { if(stringVar!=null) {
val heapId = stringVar.heapId(program.namespace) val vardecl = stringVar.targetVarDecl(program.namespace)!!
val string = program.heap.get(heapId).str!! val string = vardecl.value!! as StringLiteralValue
if(string.length==1) { if(string.value.length==1) {
val petscii = Petscii.encodePetscii(string, true)[0] val firstCharEncoded = CompilationTarget.encodeString(string.value)[0]
functionCallStatement.arglist.clear() functionCallStatement.args.clear()
functionCallStatement.arglist.add(NumericLiteralValue.optimalInteger(petscii.toInt(), functionCallStatement.position)) functionCallStatement.args.add(NumericLiteralValue.optimalInteger(firstCharEncoded.toInt(), functionCallStatement.position))
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position) functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
vardeclsToRemove.add(vardecl)
optimizationsDone++ optimizationsDone++
return functionCallStatement return functionCallStatement
} else if(string.length==2) { } else if(string.value.length==2) {
val petscii = Petscii.encodePetscii(string, true) val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2))
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position) val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position), scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(NumericLiteralValue.optimalInteger(petscii[0].toInt(), functionCallStatement.position)), functionCallStatement.position)) mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[0].toInt(), functionCallStatement.position)),
functionCallStatement.void, functionCallStatement.position))
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position), scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(NumericLiteralValue.optimalInteger(petscii[1].toInt(), functionCallStatement.position)), functionCallStatement.position)) mutableListOf(NumericLiteralValue.optimalInteger(firstTwoCharsEncoded[1].toInt(), functionCallStatement.position)),
functionCallStatement.void, functionCallStatement.position))
vardeclsToRemove.add(vardecl)
optimizationsDone++ optimizationsDone++
return scope return scope
} }
@ -270,7 +211,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) { if(first is Jump && first.identifier!=null) {
optimizationsDone++ optimizationsDone++
return FunctionCallStatement(first.identifier, functionCallStatement.arglist, functionCallStatement.position) return FunctionCallStatement(first.identifier, functionCallStatement.args, functionCallStatement.void, functionCallStatement.position)
} }
if(first is ReturnFromIrq || first is Return) { if(first is ReturnFromIrq || first is Return) {
optimizationsDone++ optimizationsDone++
@ -290,7 +231,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull() val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) { if(first is Jump && first.identifier!=null) {
optimizationsDone++ optimizationsDone++
return FunctionCall(first.identifier, functionCall.arglist, functionCall.position) return FunctionCall(first.identifier, functionCall.args, functionCall.position)
} }
if(first is Return && first.value!=null) { if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program) val constval = first.value?.constValue(program)
@ -322,7 +263,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue){ return if(constvalue.asBooleanValue){
// always true -> keep only if-part // always true -> keep only if-part
printWarning("condition is always true", ifStatement.position) printWarning("condition is always true", ifStatement.position) // TODO don't warn this if the condition is just the single value 'true'
optimizationsDone++ optimizationsDone++
ifStatement.truepart ifStatement.truepart
} else { } else {
@ -371,19 +312,20 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue){ return if(constvalue.asBooleanValue){
// always true -> print a warning, and optimize into body + jump (if there are no continue and break statements) // always true -> print a warning, and optimize into body + jump (if there are no continue and break statements)
printWarning("condition is always true", whileLoop.position) printWarning("condition is always true", whileLoop.condition.position)
if(hasContinueOrBreak(whileLoop.body)) if(hasContinueOrBreak(whileLoop.body))
return whileLoop return whileLoop
val label = Label("_prog8_back", whileLoop.condition.position) val backLabelName = "_prog8_back${whileLoop.position.line}"
val label = Label(backLabelName, whileLoop.condition.position)
whileLoop.body.statements.add(0, label) whileLoop.body.statements.add(0, label)
whileLoop.body.statements.add(Jump(null, whileLoop.body.statements.add(Jump(null,
IdentifierReference(listOf("_prog8_back"), whileLoop.condition.position), IdentifierReference(listOf(backLabelName), whileLoop.condition.position),
null, whileLoop.condition.position)) null, whileLoop.condition.position))
optimizationsDone++ optimizationsDone++
return whileLoop.body return whileLoop.body
} else { } else {
// always false -> ditch whole statement // always false -> ditch whole statement
printWarning("condition is always false", whileLoop.position) printWarning("condition is always false", whileLoop.condition.position)
optimizationsDone++ optimizationsDone++
NopStatement.insteadOf(whileLoop) NopStatement.insteadOf(whileLoop)
} }
@ -397,7 +339,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
if(constvalue!=null) { if(constvalue!=null) {
return if(constvalue.asBooleanValue){ return if(constvalue.asBooleanValue){
// always true -> keep only the statement block (if there are no continue and break statements) // always true -> keep only the statement block (if there are no continue and break statements)
printWarning("condition is always true", repeatLoop.position) printWarning("condition is always true", repeatLoop.untilCondition.position)
if(hasContinueOrBreak(repeatLoop.body)) if(hasContinueOrBreak(repeatLoop.body))
repeatLoop repeatLoop
else { else {
@ -406,13 +348,14 @@ internal class StatementOptimizer(private val program: Program, private val opti
} }
} else { } else {
// always false -> print a warning, and optimize into body + jump (if there are no continue and break statements) // always false -> print a warning, and optimize into body + jump (if there are no continue and break statements)
printWarning("condition is always false", repeatLoop.position) printWarning("condition is always false", repeatLoop.untilCondition.position)
if(hasContinueOrBreak(repeatLoop.body)) if(hasContinueOrBreak(repeatLoop.body))
return repeatLoop return repeatLoop
val label = Label("__back", repeatLoop.untilCondition.position) val backLabelName = "_prog8_back${repeatLoop.position.line}"
val label = Label(backLabelName, repeatLoop.untilCondition.position)
repeatLoop.body.statements.add(0, label) repeatLoop.body.statements.add(0, label)
repeatLoop.body.statements.add(Jump(null, repeatLoop.body.statements.add(Jump(null,
IdentifierReference(listOf("__back"), repeatLoop.untilCondition.position), IdentifierReference(listOf(backLabelName), repeatLoop.untilCondition.position),
null, repeatLoop.untilCondition.position)) null, repeatLoop.untilCondition.position))
optimizationsDone++ optimizationsDone++
return repeatLoop.body return repeatLoop.body
@ -489,7 +432,10 @@ internal class StatementOptimizer(private val program: Program, private val opti
return NopStatement.insteadOf(assignment) return NopStatement.insteadOf(assignment)
} }
} }
val targetDt = assignment.target.inferType(program, assignment) val targetIDt = assignment.target.inferType(program, assignment)
if(!targetIDt.isKnown)
throw FatalAstException("can't infer type of assignment target")
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) { if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.number?.toDouble() val cv = bexpr.right.constValue(program)?.number?.toDouble()
@ -574,7 +520,8 @@ internal class StatementOptimizer(private val program: Program, private val opti
val scope = AnonymousScope(mutableListOf(), assignment.position) val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt() var numshifts = cv.toInt()
while (numshifts > 0) { while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position), mutableListOf(bexpr.left), assignment.position)) scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsl"), assignment.position),
mutableListOf(bexpr.left), true, assignment.position))
numshifts-- numshifts--
} }
optimizationsDone++ optimizationsDone++
@ -586,8 +533,7 @@ internal class StatementOptimizer(private val program: Program, private val opti
optimizationsDone++ optimizationsDone++
return NopStatement.insteadOf(assignment) return NopStatement.insteadOf(assignment)
} }
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) || if ((targetDt == DataType.UWORD && cv > 15.0) || (targetDt == DataType.UBYTE && cv > 7.0)) {
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position) assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment) assignment.value.linkParents(assignment)
optimizationsDone++ optimizationsDone++
@ -596,7 +542,8 @@ internal class StatementOptimizer(private val program: Program, private val opti
val scope = AnonymousScope(mutableListOf(), assignment.position) val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt() var numshifts = cv.toInt()
while (numshifts > 0) { while (numshifts > 0) {
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position), mutableListOf(bexpr.left), assignment.position)) scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("lsr"), assignment.position),
mutableListOf(bexpr.left), true, assignment.position))
numshifts-- numshifts--
} }
optimizationsDone++ optimizationsDone++

View File

@ -9,6 +9,7 @@ import prog8.ast.base.SyntaxError
import prog8.ast.base.checkImportedValid import prog8.ast.base.checkImportedValid
import prog8.ast.statements.Directive import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg import prog8.ast.statements.DirectiveArg
import prog8.pathFrom
import java.io.InputStream import java.io.InputStream
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
@ -91,18 +92,18 @@ internal fun importModule(program: Program, stream: CharStream, modulePath: Path
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path { private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8" val fileName = "$name.p8"
val locations = mutableListOf(Paths.get(source.parent.toString())) val locations = mutableListOf(source.parent)
val propPath = System.getProperty("prog8.libdir") val propPath = System.getProperty("prog8.libdir")
if(propPath!=null) if(propPath!=null)
locations.add(Paths.get(propPath)) locations.add(pathFrom(propPath))
val envPath = System.getenv("PROG8_LIBDIR") val envPath = System.getenv("PROG8_LIBDIR")
if(envPath!=null) if(envPath!=null)
locations.add(Paths.get(envPath)) locations.add(pathFrom(envPath))
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib")) locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
locations.forEach { locations.forEach {
val file = Paths.get(it.toString(), fileName) val file = pathFrom(it.toString(), fileName)
if (Files.isReadable(file)) return file if (Files.isReadable(file)) return file
} }
@ -120,7 +121,7 @@ private fun executeImportDirective(program: Program, import: Directive, source:
if(existing!=null) if(existing!=null)
return null return null
val resource = tryGetEmbeddedResource(moduleName+".p8") val resource = tryGetEmbeddedResource("$moduleName.p8")
val importedModule = val importedModule =
if(resource!=null) { if(resource!=null) {
// load the module from the embedded resource // load the module from the embedded resource

View File

@ -1,10 +1,13 @@
package prog8.vm package prog8.vm
import prog8.ast.base.* import prog8.ast.base.ByteDatatypes
import prog8.ast.base.DataType
import prog8.ast.base.WordDatatypes
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.HeapValues import prog8.vm.astvm.VmExecutionException
import prog8.compiler.target.c64.Petscii import java.util.Objects
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.pow import kotlin.math.pow
@ -14,8 +17,14 @@ import kotlin.math.pow
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form) * 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. * 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) { abstract class RuntimeValueBase(val type: DataType) {
abstract fun numericValue(): Number
abstract fun integerValue(): Int
}
class RuntimeValueNumeric(type: DataType, num: Number): RuntimeValueBase(type) {
val byteval: Short? val byteval: Short?
val wordval: Int? val wordval: Int?
@ -23,121 +32,78 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
val asBoolean: Boolean val asBoolean: Boolean
companion object { companion object {
fun fromLv(literalValue: NumericLiteralValue): RuntimeValue { fun fromLv(literalValue: NumericLiteralValue): RuntimeValueNumeric {
return RuntimeValue(literalValue.type, num = literalValue.number) return RuntimeValueNumeric(literalValue.type, num = literalValue.number)
} }
fun fromLv(literalValue: ReferenceLiteralValue, heap: HeapValues): RuntimeValue {
return when(literalValue.type) {
in StringDatatypes -> fromHeapId(literalValue.heapId!!, heap)
in ArrayDatatypes -> fromHeapId(literalValue.heapId!!, heap)
else -> throw IllegalArgumentException("weird source value $literalValue")
}
}
fun fromHeapId(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!!
val resultArray = mutableListOf<Number>()
for(elt in array.withIndex()){
if(elt.value.integer!=null)
resultArray.add(elt.value.integer!!)
else {
TODO("ADDRESSOF ${elt.value}")
}
}
RuntimeValue(value.type, array = resultArray.toTypedArray(), heapId = heapId)
//RuntimeValue(value.type, array = array.map { it.integer!! }.toTypedArray(), heapId = heapId)
}
else -> throw IllegalArgumentException("weird value type on heap $value")
}
}
} }
init { init {
when(type) { when (type) {
DataType.UBYTE -> { DataType.UBYTE -> {
val inum = num!!.toInt() val inum = num.toInt()
if(inum !in 0 .. 255) require(inum in 0..255) { "invalid value for ubyte: $inum" }
throw IllegalArgumentException("invalid value for ubyte: $inum")
byteval = inum.toShort() byteval = inum.toShort()
wordval = null wordval = null
floatval = null floatval = null
asBoolean = byteval != 0.toShort() asBoolean = byteval != 0.toShort()
} }
DataType.BYTE -> { DataType.BYTE -> {
val inum = num!!.toInt() val inum = num.toInt()
if(inum !in -128 .. 127) require(inum in -128..127) { "invalid value for byte: $inum" }
throw IllegalArgumentException("invalid value for byte: $inum")
byteval = inum.toShort() byteval = inum.toShort()
wordval = null wordval = null
floatval = null floatval = null
asBoolean = byteval != 0.toShort() asBoolean = byteval != 0.toShort()
} }
DataType.UWORD -> { DataType.UWORD -> {
val inum = num!!.toInt() val inum = num.toInt()
if(inum !in 0 .. 65535) require(inum in 0..65535) { "invalid value for uword: $inum" }
throw IllegalArgumentException("invalid value for uword: $inum")
wordval = inum wordval = inum
byteval = null byteval = null
floatval = null floatval = null
asBoolean = wordval != 0 asBoolean = wordval != 0
} }
DataType.WORD -> { DataType.WORD -> {
val inum = num!!.toInt() val inum = num.toInt()
if(inum !in -32768 .. 32767) require(inum in -32768..32767) { "invalid value for word: $inum" }
throw IllegalArgumentException("invalid value for word: $inum")
wordval = inum wordval = inum
byteval = null byteval = null
floatval = null floatval = null
asBoolean = wordval != 0 asBoolean = wordval != 0
} }
DataType.FLOAT -> { DataType.FLOAT -> {
floatval = num!!.toDouble() floatval = num.toDouble()
byteval = null byteval = null
wordval = null wordval = null
asBoolean = floatval != 0.0 asBoolean = floatval != 0.0
} }
else -> { else -> throw VmExecutionException("not a numeric value")
byteval = null
wordval = null
floatval = null
asBoolean = true
}
} }
} }
override fun toString(): String { override fun toString(): String {
return when(type) { return when (type) {
DataType.UBYTE -> "ub:%02x".format(byteval) DataType.UBYTE -> "ub:%02x".format(byteval)
DataType.BYTE -> { DataType.BYTE -> {
if(byteval!!<0) if (byteval!! < 0)
"b:-%02x".format(abs(byteval.toInt())) "b:-%02x".format(abs(byteval.toInt()))
else else
"b:%02x".format(byteval) "b:%02x".format(byteval)
} }
DataType.UWORD -> "uw:%04x".format(wordval) DataType.UWORD -> "uw:%04x".format(wordval)
DataType.WORD -> { DataType.WORD -> {
if(wordval!!<0) if (wordval!! < 0)
"w:-%04x".format(abs(wordval)) "w:-%04x".format(abs(wordval))
else else
"w:%04x".format(wordval) "w:%04x".format(wordval)
} }
DataType.FLOAT -> "f:$floatval" DataType.FLOAT -> "f:$floatval"
else -> "heap:$heapId" else -> "???"
} }
} }
fun numericValue(): Number { override fun numericValue(): Number {
return when(type) { return when (type) {
in ByteDatatypes -> byteval!! in ByteDatatypes -> byteval!!
in WordDatatypes -> wordval!! in WordDatatypes -> wordval!!
DataType.FLOAT -> floatval!! DataType.FLOAT -> floatval!!
@ -145,8 +111,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
} }
} }
fun integerValue(): Int { override fun integerValue(): Int {
return when(type) { return when (type) {
in ByteDatatypes -> byteval!!.toInt() in ByteDatatypes -> byteval!!.toInt()
in WordDatatypes -> wordval!! in WordDatatypes -> wordval!!
DataType.FLOAT -> throw ArithmeticException("float to integer loss of precision") DataType.FLOAT -> throw ArithmeticException("float to integer loss of precision")
@ -154,83 +120,72 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
} }
} }
override fun hashCode(): Int { override fun hashCode(): Int = Objects.hash(byteval, wordval, floatval, type)
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 { override fun equals(other: Any?): Boolean {
if(other==null || other !is RuntimeValue) if (other == null || other !is RuntimeValueNumeric)
return false return false
if(type==other.type) return compareTo(other) == 0 // note: datatype doesn't matter
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 { operator fun compareTo(other: RuntimeValueNumeric): Int = numericValue().toDouble().compareTo(other.numericValue().toDouble())
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 { private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): RuntimeValueNumeric {
if(leftDt!=rightDt) if (leftDt != rightDt)
throw ArithmeticException("left and right datatypes are not the same") throw ArithmeticException("left and right datatypes are not the same")
if(result.toDouble() < 0 ) { if (result.toDouble() < 0) {
return when(leftDt) { return when (leftDt) {
DataType.UBYTE, DataType.UWORD -> { DataType.UBYTE, DataType.UWORD -> {
// storing a negative number in an unsigned one is done by storing the 2's complement instead // storing a negative number in an unsigned one is done by storing the 2's complement instead
val number = abs(result.toDouble().toInt()) val number = abs(result.toDouble().toInt())
if(leftDt== DataType.UBYTE) if (leftDt == DataType.UBYTE)
RuntimeValue(DataType.UBYTE, (number xor 255) + 1) RuntimeValueNumeric(DataType.UBYTE, (number xor 255) + 1)
else else
RuntimeValue(DataType.UWORD, (number xor 65535) + 1) RuntimeValueNumeric(DataType.UWORD, (number xor 65535) + 1)
} }
DataType.BYTE -> { DataType.BYTE -> {
val v=result.toInt() and 255 val v = result.toInt() and 255
if(v<128) if (v < 128)
RuntimeValue(DataType.BYTE, v) RuntimeValueNumeric(DataType.BYTE, v)
else else
RuntimeValue(DataType.BYTE, v-256) RuntimeValueNumeric(DataType.BYTE, v - 256)
} }
DataType.WORD -> { DataType.WORD -> {
val v=result.toInt() and 65535 val v = result.toInt() and 65535
if(v<32768) if (v < 32768)
RuntimeValue(DataType.WORD, v) RuntimeValueNumeric(DataType.WORD, v)
else else
RuntimeValue(DataType.WORD, v-65536) RuntimeValueNumeric(DataType.WORD, v - 65536)
} }
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type") else -> throw ArithmeticException("$op on non-numeric type")
} }
} }
return when(leftDt) { return when (leftDt) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result.toInt() and 255) DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result.toInt() and 255)
DataType.BYTE -> { DataType.BYTE -> {
val v = result.toInt() and 255 val v = result.toInt() and 255
if(v<128) if (v < 128)
RuntimeValue(DataType.BYTE, v) RuntimeValueNumeric(DataType.BYTE, v)
else else
RuntimeValue(DataType.BYTE, v-256) RuntimeValueNumeric(DataType.BYTE, v - 256)
} }
DataType.UWORD -> RuntimeValue(DataType.UWORD, result.toInt() and 65535) DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, result.toInt() and 65535)
DataType.WORD -> { DataType.WORD -> {
val v = result.toInt() and 65535 val v = result.toInt() and 65535
if(v<32768) if (v < 32768)
RuntimeValue(DataType.WORD, v) RuntimeValueNumeric(DataType.WORD, v)
else else
RuntimeValue(DataType.WORD, v-65536) RuntimeValueNumeric(DataType.WORD, v - 65536)
} }
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type") else -> throw ArithmeticException("$op on non-numeric type")
} }
} }
fun add(other: RuntimeValue): RuntimeValue { fun add(other: RuntimeValueNumeric): RuntimeValueNumeric {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT)) if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type") throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue() val v1 = numericValue()
val v2 = other.numericValue() val v2 = other.numericValue()
@ -238,8 +193,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
return arithResult(type, result, other.type, "add") return arithResult(type, result, other.type, "add")
} }
fun sub(other: RuntimeValue): RuntimeValue { fun sub(other: RuntimeValueNumeric): RuntimeValueNumeric {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT)) if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type") throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue() val v1 = numericValue()
val v2 = other.numericValue() val v2 = other.numericValue()
@ -247,8 +202,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
return arithResult(type, result, other.type, "sub") return arithResult(type, result, other.type, "sub")
} }
fun mul(other: RuntimeValue): RuntimeValue { fun mul(other: RuntimeValueNumeric): RuntimeValueNumeric {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT)) if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type") throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue() val v1 = numericValue()
val v2 = other.numericValue() val v2 = other.numericValue()
@ -256,317 +211,318 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
return arithResult(type, result, other.type, "mul") return arithResult(type, result, other.type, "mul")
} }
fun div(other: RuntimeValue): RuntimeValue { fun div(other: RuntimeValueNumeric): RuntimeValueNumeric {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT)) if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type") throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue() val v1 = numericValue()
val v2 = other.numericValue() val v2 = other.numericValue()
if(v2.toDouble()==0.0) { if (v2.toDouble() == 0.0) {
when (type) { when (type) {
DataType.UBYTE -> return RuntimeValue(DataType.UBYTE, 255) DataType.UBYTE -> return RuntimeValueNumeric(DataType.UBYTE, 255)
DataType.BYTE -> return RuntimeValue(DataType.BYTE, 127) DataType.BYTE -> return RuntimeValueNumeric(DataType.BYTE, 127)
DataType.UWORD -> return RuntimeValue(DataType.UWORD, 65535) DataType.UWORD -> return RuntimeValueNumeric(DataType.UWORD, 65535)
DataType.WORD -> return RuntimeValue(DataType.WORD, 32767) DataType.WORD -> return RuntimeValueNumeric(DataType.WORD, 32767)
else -> {} else -> {
}
} }
} }
val result = v1.toDouble() / v2.toDouble() val result = v1.toDouble() / v2.toDouble()
// NOTE: integer division returns integer result! // NOTE: integer division returns integer result!
return when(type) { return when (type) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result) DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result)
DataType.BYTE -> RuntimeValue(DataType.BYTE, result) DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, result)
DataType.UWORD -> RuntimeValue(DataType.UWORD, result) DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, result)
DataType.WORD -> RuntimeValue(DataType.WORD, result) DataType.WORD -> RuntimeValueNumeric(DataType.WORD, result)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
else -> throw ArithmeticException("div on non-numeric type") else -> throw ArithmeticException("div on non-numeric type")
} }
} }
fun remainder(other: RuntimeValue): RuntimeValue { fun remainder(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = numericValue() val v1 = numericValue()
val v2 = other.numericValue() val v2 = other.numericValue()
val result = v1.toDouble() % v2.toDouble() val result = v1.toDouble() % v2.toDouble()
return arithResult(type, result, other.type, "remainder") return arithResult(type, result, other.type, "remainder")
} }
fun pow(other: RuntimeValue): RuntimeValue { fun pow(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = numericValue() val v1 = numericValue()
val v2 = other.numericValue() val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble()) val result = v1.toDouble().pow(v2.toDouble())
return arithResult(type, result, other.type,"pow") return arithResult(type, result, other.type, "pow")
} }
fun shl(): RuntimeValue { fun shl(): RuntimeValueNumeric {
val v = integerValue() val v = integerValue()
return when (type) { return when (type) {
DataType.UBYTE -> RuntimeValue(type, (v shl 1) and 255) DataType.UBYTE -> RuntimeValueNumeric(type, (v shl 1) and 255)
DataType.UWORD -> RuntimeValue(type, (v shl 1) and 65535) DataType.UWORD -> RuntimeValueNumeric(type, (v shl 1) and 65535)
DataType.BYTE -> { DataType.BYTE -> {
val value = v shl 1 val value = v shl 1
if(value<128) if (value < 128)
RuntimeValue(type, value) RuntimeValueNumeric(type, value)
else else
RuntimeValue(type, value-256) RuntimeValueNumeric(type, value - 256)
} }
DataType.WORD -> { DataType.WORD -> {
val value = v shl 1 val value = v shl 1
if(value<32768) if (value < 32768)
RuntimeValue(type, value) RuntimeValueNumeric(type, value)
else else
RuntimeValue(type, value-65536) RuntimeValueNumeric(type, value - 65536)
} }
else -> throw ArithmeticException("invalid type for shl: $type") else -> throw ArithmeticException("invalid type for shl: $type")
} }
} }
fun shr(): RuntimeValue { fun shr(): RuntimeValueNumeric {
val v = integerValue() val v = integerValue()
return when(type){ return when (type) {
DataType.UBYTE -> RuntimeValue(type, v ushr 1) DataType.UBYTE -> RuntimeValueNumeric(type, v ushr 1)
DataType.BYTE -> RuntimeValue(type, v shr 1) DataType.BYTE -> RuntimeValueNumeric(type, v shr 1)
DataType.UWORD -> RuntimeValue(type, v ushr 1) DataType.UWORD -> RuntimeValueNumeric(type, v ushr 1)
DataType.WORD -> RuntimeValue(type, v shr 1) DataType.WORD -> RuntimeValueNumeric(type, v shr 1)
else -> throw ArithmeticException("invalid type for shr: $type") else -> throw ArithmeticException("invalid type for shr: $type")
} }
} }
fun rol(carry: Boolean): Pair<RuntimeValue, Boolean> { fun rol(carry: Boolean): Pair<RuntimeValueNumeric, Boolean> {
// 9 or 17 bit rotate left (with carry)) // 9 or 17 bit rotate left (with carry))
return when(type) { return when (type) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt() val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0 val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0) val newval = (v and 0x7f shl 1) or (if (carry) 1 else 0)
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry) Pair(RuntimeValueNumeric(DataType.UBYTE, newval), newCarry)
} }
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
val v = wordval!! val v = wordval!!
val newCarry = (v and 0x8000) != 0 val newCarry = (v and 0x8000) != 0
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0) val newval = (v and 0x7fff shl 1) or (if (carry) 1 else 0)
Pair(RuntimeValue(DataType.UWORD, newval), newCarry) Pair(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
} }
else -> throw ArithmeticException("rol can only work on byte/word") else -> throw ArithmeticException("rol can only work on byte/word")
} }
} }
fun ror(carry: Boolean): Pair<RuntimeValue, Boolean> { fun ror(carry: Boolean): Pair<RuntimeValueNumeric, Boolean> {
// 9 or 17 bit rotate right (with carry) // 9 or 17 bit rotate right (with carry)
return when(type) { return when (type) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt() val v = byteval!!.toInt()
val newCarry = v and 1 != 0 val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x80 else 0) val newval = (v ushr 1) or (if (carry) 0x80 else 0)
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry) Pair(RuntimeValueNumeric(DataType.UBYTE, newval), newCarry)
} }
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
val v = wordval!! val v = wordval!!
val newCarry = v and 1 != 0 val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x8000 else 0) val newval = (v ushr 1) or (if (carry) 0x8000 else 0)
Pair(RuntimeValue(DataType.UWORD, newval), newCarry) Pair(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
} }
else -> throw ArithmeticException("ror2 can only work on byte/word") else -> throw ArithmeticException("ror2 can only work on byte/word")
} }
} }
fun rol2(): RuntimeValue { fun rol2(): RuntimeValueNumeric {
// 8 or 16 bit rotate left // 8 or 16 bit rotate left
return when(type) { return when (type) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt() val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7 val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry val newval = (v and 0x7f shl 1) or carry
RuntimeValue(DataType.UBYTE, newval) RuntimeValueNumeric(DataType.UBYTE, newval)
} }
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
val v = wordval!! val v = wordval!!
val carry = (v and 0x8000) ushr 15 val carry = (v and 0x8000) ushr 15
val newval = (v and 0x7fff shl 1) or carry val newval = (v and 0x7fff shl 1) or carry
RuntimeValue(DataType.UWORD, newval) RuntimeValueNumeric(DataType.UWORD, newval)
} }
else -> throw ArithmeticException("rol2 can only work on byte/word") else -> throw ArithmeticException("rol2 can only work on byte/word")
} }
} }
fun ror2(): RuntimeValue { fun ror2(): RuntimeValueNumeric {
// 8 or 16 bit rotate right // 8 or 16 bit rotate right
return when(type) { return when (type) {
DataType.UBYTE, DataType.BYTE -> { DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt() val v = byteval!!.toInt()
val carry = v and 1 shl 7 val carry = v and 1 shl 7
val newval = (v ushr 1) or carry val newval = (v ushr 1) or carry
RuntimeValue(DataType.UBYTE, newval) RuntimeValueNumeric(DataType.UBYTE, newval)
} }
DataType.UWORD, DataType.WORD -> { DataType.UWORD, DataType.WORD -> {
val v = wordval!! val v = wordval!!
val carry = v and 1 shl 15 val carry = v and 1 shl 15
val newval = (v ushr 1) or carry val newval = (v ushr 1) or carry
RuntimeValue(DataType.UWORD, newval) RuntimeValueNumeric(DataType.UWORD, newval)
} }
else -> throw ArithmeticException("ror2 can only work on byte/word") else -> throw ArithmeticException("ror2 can only work on byte/word")
} }
} }
fun neg(): RuntimeValue { fun neg(): RuntimeValueNumeric {
return when(type) { return when (type) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, -(byteval!!)) DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, -(byteval!!))
DataType.WORD -> RuntimeValue(DataType.WORD, -(wordval!!)) DataType.WORD -> RuntimeValueNumeric(DataType.WORD, -(wordval!!))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, -(floatval)!!) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, -(floatval)!!)
else -> throw ArithmeticException("neg can only work on byte/word/float") else -> throw ArithmeticException("neg can only work on byte/word/float")
} }
} }
fun abs(): RuntimeValue { fun abs(): RuntimeValueNumeric {
return when(type) { return when (type) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, abs(byteval!!.toInt())) DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, abs(byteval!!.toInt()))
DataType.WORD -> RuntimeValue(DataType.WORD, abs(wordval!!)) DataType.WORD -> RuntimeValueNumeric(DataType.WORD, abs(wordval!!))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(floatval!!)) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, abs(floatval!!))
else -> throw ArithmeticException("abs can only work on byte/word/float") else -> throw ArithmeticException("abs can only work on byte/word/float")
} }
} }
fun bitand(other: RuntimeValue): RuntimeValue { fun bitand(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = integerValue() val v1 = integerValue()
val v2 = other.integerValue() val v2 = other.integerValue()
val result = v1 and v2 val result = v1 and v2
return RuntimeValue(type, result) return RuntimeValueNumeric(type, result)
} }
fun bitor(other: RuntimeValue): RuntimeValue { fun bitor(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = integerValue() val v1 = integerValue()
val v2 = other.integerValue() val v2 = other.integerValue()
val result = v1 or v2 val result = v1 or v2
return RuntimeValue(type, result) return RuntimeValueNumeric(type, result)
} }
fun bitxor(other: RuntimeValue): RuntimeValue { fun bitxor(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = integerValue() val v1 = integerValue()
val v2 = other.integerValue() val v2 = other.integerValue()
val result = v1 xor v2 val result = v1 xor v2
return RuntimeValue(type, result) return RuntimeValueNumeric(type, result)
} }
fun and(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean && other.asBoolean) 1 else 0) fun and(other: RuntimeValueNumeric) = RuntimeValueNumeric(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 or(other: RuntimeValueNumeric) = RuntimeValueNumeric(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 xor(other: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean xor other.asBoolean) 1 else 0)
fun not() = RuntimeValue(DataType.UBYTE, if (this.asBoolean) 0 else 1) fun not() = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean) 0 else 1)
fun inv(): RuntimeValue { fun inv(): RuntimeValueNumeric {
return when(type) { return when (type) {
DataType.UBYTE -> RuntimeValue(type, byteval!!.toInt().inv() and 255) DataType.UBYTE -> RuntimeValueNumeric(type, byteval!!.toInt().inv() and 255)
DataType.UWORD -> RuntimeValue(type, wordval!!.inv() and 65535) DataType.UWORD -> RuntimeValueNumeric(type, wordval!!.inv() and 65535)
DataType.BYTE -> RuntimeValue(type, byteval!!.toInt().inv()) DataType.BYTE -> RuntimeValueNumeric(type, byteval!!.toInt().inv())
DataType.WORD -> RuntimeValue(type, wordval!!.inv()) DataType.WORD -> RuntimeValueNumeric(type, wordval!!.inv())
else -> throw ArithmeticException("inv can only work on byte/word") else -> throw ArithmeticException("inv can only work on byte/word")
} }
} }
fun inc(): RuntimeValue { fun inc(): RuntimeValueNumeric {
return when(type) { return when (type) {
DataType.UBYTE -> RuntimeValue(type, (byteval!! + 1) and 255) DataType.UBYTE -> RuntimeValueNumeric(type, (byteval!! + 1) and 255)
DataType.UWORD -> RuntimeValue(type, (wordval!! + 1) and 65535) DataType.UWORD -> RuntimeValueNumeric(type, (wordval!! + 1) and 65535)
DataType.BYTE -> { DataType.BYTE -> {
val newval = byteval!! + 1 val newval = byteval!! + 1
if(newval == 128) if (newval == 128)
RuntimeValue(type, -128) RuntimeValueNumeric(type, -128)
else else
RuntimeValue(type, newval) RuntimeValueNumeric(type, newval)
} }
DataType.WORD -> { DataType.WORD -> {
val newval = wordval!! + 1 val newval = wordval!! + 1
if(newval == 32768) if (newval == 32768)
RuntimeValue(type, -32768) RuntimeValueNumeric(type, -32768)
else else
RuntimeValue(type, newval) RuntimeValueNumeric(type, newval)
} }
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! + 1) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, floatval!! + 1)
else -> throw ArithmeticException("inc can only work on numeric types") else -> throw ArithmeticException("inc can only work on numeric types")
} }
} }
fun dec(): RuntimeValue { fun dec(): RuntimeValueNumeric {
return when(type) { return when (type) {
DataType.UBYTE -> RuntimeValue(type, (byteval!! - 1) and 255) DataType.UBYTE -> RuntimeValueNumeric(type, (byteval!! - 1) and 255)
DataType.UWORD -> RuntimeValue(type, (wordval!! - 1) and 65535) DataType.UWORD -> RuntimeValueNumeric(type, (wordval!! - 1) and 65535)
DataType.BYTE -> { DataType.BYTE -> {
val newval = byteval!! - 1 val newval = byteval!! - 1
if(newval == -129) if (newval == -129)
RuntimeValue(type, 127) RuntimeValueNumeric(type, 127)
else else
RuntimeValue(type, newval) RuntimeValueNumeric(type, newval)
} }
DataType.WORD -> { DataType.WORD -> {
val newval = wordval!! - 1 val newval = wordval!! - 1
if(newval == -32769) if (newval == -32769)
RuntimeValue(type, 32767) RuntimeValueNumeric(type, 32767)
else else
RuntimeValue(type, newval) RuntimeValueNumeric(type, newval)
} }
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, floatval!! - 1) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, floatval!! - 1)
else -> throw ArithmeticException("dec can only work on numeric types") else -> throw ArithmeticException("dec can only work on numeric types")
} }
} }
fun msb(): RuntimeValue { fun msb(): RuntimeValueNumeric {
return when(type) { return when (type) {
in ByteDatatypes -> RuntimeValue(DataType.UBYTE, 0) in ByteDatatypes -> RuntimeValueNumeric(DataType.UBYTE, 0)
in WordDatatypes -> RuntimeValue(DataType.UBYTE, wordval!! ushr 8 and 255) in WordDatatypes -> RuntimeValueNumeric(DataType.UBYTE, wordval!! ushr 8 and 255)
else -> throw ArithmeticException("msb can only work on (u)byte/(u)word") else -> throw ArithmeticException("msb can only work on (u)byte/(u)word")
} }
} }
fun cast(targetType: DataType): RuntimeValue { fun cast(targetType: DataType): RuntimeValueNumeric {
return when (type) { return when (type) {
DataType.UBYTE -> { DataType.UBYTE -> {
when (targetType) { when (targetType) {
DataType.UBYTE -> this DataType.UBYTE -> this
DataType.BYTE -> { DataType.BYTE -> {
val nval=byteval!!.toInt() val nval = byteval!!.toInt()
if(nval<128) if (nval < 128)
RuntimeValue(DataType.BYTE, nval) RuntimeValueNumeric(DataType.BYTE, nval)
else else
RuntimeValue(DataType.BYTE, nval-256) RuntimeValueNumeric(DataType.BYTE, nval - 256)
} }
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue()) DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, numericValue())
DataType.WORD -> { DataType.WORD -> {
val nval = numericValue().toInt() val nval = numericValue().toInt()
if(nval<32768) if (nval < 32768)
RuntimeValue(DataType.WORD, nval) RuntimeValueNumeric(DataType.WORD, nval)
else else
RuntimeValue(DataType.WORD, nval-65536) RuntimeValueNumeric(DataType.WORD, nval - 65536)
} }
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue()) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType") else -> throw ArithmeticException("invalid type cast from $type to $targetType")
} }
} }
DataType.BYTE -> { DataType.BYTE -> {
when (targetType) { when (targetType) {
DataType.BYTE -> this DataType.BYTE -> this
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255) DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue() and 65535) DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue()) DataType.WORD -> RuntimeValueNumeric(DataType.WORD, integerValue())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue()) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType") else -> throw ArithmeticException("invalid type cast from $type to $targetType")
} }
} }
DataType.UWORD -> { DataType.UWORD -> {
when (targetType) { when (targetType) {
DataType.BYTE -> { DataType.BYTE -> {
val v=integerValue() val v = integerValue()
if(v<128) if (v < 128)
RuntimeValue(DataType.BYTE, v) RuntimeValueNumeric(DataType.BYTE, v)
else else
RuntimeValue(DataType.BYTE, v-256) RuntimeValueNumeric(DataType.BYTE, v - 256)
} }
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255) DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> this DataType.UWORD -> this
DataType.WORD -> { DataType.WORD -> {
val v=integerValue() val v = integerValue()
if(v<32768) if (v < 32768)
RuntimeValue(DataType.WORD, v) RuntimeValueNumeric(DataType.WORD, v)
else else
RuntimeValue(DataType.WORD, v-65536) RuntimeValueNumeric(DataType.WORD, v - 65536)
} }
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue()) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType") else -> throw ArithmeticException("invalid type cast from $type to $targetType")
} }
} }
@ -574,33 +530,33 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
when (targetType) { when (targetType) {
DataType.BYTE -> { DataType.BYTE -> {
val v = integerValue() and 255 val v = integerValue() and 255
if(v<128) if (v < 128)
RuntimeValue(DataType.BYTE, v) RuntimeValueNumeric(DataType.BYTE, v)
else else
RuntimeValue(DataType.BYTE, v-256) RuntimeValueNumeric(DataType.BYTE, v - 256)
} }
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 65535) DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 65535)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue()) DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue())
DataType.WORD -> this DataType.WORD -> this
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue()) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType") else -> throw ArithmeticException("invalid type cast from $type to $targetType")
} }
} }
DataType.FLOAT -> { DataType.FLOAT -> {
when (targetType) { when (targetType) {
DataType.BYTE -> { DataType.BYTE -> {
val integer=numericValue().toInt() val integer = numericValue().toInt()
if(integer in -128..127) if (integer in -128..127)
RuntimeValue(DataType.BYTE, integer) RuntimeValueNumeric(DataType.BYTE, integer)
else else
throw ArithmeticException("overflow when casting float to byte: $this") throw ArithmeticException("overflow when casting float to byte: $this")
} }
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, numericValue().toInt()) DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, numericValue().toInt())
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue().toInt()) DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, numericValue().toInt())
DataType.WORD -> { DataType.WORD -> {
val integer=numericValue().toInt() val integer = numericValue().toInt()
if(integer in -32768..32767) if (integer in -32768..32767)
RuntimeValue(DataType.WORD, integer) RuntimeValueNumeric(DataType.WORD, integer)
else else
throw ArithmeticException("overflow when casting float to word: $this") throw ArithmeticException("overflow when casting float to word: $this")
} }
@ -611,22 +567,91 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
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) { class RuntimeValueString(val str: String, val heapId: Int?): RuntimeValueBase(DataType.STR) {
in StringDatatypes -> { companion object {
Petscii.encodePetscii(str!!, true).iterator() fun fromLv(string: StringLiteralValue): RuntimeValueString {
} return RuntimeValueString(string.value, string.heapId)
in ArrayDatatypes -> {
array!!.iterator()
}
else -> throw IllegalArgumentException("cannot iterate over $this")
} }
} }
override fun toString(): String = if(type==DataType.STR) "str:$str" else "???"
override fun hashCode(): Int = Objects.hash(type, str)
override fun equals(other: Any?): Boolean {
if (other == null || other !is RuntimeValueString)
return false
return type == other.type && str == other.str
}
fun iterator(): Iterator<Number> = str.map { it.toShort() }.iterator()
override fun numericValue(): Number {
throw VmExecutionException("string is not a number")
}
override fun integerValue(): Int {
throw VmExecutionException("string is not a number")
}
} }
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValue(type, array=range.toList().toTypedArray()) { open class RuntimeValueArray(type: DataType, val array: Array<Number>, val heapId: Int?): RuntimeValueBase(type) {
companion object {
fun fromLv(array: ArrayLiteralValue): RuntimeValueArray {
return if (array.type == DataType.ARRAY_F) {
val doubleArray = array.value.map { (it as NumericLiteralValue).number }.toTypedArray()
RuntimeValueArray(array.type, doubleArray, array.heapId)
} else {
val resultArray = mutableListOf<Number>()
for (elt in array.value.withIndex()) {
if (elt.value is NumericLiteralValue)
resultArray.add((elt.value as NumericLiteralValue).number.toInt())
else {
resultArray.add((elt.hashCode())) // ...poor man's implementation of ADDRESSOF(array), it probably won't work very well
}
}
RuntimeValueArray(array.type, resultArray.toTypedArray(), array.heapId)
}
}
}
override fun toString(): String {
return when (type) {
DataType.ARRAY_UB -> "array_ub:..."
DataType.ARRAY_B -> "array_b:..."
DataType.ARRAY_UW -> "array_uw:..."
DataType.ARRAY_W -> "array_w:..."
DataType.ARRAY_F -> "array_f:..."
else -> "???"
}
}
override fun hashCode(): Int = Objects.hash(type, array)
override fun equals(other: Any?): Boolean {
if (other == null || other !is RuntimeValueArray)
return false
return type == other.type && array.contentEquals(other.array)
}
open fun iterator(): Iterator<Number> = array.iterator()
override fun numericValue(): Number {
throw VmExecutionException("array is not a number")
}
override fun integerValue(): Int {
throw VmExecutionException("array is not a number")
}
}
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValueArray(type, range.toList().toTypedArray(), null) {
override fun iterator(): Iterator<Number> { override fun iterator(): Iterator<Number> {
return range.iterator() return range.iterator()
} }

View File

@ -7,14 +7,10 @@ import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.* import prog8.ast.statements.*
import prog8.compiler.IntegerOrAddressOf import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.MachineDefinition import prog8.vm.*
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange
import java.awt.EventQueue import java.awt.EventQueue
import java.io.CharConversionException import java.util.ArrayDeque
import java.util.*
import kotlin.NoSuchElementException import kotlin.NoSuchElementException
import kotlin.concurrent.fixedRateTimer import kotlin.concurrent.fixedRateTimer
import kotlin.math.* import kotlin.math.*
@ -72,10 +68,14 @@ class StatusFlags {
class RuntimeVariables { class RuntimeVariables {
fun define(scope: INameScope, name: String, initialValue: RuntimeValue) { fun define(scope: INameScope, name: String, initialValue: RuntimeValueBase) {
val where = vars.getValue(scope) val where = vars.getValue(scope)
where[name] = initialValue where[name] = initialValue
vars[scope] = where vars[scope] = where
if(initialValue is RuntimeValueString)
byHeapId[initialValue.heapId!!] = initialValue
else if(initialValue is RuntimeValueArray)
byHeapId[initialValue.heapId!!] = initialValue
} }
fun defineMemory(scope: INameScope, name: String, address: Int) { fun defineMemory(scope: INameScope, name: String, address: Int) {
@ -84,7 +84,7 @@ class RuntimeVariables {
memvars[scope] = where memvars[scope] = where
} }
fun set(scope: INameScope, name: String, value: RuntimeValue) { fun set(scope: INameScope, name: String, value: RuntimeValueBase) {
val where = vars.getValue(scope) val where = vars.getValue(scope)
val existing = where[name] val existing = where[name]
if(existing==null) { if(existing==null) {
@ -96,9 +96,15 @@ class RuntimeVariables {
throw VmExecutionException("new value is of different datatype ${value.type} expected ${existing.type} for $name") throw VmExecutionException("new value is of different datatype ${value.type} expected ${existing.type} for $name")
where[name] = value where[name] = value
vars[scope] = where vars[scope] = where
if(value is RuntimeValueString)
byHeapId[value.heapId!!] = value
else if(value is RuntimeValueArray)
byHeapId[value.heapId!!] = value
} }
fun get(scope: INameScope, name: String): RuntimeValue { fun getByHeapId(heapId: Int): RuntimeValueBase = byHeapId.getValue(heapId)
fun get(scope: INameScope, name: String): RuntimeValueBase {
val where = vars.getValue(scope) val where = vars.getValue(scope)
return where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name") return where[name] ?: throw NoSuchElementException("no such runtime variable: ${scope.name}.$name")
} }
@ -108,8 +114,6 @@ class RuntimeVariables {
return where[name] ?: throw NoSuchElementException("no such runtime memory-variable: ${scope.name}.$name") return where[name] ?: throw NoSuchElementException("no such runtime memory-variable: ${scope.name}.$name")
} }
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) { fun swap(scope1: INameScope, name1: String, scope2: INameScope, name2: String) {
val v1 = get(scope1, name1) val v1 = get(scope1, name1)
val v2 = get(scope2, name2) val v2 = get(scope2, name2)
@ -117,12 +121,13 @@ class RuntimeVariables {
set(scope2, name2, v1) set(scope2, name2, v1)
} }
private val vars = mutableMapOf<INameScope, MutableMap<String, RuntimeValue>>().withDefault { mutableMapOf() } private val vars = mutableMapOf<INameScope, MutableMap<String, RuntimeValueBase>>().withDefault { mutableMapOf() }
private val memvars = mutableMapOf<INameScope, MutableMap<String, Int>>().withDefault { mutableMapOf() } private val memvars = mutableMapOf<INameScope, MutableMap<String, Int>>().withDefault { mutableMapOf() }
private val byHeapId = mutableMapOf<Int, RuntimeValueBase>()
} }
class AstVm(val program: Program) { class AstVm(val program: Program, compilationTarget: String) {
val mem = Memory(::memread, ::memwrite) val mem = Memory(::memread, ::memwrite)
val statusflags = StatusFlags() val statusflags = StatusFlags()
@ -133,13 +138,15 @@ class AstVm(val program: Program) {
var rtcOffset = bootTime var rtcOffset = bootTime
private val rnd = Random(0) private val rnd = Random(0)
private val statusFlagsSave = Stack<StatusFlags>() private val statusFlagsSave = ArrayDeque<StatusFlags>()
private val registerXsave = Stack<RuntimeValue>() private val registerXsave = ArrayDeque<RuntimeValueNumeric>()
private val registerYsave = Stack<RuntimeValue>() private val registerYsave = ArrayDeque<RuntimeValueNumeric>()
private val registerAsave = Stack<RuntimeValue>() private val registerAsave = ArrayDeque<RuntimeValueNumeric>()
init { init {
require(compilationTarget == "c64") {"using the AstVm only works for the C64 compiler target"}
// observe the jiffyclock and screen matrix // observe the jiffyclock and screen matrix
mem.observe(0xa0, 0xa1, 0xa2) mem.observe(0xa0, 0xa1, 0xa2)
for(i in 1024..2023) for(i in 1024..2023)
@ -166,23 +173,23 @@ class AstVm(val program: Program) {
fun memwrite(address: Int, value: Short): Short { fun memwrite(address: Int, value: Short): Short {
if(address==0xa0 || address==0xa1 || address==0xa2) { if(address==0xa0 || address==0xa1 || address==0xa2) {
// a write to the jiffy clock, update the clock offset for the irq // a write to the jiffy clock, update the clock offset for the irq
val time_hi = if(address==0xa0) value else mem.getUByte_DMA(0xa0) val timeHi = if(address==0xa0) value else mem.getUByteDirectly(0xa0)
val time_mid = if(address==0xa1) value else mem.getUByte_DMA(0xa1) val timeMid = if(address==0xa1) value else mem.getUByteDirectly(0xa1)
val time_lo = if(address==0xa2) value else mem.getUByte_DMA(0xa2) val timeLo = if(address==0xa2) value else mem.getUByteDirectly(0xa2)
val jiffies = (time_hi.toInt() shl 16) + (time_mid.toInt() shl 8) + time_lo val jiffies = (timeHi.toInt() shl 16) + (timeMid.toInt() shl 8) + timeLo
rtcOffset = bootTime - (jiffies*1000/60) rtcOffset = bootTime - (jiffies*1000/60)
} }
if(address in 1024..2023) { if(address in 1024..2023) {
// write to the screen matrix // write to the screen matrix
val scraddr = address-1024 val scraddr = address-1024
dialog.canvas.setChar(scraddr % 40, scraddr / 40, value, 1) dialog.canvas.setScreenChar(scraddr % 40, scraddr / 40, value, 1)
} }
return value return value
} }
fun run() { fun run() {
try { try {
val init = VariablesCreator(runtimeVariables, program.heap) val init = VariablesCreator(runtimeVariables)
init.visit(program) init.visit(program)
// initialize all global variables // initialize all global variables
@ -231,7 +238,7 @@ class AstVm(val program: Program) {
} }
} }
} }
dialog.canvas.printText("\n<program ended>", true) dialog.canvas.printAsciiText("\n<program ended>")
println("PROGRAM EXITED!") println("PROGRAM EXITED!")
dialog.title = "PROGRAM EXITED" dialog.title = "PROGRAM EXITED"
} catch (tx: VmTerminationException) { } catch (tx: VmTerminationException) {
@ -253,9 +260,9 @@ class AstVm(val program: Program) {
rtcOffset = timeStamp rtcOffset = timeStamp
} }
// update the C-64 60hz jiffy clock in the ZP addresses: // update the C-64 60hz jiffy clock in the ZP addresses:
mem.setUByte_DMA(0x00a0, (jiffies ushr 16).toShort()) mem.setUByteDirectly(0x00a0, (jiffies ushr 16).toShort())
mem.setUByte_DMA(0x00a1, (jiffies ushr 8 and 255).toShort()) mem.setUByteDirectly(0x00a1, (jiffies ushr 8 and 255).toShort())
mem.setUByte_DMA(0x00a2, (jiffies and 255).toShort()) mem.setUByteDirectly(0x00a2, (jiffies and 255).toShort())
} }
private val runtimeVariables = RuntimeVariables() private val runtimeVariables = RuntimeVariables()
@ -263,11 +270,11 @@ class AstVm(val program: Program) {
class LoopControlBreak : Exception() class LoopControlBreak : Exception()
class LoopControlContinue : Exception() class LoopControlContinue : Exception()
class LoopControlReturn(val returnvalue: RuntimeValue?) : Exception() class LoopControlReturn(val returnvalue: RuntimeValueNumeric?) : Exception()
class LoopControlJump(val identifier: IdentifierReference?, val address: Int?, val generatedLabel: String?) : Exception() class LoopControlJump(val identifier: IdentifierReference?, val address: Int?, val generatedLabel: String?) : Exception()
internal fun executeSubroutine(sub: Subroutine, arguments: List<RuntimeValue>, startAtLabel: Label?=null): RuntimeValue? { internal fun executeSubroutine(sub: Subroutine, arguments: List<RuntimeValueNumeric>, startAtLabel: Label?=null): RuntimeValueNumeric? {
if(sub.isAsmSubroutine) { if(sub.isAsmSubroutine) {
return performSyscall(sub, arguments) return performSyscall(sub, arguments)
} }
@ -332,10 +339,9 @@ class AstVm(val program: Program) {
// should have been defined already when the program started // should have been defined already when the program started
} }
is FunctionCallStatement -> { is FunctionCallStatement -> {
val target = stmt.target.targetStatement(program.namespace) when (val target = stmt.target.targetStatement(program.namespace)) {
when (target) {
is Subroutine -> { is Subroutine -> {
val args = evaluate(stmt.arglist) val args = evaluate(stmt.args).map { it as RuntimeValueNumeric }
if (target.isAsmSubroutine) { if (target.isAsmSubroutine) {
performSyscall(target, args) performSyscall(target, args)
} else { } else {
@ -348,7 +354,7 @@ class AstVm(val program: Program) {
// swap cannot be implemented as a function, so inline it here // swap cannot be implemented as a function, so inline it here
executeSwap(stmt) executeSwap(stmt)
} else { } else {
val args = evaluate(stmt.arglist) val args = evaluate(stmt.args)
performBuiltinFunction(target.name, args, statusflags) performBuiltinFunction(target.name, args, statusflags)
} }
} }
@ -362,7 +368,7 @@ class AstVm(val program: Program) {
if(stmt.value==null) if(stmt.value==null)
null null
else else
evaluate(stmt.value!!, evalCtx) evaluate(stmt.value!!, evalCtx) as RuntimeValueNumeric
throw LoopControlReturn(value) throw LoopControlReturn(value)
} }
is Continue -> throw LoopControlContinue() is Continue -> throw LoopControlContinue()
@ -380,19 +386,19 @@ class AstVm(val program: Program) {
val identScope = ident.definingScope() val identScope = ident.definingScope()
when(ident.type){ when(ident.type){
VarDeclType.VAR -> { VarDeclType.VAR -> {
var value = runtimeVariables.get(identScope, ident.name) var value = runtimeVariables.get(identScope, ident.name) as RuntimeValueNumeric
value = when { value = when (stmt.operator) {
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1)) "++" -> value.add(RuntimeValueNumeric(value.type, 1))
stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1)) "--" -> value.sub(RuntimeValueNumeric(value.type, 1))
else -> throw VmExecutionException("strange postincdec operator $stmt") else -> throw VmExecutionException("strange postincdec operator $stmt")
} }
runtimeVariables.set(identScope, ident.name, value) runtimeVariables.set(identScope, ident.name, value)
} }
VarDeclType.MEMORY -> { VarDeclType.MEMORY -> {
val addr=ident.value!!.constValue(program)!!.number.toInt() val addr=ident.value!!.constValue(program)!!.number.toInt()
val newval = when { val newval = when (stmt.operator) {
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255 "++" -> mem.getUByte(addr)+1 and 255
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255 "--" -> mem.getUByte(addr)-1 and 255
else -> throw VmExecutionException("strange postincdec operator $stmt") else -> throw VmExecutionException("strange postincdec operator $stmt")
} }
mem.setUByte(addr,newval.toShort()) mem.setUByte(addr,newval.toShort())
@ -401,32 +407,34 @@ class AstVm(val program: Program) {
} }
} }
stmt.target.memoryAddress != null -> { stmt.target.memoryAddress != null -> {
val addr = evaluate(stmt.target.memoryAddress!!.addressExpression, evalCtx).integerValue() val addr = (evaluate(stmt.target.memoryAddress!!.addressExpression, evalCtx) as RuntimeValueNumeric).integerValue()
val newval = when { val newval = when (stmt.operator) {
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255 "++" -> mem.getUByte(addr)+1 and 255
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255 "--" -> mem.getUByte(addr)-1 and 255
else -> throw VmExecutionException("strange postincdec operator $stmt") else -> throw VmExecutionException("strange postincdec operator $stmt")
} }
mem.setUByte(addr,newval.toShort()) mem.setUByte(addr,newval.toShort())
} }
stmt.target.arrayindexed != null -> { stmt.target.arrayindexed != null -> {
val arrayvar = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!! val arrayvar = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name) val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name) as RuntimeValueArray
val elementType = stmt.target.arrayindexed!!.inferType(program)!! val index = (evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx) as RuntimeValueNumeric).integerValue()
val index = evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx).integerValue() val elementType = stmt.target.arrayindexed!!.inferType(program)
var value = RuntimeValue(elementType, arrayvalue.array!![index].toInt()) if(!elementType.isKnown)
value = when { throw VmExecutionException("unknown/void elt type")
stmt.operator == "++" -> value.inc() var value = RuntimeValueNumeric(elementType.typeOrElse(DataType.BYTE), arrayvalue.array[index].toInt())
stmt.operator == "--" -> value.dec() value = when (stmt.operator) {
"++" -> value.inc()
"--" -> value.dec()
else -> throw VmExecutionException("strange postincdec operator $stmt") else -> throw VmExecutionException("strange postincdec operator $stmt")
} }
arrayvalue.array[index] = value.numericValue() arrayvalue.array[index] = value.numericValue()
} }
stmt.target.register != null -> { stmt.target.register != null -> {
var value = runtimeVariables.get(program.namespace, stmt.target.register!!.name) var value = runtimeVariables.get(program.namespace, stmt.target.register!!.name) as RuntimeValueNumeric
value = when { value = when (stmt.operator) {
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1)) "++" -> value.add(RuntimeValueNumeric(value.type, 1))
stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1)) "--" -> value.sub(RuntimeValueNumeric(value.type, 1))
else -> throw VmExecutionException("strange postincdec operator $stmt") else -> throw VmExecutionException("strange postincdec operator $stmt")
} }
runtimeVariables.set(program.namespace, stmt.target.register!!.name, value) runtimeVariables.set(program.namespace, stmt.target.register!!.name, value)
@ -437,7 +445,7 @@ class AstVm(val program: Program) {
is Jump -> throw LoopControlJump(stmt.identifier, stmt.address, stmt.generatedLabel) is Jump -> throw LoopControlJump(stmt.identifier, stmt.address, stmt.generatedLabel)
is InlineAssembly -> { is InlineAssembly -> {
if (sub is Subroutine) { if (sub is Subroutine) {
val args = sub.parameters.map { runtimeVariables.get(sub, it.name) } val args = sub.parameters.map { runtimeVariables.get(sub, it.name) as RuntimeValueNumeric }
performSyscall(sub, args) performSyscall(sub, args)
throw LoopControlReturn(null) throw LoopControlReturn(null)
} }
@ -445,7 +453,7 @@ class AstVm(val program: Program) {
} }
is AnonymousScope -> executeAnonymousScope(stmt) is AnonymousScope -> executeAnonymousScope(stmt)
is IfStatement -> { is IfStatement -> {
val condition = evaluate(stmt.condition, evalCtx) val condition = evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
if (condition.asBoolean) if (condition.asBoolean)
executeAnonymousScope(stmt.truepart) executeAnonymousScope(stmt.truepart)
else else
@ -472,10 +480,17 @@ class AstVm(val program: Program) {
loopvarDt = DataType.UBYTE loopvarDt = DataType.UBYTE
loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position) loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position)
} else { } else {
loopvarDt = stmt.loopVar!!.inferType(program)!! val dt = stmt.loopVar!!.inferType(program)
loopvarDt = dt.typeOrElse(DataType.UBYTE)
loopvar = stmt.loopVar!! loopvar = stmt.loopVar!!
} }
val iterator = iterable.iterator() val iterator =
when (iterable) {
is RuntimeValueRange -> iterable.iterator()
is RuntimeValueArray -> iterable.iterator()
is RuntimeValueString -> iterable.iterator()
else -> throw VmExecutionException("not iterable")
}
for (loopvalue in iterator) { for (loopvalue in iterator) {
try { try {
oneForCycle(stmt, loopvarDt, loopvalue, loopvar) oneForCycle(stmt, loopvarDt, loopvalue, loopvar)
@ -487,11 +502,11 @@ class AstVm(val program: Program) {
} }
} }
is WhileLoop -> { is WhileLoop -> {
var condition = evaluate(stmt.condition, evalCtx) var condition = evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
while (condition.asBoolean) { while (condition.asBoolean) {
try { try {
executeAnonymousScope(stmt.body) executeAnonymousScope(stmt.body)
condition = evaluate(stmt.condition, evalCtx) condition = evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
} catch (b: LoopControlBreak) { } catch (b: LoopControlBreak) {
break break
} catch (c: LoopControlContinue) { } catch (c: LoopControlContinue) {
@ -501,7 +516,7 @@ class AstVm(val program: Program) {
} }
is RepeatLoop -> { is RepeatLoop -> {
do { do {
val condition = evaluate(stmt.untilCondition, evalCtx) val condition = evaluate(stmt.untilCondition, evalCtx) as RuntimeValueNumeric
try { try {
executeAnonymousScope(stmt.body) executeAnonymousScope(stmt.body)
} catch (b: LoopControlBreak) { } catch (b: LoopControlBreak) {
@ -512,7 +527,7 @@ class AstVm(val program: Program) {
} while (!condition.asBoolean) } while (!condition.asBoolean)
} }
is WhenStatement -> { is WhenStatement -> {
val condition=evaluate(stmt.condition, evalCtx) val condition=evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
for(choice in stmt.choices) { for(choice in stmt.choices) {
if(choice.values==null) { if(choice.values==null) {
// the 'else' choice // the 'else' choice
@ -520,7 +535,7 @@ class AstVm(val program: Program) {
break break
} else { } else {
val value = choice.values!!.single().constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}") val value = choice.values!!.single().constValue(evalCtx.program) ?: throw VmExecutionException("can only use const values in when choices ${choice.position}")
val rtval = RuntimeValue.fromLv(value) val rtval = RuntimeValueNumeric.fromLv(value)
if(condition==rtval) { if(condition==rtval) {
executeAnonymousScope(choice.statements) executeAnonymousScope(choice.statements)
break break
@ -535,8 +550,8 @@ class AstVm(val program: Program) {
} }
private fun executeSwap(swap: FunctionCallStatement) { private fun executeSwap(swap: FunctionCallStatement) {
val v1 = swap.arglist[0] val v1 = swap.args[0]
val v2 = swap.arglist[1] val v2 = swap.args[1]
val value1 = evaluate(v1, evalCtx) val value1 = evaluate(v1, evalCtx)
val value2 = evaluate(v2, evalCtx) val value2 = evaluate(v2, evalCtx)
val target1 = AssignTarget.fromExpr(v1) val target1 = AssignTarget.fromExpr(v1)
@ -545,7 +560,7 @@ class AstVm(val program: Program) {
performAssignment(target2, value1, swap, evalCtx) performAssignment(target2, value1, swap, evalCtx)
} }
fun performAssignment(target: AssignTarget, value: RuntimeValue, contextStmt: Statement, evalCtx: EvalContext) { fun performAssignment(target: AssignTarget, value: RuntimeValueBase, contextStmt: Statement, evalCtx: EvalContext) {
val targetIdent = target.identifier val targetIdent = target.identifier
val targetArrayIndexed = target.arrayindexed val targetArrayIndexed = target.arrayindexed
when { when {
@ -555,27 +570,26 @@ class AstVm(val program: Program) {
if (decl.type == VarDeclType.MEMORY) { if (decl.type == VarDeclType.MEMORY) {
val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name) val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name)
when (decl.datatype) { when (decl.datatype) {
DataType.UBYTE -> mem.setUByte(address, value.byteval!!) DataType.UBYTE -> mem.setUByte(address, (value as RuntimeValueNumeric).byteval!!)
DataType.BYTE -> mem.setSByte(address, value.byteval!!) DataType.BYTE -> mem.setSByte(address, (value as RuntimeValueNumeric).byteval!!)
DataType.UWORD -> mem.setUWord(address, value.wordval!!) DataType.UWORD -> mem.setUWord(address, (value as RuntimeValueNumeric).wordval!!)
DataType.WORD -> mem.setSWord(address, value.wordval!!) DataType.WORD -> mem.setSWord(address, (value as RuntimeValueNumeric).wordval!!)
DataType.FLOAT -> mem.setFloat(address, value.floatval!!) DataType.FLOAT -> mem.setFloat(address, (value as RuntimeValueNumeric).floatval!!)
DataType.STR -> mem.setString(address, value.str!!) DataType.STR -> mem.setString(address, (value as RuntimeValueString).str)
DataType.STR_S -> mem.setScreencodeString(address, value.str!!)
else -> throw VmExecutionException("weird memaddress type $decl") else -> throw VmExecutionException("weird memaddress type $decl")
} }
} else } else
runtimeVariables.set(decl.definingScope(), decl.name, value) runtimeVariables.set(decl.definingScope(), decl.name, value)
} }
target.memoryAddress != null -> { target.memoryAddress != null -> {
val address = evaluate(target.memoryAddress.addressExpression, evalCtx).wordval!! val address = (evaluate(target.memoryAddress.addressExpression, evalCtx) as RuntimeValueNumeric).wordval!!
evalCtx.mem.setUByte(address, value.byteval!!) evalCtx.mem.setUByte(address, (value as RuntimeValueNumeric).byteval!!)
} }
targetArrayIndexed != null -> { targetArrayIndexed != null -> {
val vardecl = targetArrayIndexed.identifier.targetVarDecl(program.namespace)!! val vardecl = targetArrayIndexed.identifier.targetVarDecl(program.namespace)!!
if(vardecl.type==VarDeclType.VAR) { if(vardecl.type==VarDeclType.VAR) {
val array = evaluate(targetArrayIndexed.identifier, evalCtx) val array = evaluate(targetArrayIndexed.identifier, evalCtx)
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx) val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx) as RuntimeValueNumeric
when (array.type) { when (array.type) {
DataType.ARRAY_UB -> { DataType.ARRAY_UB -> {
if (value.type != DataType.UBYTE) if (value.type != DataType.UBYTE)
@ -597,35 +611,37 @@ class AstVm(val program: Program) {
if (value.type != DataType.FLOAT) if (value.type != DataType.FLOAT)
throw VmExecutionException("new value is of different datatype ${value.type} for $array") throw VmExecutionException("new value is of different datatype ${value.type} for $array")
} }
DataType.STR, DataType.STR_S -> { DataType.STR -> {
if (value.type !in ByteDatatypes) if (value.type !in ByteDatatypes)
throw VmExecutionException("new value is of different datatype ${value.type} for $array") throw VmExecutionException("new value is of different datatype ${value.type} for $array")
} }
else -> throw VmExecutionException("strange array type ${array.type}") else -> throw VmExecutionException("strange array type ${array.type}")
} }
if (array.type in ArrayDatatypes) if (array.type in ArrayDatatypes)
array.array!![index.integerValue()] = value.numericValue() (array as RuntimeValueArray).array[index.integerValue()] = value.numericValue()
else if (array.type in StringDatatypes) { else if (array.type == DataType.STR) {
val indexInt = index.integerValue() val indexInt = index.integerValue()
val newchr = Petscii.decodePetscii(listOf(value.numericValue().toShort()), true) val newchr = value.numericValue().toChar().toString()
val newstr = array.str!!.replaceRange(indexInt, indexInt + 1, newchr) val newstr = (array as RuntimeValueString).str.replaceRange(indexInt, indexInt + 1, newchr)
val ident = contextStmt.definingScope().lookup(targetArrayIndexed.identifier.nameInSource, contextStmt) as? VarDecl val ident = contextStmt.definingScope().lookup(targetArrayIndexed.identifier.nameInSource, contextStmt) as? VarDecl
?: throw VmExecutionException("can't find assignment target ${target.identifier}") ?: throw VmExecutionException("can't find assignment target ${target.identifier}")
val identScope = ident.definingScope() val identScope = ident.definingScope()
program.heap.update(array.heapId!!, newstr) runtimeVariables.set(identScope, ident.name, RuntimeValueString(newstr, array.heapId))
runtimeVariables.set(identScope, ident.name, RuntimeValue(array.type, str = newstr, heapId = array.heapId))
} }
} }
else { else {
value as RuntimeValueNumeric
val address = (vardecl.value as NumericLiteralValue).number.toInt() val address = (vardecl.value as NumericLiteralValue).number.toInt()
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx).integerValue() val index = (evaluate(targetArrayIndexed.arrayspec.index, evalCtx) as RuntimeValueNumeric).integerValue()
val elementType = targetArrayIndexed.inferType(program)!! val elementType = targetArrayIndexed.inferType(program)
when(elementType) { if(!elementType.isKnown)
throw VmExecutionException("unknown/void array elt type $targetArrayIndexed")
when(elementType.typeOrElse(DataType.UBYTE)) {
DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!) DataType.UBYTE -> mem.setUByte(address+index, value.byteval!!)
DataType.BYTE -> mem.setSByte(address+index, value.byteval!!) DataType.BYTE -> mem.setSByte(address+index, value.byteval!!)
DataType.UWORD -> mem.setUWord(address+index*2, value.wordval!!) DataType.UWORD -> mem.setUWord(address+index*2, value.wordval!!)
DataType.WORD -> mem.setSWord(address+index*2, value.wordval!!) DataType.WORD -> mem.setSWord(address+index*2, value.wordval!!)
DataType.FLOAT -> mem.setFloat(address+index* MachineDefinition.Mflpt5.MemorySize, value.floatval!!) DataType.FLOAT -> mem.setFloat(address+index* C64MachineDefinition.FLOAT_MEM_SIZE, value.floatval!!)
else -> throw VmExecutionException("strange array elt type $elementType") else -> throw VmExecutionException("strange array elt type $elementType")
} }
} }
@ -640,60 +656,60 @@ class AstVm(val program: Program) {
private fun oneForCycle(stmt: ForLoop, loopvarDt: DataType, loopValue: Number, loopVar: IdentifierReference) { private fun oneForCycle(stmt: ForLoop, loopvarDt: DataType, loopValue: Number, loopVar: IdentifierReference) {
// assign the new loop value to the loopvar, and run the code // assign the new loop value to the loopvar, and run the code
performAssignment(AssignTarget(null, loopVar, null, null, loopVar.position), performAssignment(AssignTarget(null, loopVar, null, null, loopVar.position),
RuntimeValue(loopvarDt, loopValue), stmt.body.statements.first(), evalCtx) RuntimeValueNumeric(loopvarDt, loopValue), stmt.body.statements.first(), evalCtx)
executeAnonymousScope(stmt.body) executeAnonymousScope(stmt.body)
} }
private fun evaluate(args: List<Expression>) = args.map { evaluate(it, evalCtx) } private fun evaluate(args: List<Expression>) = args.map { evaluate(it, evalCtx) }
private fun performSyscall(sub: Subroutine, args: List<RuntimeValue>): RuntimeValue? { private fun performSyscall(sub: Subroutine, args: List<RuntimeValueNumeric>): RuntimeValueNumeric? {
var result: RuntimeValue? = null var result: RuntimeValueNumeric? = null
when (sub.scopedname) { when (sub.scopedname) {
"c64scr.print" -> { "c64scr.print" -> {
// if the argument is an UWORD, consider it to be the "address" of the string (=heapId) // if the argument is an UWORD, consider it to be the "address" of the string (=heapId)
if (args[0].wordval != null) { if (args[0].wordval != null) {
val str = program.heap.get(args[0].wordval!!).str!! val string = getAsciiStringFromRuntimeVars(args[0].wordval!!)
dialog.canvas.printText(str, true) dialog.canvas.printAsciiText(string)
} else } else
dialog.canvas.printText(args[0].str!!, true) throw VmExecutionException("print non-heap string")
} }
"c64scr.print_ub" -> { "c64scr.print_ub" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), true) dialog.canvas.printAsciiText(args[0].byteval!!.toString())
} }
"c64scr.print_ub0" -> { "c64scr.print_ub0" -> {
dialog.canvas.printText("%03d".format(args[0].byteval!!), true) dialog.canvas.printAsciiText("%03d".format(args[0].byteval!!))
} }
"c64scr.print_b" -> { "c64scr.print_b" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), true) dialog.canvas.printAsciiText(args[0].byteval!!.toString())
} }
"c64scr.print_uw" -> { "c64scr.print_uw" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), true) dialog.canvas.printAsciiText(args[0].wordval!!.toString())
} }
"c64scr.print_uw0" -> { "c64scr.print_uw0" -> {
dialog.canvas.printText("%05d".format(args[0].wordval!!), true) dialog.canvas.printAsciiText("%05d".format(args[0].wordval!!))
} }
"c64scr.print_w" -> { "c64scr.print_w" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), true) dialog.canvas.printAsciiText(args[0].wordval!!.toString())
} }
"c64scr.print_ubhex" -> { "c64scr.print_ubhex" -> {
val number = args[0].byteval!! val number = args[0].byteval!!
val prefix = if (args[1].asBoolean) "$" else "" val prefix = if (args[1].asBoolean) "$" else ""
dialog.canvas.printText("$prefix${number.toString(16).padStart(2, '0')}", true) dialog.canvas.printAsciiText("$prefix${number.toString(16).padStart(2, '0')}")
} }
"c64scr.print_uwhex" -> { "c64scr.print_uwhex" -> {
val number = args[0].wordval!! val number = args[0].wordval!!
val prefix = if (args[1].asBoolean) "$" else "" val prefix = if (args[1].asBoolean) "$" else ""
dialog.canvas.printText("$prefix${number.toString(16).padStart(4, '0')}", true) dialog.canvas.printAsciiText("$prefix${number.toString(16).padStart(4, '0')}")
} }
"c64scr.print_uwbin" -> { "c64scr.print_uwbin" -> {
val number = args[0].wordval!! val number = args[0].wordval!!
val prefix = if (args[1].asBoolean) "%" else "" val prefix = if (args[1].asBoolean) "%" else ""
dialog.canvas.printText("$prefix${number.toString(2).padStart(16, '0')}", true) dialog.canvas.printAsciiText("$prefix${number.toString(2).padStart(16, '0')}")
} }
"c64scr.print_ubbin" -> { "c64scr.print_ubbin" -> {
val number = args[0].byteval!! val number = args[0].byteval!!
val prefix = if (args[1].asBoolean) "%" else "" val prefix = if (args[1].asBoolean) "%" else ""
dialog.canvas.printText("$prefix${number.toString(2).padStart(8, '0')}", true) dialog.canvas.printAsciiText("$prefix${number.toString(2).padStart(8, '0')}")
} }
"c64scr.clear_screenchars" -> { "c64scr.clear_screenchars" -> {
dialog.canvas.clearScreen(6) dialog.canvas.clearScreen(6)
@ -702,7 +718,7 @@ class AstVm(val program: Program) {
dialog.canvas.clearScreen(args[0].integerValue().toShort()) dialog.canvas.clearScreen(args[0].integerValue().toShort())
} }
"c64scr.setcc" -> { "c64scr.setcc" -> {
dialog.canvas.setChar(args[0].integerValue(), args[1].integerValue(), args[2].integerValue().toShort(), args[3].integerValue().toShort()) dialog.canvas.setScreenChar(args[0].integerValue(), args[1].integerValue(), args[2].integerValue().toShort(), args[3].integerValue().toShort())
} }
"c64scr.plot" -> { "c64scr.plot" -> {
dialog.canvas.setCursorPos(args[0].integerValue(), args[1].integerValue()) dialog.canvas.setCursorPos(args[0].integerValue(), args[1].integerValue())
@ -718,26 +734,23 @@ class AstVm(val program: Program) {
break break
else { else {
input.add(char) input.add(char)
val printChar = try { dialog.canvas.printAsciiText(char.toString())
Petscii.encodePetscii("" + char, true).first()
} catch (cv: CharConversionException) {
0x3f.toShort()
}
dialog.canvas.printPetscii(printChar)
} }
} }
val inputStr = input.joinToString("") var inputStr = input.joinToString("")
val inputLength = inputStr.length
val heapId = args[0].wordval!! val heapId = args[0].wordval!!
val origStr = program.heap.get(heapId).str!! val origStrLength = getAsciiStringFromRuntimeVars(heapId).length
val paddedStr=inputStr.padEnd(origStr.length+1, '\u0000').substring(0, origStr.length) while(inputStr.length < origStrLength) {
program.heap.update(heapId, paddedStr) inputStr += '\u0000'
result = RuntimeValue(DataType.UBYTE, paddedStr.indexOf('\u0000')) }
result = RuntimeValueNumeric(DataType.UBYTE, inputLength)
} }
"c64flt.print_f" -> { "c64flt.print_f" -> {
dialog.canvas.printText(args[0].floatval.toString(), false) dialog.canvas.printAsciiText(args[0].floatval.toString())
} }
"c64.CHROUT" -> { "c64.CHROUT" -> {
dialog.canvas.printPetscii(args[0].byteval!!) dialog.canvas.printPetsciiChar(args[0].byteval!!)
} }
"c64.CLEARSCR" -> { "c64.CLEARSCR" -> {
dialog.canvas.clearScreen(6) dialog.canvas.clearScreen(6)
@ -747,13 +760,20 @@ class AstVm(val program: Program) {
Thread.sleep(10) Thread.sleep(10)
} }
val char=dialog.keyboardBuffer.pop() val char=dialog.keyboardBuffer.pop()
result = RuntimeValue(DataType.UBYTE, char.toShort()) result = RuntimeValueNumeric(DataType.UBYTE, char.toShort())
}
"c64.GETIN" -> {
Thread.sleep(1)
result = if(dialog.keyboardBuffer.isEmpty())
RuntimeValueNumeric(DataType.UBYTE, 0)
else
RuntimeValueNumeric(DataType.UBYTE, dialog.keyboardBuffer.pop().toShort())
} }
"c64utils.str2uword" -> { "c64utils.str2uword" -> {
val heapId = args[0].wordval!! val heapId = args[0].wordval!!
val argString = program.heap.get(heapId).str!! val argString = getAsciiStringFromRuntimeVars(heapId)
val numericpart = argString.takeWhile { it.isDigit() } val numericpart = argString.takeWhile { it.isDigit() }
result = RuntimeValue(DataType.UWORD, numericpart.toInt() and 65535) result = RuntimeValueNumeric(DataType.UWORD, numericpart.toInt() and 65535)
} }
else -> TODO("syscall ${sub.scopedname} $sub") else -> TODO("syscall ${sub.scopedname} $sub")
} }
@ -761,148 +781,152 @@ class AstVm(val program: Program) {
return result return result
} }
private fun performBuiltinFunction(name: String, args: List<RuntimeValue>, statusflags: StatusFlags): RuntimeValue? { private fun getAsciiStringFromRuntimeVars(heapId: Int): String =
(runtimeVariables.getByHeapId(heapId) as RuntimeValueString).str
private fun getArrayFromRuntimeVars(heapId: Int): IntArray {
val arrayvar = runtimeVariables.getByHeapId(heapId) as RuntimeValueArray
return arrayvar.array.map { it.toInt() }.toIntArray()
}
private fun performBuiltinFunction(name: String, args: List<RuntimeValueBase>, statusflags: StatusFlags): RuntimeValueNumeric? {
return when (name) { return when (name) {
"rnd" -> RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255) "rnd" -> RuntimeValueNumeric(DataType.UBYTE, rnd.nextInt() and 255)
"rndw" -> RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535) "rndw" -> RuntimeValueNumeric(DataType.UWORD, rnd.nextInt() and 65535)
"rndf" -> RuntimeValue(DataType.FLOAT, rnd.nextDouble()) "rndf" -> RuntimeValueNumeric(DataType.FLOAT, rnd.nextDouble())
"lsb" -> RuntimeValue(DataType.UBYTE, args[0].integerValue() and 255) "lsb" -> RuntimeValueNumeric(DataType.UBYTE, args[0].integerValue() and 255)
"msb" -> RuntimeValue(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255) "msb" -> RuntimeValueNumeric(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
"sin" -> RuntimeValue(DataType.FLOAT, sin(args[0].numericValue().toDouble())) "sin" -> RuntimeValueNumeric(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
"sin8" -> { "sin8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * sin(rad)).toShort()) RuntimeValueNumeric(DataType.BYTE, (127.0 * sin(rad)).toShort())
} }
"sin8u" -> { "sin8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort()) RuntimeValueNumeric(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
} }
"sin16" -> { "sin16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * sin(rad)).toShort()) RuntimeValueNumeric(DataType.BYTE, (32767.0 * sin(rad)).toShort())
} }
"sin16u" -> { "sin16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort()) RuntimeValueNumeric(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort())
} }
"cos" -> RuntimeValue(DataType.FLOAT, cos(args[0].numericValue().toDouble())) "cos" -> RuntimeValueNumeric(DataType.FLOAT, cos(args[0].numericValue().toDouble()))
"cos8" -> { "cos8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * cos(rad)).toShort()) RuntimeValueNumeric(DataType.BYTE, (127.0 * cos(rad)).toShort())
} }
"cos8u" -> { "cos8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort()) RuntimeValueNumeric(DataType.UBYTE, (128.0 + 127.5 * cos(rad)).toShort())
} }
"cos16" -> { "cos16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * cos(rad)).toShort()) RuntimeValueNumeric(DataType.BYTE, (32767.0 * cos(rad)).toShort())
} }
"cos16u" -> { "cos16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * cos(rad)).toShort()) RuntimeValueNumeric(DataType.UBYTE, (32768.0 + 32767.5 * cos(rad)).toShort())
} }
"tan" -> RuntimeValue(DataType.FLOAT, tan(args[0].numericValue().toDouble())) "tan" -> RuntimeValueNumeric(DataType.FLOAT, tan(args[0].numericValue().toDouble()))
"atan" -> RuntimeValue(DataType.FLOAT, atan(args[0].numericValue().toDouble())) "atan" -> RuntimeValueNumeric(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
"ln" -> RuntimeValue(DataType.FLOAT, ln(args[0].numericValue().toDouble())) "ln" -> RuntimeValueNumeric(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
"log2" -> RuntimeValue(DataType.FLOAT, log2(args[0].numericValue().toDouble())) "log2" -> RuntimeValueNumeric(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
"sqrt" -> RuntimeValue(DataType.FLOAT, sqrt(args[0].numericValue().toDouble())) "sqrt" -> RuntimeValueNumeric(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
"sqrt16" -> RuntimeValue(DataType.UBYTE, sqrt(args[0].wordval!!.toDouble()).toInt()) "sqrt16" -> RuntimeValueNumeric(DataType.UBYTE, sqrt((args[0] as RuntimeValueNumeric).wordval!!.toDouble()).toInt())
"rad" -> RuntimeValue(DataType.FLOAT, Math.toRadians(args[0].numericValue().toDouble())) "rad" -> RuntimeValueNumeric(DataType.FLOAT, Math.toRadians(args[0].numericValue().toDouble()))
"deg" -> RuntimeValue(DataType.FLOAT, Math.toDegrees(args[0].numericValue().toDouble())) "deg" -> RuntimeValueNumeric(DataType.FLOAT, Math.toDegrees(args[0].numericValue().toDouble()))
"round" -> RuntimeValue(DataType.FLOAT, round(args[0].numericValue().toDouble())) "round" -> RuntimeValueNumeric(DataType.FLOAT, round(args[0].numericValue().toDouble()))
"floor" -> RuntimeValue(DataType.FLOAT, floor(args[0].numericValue().toDouble())) "floor" -> RuntimeValueNumeric(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
"ceil" -> RuntimeValue(DataType.FLOAT, ceil(args[0].numericValue().toDouble())) "ceil" -> RuntimeValueNumeric(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
"rol" -> { "rol" -> {
val (result, newCarry) = args[0].rol(statusflags.carry) val (result, newCarry) = (args[0] as RuntimeValueNumeric).rol(statusflags.carry)
statusflags.carry = newCarry statusflags.carry = newCarry
return result return result
} }
"rol2" -> args[0].rol2() "rol2" -> (args[0] as RuntimeValueNumeric).rol2()
"ror" -> { "ror" -> {
val (result, newCarry) = args[0].ror(statusflags.carry) val (result, newCarry) = (args[0] as RuntimeValueNumeric).ror(statusflags.carry)
statusflags.carry = newCarry statusflags.carry = newCarry
return result return result
} }
"ror2" -> args[0].ror2() "ror2" -> (args[0] as RuntimeValueNumeric).ror2()
"lsl" -> args[0].shl() "lsl" -> (args[0] as RuntimeValueNumeric).shl()
"lsr" -> args[0].shr() "lsr" -> (args[0] as RuntimeValueNumeric).shr()
"abs" -> { "abs" -> {
when (args[0].type) { when (args[0].type) {
DataType.UBYTE -> args[0] DataType.UBYTE -> (args[0] as RuntimeValueNumeric)
DataType.BYTE -> RuntimeValue(DataType.UBYTE, abs(args[0].numericValue().toDouble())) DataType.BYTE -> RuntimeValueNumeric(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
DataType.UWORD -> args[0] DataType.UWORD -> (args[0] as RuntimeValueNumeric)
DataType.WORD -> RuntimeValue(DataType.UWORD, abs(args[0].numericValue().toDouble())) DataType.WORD -> RuntimeValueNumeric(DataType.UWORD, abs(args[0].numericValue().toDouble()))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(args[0].numericValue().toDouble())) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
else -> throw VmExecutionException("strange abs type ${args[0]}") else -> throw VmExecutionException("strange abs type ${args[0]}")
} }
} }
"max" -> { "max" -> {
val numbers = args.single().array!!.map { it.toDouble() } val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.max()) RuntimeValueNumeric(ArrayElementTypes.getValue(args[0].type), numbers.max()!!)
} }
"min" -> { "min" -> {
val numbers = args.single().array!!.map { it.toDouble() } val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.min()) RuntimeValueNumeric(ArrayElementTypes.getValue(args[0].type), numbers.min()!!)
}
"avg" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.FLOAT, numbers.average())
} }
"sum" -> { "sum" -> {
val sum = args.single().array!!.map { it.toDouble() }.sum() val sum = (args.single() as RuntimeValueArray).array.map { it.toDouble() }.sum()
when (args[0].type) { when (args[0].type) {
DataType.ARRAY_UB -> RuntimeValue(DataType.UWORD, sum) DataType.ARRAY_UB -> RuntimeValueNumeric(DataType.UWORD, sum)
DataType.ARRAY_B -> RuntimeValue(DataType.WORD, sum) DataType.ARRAY_B -> RuntimeValueNumeric(DataType.WORD, sum)
DataType.ARRAY_UW -> RuntimeValue(DataType.UWORD, sum) DataType.ARRAY_UW -> RuntimeValueNumeric(DataType.UWORD, sum)
DataType.ARRAY_W -> RuntimeValue(DataType.WORD, sum) DataType.ARRAY_W -> RuntimeValueNumeric(DataType.WORD, sum)
DataType.ARRAY_F -> RuntimeValue(DataType.FLOAT, sum) DataType.ARRAY_F -> RuntimeValueNumeric(DataType.FLOAT, sum)
else -> throw VmExecutionException("weird sum type ${args[0]}") else -> throw VmExecutionException("weird sum type ${args[0]}")
} }
} }
"any" -> { "any" -> {
val numbers = args.single().array!!.map { it.toDouble() } val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0) RuntimeValueNumeric(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
} }
"all" -> { "all" -> {
val numbers = args.single().array!!.map { it.toDouble() } val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0) RuntimeValueNumeric(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
} }
"swap" -> "swap" ->
throw VmExecutionException("swap() cannot be implemented as a function") throw VmExecutionException("swap() cannot be implemented as a function")
"strlen" -> { "strlen" -> {
val zeroIndex = args[0].str!!.indexOf(0.toChar()) val zeroIndex = (args[0] as RuntimeValueString).str.indexOf(0.toChar())
if (zeroIndex >= 0) if (zeroIndex >= 0)
RuntimeValue(DataType.UBYTE, zeroIndex) RuntimeValueNumeric(DataType.UBYTE, zeroIndex)
else else
RuntimeValue(DataType.UBYTE, args[0].str!!.length) RuntimeValueNumeric(DataType.UBYTE, (args[0] as RuntimeValueString).str.length)
} }
"memset" -> { "memset" -> {
val heapId = args[0].wordval!! val heapId = (args[0] as RuntimeValueNumeric).wordval!!
val target = program.heap.get(heapId).array ?: throw VmExecutionException("memset target is not an array") val target = getArrayFromRuntimeVars(heapId)
val amount = args[1].integerValue() val amount = args[1].integerValue()
val value = args[2].integerValue() val value = args[2].integerValue()
for (i in 0 until amount) { for (i in 0 until amount) {
target[i] = IntegerOrAddressOf(value, null) target[i] = value
} }
null null
} }
"memsetw" -> { "memsetw" -> {
val heapId = args[0].wordval!! val heapId = (args[0] as RuntimeValueNumeric).wordval!!
val target = program.heap.get(heapId).array ?: throw VmExecutionException("memset target is not an array") val target = getArrayFromRuntimeVars(heapId)
val amount = args[1].integerValue() val amount = args[1].integerValue()
val value = args[2].integerValue() val value = args[2].integerValue()
for (i in 0 until amount step 2) { for (i in 0 until amount step 2) {
target[i * 2] = IntegerOrAddressOf(value and 255, null) target[i * 2] = value and 255
target[i * 2 + 1] = IntegerOrAddressOf(value ushr 8, null) target[i * 2 + 1] = value ushr 8
} }
null null
} }
"memcopy" -> { "memcopy" -> {
val sourceHeapId = args[0].wordval!! val sourceHeapId = (args[0] as RuntimeValueNumeric).wordval!!
val destHeapId = args[1].wordval!! val destHeapId = (args[1] as RuntimeValueNumeric).wordval!!
val source = program.heap.get(sourceHeapId).array!! val source = getArrayFromRuntimeVars(sourceHeapId)
val dest = program.heap.get(destHeapId).array!! val dest = getArrayFromRuntimeVars(destHeapId)
val amount = args[2].integerValue() val amount = args[2].integerValue()
for(i in 0 until amount) { for(i in 0 until amount) {
dest[i] = source[i] dest[i] = source[i]
@ -911,7 +935,7 @@ class AstVm(val program: Program) {
} }
"mkword" -> { "mkword" -> {
val result = (args[1].integerValue() shl 8) or args[0].integerValue() val result = (args[1].integerValue() shl 8) or args[0].integerValue()
RuntimeValue(DataType.UWORD, result) RuntimeValueNumeric(DataType.UWORD, result)
} }
"set_carry" -> { "set_carry" -> {
statusflags.carry=true statusflags.carry=true
@ -934,13 +958,13 @@ class AstVm(val program: Program) {
val zero = if(statusflags.zero) 2 else 0 val zero = if(statusflags.zero) 2 else 0
val irqd = if(statusflags.irqd) 4 else 0 val irqd = if(statusflags.irqd) 4 else 0
val negative = if(statusflags.negative) 128 else 0 val negative = if(statusflags.negative) 128 else 0
RuntimeValue(DataType.UBYTE, carry or zero or irqd or negative) RuntimeValueNumeric(DataType.UBYTE, carry or zero or irqd or negative)
} }
"rsave" -> { "rsave" -> {
statusFlagsSave.push(statusflags) statusFlagsSave.push(statusflags)
registerAsave.push(runtimeVariables.get(program.namespace, Register.A.name)) registerAsave.push(runtimeVariables.get(program.namespace, Register.A.name) as RuntimeValueNumeric)
registerXsave.push(runtimeVariables.get(program.namespace, Register.X.name)) registerXsave.push(runtimeVariables.get(program.namespace, Register.X.name) as RuntimeValueNumeric)
registerYsave.push(runtimeVariables.get(program.namespace, Register.Y.name)) registerYsave.push(runtimeVariables.get(program.namespace, Register.Y.name) as RuntimeValueNumeric)
null null
} }
"rrestore" -> { "rrestore" -> {
@ -954,8 +978,25 @@ class AstVm(val program: Program) {
runtimeVariables.set(program.namespace, Register.Y.name, registerYsave.pop()) runtimeVariables.set(program.namespace, Register.Y.name, registerYsave.pop())
null null
} }
"sort" -> {
val array=args.single() as RuntimeValueArray
array.array.sort()
null
}
"reverse" -> {
val array=args.single() as RuntimeValueArray
array.array.reverse()
null
}
"sgn" -> {
val value = args.single().numericValue().toDouble()
when {
value<0.0 -> RuntimeValueNumeric(DataType.BYTE, -1)
value==0.0 -> RuntimeValueNumeric(DataType.BYTE, 0)
else -> RuntimeValueNumeric(DataType.BYTE, 1)
}
}
else -> TODO("builtin function $name") else -> TODO("builtin function $name")
} }
} }
} }

View File

@ -10,13 +10,11 @@ import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Label import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.vm.RuntimeValue import prog8.vm.*
import prog8.vm.RuntimeValueRange
import kotlin.math.abs
typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValue>, flags: StatusFlags) -> RuntimeValue? typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValueNumeric>, flags: StatusFlags) -> RuntimeValueNumeric?
typealias SubroutineCaller = (sub: Subroutine, args: List<RuntimeValue>, startAtLabel: Label?) -> RuntimeValue? typealias SubroutineCaller = (sub: Subroutine, args: List<RuntimeValueNumeric>, startAtLabel: Label?) -> RuntimeValueNumeric?
class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags, class EvalContext(val program: Program, val mem: Memory, val statusflags: StatusFlags,
@ -24,37 +22,34 @@ class EvalContext(val program: Program, val mem: Memory, val statusflags: Status
val performBuiltinFunction: BuiltinfunctionCaller, val performBuiltinFunction: BuiltinfunctionCaller,
val executeSubroutine: SubroutineCaller) val executeSubroutine: SubroutineCaller)
fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue { fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValueBase {
val constval = expr.constValue(ctx.program) val constval = expr.constValue(ctx.program)
if(constval!=null) if(constval!=null)
return RuntimeValue.fromLv(constval) return RuntimeValueNumeric.fromLv(constval)
when(expr) { when(expr) {
is NumericLiteralValue -> { is NumericLiteralValue -> return RuntimeValueNumeric.fromLv(expr)
return RuntimeValue.fromLv(expr) is StringLiteralValue -> return RuntimeValueString.fromLv(expr)
} is ArrayLiteralValue -> return RuntimeValueArray.fromLv(expr)
is ReferenceLiteralValue -> {
return RuntimeValue.fromLv(expr, ctx.program.heap)
}
is PrefixExpression -> { is PrefixExpression -> {
return when(expr.operator) { return when(expr.operator) {
"-" -> evaluate(expr.expression, ctx).neg() "-" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).neg()
"~" -> evaluate(expr.expression, ctx).inv() "~" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).inv()
"not" -> evaluate(expr.expression, ctx).not() "not" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).not()
// unary '+' should have been optimized away // unary '+' should have been optimized away
else -> throw VmExecutionException("unsupported prefix operator "+expr.operator) else -> throw VmExecutionException("unsupported prefix operator "+expr.operator)
} }
} }
is BinaryExpression -> { is BinaryExpression -> {
val left = evaluate(expr.left, ctx) val left = evaluate(expr.left, ctx) as RuntimeValueNumeric
val right = evaluate(expr.right, ctx) val right = evaluate(expr.right, ctx) as RuntimeValueNumeric
return when(expr.operator) { return when(expr.operator) {
"<" -> RuntimeValue(DataType.UBYTE, if (left < right) 1 else 0) "<" -> RuntimeValueNumeric(DataType.UBYTE, if (left < right) 1 else 0)
"<=" -> RuntimeValue(DataType.UBYTE, if (left <= right) 1 else 0) "<=" -> RuntimeValueNumeric(DataType.UBYTE, if (left <= right) 1 else 0)
">" -> RuntimeValue(DataType.UBYTE, if (left > right) 1 else 0) ">" -> RuntimeValueNumeric(DataType.UBYTE, if (left > right) 1 else 0)
">=" -> RuntimeValue(DataType.UBYTE, if (left >= right) 1 else 0) ">=" -> RuntimeValueNumeric(DataType.UBYTE, if (left >= right) 1 else 0)
"==" -> RuntimeValue(DataType.UBYTE, if (left == right) 1 else 0) "==" -> RuntimeValueNumeric(DataType.UBYTE, if (left == right) 1 else 0)
"!=" -> RuntimeValue(DataType.UBYTE, if (left != right) 1 else 0) "!=" -> RuntimeValueNumeric(DataType.UBYTE, if (left != right) 1 else 0)
"+" -> left.add(right) "+" -> left.add(right)
"-" -> left.sub(right) "-" -> left.sub(right)
"*" -> left.mul(right) "*" -> left.mul(right)
@ -82,27 +77,36 @@ fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
} }
is ArrayIndexedExpression -> { is ArrayIndexedExpression -> {
val array = evaluate(expr.identifier, ctx) val array = evaluate(expr.identifier, ctx)
val index = evaluate(expr.arrayspec.index, ctx) val index = evaluate(expr.arrayspec.index, ctx) as RuntimeValueNumeric
val value = array.array!![index.integerValue()] return when (array) {
return RuntimeValue(ArrayElementTypes.getValue(array.type), value) is RuntimeValueString -> {
val value = array.str[index.integerValue()]
RuntimeValueNumeric(ArrayElementTypes.getValue(array.type), value.toShort())
}
is RuntimeValueArray -> {
val value = array.array[index.integerValue()]
RuntimeValueNumeric(ArrayElementTypes.getValue(array.type), value)
}
else -> throw VmExecutionException("weird type")
}
} }
is TypecastExpression -> { is TypecastExpression -> {
return evaluate(expr.expression, ctx).cast(expr.type) return (evaluate(expr.expression, ctx) as RuntimeValueNumeric).cast(expr.type)
} }
is AddressOf -> { is AddressOf -> {
// we support: address of heap var -> the heap id // we support: address of heap var -> the heap id
return try { return try {
val heapId = expr.identifier.heapId(ctx.program.namespace) val heapId = expr.identifier.heapId(ctx.program.namespace)
RuntimeValue(DataType.UWORD, heapId) RuntimeValueNumeric(DataType.UWORD, heapId)
} catch( f: FatalAstException) { } catch( f: FatalAstException) {
// fallback: use the hash of the name, so we have at least *a* value... // fallback: use the hash of the name, so we have at least *a* value...
val address = expr.identifier.hashCode() and 65535 val address = expr.identifier.hashCode() and 65535
RuntimeValue(DataType.UWORD, address) RuntimeValueNumeric(DataType.UWORD, address)
} }
} }
is DirectMemoryRead -> { is DirectMemoryRead -> {
val address = evaluate(expr.addressExpression, ctx).wordval!! val address = (evaluate(expr.addressExpression, ctx) as RuntimeValueNumeric).wordval!!
return RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address)) return RuntimeValueNumeric(DataType.UBYTE, ctx.mem.getUByte(address))
} }
is RegisterExpr -> return ctx.runtimeVars.get(ctx.program.namespace, expr.register.name) is RegisterExpr -> return ctx.runtimeVars.get(ctx.program.namespace, expr.register.name)
is IdentifierReference -> { is IdentifierReference -> {
@ -115,13 +119,12 @@ fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
else -> { else -> {
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name) val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
return when(variable.datatype) { return when(variable.datatype) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address)) DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, ctx.mem.getUByte(address))
DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address)) DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, ctx.mem.getSByte(address))
DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address)) DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, ctx.mem.getUWord(address))
DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address)) DataType.WORD -> RuntimeValueNumeric(DataType.WORD, ctx.mem.getSWord(address))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address)) DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, ctx.mem.getFloat(address))
DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address)) DataType.STR -> RuntimeValueString(ctx.mem.getString(address), null)
DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address))
else -> throw VmExecutionException("unexpected datatype $variable") else -> throw VmExecutionException("unexpected datatype $variable")
} }
} }
@ -131,7 +134,7 @@ fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
} }
is FunctionCall -> { is FunctionCall -> {
val sub = expr.target.targetStatement(ctx.program.namespace) val sub = expr.target.targetStatement(ctx.program.namespace)
val args = expr.arglist.map { evaluate(it, ctx) } val args = expr.args.map { evaluate(it, ctx) as RuntimeValueNumeric }
return when(sub) { return when(sub) {
is Subroutine -> { is Subroutine -> {
val result = ctx.executeSubroutine(sub, args, null) val result = ctx.executeSubroutine(sub, args, null)
@ -150,24 +153,22 @@ fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
} }
is RangeExpr -> { is RangeExpr -> {
val cRange = expr.toConstantIntegerRange() val cRange = expr.toConstantIntegerRange()
if(cRange!=null) if(cRange!=null) {
return RuntimeValueRange(expr.inferType(ctx.program)!!, cRange) val dt = expr.inferType(ctx.program)
val fromVal = evaluate(expr.from, ctx).integerValue() if(dt.isKnown)
val toVal = evaluate(expr.to, ctx).integerValue() return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), cRange)
val stepVal = evaluate(expr.step, ctx).integerValue() else
val range = when { throw VmExecutionException("couldn't determine datatype")
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) val fromVal = (evaluate(expr.from, ctx) as RuntimeValueNumeric).integerValue()
val toVal = (evaluate(expr.to, ctx) as RuntimeValueNumeric).integerValue()
val stepVal = (evaluate(expr.step, ctx) as RuntimeValueNumeric).integerValue()
val range = makeRange(fromVal, toVal, stepVal)
val dt = expr.inferType(ctx.program)
if(dt.isKnown)
return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), range)
else
throw VmExecutionException("couldn't determine datatype")
} }
else -> { else -> {
throw VmExecutionException("unimplemented expression node $expr") throw VmExecutionException("unimplemented expression node $expr")

View File

@ -1,7 +1,7 @@
package prog8.vm.astvm package prog8.vm.astvm
import prog8.compiler.target.c64.MachineDefinition import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.C64MachineDefinition
import kotlin.math.abs import kotlin.math.abs
class Memory(private val readObserver: (address: Int, value: Short) -> Short, class Memory(private val readObserver: (address: Int, value: Short) -> Short,
@ -21,7 +21,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
else mem[address] else mem[address]
} }
fun getUByte_DMA(address: Int): Short { fun getUByteDirectly(address: Int): Short {
return mem[address] return mem[address]
} }
@ -39,7 +39,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
else value else value
} }
fun setUByte_DMA(address: Int, value: Short) { fun setUByteDirectly(address: Int, value: Short) {
if(value !in 0..255) if(value !in 0..255)
throw VmExecutionException("ubyte value out of range $value") throw VmExecutionException("ubyte value out of range $value")
mem[address] = value mem[address] = value
@ -80,7 +80,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
} }
fun setFloat(address: Int, value: Double) { fun setFloat(address: Int, value: Double) {
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(value) val mflpt5 = C64MachineDefinition.Mflpt5.fromNumber(value)
setUByte(address, mflpt5.b0) setUByte(address, mflpt5.b0)
setUByte(address+1, mflpt5.b1) setUByte(address+1, mflpt5.b1)
setUByte(address+2, mflpt5.b2) setUByte(address+2, mflpt5.b2)
@ -89,28 +89,26 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
} }
fun getFloat(address: Int): Double { fun getFloat(address: Int): Double {
return MachineDefinition.Mflpt5(getUByte(address), getUByte(address + 1), getUByte(address + 2), return C64MachineDefinition.Mflpt5(getUByte(address), getUByte(address + 1), getUByte(address + 2),
getUByte(address + 3), getUByte(address + 4)).toDouble() getUByte(address + 3), getUByte(address + 4)).toDouble()
} }
fun setString(address: Int, str: String) { fun setString(address: Int, str: String) {
// lowercase PETSCII val encoded = CompilationTarget.encodeString(str)
val petscii = Petscii.encodePetscii(str, true)
var addr = address var addr = address
for (c in petscii) setUByte(addr++, c) for (c in encoded) setUByte(addr++, c)
setUByte(addr, 0) setUByte(addr, 0)
} }
fun getString(strAddress: Int): String { fun getString(strAddress: Int): String {
// lowercase PETSCII val encoded = mutableListOf<Short>()
val petscii = mutableListOf<Short>()
var addr = strAddress var addr = strAddress
while(true) { while(true) {
val byte = getUByte(addr++) val byte = getUByte(addr++)
if(byte==0.toShort()) break if(byte==0.toShort()) break
petscii.add(byte) encoded.add(byte)
} }
return Petscii.decodePetscii(petscii, true) return CompilationTarget.decodeString(encoded)
} }
fun clear() { fun clear() {
@ -121,24 +119,4 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
for(i in 0 until numbytes) for(i in 0 until numbytes)
setUByte(to+i, getUByte(from+i)) setUByte(to+i, getUByte(from+i))
} }
fun getScreencodeString(strAddress: Int): String? {
// lowercase Screencodes
val screencodes = mutableListOf<Short>()
var addr = strAddress
while(true) {
val byte = getUByte(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) setUByte(addr++, c)
setUByte(addr, 0)
}
} }

View File

@ -1,12 +1,12 @@
package prog8.vm.astvm package prog8.vm.astvm
import prog8.compiler.target.c64.MachineDefinition import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import java.awt.* import java.awt.*
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import java.awt.event.KeyListener import java.awt.event.KeyListener
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.util.* import java.util.ArrayDeque
import javax.swing.JFrame import javax.swing.JFrame
import javax.swing.JPanel import javax.swing.JPanel
import javax.swing.Timer import javax.swing.Timer
@ -18,7 +18,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
private val g2d = image.graphics as Graphics2D private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0 private var cursorX: Int=0
private var cursorY: Int=0 private var cursorY: Int=0
val keyboardBuffer: Deque<Char> = LinkedList() val keyboardBuffer = ArrayDeque<Char>()
init { init {
val size = Dimension(image.width * SCALING, image.height * SCALING) val size = Dimension(image.width * SCALING, image.height * SCALING)
@ -50,37 +50,33 @@ class BitmapScreenPanel : KeyListener, JPanel() {
} }
fun clearScreen(color: Short) { fun clearScreen(color: Short) {
g2d.background = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size] g2d.background = C64MachineDefinition.colorPalette[color % C64MachineDefinition.colorPalette.size]
g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT) g2d.clearRect(0, 0, SCREENWIDTH, SCREENHEIGHT)
cursorX = 0 cursorX = 0
cursorY = 0 cursorY = 0
} }
fun setPixel(x: Int, y: Int, color: Short) { fun setPixel(x: Int, y: Int, color: Short) {
image.setRGB(x, y, MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size].rgb) image.setRGB(x, y, C64MachineDefinition.colorPalette[color % C64MachineDefinition.colorPalette.size].rgb)
} }
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) { fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) {
g2d.color = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size] g2d.color = C64MachineDefinition.colorPalette[color % C64MachineDefinition.colorPalette.size]
g2d.drawLine(x1, y1, x2, y2) g2d.drawLine(x1, y1, x2, y2)
} }
fun printText(text: String, lowercase: Boolean, inverseVideo: Boolean=false) { fun printAsciiText(text: String) {
val t2 = text.substringBefore(0.toChar()) val t2 = text.substringBefore(0.toChar())
val lines = t2.split('\n') val petscii = Petscii.encodePetscii(t2, true)
for(line in lines.withIndex()) { petscii.forEach { printPetsciiChar(it) }
val petscii = Petscii.encodePetscii(line.value, lowercase)
petscii.forEach { printPetscii(it, inverseVideo) }
if(line.index<lines.size-1) {
printPetscii(13) // newline
}
}
} }
fun printPetscii(char: Short, inverseVideo: Boolean=false) { fun printPetsciiChar(petscii: Short) {
if(char==13.toShort() || char==141.toShort()) { if(petscii in listOf(0x0d.toShort(), 0x8d.toShort())) {
// Return and shift-Return
cursorX=0 cursorX=0
cursorY++ cursorY++
} else { } else {
setPetscii(cursorX, cursorY, char, 1, inverseVideo) val scr = Petscii.petscii2scr(petscii, false)
setScreenChar(cursorX, cursorY, scr, 1)
cursorX++ cursorX++
if (cursorX >= (SCREENWIDTH / 8)) { if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++ cursorY++
@ -94,38 +90,17 @@ class BitmapScreenPanel : KeyListener, JPanel() {
val graphics = image.graphics as Graphics2D val graphics = image.graphics as Graphics2D
graphics.drawImage(screen, 0, -8, null) graphics.drawImage(screen, 0, -8, null)
val color = graphics.color val color = graphics.color
graphics.color = MachineDefinition.colorPalette[6] graphics.color = C64MachineDefinition.colorPalette[6]
graphics.fillRect(0, 24*8, SCREENWIDTH, 25*8) graphics.fillRect(0, 24*8, SCREENWIDTH, 25*8)
graphics.color=color graphics.color=color
cursorY-- cursorY--
} }
} }
fun writeTextAt(x: Int, y: Int, text: String, color: Short, lowercase: Boolean, inverseVideo: Boolean=false) { fun setScreenChar(x: Int, y: Int, screencode: Short, color: Short) {
val colorIdx = (color % MachineDefinition.colorPalette.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.encodePetscii(text, lowercase)) {
if(sc==0.toShort())
break
setPetscii(xx++, y, sc, colorIdx, inverseVideo)
}
}
fun setPetscii(x: Int, y: Int, petscii: Short, color: Short, inverseVideo: Boolean) {
g2d.clearRect(8*x, 8*y, 8, 8) g2d.clearRect(8*x, 8*y, 8, 8)
val colorIdx = (color % MachineDefinition.colorPalette.size).toShort() val colorIdx = (color % C64MachineDefinition.colorPalette.size).toShort()
val screencode = Petscii.petscii2scr(petscii, inverseVideo) val coloredImage = C64MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
val coloredImage = MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null)
}
fun setChar(x: Int, y: Int, screencode: Short, color: Short) {
g2d.clearRect(8*x, 8*y, 8, 8)
val colorIdx = (color % MachineDefinition.colorPalette.size).toShort()
val coloredImage = MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null) g2d.drawImage(coloredImage, 8*x, 8*y , null)
} }
@ -159,19 +134,19 @@ class ScreenDialog(title: String) : JFrame(title) {
// the borders (top, left, right, bottom) // the borders (top, left, right, bottom)
val borderTop = JPanel().apply { val borderTop = JPanel().apply {
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth) preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = MachineDefinition.colorPalette[14] background = C64MachineDefinition.colorPalette[14]
} }
val borderBottom = JPanel().apply { val borderBottom = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth) preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = MachineDefinition.colorPalette[14] background = C64MachineDefinition.colorPalette[14]
} }
val borderLeft = JPanel().apply { val borderLeft = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT) preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = MachineDefinition.colorPalette[14] background = C64MachineDefinition.colorPalette[14]
} }
val borderRight = JPanel().apply { val borderRight = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT) preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = MachineDefinition.colorPalette[14] background = C64MachineDefinition.colorPalette[14]
} }
var c = GridBagConstraints() var c = GridBagConstraints()
c.gridx=0; c.gridy=1; c.gridwidth=3 c.gridx=0; c.gridy=1; c.gridwidth=3

View File

@ -5,23 +5,25 @@ import prog8.ast.base.DataType
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.base.Register import prog8.ast.base.Register
import prog8.ast.base.VarDeclType import prog8.ast.base.VarDeclType
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue import prog8.ast.expressions.StringLiteralValue
import prog8.ast.processing.IAstModifyingVisitor import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.Statement import prog8.ast.statements.Statement
import prog8.ast.statements.StructDecl import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish import prog8.ast.statements.ZeropageWish
import prog8.compiler.HeapValues import prog8.vm.RuntimeValueArray
import prog8.vm.RuntimeValue import prog8.vm.RuntimeValueNumeric
import prog8.vm.RuntimeValueString
class VariablesCreator(private val runtimeVariables: RuntimeVariables, private val heap: HeapValues) : IAstModifyingVisitor { class VariablesCreator(private val runtimeVariables: RuntimeVariables) : IAstModifyingVisitor {
override fun visit(program: Program) { override fun visit(program: Program) {
// define the three registers as global variables // define the three registers as global variables
runtimeVariables.define(program.namespace, Register.A.name, RuntimeValue(DataType.UBYTE, 0)) runtimeVariables.define(program.namespace, Register.A.name, RuntimeValueNumeric(DataType.UBYTE, 0))
runtimeVariables.define(program.namespace, Register.X.name, RuntimeValue(DataType.UBYTE, 255)) runtimeVariables.define(program.namespace, Register.X.name, RuntimeValueNumeric(DataType.UBYTE, 255))
runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValue(DataType.UBYTE, 0)) runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValueNumeric(DataType.UBYTE, 0))
val globalpos = Position("<<global>>", 0, 0, 0) val globalpos = Position("<<global>>", 0, 0, 0)
val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.A.name, null, val vdA = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.A.name, null,
@ -48,10 +50,19 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
if(decl.datatype!=DataType.STRUCT) { if(decl.datatype!=DataType.STRUCT) {
val numericLv = decl.value as? NumericLiteralValue val numericLv = decl.value as? NumericLiteralValue
val value = if(numericLv!=null) { val value = if(numericLv!=null) {
RuntimeValue.fromLv(numericLv) RuntimeValueNumeric.fromLv(numericLv)
} else { } else {
val referenceLv = decl.value as ReferenceLiteralValue val strLv = decl.value as? StringLiteralValue
RuntimeValue.fromLv(referenceLv, heap) val arrayLv = decl.value as? ArrayLiteralValue
when {
strLv!=null -> {
RuntimeValueString.fromLv(strLv)
}
arrayLv!=null -> {
RuntimeValueArray.fromLv(arrayLv)
}
else -> throw VmExecutionException("weird var type")
}
} }
runtimeVariables.define(decl.definingScope(), decl.name, value) runtimeVariables.define(decl.definingScope(), decl.name, value)
} }
@ -66,12 +77,4 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
} }
return super.visit(decl) return super.visit(decl)
} }
// override fun accept(assignment: Assignment): Statement {
// if(assignment is VariableInitializationAssignment) {
// println("INIT VAR $assignment")
// }
// return super.accept(assignment)
// }
} }

View File

@ -4,8 +4,9 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue import prog8.ast.expressions.StringLiteralValue
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
import kotlin.test.assertNotEquals import kotlin.test.assertNotEquals
@ -16,10 +17,6 @@ private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue)
return lv1.type==lv2.type && lv1==lv2 return lv1.type==lv2.type && lv1==lv2
} }
private fun sameValueAndType(rv1: ReferenceLiteralValue, rv2: ReferenceLiteralValue): Boolean {
return rv1.type==rv2.type && rv1==rv2
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestParserNumericLiteralValue { class TestParserNumericLiteralValue {
@ -86,8 +83,8 @@ class TestParserNumericLiteralValue {
@Test @Test
fun testEqualsRef() { fun testEqualsRef() {
assertTrue(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos))) assertEquals(StringLiteralValue("hello", dummyPos), StringLiteralValue("hello", dummyPos))
assertFalse(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "bye", position = dummyPos))) assertNotEquals(StringLiteralValue("hello", dummyPos), StringLiteralValue("bye", dummyPos))
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos) val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos) val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
@ -96,9 +93,9 @@ class TestParserNumericLiteralValue {
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos) val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos) val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos) val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
val lv1 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos) val lv1 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOne, lvTwo, lvThree), dummyPos)
val lv2 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos) val lv2 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
val lv3 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos) val lv3 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
assertEquals(lv1, lv2) assertEquals(lv1, lv2)
assertNotEquals(lv1, lv3) assertNotEquals(lv1, lv3)
} }

View File

@ -3,66 +3,66 @@ package prog8tests
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.vm.RuntimeValue import prog8.vm.RuntimeValueNumeric
import kotlin.test.* import kotlin.test.*
private fun sameValueAndType(v1: RuntimeValue, v2: RuntimeValue): Boolean { private fun sameValueAndType(v1: RuntimeValueNumeric, v2: RuntimeValueNumeric): Boolean {
return v1.type==v2.type && v1==v2 return v1.type==v2.type && v1==v2
} }
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestRuntimeValue { class TestRuntimeValueNumeric {
@Test @Test
fun testValueRanges() { fun testValueRanges() {
assertEquals(0, RuntimeValue(DataType.UBYTE, 0).integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 255).integerValue()) assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 255).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UBYTE, -1)} assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UBYTE, -1)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UBYTE, 256)} assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UBYTE, 256)}
assertEquals(0, RuntimeValue(DataType.BYTE, 0).integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 0).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -128).integerValue()) assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -128).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, 127).integerValue()) assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 127).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.BYTE, -129)} assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.BYTE, -129)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.BYTE, 128)} assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.BYTE, 128)}
assertEquals(0, RuntimeValue(DataType.UWORD, 0).integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 65535).integerValue()) assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 65535).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UWORD, -1)} assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UWORD, -1)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UWORD, 65536)} assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UWORD, 65536)}
assertEquals(0, RuntimeValue(DataType.WORD, 0).integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.WORD, 0).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32768).integerValue()) assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32768).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, 32767).integerValue()) assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32767).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.WORD, -32769)} assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.WORD, -32769)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.WORD, 32768)} assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.WORD, 32768)}
} }
@Test @Test
fun testTruthiness() fun testTruthiness()
{ {
assertFalse(RuntimeValue(DataType.BYTE, 0).asBoolean) assertFalse(RuntimeValueNumeric(DataType.BYTE, 0).asBoolean)
assertFalse(RuntimeValue(DataType.UBYTE, 0).asBoolean) assertFalse(RuntimeValueNumeric(DataType.UBYTE, 0).asBoolean)
assertFalse(RuntimeValue(DataType.WORD, 0).asBoolean) assertFalse(RuntimeValueNumeric(DataType.WORD, 0).asBoolean)
assertFalse(RuntimeValue(DataType.UWORD, 0).asBoolean) assertFalse(RuntimeValueNumeric(DataType.UWORD, 0).asBoolean)
assertFalse(RuntimeValue(DataType.FLOAT, 0.0).asBoolean) assertFalse(RuntimeValueNumeric(DataType.FLOAT, 0.0).asBoolean)
assertTrue(RuntimeValue(DataType.BYTE, 42).asBoolean) assertTrue(RuntimeValueNumeric(DataType.BYTE, 42).asBoolean)
assertTrue(RuntimeValue(DataType.UBYTE, 42).asBoolean) assertTrue(RuntimeValueNumeric(DataType.UBYTE, 42).asBoolean)
assertTrue(RuntimeValue(DataType.WORD, 42).asBoolean) assertTrue(RuntimeValueNumeric(DataType.WORD, 42).asBoolean)
assertTrue(RuntimeValue(DataType.UWORD, 42).asBoolean) assertTrue(RuntimeValueNumeric(DataType.UWORD, 42).asBoolean)
assertTrue(RuntimeValue(DataType.FLOAT, 42.0).asBoolean) assertTrue(RuntimeValueNumeric(DataType.FLOAT, 42.0).asBoolean)
assertTrue(RuntimeValue(DataType.BYTE, -42).asBoolean) assertTrue(RuntimeValueNumeric(DataType.BYTE, -42).asBoolean)
assertTrue(RuntimeValue(DataType.WORD, -42).asBoolean) assertTrue(RuntimeValueNumeric(DataType.WORD, -42).asBoolean)
assertTrue(RuntimeValue(DataType.FLOAT, -42.0).asBoolean) assertTrue(RuntimeValueNumeric(DataType.FLOAT, -42.0).asBoolean)
} }
@Test @Test
fun testIdentity() { fun testIdentity() {
val v = RuntimeValue(DataType.UWORD, 12345) val v = RuntimeValueNumeric(DataType.UWORD, 12345)
assertEquals(v, v) assertEquals(v, v)
assertFalse(v != v) assertFalse(v != v)
assertTrue(v<=v) assertTrue(v<=v)
@ -70,300 +70,283 @@ class TestRuntimeValue {
assertFalse(v<v) assertFalse(v<v)
assertFalse(v>v) assertFalse(v>v)
assertTrue(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100))) assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100)))
} }
@Test @Test
fun testEqualsAndNotEquals() { fun testEqualsAndNotEquals() {
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100)) assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100))
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 100)) assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 100))
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 100)) assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 100))
assertEquals(RuntimeValue(DataType.UWORD, 254), RuntimeValue(DataType.UBYTE, 254)) assertEquals(RuntimeValueNumeric(DataType.UWORD, 254), RuntimeValueNumeric(DataType.UBYTE, 254))
assertEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12345)) assertEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12345))
assertEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12345)) assertEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12345))
assertEquals(RuntimeValue(DataType.FLOAT, 100.0), RuntimeValue(DataType.UBYTE, 100)) assertEquals(RuntimeValueNumeric(DataType.FLOAT, 100.0), RuntimeValueNumeric(DataType.UBYTE, 100))
assertEquals(RuntimeValue(DataType.FLOAT, 22239.0), RuntimeValue(DataType.UWORD, 22239)) assertEquals(RuntimeValueNumeric(DataType.FLOAT, 22239.0), RuntimeValueNumeric(DataType.UWORD, 22239))
assertEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.99)) assertEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.99))
assertTrue(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100))) assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 100))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 100))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 254), RuntimeValue(DataType.UBYTE, 254))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 254), RuntimeValueNumeric(DataType.UBYTE, 254)))
assertTrue(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12345))) assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12345)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12345))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12345)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 100.0), RuntimeValue(DataType.UBYTE, 100))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 100.0), RuntimeValueNumeric(DataType.UBYTE, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 22239.0), RuntimeValue(DataType.UWORD, 22239))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 22239.0), RuntimeValueNumeric(DataType.UWORD, 22239)))
assertTrue(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.99))) assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.99)))
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 101)) assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 101))
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 101)) assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 101))
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 101)) assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 101))
assertNotEquals(RuntimeValue(DataType.UWORD, 245), RuntimeValue(DataType.UBYTE, 246)) assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 245), RuntimeValueNumeric(DataType.UBYTE, 246))
assertNotEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12346)) assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12346))
assertNotEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12346)) assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12346))
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UBYTE, 9)) assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UBYTE, 9))
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UWORD, 9)) assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UWORD, 9))
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.0)) assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.0))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 101))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 101)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 101))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 101)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 101))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 101)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 245), RuntimeValue(DataType.UBYTE, 246))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 245), RuntimeValueNumeric(DataType.UBYTE, 246)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12346))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12346)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12346))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12346)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UBYTE, 9))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UBYTE, 9)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UWORD, 9))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UWORD, 9)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.0))) assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.0)))
}
@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 @Test
fun testGreaterThan(){ fun testGreaterThan(){
assertTrue(RuntimeValue(DataType.UBYTE, 100) > RuntimeValue(DataType.UBYTE, 99)) assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) > RuntimeValueNumeric(DataType.UBYTE, 99))
assertTrue(RuntimeValue(DataType.UWORD, 254) > RuntimeValue(DataType.UWORD, 253)) assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) > RuntimeValueNumeric(DataType.UWORD, 253))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) > RuntimeValue(DataType.FLOAT, 99.9)) assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) > RuntimeValueNumeric(DataType.FLOAT, 99.9))
assertTrue(RuntimeValue(DataType.UBYTE, 100) >= RuntimeValue(DataType.UBYTE, 100)) assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) >= RuntimeValueNumeric(DataType.UBYTE, 100))
assertTrue(RuntimeValue(DataType.UWORD, 254) >= RuntimeValue(DataType.UWORD, 254)) assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) >= RuntimeValueNumeric(DataType.UWORD, 254))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) >= RuntimeValue(DataType.FLOAT, 100.0)) assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) >= RuntimeValueNumeric(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) > RuntimeValue(DataType.UBYTE, 100)) assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) > RuntimeValueNumeric(DataType.UBYTE, 100))
assertFalse(RuntimeValue(DataType.UWORD, 254) > RuntimeValue(DataType.UWORD, 254)) assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) > RuntimeValueNumeric(DataType.UWORD, 254))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) > RuntimeValue(DataType.FLOAT, 100.0)) assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) > RuntimeValueNumeric(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) >= RuntimeValue(DataType.UBYTE, 101)) assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) >= RuntimeValueNumeric(DataType.UBYTE, 101))
assertFalse(RuntimeValue(DataType.UWORD, 254) >= RuntimeValue(DataType.UWORD, 255)) assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) >= RuntimeValueNumeric(DataType.UWORD, 255))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) >= RuntimeValue(DataType.FLOAT, 100.1)) assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) >= RuntimeValueNumeric(DataType.FLOAT, 100.1))
} }
@Test @Test
fun testLessThan() { fun testLessThan() {
assertTrue(RuntimeValue(DataType.UBYTE, 100) < RuntimeValue(DataType.UBYTE, 101)) assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) < RuntimeValueNumeric(DataType.UBYTE, 101))
assertTrue(RuntimeValue(DataType.UWORD, 254) < RuntimeValue(DataType.UWORD, 255)) assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) < RuntimeValueNumeric(DataType.UWORD, 255))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) < RuntimeValue(DataType.FLOAT, 100.1)) assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) < RuntimeValueNumeric(DataType.FLOAT, 100.1))
assertTrue(RuntimeValue(DataType.UBYTE, 100) <= RuntimeValue(DataType.UBYTE, 100)) assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) <= RuntimeValueNumeric(DataType.UBYTE, 100))
assertTrue(RuntimeValue(DataType.UWORD, 254) <= RuntimeValue(DataType.UWORD, 254)) assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) <= RuntimeValueNumeric(DataType.UWORD, 254))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) <= RuntimeValue(DataType.FLOAT, 100.0)) assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) <= RuntimeValueNumeric(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) < RuntimeValue(DataType.UBYTE, 100)) assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) < RuntimeValueNumeric(DataType.UBYTE, 100))
assertFalse(RuntimeValue(DataType.UWORD, 254) < RuntimeValue(DataType.UWORD, 254)) assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) < RuntimeValueNumeric(DataType.UWORD, 254))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) < RuntimeValue(DataType.FLOAT, 100.0)) assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) < RuntimeValueNumeric(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) <= RuntimeValue(DataType.UBYTE, 99)) assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) <= RuntimeValueNumeric(DataType.UBYTE, 99))
assertFalse(RuntimeValue(DataType.UWORD, 254) <= RuntimeValue(DataType.UWORD, 253)) assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) <= RuntimeValueNumeric(DataType.UWORD, 253))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) <= RuntimeValue(DataType.FLOAT, 99.9)) assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) <= RuntimeValueNumeric(DataType.FLOAT, 99.9))
} }
@Test @Test
fun testNoDtConversion() { fun testNoDtConversion() {
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 100).add(RuntimeValue(DataType.UBYTE, 120)) RuntimeValueNumeric(DataType.UWORD, 100).add(RuntimeValueNumeric(DataType.UBYTE, 120))
} }
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 100).add(RuntimeValue(DataType.UWORD, 120)) RuntimeValueNumeric(DataType.UBYTE, 100).add(RuntimeValueNumeric(DataType.UWORD, 120))
} }
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.FLOAT, 100.22).add(RuntimeValue(DataType.UWORD, 120)) RuntimeValueNumeric(DataType.FLOAT, 100.22).add(RuntimeValueNumeric(DataType.UWORD, 120))
} }
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 1002).add(RuntimeValue(DataType.FLOAT, 120.22)) RuntimeValueNumeric(DataType.UWORD, 1002).add(RuntimeValueNumeric(DataType.FLOAT, 120.22))
} }
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.FLOAT, 100.22).add(RuntimeValue(DataType.UBYTE, 120)) RuntimeValueNumeric(DataType.FLOAT, 100.22).add(RuntimeValueNumeric(DataType.UBYTE, 120))
} }
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 12).add(RuntimeValue(DataType.FLOAT, 120.22)) RuntimeValueNumeric(DataType.UBYTE, 12).add(RuntimeValueNumeric(DataType.FLOAT, 120.22))
} }
} }
@Test @Test
fun testNoAutoFloatConversion() { fun testNoAutoFloatConversion() {
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 233).add(RuntimeValue(DataType.FLOAT, 1.234)) RuntimeValueNumeric(DataType.UBYTE, 233).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
} }
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 233).add(RuntimeValue(DataType.FLOAT, 1.234)) RuntimeValueNumeric(DataType.UWORD, 233).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
} }
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 233).mul(RuntimeValue(DataType.FLOAT, 1.234)) RuntimeValueNumeric(DataType.UBYTE, 233).mul(RuntimeValueNumeric(DataType.FLOAT, 1.234))
} }
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 233).mul(RuntimeValue(DataType.FLOAT, 1.234)) RuntimeValueNumeric(DataType.UWORD, 233).mul(RuntimeValueNumeric(DataType.FLOAT, 1.234))
} }
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 233).div(RuntimeValue(DataType.FLOAT, 1.234)) RuntimeValueNumeric(DataType.UBYTE, 233).div(RuntimeValueNumeric(DataType.FLOAT, 1.234))
} }
assertFailsWith<ArithmeticException> { assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 233).div(RuntimeValue(DataType.FLOAT, 1.234)) RuntimeValueNumeric(DataType.UWORD, 233).div(RuntimeValueNumeric(DataType.FLOAT, 1.234))
} }
val result = RuntimeValue(DataType.FLOAT, 233.333).add(RuntimeValue(DataType.FLOAT, 1.234)) val result = RuntimeValueNumeric(DataType.FLOAT, 233.333).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
} }
@Test @Test
fun arithmetictestUbyte() { fun arithmetictestUbyte() {
assertEquals(255, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 55)).integerValue()) assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 55)).integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 56)).integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 56)).integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 57)).integerValue()) assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 57)).integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 1)).integerValue()) assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 2)).integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 2)).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 3)).integerValue()) assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 3)).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 254).inc().integerValue()) assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 254).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 255).inc().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 255).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 1).dec().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 1).dec().integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 0).dec().integerValue()) assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 0).dec().integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 0).inv().integerValue()) assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 0).inv().integerValue())
assertEquals(0b00110011, RuntimeValue(DataType.UBYTE, 0b11001100).inv().integerValue()) assertEquals(0b00110011, RuntimeValueNumeric(DataType.UBYTE, 0b11001100).inv().integerValue())
// assertEquals(0, RuntimeValue(DataType.UBYTE, 0).neg().integerValue()) // assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).neg().integerValue())
// assertEquals(0, RuntimeValue(DataType.UBYTE, 0).neg().integerValue()) // assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 0).not().integerValue()) assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 1).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 111).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 255).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 255).not().integerValue())
assertEquals(200, RuntimeValue(DataType.UBYTE, 20).mul(RuntimeValue(DataType.UBYTE, 10)).integerValue()) assertEquals(200, RuntimeValueNumeric(DataType.UBYTE, 20).mul(RuntimeValueNumeric(DataType.UBYTE, 10)).integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 20).mul(RuntimeValue(DataType.UBYTE, 20)).integerValue()) assertEquals(144, RuntimeValueNumeric(DataType.UBYTE, 20).mul(RuntimeValueNumeric(DataType.UBYTE, 20)).integerValue())
assertEquals(25, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 2)).integerValue()) assertEquals(25, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 2)).integerValue())
assertEquals(125, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 3)).integerValue()) assertEquals(125, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 3)).integerValue())
assertEquals(113, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 4)).integerValue()) assertEquals(113, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 4)).integerValue())
assertEquals(100, RuntimeValue(DataType.UBYTE, 50).shl().integerValue()) assertEquals(100, RuntimeValueNumeric(DataType.UBYTE, 50).shl().integerValue())
assertEquals(200, RuntimeValue(DataType.UBYTE, 100).shl().integerValue()) assertEquals(200, RuntimeValueNumeric(DataType.UBYTE, 100).shl().integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 200).shl().integerValue()) assertEquals(144, RuntimeValueNumeric(DataType.UBYTE, 200).shl().integerValue())
} }
@Test @Test
fun arithmetictestUWord() { fun arithmetictestUWord() {
assertEquals(65535, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5535)).integerValue()) assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5535)).integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5536)).integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5536)).integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5537)).integerValue()) assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5537)).integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 1)).integerValue()) assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 2)).integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 2)).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 3)).integerValue()) assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 3)).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 65534).inc().integerValue()) assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 65534).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 65535).inc().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 65535).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 1).dec().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 1).dec().integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 0).dec().integerValue()) assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 0).dec().integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 0).inv().integerValue()) assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 0).inv().integerValue())
assertEquals(0b0011001101010101, RuntimeValue(DataType.UWORD, 0b1100110010101010).inv().integerValue()) assertEquals(0b0011001101010101, RuntimeValueNumeric(DataType.UWORD, 0b1100110010101010).inv().integerValue())
// assertEquals(0, RuntimeValue(DataType.UWORD, 0).neg().integerValue()) // assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).neg().integerValue())
// assertEquals(0, RuntimeValue(DataType.UWORD, 0).neg().integerValue()) // assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 0).not().integerValue()) assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 1).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 11111).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 11111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 65535).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 65535).not().integerValue())
assertEquals(2000, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 10)).integerValue()) assertEquals(2000, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 10)).integerValue())
assertEquals(40000, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 200)).integerValue()) assertEquals(40000, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 200)).integerValue())
assertEquals(14464, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 400)).integerValue()) assertEquals(14464, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 400)).integerValue())
assertEquals(15625, RuntimeValue(DataType.UWORD, 5).pow(RuntimeValue(DataType.UWORD, 6)).integerValue()) assertEquals(15625, RuntimeValueNumeric(DataType.UWORD, 5).pow(RuntimeValueNumeric(DataType.UWORD, 6)).integerValue())
assertEquals(12589, RuntimeValue(DataType.UWORD, 5).pow(RuntimeValue(DataType.UWORD, 7)).integerValue()) assertEquals(12589, RuntimeValueNumeric(DataType.UWORD, 5).pow(RuntimeValueNumeric(DataType.UWORD, 7)).integerValue())
assertEquals(10000, RuntimeValue(DataType.UWORD, 5000).shl().integerValue()) assertEquals(10000, RuntimeValueNumeric(DataType.UWORD, 5000).shl().integerValue())
assertEquals(60000, RuntimeValue(DataType.UWORD, 30000).shl().integerValue()) assertEquals(60000, RuntimeValueNumeric(DataType.UWORD, 30000).shl().integerValue())
assertEquals(14464, RuntimeValue(DataType.UWORD, 40000).shl().integerValue()) assertEquals(14464, RuntimeValueNumeric(DataType.UWORD, 40000).shl().integerValue())
} }
@Test @Test
fun arithmetictestByte() { fun arithmetictestByte() {
assertEquals(127, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 27)).integerValue()) assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 100).add(RuntimeValueNumeric(DataType.BYTE, 27)).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 28)).integerValue()) assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, 100).add(RuntimeValueNumeric(DataType.BYTE, 28)).integerValue())
assertEquals(-127, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 29)).integerValue()) assertEquals(-127, RuntimeValueNumeric(DataType.BYTE, 100).add(RuntimeValueNumeric(DataType.BYTE, 29)).integerValue())
assertEquals(1, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 1)).integerValue()) assertEquals(1, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 2)).integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 2)).integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 3)).integerValue()) assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 3)).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -100).sub(RuntimeValue(DataType.BYTE, 28)).integerValue()) assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -100).sub(RuntimeValueNumeric(DataType.BYTE, 28)).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, -100).sub(RuntimeValue(DataType.BYTE, 29)).integerValue()) assertEquals(127, RuntimeValueNumeric(DataType.BYTE, -100).sub(RuntimeValueNumeric(DataType.BYTE, 29)).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, 126).inc().integerValue()) assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 126).inc().integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, 127).inc().integerValue()) assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, 127).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 1).dec().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 1).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 0).dec().integerValue()) assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 0).dec().integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -127).dec().integerValue()) assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -127).dec().integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, -128).dec().integerValue()) assertEquals(127, RuntimeValueNumeric(DataType.BYTE, -128).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 0).inv().integerValue()) assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 0).inv().integerValue())
assertEquals(-103, RuntimeValue(DataType.BYTE, 0b01100110).inv().integerValue()) assertEquals(-103, RuntimeValueNumeric(DataType.BYTE, 0b01100110).inv().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 0).neg().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 0).neg().integerValue())
assertEquals(-2, RuntimeValue(DataType.BYTE, 2).neg().integerValue()) assertEquals(-2, RuntimeValueNumeric(DataType.BYTE, 2).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.BYTE, 0).not().integerValue()) assertEquals(1, RuntimeValueNumeric(DataType.BYTE, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 1).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 111).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, -33).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.BYTE, -33).not().integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 10).mul(RuntimeValue(DataType.BYTE, 10)).integerValue()) assertEquals(100, RuntimeValueNumeric(DataType.BYTE, 10).mul(RuntimeValueNumeric(DataType.BYTE, 10)).integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 20).mul(RuntimeValue(DataType.BYTE, 10)).integerValue()) assertEquals(-56, RuntimeValueNumeric(DataType.BYTE, 20).mul(RuntimeValueNumeric(DataType.BYTE, 10)).integerValue())
assertEquals(25, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 2)).integerValue()) assertEquals(25, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 2)).integerValue())
assertEquals(125, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 3)).integerValue()) assertEquals(125, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 3)).integerValue())
assertEquals(113, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 4)).integerValue()) assertEquals(113, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 4)).integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 50).shl().integerValue()) assertEquals(100, RuntimeValueNumeric(DataType.BYTE, 50).shl().integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 100).shl().integerValue()) assertEquals(-56, RuntimeValueNumeric(DataType.BYTE, 100).shl().integerValue())
assertEquals(-2, RuntimeValue(DataType.BYTE, -1).shl().integerValue()) assertEquals(-2, RuntimeValueNumeric(DataType.BYTE, -1).shl().integerValue())
} }
@Test @Test
fun arithmetictestWorrd() { fun arithmetictestWorrd() {
assertEquals(32767, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 67)).integerValue()) assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32700).add(RuntimeValueNumeric(DataType.WORD, 67)).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 68)).integerValue()) assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, 32700).add(RuntimeValueNumeric(DataType.WORD, 68)).integerValue())
assertEquals(-32767, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 69)).integerValue()) assertEquals(-32767, RuntimeValueNumeric(DataType.WORD, 32700).add(RuntimeValueNumeric(DataType.WORD, 69)).integerValue())
assertEquals(1, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 1)).integerValue()) assertEquals(1, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 1)).integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 2)).integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 2)).integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 3)).integerValue()) assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 3)).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32700).sub(RuntimeValue(DataType.WORD, 68)).integerValue()) assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32700).sub(RuntimeValueNumeric(DataType.WORD, 68)).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, -32700).sub(RuntimeValue(DataType.WORD, 69)).integerValue()) assertEquals(32767, RuntimeValueNumeric(DataType.WORD, -32700).sub(RuntimeValueNumeric(DataType.WORD, 69)).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, 32766).inc().integerValue()) assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32766).inc().integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, 32767).inc().integerValue()) assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, 32767).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 1).dec().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.WORD, 1).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 0).dec().integerValue()) assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 0).dec().integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32767).dec().integerValue()) assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32767).dec().integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, -32768).dec().integerValue()) assertEquals(32767, RuntimeValueNumeric(DataType.WORD, -32768).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 0).inv().integerValue()) assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 0).inv().integerValue())
assertEquals(-103, RuntimeValue(DataType.WORD, 0b01100110).inv().integerValue()) assertEquals(-103, RuntimeValueNumeric(DataType.WORD, 0b01100110).inv().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 0).neg().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.WORD, 0).neg().integerValue())
assertEquals(-2, RuntimeValue(DataType.WORD, 2).neg().integerValue()) assertEquals(-2, RuntimeValueNumeric(DataType.WORD, 2).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.WORD, 0).not().integerValue()) assertEquals(1, RuntimeValueNumeric(DataType.WORD, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 1).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.WORD, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 111).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.WORD, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, -33).not().integerValue()) assertEquals(0, RuntimeValueNumeric(DataType.WORD, -33).not().integerValue())
assertEquals(10000, RuntimeValue(DataType.WORD, 100).mul(RuntimeValue(DataType.WORD, 100)).integerValue()) assertEquals(10000, RuntimeValueNumeric(DataType.WORD, 100).mul(RuntimeValueNumeric(DataType.WORD, 100)).integerValue())
assertEquals(-25536, RuntimeValue(DataType.WORD, 200).mul(RuntimeValue(DataType.WORD, 200)).integerValue()) assertEquals(-25536, RuntimeValueNumeric(DataType.WORD, 200).mul(RuntimeValueNumeric(DataType.WORD, 200)).integerValue())
assertEquals(15625, RuntimeValue(DataType.WORD, 5).pow(RuntimeValue(DataType.WORD, 6)).integerValue()) assertEquals(15625, RuntimeValueNumeric(DataType.WORD, 5).pow(RuntimeValueNumeric(DataType.WORD, 6)).integerValue())
assertEquals(-6487, RuntimeValue(DataType.WORD, 9).pow(RuntimeValue(DataType.WORD, 5)).integerValue()) assertEquals(-6487, RuntimeValueNumeric(DataType.WORD, 9).pow(RuntimeValueNumeric(DataType.WORD, 5)).integerValue())
assertEquals(18000, RuntimeValue(DataType.WORD, 9000).shl().integerValue()) assertEquals(18000, RuntimeValueNumeric(DataType.WORD, 9000).shl().integerValue())
assertEquals(-25536, RuntimeValue(DataType.WORD, 20000).shl().integerValue()) assertEquals(-25536, RuntimeValueNumeric(DataType.WORD, 20000).shl().integerValue())
assertEquals(-2, RuntimeValue(DataType.WORD, -1).shl().integerValue()) assertEquals(-2, RuntimeValueNumeric(DataType.WORD, -1).shl().integerValue())
} }
} }

View File

@ -8,18 +8,17 @@ import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType import prog8.ast.base.DataType
import prog8.ast.base.Position import prog8.ast.base.Position
import prog8.ast.expressions.NumericLiteralValue import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.* import prog8.compiler.*
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.MachineDefinition.Mflpt5 import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue import prog8.vm.RuntimeValueNumeric
import java.io.CharConversionException import java.io.CharConversionException
import kotlin.test.* import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompiler { class TestCompiler {
@Test @Test
@ -54,7 +53,6 @@ class TestCompiler {
assertFailsWith<CompilerException> { 65536L.toHex() } assertFailsWith<CompilerException> { 65536L.toHex() }
} }
@Test @Test
fun testFloatToMflpt5() { fun testFloatToMflpt5() {
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00))) assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
@ -97,29 +95,29 @@ class TestCompiler {
@Test @Test
fun testMflpt5ToFloat() { fun testMflpt5ToFloat() {
val PRECISION=0.000000001 val epsilon=0.000000001
assertThat(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(0.0)) assertThat(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(0.0))
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble(), closeTo(3.141592653, PRECISION)) assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble(), closeTo(3.141592653, epsilon))
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(3.141592653589793, PRECISION)) assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(3.141592653589793, epsilon))
assertThat(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(32768.0)) assertThat(Mflpt5(0x90, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(32768.0))
assertThat(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-32768.0)) assertThat(Mflpt5(0x90, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-32768.0))
assertThat(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(1.0)) assertThat(Mflpt5(0x81, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(1.0))
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(0.7071067812, PRECISION)) assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(0.7071067812, epsilon))
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(0.7071067811865476, PRECISION)) assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(0.7071067811865476, epsilon))
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, PRECISION)) assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, epsilon))
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(1.4142135623730951, PRECISION)) assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(1.4142135623730951, epsilon))
assertThat(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-.5)) assertThat(Mflpt5(0x80, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-.5))
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, PRECISION)) assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, epsilon))
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble(), closeTo(0.6931471805599453, PRECISION)) assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble(), closeTo(0.6931471805599453, epsilon))
assertThat(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble(), equalTo(10.0)) assertThat(Mflpt5(0x84, 0x20, 0x00, 0x00, 0x00).toDouble(), equalTo(10.0))
assertThat(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble(), equalTo(1000000000.0)) assertThat(Mflpt5(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble(), equalTo(1000000000.0))
assertThat(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.5)) assertThat(Mflpt5(0x80, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.5))
assertThat(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble(), closeTo(1.4426950408889634, PRECISION)) assertThat(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble(), closeTo(1.4426950408889634, epsilon))
assertThat(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, PRECISION)) assertThat(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, epsilon))
assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, PRECISION)) assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, epsilon))
assertThat(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.25)) assertThat(Mflpt5(0x7F, 0x00, 0x00, 0x00, 0x00).toDouble(), equalTo(.25))
assertThat(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble(), closeTo(123.45678e22, 1.0e15)) assertThat(Mflpt5(0xd1, 0x02, 0xb7, 0x06, 0xfb).toDouble(), closeTo(123.45678e22, 1.0e15))
assertThat(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, PRECISION)) assertThat(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, epsilon))
} }
} }
@ -169,7 +167,15 @@ class TestZeropage {
} }
} }
// TODO test dontuse option @Test
fun testZpDontuse() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), false))
println(zp.free)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
zp.allocate("", DataType.BYTE, null)
}
}
@Test @Test
fun testFreeSpaces() { fun testFreeSpaces() {
@ -363,8 +369,8 @@ class TestPetscii {
assertTrue(ten <= ten) assertTrue(ten <= ten)
assertFalse(ten < ten) assertFalse(ten < ten)
val abc = ReferenceLiteralValue(DataType.STR, str = "abc", position = Position("", 0, 0, 0)) val abc = StringLiteralValue("abc", Position("", 0, 0, 0))
val abd = ReferenceLiteralValue(DataType.STR, str = "abd", position = Position("", 0, 0, 0)) val abd = StringLiteralValue("abd", Position("", 0, 0, 0))
assertEquals(abc, abc) assertEquals(abc, abc)
assertTrue(abc!=abd) assertTrue(abc!=abd)
assertFalse(abc!=abc) assertFalse(abc!=abc)
@ -372,8 +378,8 @@ class TestPetscii {
@Test @Test
fun testStackvmValueComparisons() { fun testStackvmValueComparisons() {
val ten = RuntimeValue(DataType.FLOAT, 10) val ten = RuntimeValueNumeric(DataType.FLOAT, 10)
val nine = RuntimeValue(DataType.UWORD, 9) val nine = RuntimeValueNumeric(DataType.UWORD, 9)
assertEquals(ten, ten) assertEquals(ten, ten)
assertNotEquals(ten, nine) assertNotEquals(ten, nine)
assertFalse(ten != ten) assertFalse(ten != ten)

View File

@ -5,7 +5,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.7 (py3)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.8 virtualenv" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

View File

@ -89,11 +89,18 @@ a successful compilation. This will load your program and the symbol and breakpo
Continuous compilation mode Continuous compilation mode
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
Almost instant compilation times (<0.1 second) can be achieved when using the continuous compilation mode. Almost instant compilation times (less than a second) can be achieved when using the continuous compilation mode.
Start the compiler with the ``-watch`` argument to enable this. Start the compiler with the ``-watch`` argument to enable this.
It will compile your program and then instead of exiting, it waits for any changes in the module source files. It will compile your program and then instead of exiting, it waits for any changes in the module source files.
As soon as a change happens, the program gets compiled again. As soon as a change happens, the program gets compiled again.
Other options
^^^^^^^^^^^^^
There's an option to specify the output directory if you're not happy with the default (the current working directory).
Also it is possible to specify more than one main module to compile:
this can be useful to quickly recompile multiple separate programs quickly.
(compiling in a batch like this is a lot faster than invoking the compiler again once per main file)
Module source code files Module source code files
------------------------ ------------------------
@ -163,22 +170,16 @@ or::
Virtual Machine Virtual Machine / Simulator
--------------- ---------------------------
You may have noticed the ``-avm`` and ``-vm`` command line options for the compiler: You may have noticed the ``-sim`` command line option for the compiler:
-avm -sim
Launches the "AST virtual machine" that directly executes the parsed program. Launches the "AST virtual machine Simulator" that directly executes the parsed program.
No compilation steps will be performed. No compilation steps will be performed.
Allows for very fast testing and debugging before actually compiling programs Allows for very fast testing and debugging before actually compiling programs
to machine code. to machine code.
It simulates a bare minimum of features from the target platform, so most stuff It simulates a bare minimum of features from the target platform, so most stuff
that calls ROM routines or writes into hardware registers won't work. But basic that calls ROM routines or writes into hardware registers won't work. But basic
system routines are emulated. system routines are emulated.
-vm <vm bytecode file>
Launches the "intermediate code VM"
it interprets the intermediate code that the compiler can write when using the ``-writevm``
option. This is the code that will be fed to the assembly code generator,
so you'll skip that last step.

View File

@ -151,13 +151,14 @@ Design principles and features
the compiled program in an emulator and provide debugging information to the emulator. the compiled program in an emulator and provide debugging information to the emulator.
- The compiler outputs a regular 6502 assembly source code file, but doesn't assemble this itself. - The compiler outputs a regular 6502 assembly source code file, but doesn't assemble this itself.
The (separate) '64tass' cross-assembler tool is used for that. The (separate) '64tass' cross-assembler tool is used for that.
- Goto is usually considered harmful, but not here: arbitrary control flow jumps and branches are possible, - Arbitrary control flow jumps and branches are possible,
and will usually translate directly into the appropriate single 6502 jump/branch instruction. and will usually translate directly into the appropriate single 6502 jump/branch instruction.
- There are no complicated built-in error handling or overflow checks, you'll have to take care - There are no complicated built-in error handling or overflow checks, you'll have to take care
of this yourself if required. This keeps the language and code simple and efficient. of this yourself if required. This keeps the language and code simple and efficient.
- The compiler tries to optimize the program and generated code, but hand-tuning of the - The compiler tries to optimize the program and generated code, but hand-tuning of the
performance or space-critical parts will likely still be required. This is supported by performance or space-critical parts will likely still be required. This is supported by
the ability to easily write embedded assembly code directly in the program source code. the ability to easily write embedded assembly code directly in the program source code.
- There are many built-in functions such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``sort`` and ``reverse``
.. _requirements: .. _requirements:

View File

@ -271,9 +271,8 @@ Strings
Strings are a sequence of characters enclosed in ``"`` quotes. The length is limited to 255 characters. Strings are a sequence of characters enclosed in ``"`` quotes. The length is limited to 255 characters.
They're stored and treated much the same as a byte array, They're stored and treated much the same as a byte array,
but they have some special properties because they are considered to be *text*. but they have some special properties because they are considered to be *text*.
Strings in your source code files will be encoded (translated from ASCII/UTF-8) into either CBM PETSCII or C-64 screencodes. Strings in your source code files will be encoded (translated from ASCII/UTF-8) into the byte-encoding
PETSCII is the default choice. If you need screencodes (also called 'poke' codes) instead, that is used on the target platform. For the C-64, this is CBM PETSCII.
you have to use the ``str_s`` variants of the string type identifier.
You can concatenate two string literals using '+' (not very useful though) or repeat You can concatenate two string literals using '+' (not very useful though) or repeat
a string literal a given number of times using '*':: a string literal a given number of times using '*'::
@ -283,10 +282,10 @@ a string literal a given number of times using '*'::
.. caution:: .. caution::
It's probably best that you don't change strings after they're created. Avoid changing strings after they've been created.
This is because if your program exits and is restarted (without loading it again), This is because if your program exits and is restarted (without loading it again),
it will then operate on the changed strings instead of the original ones. it will then start working with the changed strings instead of the original ones.
The same is true for arrays by the way. The same is true for arrays.
Structs Structs
@ -395,8 +394,7 @@ Loops
----- -----
The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this. The *for*-loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this.
The loop variable can be declared as byte or word earlier so you can reuse it for multiple occasions, The loop variable must be declared as byte or word earlier so you can reuse it for multiple occasions.
or you can declare one directly in the for statement which will only be visible in the for loop body.
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead. Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
The *while*-loop is used to repeat a piece of code while a certain condition is still true. The *while*-loop is used to repeat a piece of code while a certain condition is still true.
@ -408,9 +406,6 @@ You can also create loops by using the ``goto`` statement, but this should usual
The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately The value of the loop variable or register after executing the loop *is undefined*. Don't use it immediately
after the loop without first assigning a new value to it! after the loop without first assigning a new value to it!
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value) (this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
Loop variables that are declared inline are not different to them being
defined in a separate var declaration in the subroutine, it's just a readability convenience.
(this may change in the future if the compiler gets more advanced with additional sub-scopes)
Conditional Execution Conditional Execution
@ -612,8 +607,8 @@ Calling a subroutine
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
The arguments in parentheses after the function name, should match the parameters in the subroutine definition. The arguments in parentheses after the function name, should match the parameters in the subroutine definition.
It is possible to not store the return value but the compiler If you want to ignore a return value of a subroutine, you should prefix the call with the ``void`` keyword.
will issue a warning then telling you the result values of a subroutine call are discarded. Otherwise the compiler will issue a warning about discarding a result value.
.. caution:: .. caution::
Note that due to the way parameters are processed by the compiler, Note that due to the way parameters are processed by the compiler,
@ -663,55 +658,58 @@ cos16u(x)
Fast 16-bit uword cosine of angle 0..255, result is in range 0..65535 Fast 16-bit uword cosine of angle 0..255, result is in range 0..65535
cos16(x) cos16(x)
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767 Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
abs(x) abs(x)
Absolute value. Absolute value.
tan(x) tan(x)
Tangent. Tangent.
atan(x) atan(x)
Arctangent. Arctangent.
ln(x) ln(x)
Natural logarithm (base e). Natural logarithm (base e).
log2(x) log2(x)
Base 2 logarithm. Base 2 logarithm.
sqrt16(w) sqrt16(w)
16 bit unsigned integer Square root. Result is unsigned byte. 16 bit unsigned integer Square root. Result is unsigned byte.
sqrt(x) sqrt(x)
Floating point Square root. Floating point Square root.
round(x) round(x)
Rounds the floating point to the closest integer. Rounds the floating point to the closest integer.
floor (x) floor (x)
Rounds the floating point down to an integer towards minus infinity. Rounds the floating point down to an integer towards minus infinity.
ceil(x) ceil(x)
Rounds the floating point up to an integer towards positive infinity. Rounds the floating point up to an integer towards positive infinity.
rad(x) rad(x)
Degrees to radians. Degrees to radians.
deg(x) deg(x)
Radians to degrees. Radians to degrees.
max(x) max(x)
Maximum of the values in the array value x Maximum of the values in the array value x
min(x) min(x)
Minimum of the values in the array value x Minimum of the values in the array value x
avg(x)
Average of the values in the array value x
sum(x) sum(x)
Sum of the values in the array value x Sum of the values in the array value x
sort(array)
Sort the array in ascending order (in-place)
reverse(array)
Reverse the values in the array (in-place). Can be used after sort() to sort an array in descending order.
len(x) len(x)
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte). Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
@ -730,6 +728,9 @@ lsb(x)
msb(x) msb(x)
Get the most significant byte of the word x. Get the most significant byte of the word x.
sgn(x)
Get the sign of the value. Result is -1, 0 or 1 (negative, zero, positive).
mkword(lsb, msb) mkword(lsb, msb)
Efficiently create a word value from two bytes (the lsb and the msb). Avoids multiplication and shifting. Efficiently create a word value from two bytes (the lsb and the msb). Avoids multiplication and shifting.
@ -766,7 +767,7 @@ rol(x)
Modifies in-place, doesn't return a value (so can't be used in an expression). Modifies in-place, doesn't return a value (so can't be used in an expression).
rol2(x) rol2(x)
Like _rol but now as 8-bit or 16-bit rotation. Like ``rol`` but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit. It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression). Modifies in-place, doesn't return a value (so can't be used in an expression).
@ -778,7 +779,7 @@ ror(x)
Modifies in-place, doesn't return a value (so can't be used in an expression). Modifies in-place, doesn't return a value (so can't be used in an expression).
ror2(x) ror2(x)
Like _ror but now as 8-bit or 16-bit rotation. Like ``ror`` but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit. It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression). Modifies in-place, doesn't return a value (so can't be used in an expression).

View File

@ -268,8 +268,6 @@ type identifier type storage size example var declara
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]`` ``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
``str`` string (petscii) varies ``str myvar = "hello."`` ``str`` string (petscii) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte implicitly terminated by a 0-byte
``str_s`` string (screencodes) varies ``str_s myvar = "hello."``
implicitly terminated by a 0-byte
=============== ======================= ================= ========================================= =============== ======================= ================= =========================================
**arrays:** you can split an array initializer list over several lines if you want. When an initialization **arrays:** you can split an array initializer list over several lines if you want. When an initialization
@ -453,14 +451,17 @@ Subroutine / function calls
You call a subroutine like this:: You call a subroutine like this::
[ result = ] subroutinename_or_address ( [argument...] ) [ void / result = ] subroutinename_or_address ( [argument...] )
; example: ; example:
resultvariable = subroutine(arg1, arg2, arg3) resultvariable = subroutine(arg1, arg2, arg3)
void noresultvaluesub(arg)
Arguments are separated by commas. The argument list can also be empty if the subroutine Arguments are separated by commas. The argument list can also be empty if the subroutine
takes no parameters. If the subroutine returns a value, you can still omit the assignment to takes no parameters. If the subroutine returns a value, usually you assign it to a variable.
a result variable (but the compiler will warn you about discarding the result of the call). If you're not interested in the return value, prefix the function call with the ``void`` keyword.
Otherwise the compiler will warn you about discarding the result of the call.
Normal subroutines can only return zero or one return values. Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code or referencing However, the special ``asmsub`` routines (implemented in assembly code or referencing
@ -517,42 +518,39 @@ Loops
for loop for loop
^^^^^^^^ ^^^^^^^^
The loop variable must be a register or a byte/word variable. It must be defined in the local scope (to reuse The loop variable must be a register or a byte/word variable,
an existing variable), or you can declare it in the for loop directly to make a new one that is only visible and must be defined first in the local scope of the for loop.
in the body of the for loop.
The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``, The expression that you loop over can be anything that supports iteration (such as ranges like ``0 to 100``,
array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported). array variables and strings) *except* floating-point arrays (because a floating-point loop variable is not supported).
You can use a single statement, or a statement block like in the example below:: You can use a single statement, or a statement block like in the example below::
for [byte | word] <loopvar> in <expression> [ step <amount> ] { for <loopvar> in <expression> [ step <amount> ] {
; do something... ; do something...
break ; break out of the loop break ; break out of the loop
continue ; immediately enter next iteration continue ; immediately enter next iteration
} }
For example, this is a for loop using the existing byte variable ``i`` to loop over a certain range of numbers:: For example, this is a for loop using a byte variable ``i``, defined before, to loop over a certain range of numbers::
ubyte i
...
for i in 20 to 155 { for i in 20 to 155 {
; do something ; do something
} }
And this is a loop over the values of the array ``fibonacci_numbers`` where the loop variable is declared in the loop itself:: And this is a loop over the values of the array ``fibonacci_numbers``::
word[] fibonacci_numbers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181] uword[] fibonacci_numbers = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
for word fibnr in fibonacci_numbers { uword number
; do something for number in fibonacci_numbers {
; do something with number
} }
You can inline the loop variable declaration in the for statement, including optional zp-tag. In this case
the variable is not visible outside of the for loop::
for ubyte @zp fastindex in 10 to 20 {
; do something
}
while loop while loop
^^^^^^^^^^ ^^^^^^^^^^

View File

@ -2,45 +2,24 @@
TODO TODO
==== ====
- option to load library files from a directory instead of the embedded ones
Fixes - @"zzz" screencode encoded strings + add this to docs too
^^^^^
variable naming issue::
main {
sub start() {
for A in 0 to 10 {
ubyte note1 = 44
Y+=note1
}
delay(1)
sub delay(ubyte note1) { ; TODO: redef of note1 above, conflicts because that one was moved to the zeropage
A= note1
}
}
}
Memory Block Operations integrated in language? Memory Block Operations integrated in language?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
list,string memory block operations? array/string memory block operations?
- list operations (whole list, individual element) - array operations
operations: set, get, copy (from another list with the same length), shift-N(left,right), rotate-N(left,right) copy (from another array with the same length), shift-N(left,right), rotate-N(left,right)
clear (set whole list to the given value, default 0) clear (set whole array to the given value, default 0)
- list operations ofcourse work identical on vars and on memory mapped vars of these types. - array operations ofcourse work identical on vars and on memory mapped vars of these types.
- strings: identical operations as on lists. - strings: identical operations as on array.
these should call optimized pieces of assembly code, so they run as fast as possible
For now, we have the ``memcopy``, ``memset`` and ``strlen`` builtin functions.
For now, we have the ``memcopy`` and ``memset`` builtin functions.
More optimizations More optimizations
@ -49,14 +28,9 @@ More optimizations
Add more compiler optimizations to the existing ones. Add more compiler optimizations to the existing ones.
- on the language AST level - on the language AST level
- on the StackVM intermediate code level
- on the final assembly source level - on the final assembly source level
- can the parameter passing to subroutines be optimized to avoid copying? - can the parameter passing to subroutines be optimized to avoid copying?
- working subroutine inlining (taking care of vars and identifier refs to them)
- subroutines with 1 or 2 byte args (or 1 word arg) should be converted to asm calling convention with the args in A/Y register
this requires rethinking the way parameters are represented, simply injecting vardecls to
declare local variables for them is not always correct anymore
Also some library routines and code patterns could perhaps be optimized further Also some library routines and code patterns could perhaps be optimized further
@ -70,11 +44,18 @@ It could then even be moved into the zeropage to greatly reduce code size and sl
Or just move the LSB portion into a slab of the zeropage. Or just move the LSB portion into a slab of the zeropage.
Allocate a fixed word in ZP that is the TOS so we can operate on TOS directly Allocate a fixed word in ZP that is the TOS so we can always operate on TOS directly
without having to to index into the stack? without having to to index into the stack?
Bugs
^^^^
Ofcourse there are still bugs to fix ;)
Misc Misc
^^^^ ^^^^
- are there any other missing instructions in the code generator? Several ideas were discussed on my reddit post
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/

View File

@ -0,0 +1,110 @@
%import c64lib
%import c64utils
%import c64flt
%zeropage dontuse
main {
sub start() {
ubyte[] ubarr = [100, 0, 99, 199, 22]
byte[] barr = [-100, 0, 99, -122, 22]
uword[] uwarr = [1000, 0, 222, 4444, 999]
word[] warr = [-1000, 0, 999, -4444, 222]
float[] farr = [-1000.1, 0, 999.9, -4444.4, 222.2]
str name = "irmen"
ubyte ub
byte bb
word ww
uword uw
float ff
; LEN/STRLEN
ubyte length = len(name)
if length!=5 c64scr.print("error len1\n")
length = len(uwarr)
if length!=5 c64scr.print("error len2\n")
length=strlen(name)
if length!=5 c64scr.print("error strlen1\n")
name[3] = 0
length=strlen(name)
if length!=3 c64scr.print("error strlen2\n")
; MAX
ub = max(ubarr)
if ub!=199 c64scr.print("error max1\n")
bb = max(barr)
if bb!=99 c64scr.print("error max2\n")
uw = max(uwarr)
if uw!=4444 c64scr.print("error max3\n")
ww = max(warr)
if ww!=999 c64scr.print("error max4\n")
ff = max(farr)
if ff!=999.9 c64scr.print("error max5\n")
; MIN
ub = min(ubarr)
if ub!=0 c64scr.print("error min1\n")
bb = min(barr)
if bb!=-122 c64scr.print("error min2\n")
uw = min(uwarr)
if uw!=0 c64scr.print("error min3\n")
ww = min(warr)
if ww!=-4444 c64scr.print("error min4\n")
ff = min(farr)
if ff!=-4444.4 c64scr.print("error min5\n")
; SUM
uw = sum(ubarr)
if uw!=420 c64scr.print("error sum1\n")
ww = sum(barr)
if ww!=-101 c64scr.print("error sum2\n")
uw = sum(uwarr)
if uw!=6665 c64scr.print("error sum3\n")
ww = sum(warr)
if ww!=-4223 c64scr.print("error sum4\n")
ff = sum(farr)
if ff!=-4222.4 c64scr.print("error sum5\n")
; ANY
ub = any(ubarr)
if ub==0 c64scr.print("error any1\n")
ub = any(barr)
if ub==0 c64scr.print("error any2\n")
ub = any(uwarr)
if ub==0 c64scr.print("error any3\n")
ub = any(warr)
if ub==0 c64scr.print("error any4\n")
ub = any(farr)
if ub==0 c64scr.print("error any5\n")
; ALL
ub = all(ubarr)
if ub==1 c64scr.print("error all1\n")
ub = all(barr)
if ub==1 c64scr.print("error all2\n")
ub = all(uwarr)
if ub==1 c64scr.print("error all3\n")
ub = all(warr)
if ub==1 c64scr.print("error all4\n")
ub = all(farr)
if ub==1 c64scr.print("error all5\n")
ubarr[1]=$40
barr[1]=$40
uwarr[1]=$4000
warr[1]=$4000
farr[1]=1.1
ub = all(ubarr)
if ub==0 c64scr.print("error all6\n")
ub = all(barr)
if ub==0 c64scr.print("error all7\n")
ub = all(uwarr)
if ub==0 c64scr.print("error all8\n")
ub = all(warr)
if ub==0 c64scr.print("error all9\n")
ub = all(farr)
if ub==0 c64scr.print("error all10\n")
c64scr.print("\nyou should see no errors above.")
}
}

View File

@ -9,15 +9,50 @@ main {
uword uw uword uw
&ubyte membyte=9999 &ubyte membyte=9999
&uword memword=9999 &uword memword=9999
ubyte[10] barray ubyte[] ubarray = [8,8,8]
uword[] uwarray = [8200, 8200, 8200]
byte[] bbarray = [8,8,8]
word[] wwarray = [8200, 8200, 8200]
sub unimplemented() {
; TODO implement these asm routines
lsr(ubarray[1])
lsl(ubarray[1])
ror(ubarray[1])
rol(ubarray[1])
ror2(ubarray[1])
rol2(ubarray[1])
lsr(bbarray[1])
lsl(bbarray[1])
lsr(uwarray[1])
lsl(uwarray[1])
ror(uwarray[1])
rol(uwarray[1])
ror2(uwarray[1])
rol2(uwarray[1])
lsr(wwarray[1])
lsl(wwarray[1])
}
sub start() { sub start() {
unimplemented()
lsr(A) lsr(A)
lsl(A) lsl(A)
ror(A) ror(A)
rol(A) rol(A)
ror2(A) ror2(A)
rol2(A) rol2(A)
lsr(Y)
lsl(Y)
ror(Y)
rol(Y)
ror2(Y)
rol2(Y)
lsr(bb)
lsl(bb)
lsr(membyte) lsr(membyte)
lsl(membyte) lsl(membyte)
ror(membyte) ror(membyte)
@ -30,19 +65,20 @@ main {
rol(memword) rol(memword)
ror2(memword) ror2(memword)
rol2(memword) rol2(memword)
lsl(@(9999)) lsl(@(9999))
lsr(@(9999)) lsr(@(9999))
ror(@(9999)) ror(@(9999))
rol(@(9999)) rol(@(9999))
ror2(@(9999)) ror2(@(9999))
rol2(@(9999)) rol2(@(9999))
lsr(barray[1])
lsl(barray[1])
ror(barray[1])
rol(barray[1])
ror2(barray[1])
rol2(barray[1])
lsl(@(9999+A))
lsr(@(9999+A))
ror(@(9999+A))
rol(@(9999+A))
ror2(@(9999+A))
rol2(@(9999+A))
bb /= 2 bb /= 2
bb >>= 1 bb >>= 1

74
examples/bdmusic-irq.p8 Normal file
View File

@ -0,0 +1,74 @@
%zeropage basicsafe
%import c64lib
main {
sub start() {
c64scr.print("playing the music from boulderdash,\nmade in 1984 by peter liepa.\n\n")
c64utils.set_rasterirq(60) ; enable raster irq
}
}
irq {
const ubyte waveform = %0001 ; triangle
ubyte note_index = 0
ubyte delay = 0
sub irq() {
c64.EXTCOL++
delay++
if delay >= 8 {
delay = 0
c64.AD1 = %00011010
c64.SR1 = %00000000
c64.AD2 = %00011010
c64.SR2 = %00000000
c64.MVOL = 15
uword note = notes[note_index]
note_index++
ubyte note1 = lsb(note)
ubyte note2 = msb(note)
c64.FREQ1 = music_freq_table[note1] ; set lo+hi freq of voice 1
c64.FREQ2 = music_freq_table[note2] ; set lo+hi freq of voice 2
; retrigger voice 1 and 2 ADSR
c64.CR1 = waveform <<4 | 0
c64.CR2 = waveform <<4 | 0
c64.CR1 = waveform <<4 | 1
c64.CR2 = waveform <<4 | 1
}
c64.EXTCOL--
}
; details about the boulderdash music can be found here:
; https://www.elmerproductions.com/sp/peterb/sounds.html#Theme%20tune
uword[] notes = [
$1622, $1d26, $2229, $252e, $1424, $1f27, $2029, $2730,
$122a, $122c, $1e2e, $1231, $202c, $3337, $212d, $3135,
$1622, $162e, $161d, $1624, $1420, $1430, $1424, $1420,
$1622, $162e, $161d, $1624, $1e2a, $1e3a, $1e2e, $1e2a,
$142c, $142c, $141b, $1422, $1c28, $1c38, $1c2c, $1c28,
$111d, $292d, $111f, $292e, $0f27, $0f27, $1633, $1627,
$162e, $162e, $162e, $162e, $222e, $222e, $162e, $162e,
$142e, $142e, $142e, $142e, $202e, $202e, $142e, $142e,
$162e, $322e, $162e, $332e, $222e, $322e, $162e, $332e,
$142e, $322e, $142e, $332e, $202c, $302c, $142c, $312c,
$162e, $163a, $162e, $3538, $222e, $2237, $162e, $3135,
$142c, $1438, $142c, $1438, $202c, $2033, $142c, $1438,
$162e, $322e, $162e, $332e, $222e, $322e, $162e, $332e,
$142e, $322e, $142e, $332e, $202c, $302c, $142c, $312c,
$2e32, $292e, $2629, $2226, $2c30, $272c, $2427, $1420,
$3532, $322e, $2e29, $2926, $2730, $242c, $2027, $1420
]
uword[] music_freq_table = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
732, 778, 826, 876, 928, 978, 1042, 1100, 1170, 1238, 1312, 1390, 1464, 1556,
1652, 1752, 1856, 1956, 2084, 2200, 2340, 2476, 2624, 2780, 2928, 3112, 3304,
3504, 3712, 3912, 4168, 4400, 4680, 4952, 5248, 5560, 5856, 6224, 6608, 7008,
7424, 7824, 8336, 8800, 9360, 9904, 10496, 11120, 11712
]
}

View File

@ -13,11 +13,12 @@ sub start() {
c64.MVOL = 15 c64.MVOL = 15
c64scr.print("will play the music from boulderdash,\nmade in 1984 by peter liepa.\npress enter to start: ") c64scr.print("will play the music from boulderdash,\nmade in 1984 by peter liepa.\npress enter to start: ")
c64.CHRIN() void c64.CHRIN()
c64.CLEARSCR() c64.CLEARSCR()
while(true) { while(true) {
for uword note in notes { uword note
for note in notes {
ubyte note1 = lsb(note) ubyte note1 = lsb(note)
ubyte note2 = msb(note) ubyte note2 = msb(note)
c64.FREQ1 = music_freq_table[note1] ; set lo+hi freq of voice 1 c64.FREQ1 = music_freq_table[note1] ; set lo+hi freq of voice 1
@ -35,7 +36,8 @@ sub start() {
} }
sub delay() { sub delay() {
for ubyte d in 0 to 12 { ubyte d
for d in 0 to 12 {
while(c64.RASTER!=0) { while(c64.RASTER!=0) {
; tempo delay synced to screen refresh ; tempo delay synced to screen refresh
} }

View File

@ -106,6 +106,39 @@ main {
else else
c64scr.print("error in -222>=-222!\n") c64scr.print("error in -222>=-222!\n")
v1 = 1000
v2 = 1000
if v1==v2
c64scr.print("ok: 1000 == 1000\n")
else
c64scr.print("error in 1000==1000!\n")
if v1!=v2
c64scr.print("error in 1000!=1000!\n")
else
c64scr.print("ok: 1000 is not != 1000\n")
if v1<v2
c64scr.print("error in 1000<1000!\n")
else
c64scr.print("ok: 1000 is not < 1000\n")
if v1<=v2
c64scr.print("ok: 1000 <= 1000\n")
else
c64scr.print("error in 1000<=1000!\n")
if v1>v2
c64scr.print("error in 1000>1000!\n")
else
c64scr.print("ok: 1000 is not > 1000\n")
if v1>=v2
c64scr.print("ok: 1000 >= 1000\n")
else
c64scr.print("error in 1000>=1000!\n")
ubyte endX = X ubyte endX = X
if endX == 255 if endX == 255
c64scr.print("stack x ok!\n") c64scr.print("stack x ok!\n")

Binary file not shown.

Binary file not shown.

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