Compare commits

..
129 Commits
Author SHA1 Message Date
Irmen de Jong 6c23ae14ab ver 2020-03-15 01:37:01 +01:00
Irmen de Jong 6f000d0d26 fix datatype warning 2020-03-15 01:14:44 +01:00
Irmen de Jong 9d7eb3be5a fix error reporting of constantfolding, and number of errors printed 2020-03-15 01:10:08 +01:00
Irmen de Jong 835555171e fix function call arg type mismatch crash 2020-03-15 00:50:59 +01:00
Irmen de Jong 68ce4a1bf0 labels are now prefixed with underscore in assembly to fix undefined symbol errors from the assembler 2020-03-15 00:23:54 +01:00
Irmen de Jong a995867deb added check for duplicate label definitions 2020-03-15 00:16:50 +01:00
Irmen de Jong 6bd99d63b4 cleanup of error reporting 2020-03-14 23:47:26 +01:00
Irmen de Jong baf5d3041a cleanup of error reporting 2020-03-14 23:15:44 +01:00
Irmen de Jong a326ffa00a added warning about sgn() of unsigned type 2020-03-14 21:09:34 +01:00
Irmen de Jong d28dd92b47 refreshed examples 2020-03-14 18:11:38 +01:00
Irmen de Jong 1de328b2e8 added forever-loop and optimizer 2020-03-14 18:11:04 +01:00
Irmen de Jong 51bb902162 added bresenham and circle example 2020-03-14 17:11:10 +01:00
Irmen de Jong 4fd14f1366 doc updates 2020-03-14 15:20:04 +01:00
Irmen de Jong 91d9559f79 avoid pulling in the dbus libraries for now 2020-03-14 14:40:39 +01:00
Irmen de Jong 3245a9b157 restricted block to only directive/subroutine/vardecl/inlineasm 2020-03-14 14:20:55 +01:00
Irmen de Jong 2b28493bba simplified module grammar rules 2020-03-14 13:44:13 +01:00
Irmen de Jong 1382728bd2 warning about unreachable code after a return statement
added some dbus experiments for future compilation service
2020-03-14 13:12:01 +01:00
Irmen de Jong 0422ad080a added exit function to astvm simulator 2020-03-13 02:44:01 +01:00
Irmen de Jong 64d682bfde todo 2020-03-13 02:33:02 +01:00
Irmen de Jong b182f7e693 optimizer removes unreachable code following call to exit() 2020-03-13 02:31:53 +01:00
Irmen de Jong e6be428589 compiler warning for unreachable code following a call to exit() 2020-03-13 02:21:37 +01:00
Irmen de Jong 85c7f8314b added exit(rc) builtin function to immediately exit the program with a return code in A register 2020-03-13 02:08:18 +01:00
Irmen de Jong 796d07a7f8 fix crash in asm code generated for bitshift operation with memory address operand 2020-03-13 01:26:53 +01:00
Irmen de Jong 2af86a10b2 remove stack error comments 2020-03-13 00:52:52 +01:00
Irmen de Jong 7fbe486dff fix eval stack register X error in print_uw 2020-03-13 00:50:30 +01:00
Irmen de Jong 87e5a9859a remove autogenerated labels from vice mon list, fixes #17 2020-03-12 22:33:58 +01:00
Irmen de Jong b036e5ed72 refreshed the compiled examples 2020-03-12 01:14:10 +01:00
Irmen de Jong 5f1ec80ae0 improved array literal datatype handling, fixed some datatype compiler errors related to this 2020-03-12 01:10:19 +01:00
Irmen de Jong fbecedaf41 added error for unsupported sort(floatarray) 2020-03-11 23:33:06 +01:00
Irmen de Jong aa36acd65a implemented reverse(floatarray) builtin function 2020-03-11 23:18:03 +01:00
Irmen de Jong 8d1a4588d3 added 'downto' range expression 2020-03-11 20:59:14 +01:00
Irmen de Jong 66d2af4453 added '@' alternative string/char encoding 2020-03-11 00:41:58 +01:00
Irmen de Jong ef6c731bb3 added '@' alternative string/char encoding 2020-03-11 00:32:50 +01:00
Irmen de Jong 98a638a2f3 split asmsub and romsub declarations 2020-03-10 23:09:31 +01:00
Irmen de Jong 96d8a7f0d7 float assembly code moved to separate library file 2020-03-10 22:03:24 +01:00
Irmen de Jong 3162b10392 optimize callgraph 2020-03-10 21:47:15 +01:00
Irmen de Jong e2358de27c ver 2020-03-10 20:39:30 +01:00
Irmen de Jong 7facb4f372 correct version 1.70 2020-02-09 01:41:05 +01:00
Irmen de Jong ee90fed489 readme 2020-02-09 01:33:20 +01:00
Irmen de Jong 4796c56c35 antlr code back 2020-02-09 01:29:58 +01:00
Irmen de Jong e2cb031386 added 'void' keyword to explicitly ignore subroutine return values (and no longer get a warning) 2020-02-09 01:29:09 +01:00
Irmen de Jong a0bc97b90c fix byte array iteration for bb in [1,2,3]
improved array literal datatype detection
2020-02-09 00:45:53 +01:00
Irmen de Jong fd240899bd fix CHROUT in simulator 2020-02-09 00:12:50 +01:00
Irmen de Jong 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
Irmen de Jong 11de3db25f simplified heapId for arrayvalues 2020-02-08 18:49:48 +01:00
Irmen de Jong 14a13da7ec simplified heapId for stringvalue 2020-02-08 15:54:03 +01:00
Irmen de Jong 875a71c786 removed datatype from StringValue classes (is always STR now) 2020-02-08 02:21:18 +01:00
Irmen de Jong 0ff5b79353 code inspection cleanups 2020-02-08 01:31:41 +01:00
Irmen de Jong 8c4d276810 improvements to string encoding/decoding and text output in the simulator 2020-02-08 01:12:30 +01:00
Irmen de Jong 3dd38c0ac8 antlr library updated to 4.8 2020-02-07 23:58:07 +01:00
Irmen de Jong b8816a0e2f got rid of separate str_s datatype 2020-02-07 20:47:38 +01:00
Irmen de Jong a01a9e76f9 removed bogus clang target
fixed various simulator bugs regarding strings and chars
2020-02-07 01:22:07 +01:00
Irmen de Jong 357d704aec clean up version specifier 2020-02-02 19:33:40 +01:00
Irmen de Jong 868df1865c got rid of obsolete code 2020-02-02 19:18:40 +01:00
Irmen de Jong 654d74da1e automatic selection of best Vice C64 emulator executable 2020-02-02 13:39:56 +01:00
Irmen de Jong 59939c727a gradle updated 2020-02-02 13:39:25 +01:00
Irmen de Jong fbcf190324 sync gradle version with my manjaro packaged gradle 2020-01-27 21:32:42 +01:00
Irmen de Jong b9922a90cc update gradle wrapper to 6.1.1 2020-01-26 18:36:51 +01:00
Irmen de Jong 66e0b07428 gradle updates 2020-01-07 01:29:25 +01:00
Irmen de Jong 01e617ae8f new kotlin version 2019-12-09 16:17:20 +01:00
Irmen de Jong 52769decd4 fix assembler float truncation warning 2019-11-27 22:36:59 +01:00
Irmen de Jong 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
Irmen de Jong 8c2e602cc7 preparing for multiple compiler backends/targets 2019-10-26 23:41:15 +02:00
Irmen de Jong b68f141568 some more old code cleanups 2019-10-21 00:12:26 +02:00
Irmen de Jong b5d1e8653d tiny cleanups 2019-10-20 23:52:26 +02:00
Irmen de Jong f6d4c90dea improved number-to-decimal routines 2019-09-23 20:44:41 +02:00
Irmen de Jong b5b24636ae removed sim65 because it was moved to a separate repository 2019-09-11 02:24:44 +02:00
Irmen de Jong 9dedbbf47c use more modern java date/time api 2019-09-10 01:29:33 +02:00
Irmen de Jong c493c3e5c6 implemented IRQ handling 2019-09-09 23:28:41 +02:00
Irmen de Jong 61d4ca1d24 added functional test files to git 2019-09-09 19:57:51 +02:00
Irmen de Jong 2cf9af4a6e implemented sim timer and clock 2019-09-09 04:51:18 +02:00
Irmen de Jong bdcd10512f 6502 simulator passes all tests for regular opcodes 2019-09-09 00:27:06 +02:00
Irmen de Jong fec8db6a75 fixed sbc and adc 2019-09-08 22:35:08 +02:00
Irmen de Jong b400010426 separated the 6502 test suite into separate unit tests 2019-09-08 19:11:06 +02:00
Irmen de Jong 28109a39ac clean up of c64 tests 2019-09-08 17:19:40 +02:00
Irmen de Jong 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
Irmen de Jong e61d3df380 added missing testfiles 2019-09-06 01:09:23 +02:00
Irmen de Jong 15710207b2 fixed bcd (but the bcd test code still fails, strange) 2019-09-06 00:38:48 +02:00
Irmen de Jong adfddddac6 attempt to fix bcd 2019-09-05 21:38:40 +02:00
Irmen de Jong e46982f652 fixes 2019-09-05 01:41:48 +02:00
Irmen de Jong 900c2aea23 fixed all instructions except BCD arithmetic 2019-09-05 01:26:01 +02:00
Irmen de Jong 42f8e98cab cpu unit test suite ported from Py65 2019-09-04 22:23:31 +02:00
Irmen de Jong bed0e33b4f unit test 2019-09-04 02:41:09 +02:00
Irmen de Jong 8d6542905d beginnings of 6502 cpu simulator 2019-09-03 23:58:46 +02:00
Irmen de Jong 39798a1a4f todos 2019-08-29 22:31:29 +02:00
Irmen de Jong befe4b8e9f try to fix windows path issue with drive letter 2019-08-27 01:02:31 +02:00
Irmen de Jong 772e48105e fixed some type cast compiler errors in for loops 2019-08-26 23:38:59 +02:00
Irmen de Jong 9afe451b8d fix build script to target jdk 1.8 2019-08-26 21:27:45 +02:00
Irmen de Jong 89d469e77e examples 2019-08-25 00:46:46 +02:00
Irmen de Jong 59a43889a5 examples 2019-08-25 00:24:00 +02:00
Irmen de Jong 7caa0daffc examples 2019-08-24 21:40:50 +02:00
Irmen de Jong 5e854c2cf8 more forloop asm 2019-08-24 21:26:29 +02:00
Irmen de Jong 9edc92ec29 more bitshift asm stubs (actual functions still to be done) 2019-08-23 23:06:36 +02:00
Irmen de Jong 1d178080a3 more bitshift asm 2019-08-23 21:33:43 +02:00
Irmen de Jong aa94300bdd added output directory command line option
improved cli parser by using kotlinx.cli
2019-08-23 00:11:08 +02:00
Irmen de Jong 2d768c3f28 code cleanups 2019-08-22 22:06:21 +02:00
Irmen de Jong b79af624ae added more asmgen for bitshift operations 2019-08-22 00:34:17 +02:00
Irmen de Jong 38208a7c9e removed fake vm functions 2019-08-21 22:00:05 +02:00
Irmen de Jong 8eff51904e taking down the heapvalue mess further 2019-08-21 00:29:31 +02:00
Irmen de Jong c717f4573d taking down the heapvalue mess further 2019-08-20 23:02:13 +02:00
Irmen de Jong 984d251a6d taking down the heapvalue mess, RuntimeValue class separation 2019-08-20 00:01:31 +02:00
Irmen de Jong 8c3b43f3ed taking down the heapvalue mess 2019-08-19 22:28:41 +02:00
Irmen de Jong 0f1485f30b added sorted, sgn, reverse to the AstVm 2019-08-18 16:39:08 +02:00
Irmen de Jong eb94c678bd doc 2019-08-18 14:18:46 +02:00
Irmen de Jong 50d792a121 fix doc about for loops 2019-08-18 14:14:14 +02:00
Irmen de Jong f0d4654917 v1.60 2019-08-18 14:06:30 +02:00
Irmen de Jong 4ce93b5d9d restored proper compiler error when trying to modify a constant 2019-08-18 14:05:20 +02:00
Irmen de Jong fb0d7a1908 some array literals weren't put on the heap 2019-08-18 13:46:13 +02:00
Irmen de Jong bb7b063757 revert inline var declaration in for loops 2019-08-18 03:16:23 +02:00
Irmen de Jong c495f54bbb don't fall-through into nested subroutine 2019-08-18 02:33:42 +02:00
Irmen de Jong 1cc1f2d91d reverse() added (byte+word) 2019-08-18 02:05:51 +02:00
Irmen de Jong d837cc11f9 sort() added (bytes+words) 2019-08-18 00:04:03 +02:00
Irmen de Jong cbb7083307 fix problem with typechecking of const arrays 2019-08-17 21:43:48 +02:00
Irmen de Jong d4a17dfad1 fixed builtin functions no longer const-folding over arrays 2019-08-17 20:16:39 +02:00
Irmen de Jong 59f8b91e25 tweak 2019-08-17 18:44:44 +02:00
Irmen de Jong 80113f9208 version 1.52 2019-08-17 16:44:46 +02:00
Irmen de Jong 27f987f0ae fixed bit shifts, added sgn() function 2019-08-17 16:44:28 +02:00
Irmen de Jong 3ae2597261 irq driven music player example 2019-08-17 13:13:15 +02:00
Irmen de Jong 248e7b808c split codegen 2019-08-16 22:49:29 +02:00
Irmen de Jong a983a896f2 some asm and some for loop asm fixed, renamed asmgen2 back to just asmgen 2019-08-16 21:37:27 +02:00
Irmen de Jong 68df1730f5 cleaned up some stuff, improved checking of asmsub statement body 2019-08-14 23:17:50 +02:00
Irmen de Jong d62ab93b24 word >> 8 optimized to msb(word) 2019-08-14 22:28:44 +02:00
Irmen de Jong 47297f7e31 improved handling of inferredType 2019-08-14 02:25:27 +02:00
Irmen de Jong b64d611e02 split array and string literal classes 2019-08-13 03:00:17 +02:00
Irmen de Jong 9fb9bcfebd correction 2019-08-12 23:25:19 +02:00
Irmen de Jong dff9c5f53e tweak travis 2019-08-11 22:58:45 +02:00
Irmen de Jong d4a77321d2 tweak gradle to work with openjdk-11 2019-08-11 22:56:54 +02:00
Irmen de Jong 2665618fa6 zp test added, some cleanups 2019-08-11 22:23:18 +02:00
Irmen de Jong b5c5560af8 info 2019-08-11 18:21:15 +02:00
184 changed files with 13202 additions and 19977 deletions

No files matched your search

+6 -6
View File
@@ -1,7 +1,8 @@
.idea/workspace.xml
/build/
/dist/
/output/
.idea/discord.xml
build/
dist/
output/
.*cache/
*.directory
*.prg
@@ -11,8 +12,8 @@
*.vice-mon-list
docs/build
out/
**/*.interp
**/*.tokens
parser/**/*.interp
parser/**/*.tokens
*.py[cod]
*.egg
@@ -27,5 +28,4 @@ parsetab.py
.attach_pid*
.gradle
build/
/prog8compiler.jar
+10
View File
@@ -1,6 +1,16 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<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">
<option name="processCode" value="false" />
<option name="processLiterals" value="true" />
+9
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
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>
+9
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
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
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
View File
@@ -1,5 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<output url="file://$PROJECT_DIR$/out" />
</component>
-1
View File
@@ -2,7 +2,6 @@
<project version="4">
<component name="ProjectModuleManager">
<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$/docs/docs.iml" filepath="$PROJECT_DIR$/docs/docs.iml" />
<module fileurl="file://$PROJECT_DIR$/examples/examples.iml" filepath="$PROJECT_DIR$/examples/examples.iml" />
Generated
+1 -1
View File
@@ -3,4 +3,4 @@
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
</project>
+2 -2
View File
@@ -1,11 +1,11 @@
language: java
sudo: false
# jdk: openjdk8
# dist: xenial
# sudo: false
before_install:
- chmod +x gradlew
script:
- ./gradlew test
- gradle test
-10
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
@@ -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:"
}
}
@@ -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")
}
}
@@ -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
)
@@ -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)
}
File diff suppressed because it is too large. Load diff
@@ -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
}
}
@@ -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()
}
}
@@ -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
+12 -16
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):
- reduction of source code length
- easier program understanding (because it's higher level, and way more compact)
- modularity, symbol scoping, subroutines
- subroutines have enforced input- and output parameter definitions
- various data types other than just bytes (16-bit words, floats, strings)
- automatic variable allocations, automatic string variables and string sharing
- constant folding in expressions (compile-time evaluation)
- automatic variable allocations, automatic string and array variables and string sharing
- subroutines with a input- and output parameter signature
- constant folding in expressions
- 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
- automatic type conversions
- floating point operations (uses the C64 Basic ROM routines for this)
- floating point operations (requires the C64 Basic ROM routines for this)
- abstracting away low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- various code optimizations (code structure, logical and numerical expressions, unused code removal...)
- 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:
- use modern PC to work on
- quick compilation times (couple of seconds, and less than a second when using the continuous compilation mode)
- option to automatically run the program in the Vice emulator
- use modern PC to work on
- quick compilation times (seconds)
- option to automatically run the program in the Vice emulator
- breakpoints, that let the Vice emulator drop into the monitor if execution hits them
- source code labels automatically loaded in Vice emulator so it can show them in disassembly
- the compiler includes a virtual machine that can execute compiled code directy on the
host system without having to actually convert it to assembly to run on a real 6502.
This allows for very quick experimentation and debugging
- virtual machine that can execute compiled code directy on the host system,
without having to actually convert it to assembly to run on a real 6502
It is mainly targeted at the Commodore-64 machine at this time.
Contributions to add support for other 8-bit (or other?!) machines are welcome.
Documentation/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
--------------
+24 -19
View File
@@ -1,54 +1,62 @@
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.70"
}
}
plugins {
// id "org.jetbrains.kotlin.jvm" version $kotlinVersion
// id "org.jetbrains.kotlin.jvm" version "1.3.70"
id 'application'
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'
}
apply plugin: "kotlin"
apply plugin: "java"
targetCompatibility = 1.8
sourceCompatibility = 1.8
repositories {
mavenLocal()
mavenCentral()
jcenter()
maven { url "https://dl.bintray.com/orangy/maven/" }
}
sourceCompatibility = 1.8
def prog8version = rootProject.file('compiler/res/version.txt').text.trim()
dependencies {
implementation project(':parser')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion"
// implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
// runtime "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
runtime 'org.antlr:antlr4-runtime:4.7.2'
runtime project(':parser')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
// implementation "org.jetbrains.kotlin:kotlin-reflect"
implementation 'org.antlr:antlr4-runtime:4.8'
implementation 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
// implementation 'net.razorvine:ksim65:1.6'
// implementation "com.github.hypfvieh:dbus-java:3.2.0"
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.hamcrest:hamcrest-junit:2.0.0.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.2'
}
compileKotlin {
kotlinOptions {
jvmTarget = "1.8"
verbose = true
// verbose = true
// freeCompilerArgs += "-XXLanguage:+NewInference"
}
}
compileTestKotlin {
kotlinOptions {
jvmTarget = "1.8"
}
}
sourceSets {
main {
java {
@@ -77,12 +85,9 @@ artifacts {
}
// To create a fat-jar use the 'create_compiler_jar' script for now
// @todo investigate https://imperceptiblethoughts.com/shadow/introduction/
shadowJar {
baseName = 'prog8compiler'
version = prog8version
archiveBaseName = 'prog8compiler'
archiveVersion = prog8version
// minimize()
}
@@ -96,7 +101,7 @@ test {
// Show test results.
testLogging {
events "passed", "skipped", "failed"
events "skipped", "failed"
}
}
+3 -2
View File
@@ -8,11 +8,12 @@
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build" />
</content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" />
<orderEntry type="jdk" jdkName="openjdk-11" jdkType="JavaSDK" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
<orderEntry type="library" name="antlr-runtime-4.7.2" level="project" />
<orderEntry type="module" module-name="parser" />
<orderEntry type="library" name="unittest-libs" level="project" />
<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>
</module>
Binary file not shown.
Binary file not shown.
+741
View File
@@ -0,0 +1,741 @@
; --- low level floating point assembly routines for the C64
ub2float .proc
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPB1
jsr FREADUY
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
b2float .proc
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPB1
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
uw2float .proc
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
w2float .proc
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
jsr GIVAYF
jmp ub2float._fac_to_mem
.pend
stack_b2float .proc
; -- b2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
jsr FREADSA
jmp push_fac1_as_result
.pend
stack_w2float .proc
; -- w2float operating on the stack
inx
ldy c64.ESTACK_LO,x
lda c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr GIVAYF
jmp push_fac1_as_result
.pend
stack_ub2float .proc
; -- ub2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
tay
jsr FREADUY
jmp push_fac1_as_result
.pend
stack_uw2float .proc
; -- uw2float operating on the stack
inx
lda c64.ESTACK_LO,x
ldy c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr GIVUAYFAY
jmp push_fac1_as_result
.pend
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr AYINT
ldx c64.SCRATCH_ZPREGX
lda $64
sta c64.ESTACK_HI,x
lda $65
sta c64.ESTACK_LO,x
dex
rts
.pend
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr GETADR
ldx c64.SCRATCH_ZPREGX
sta c64.ESTACK_HI,x
tya
sta c64.ESTACK_LO,x
dex
rts
.pend
push_float .proc
; ---- push mflpt5 in A/Y onto stack
; (taking 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_HI,x
dex
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_HI,x
dex
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
dex
rts
.pend
func_rndf .proc
; -- put a random floating point value on the stack
stx c64.SCRATCH_ZPREG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx #<_rndf_rnum5
ldy #>_rndf_rnum5
jsr MOVMF ; fac1 to mem X/Y
ldx c64.SCRATCH_ZPREG
lda #<_rndf_rnum5
ldy #>_rndf_rnum5
jmp push_float
_rndf_rnum5 .byte 0,0,0,0,0
.pend
push_float_from_indexed_var .proc
; -- push the float from the array at A/Y with index on stack, onto the stack.
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp push_float
.pend
pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #4
inx
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
dey
inx
lda c64.ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y
dey
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
dey
inx
lda c64.ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y
dey
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
pop_float_fac1 .proc
; -- pops float from stack into FAC1
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp MOVFM
.pend
pop_float_to_indexed_var .proc
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp pop_float
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
rts
.pend
inc_var_f .proc
; -- add 1 to float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
jsr MOVFM
lda #<FL_FONE
ldy #>FL_FONE
jsr FADD
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
dec_var_f .proc
; -- subtract 1 from float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
lda #<FL_FONE
ldy #>FL_FONE
jsr MOVFM
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FSUB
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
inc_indexed_var_f .proc
; -- add 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp inc_var_f
.pend
dec_indexed_var_f .proc
; -- subtract 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp dec_var_f
.pend
pop_2_floats_f2_in_fac1 .proc
; -- pop 2 floats from stack, load the second one in FAC1 as well
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float2
ldy #>fmath_float2
jmp MOVFM
.pend
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
push_fac1_as_result .proc
; -- push the float in FAC1 onto the stack, and return from calculation
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
lda #<fmath_float1
ldy #>fmath_float1
ldx c64.SCRATCH_ZPREGX
jmp push_float
.pend
pow_f .proc
; -- push f1 ** f2 on stack
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr CONUPK ; fac2 = float1
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
ldx c64.SCRATCH_ZPREGX
jmp push_fac1_as_result
.pend
div_f .proc
; -- push f1/f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FDIV
jmp push_fac1_as_result
.pend
add_f .proc
; -- push f1+f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FADD
jmp push_fac1_as_result
.pend
sub_f .proc
; -- push f1-f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FSUB
jmp push_fac1_as_result
.pend
mul_f .proc
; -- push f1*f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FMULT
jmp push_fac1_as_result
.pend
neg_f .proc
; -- push -flt back on stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr NEGOP
jmp push_fac1_as_result
.pend
abs_f .proc
; -- push abs(float) on stack (as float)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr ABS
jmp push_fac1_as_result
.pend
equal_f .proc
; -- are the two mflpt5 numbers on the stack identical?
inx
inx
inx
inx
lda c64.ESTACK_LO-3,x
cmp c64.ESTACK_LO,x
bne _equals_false
lda c64.ESTACK_LO-2,x
cmp c64.ESTACK_LO+1,x
bne _equals_false
lda c64.ESTACK_LO-1,x
cmp c64.ESTACK_LO+2,x
bne _equals_false
lda c64.ESTACK_HI-2,x
cmp c64.ESTACK_HI+1,x
bne _equals_false
lda c64.ESTACK_HI-1,x
cmp c64.ESTACK_HI+2,x
bne _equals_false
_equals_true lda #1
_equals_store inx
sta c64.ESTACK_LO+1,x
rts
_equals_false lda #0
beq _equals_store
.pend
notequal_f .proc
; -- are the two mflpt5 numbers on the stack different?
jsr equal_f
eor #1 ; invert the result
sta c64.ESTACK_LO+1,x
rts
.pend
less_f .proc
; -- is f1 < f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
bne compare_floats._return_false
.pend
lesseq_f .proc
; -- is f1 <= f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greater_f .proc
; -- is f1 > f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greatereq_f .proc
; -- is f1 >= f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
compare_floats .proc
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr MOVFM ; fac1 = flt1
lda #<fmath_float2
ldy #>fmath_float2
stx c64.SCRATCH_ZPREG
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
ldx c64.SCRATCH_ZPREG
rts
_return_false lda #0
_return_result sta c64.ESTACK_LO,x
dex
rts
_return_true lda #1
bne _return_result
.pend
func_sin .proc
; -- push sin(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr SIN
jmp push_fac1_as_result
.pend
func_cos .proc
; -- push cos(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr COS
jmp push_fac1_as_result
.pend
func_tan .proc
; -- push tan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr TAN
jmp push_fac1_as_result
.pend
func_atan .proc
; -- push atan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr ATN
jmp push_fac1_as_result
.pend
func_ln .proc
; -- push ln(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr LOG
jmp push_fac1_as_result
.pend
func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr LOG
jsr MOVEF
lda #<c64.FL_LOG2
ldy #>c64.FL_LOG2
jsr MOVFM
jsr FDIVT
jmp push_fac1_as_result
.pend
func_sqrt .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr SQR
jmp push_fac1_as_result
.pend
func_rad .proc
; -- convert degrees to radians (d * pi / 180)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_pi_div_180
ldy #>_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend
func_deg .proc
; -- convert radians to degrees (d * (1/ pi * 180))
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend
func_round .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr FADDH
jsr INT
jmp push_fac1_as_result
.pend
func_floor .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr INT
jmp push_fac1_as_result
.pend
func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1
ldy #>fmath_float1
jsr FCOMP
cmp #0
beq +
lda #<FL_FONE
ldy #>FL_FONE
jsr FADD
+ jmp push_fac1_as_result
.pend
func_any_f .proc
inx
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
jmp prog8_lib.func_any_b._entry
.pend
func_all_f .proc
inx
jsr prog8_lib.peek_address
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
tay
dey
- lda (c64.SCRATCH_ZPWORD1),y
clc
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
cmp #0
beq +
cpy #255
bne -
lda #1
sta c64.ESTACK_LO+1,x
rts
+ sta c64.ESTACK_LO+1,x
rts
.pend
func_max_f .proc
lda #255
sta _minmax_cmp+1
lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FCOMP
_minmax_cmp cmp #255 ; modified
bne +
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVFM
+ lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc +
inc c64.SCRATCH_ZPWORD1+1
+ ldy c64.SCRATCH_ZPREG
dey
cpy #255
bne -
jmp push_fac1_as_result
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend
func_min_f .proc
lda #1
sta func_max_f._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f._minmax_entry
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend
func_sum_f .proc
lda #<FL_ZERO
ldy #>FL_ZERO
jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FADD
ldy c64.SCRATCH_ZPREG
dey
cpy #255
beq +
lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc -
inc c64.SCRATCH_ZPWORD1+1
bne -
+ jmp push_fac1_as_result
.pend
sign_f .proc
jsr pop_float_fac1
jsr SIGN
sta c64.ESTACK_LO,x
dex
rts
.pend
+49 -784
View File
@@ -41,25 +41,25 @@ c64flt {
; note: for subtraction and division, the left operand is in fac2, the right operand in fac1.
; checked functions below:
asmsub MOVFM (uword mflpt @ AY) clobbers(A,Y) = $bba2 ; load mflpt value from memory in A/Y into fac1
asmsub FREADMEM () clobbers(A,Y) = $bba6 ; load mflpt value from memory in $22/$23 into fac1
asmsub CONUPK (uword mflpt @ AY) clobbers(A,Y) = $ba8c ; load mflpt value from memory in A/Y into fac2
asmsub FAREADMEM () clobbers(A,Y) = $ba90 ; load mflpt value from memory in $22/$23 into fac2
asmsub MOVFA () clobbers(A,X) = $bbfc ; copy fac2 to fac1
asmsub MOVAF () clobbers(A,X) = $bc0c ; copy fac1 to fac2 (rounded)
asmsub MOVEF () clobbers(A,X) = $bc0f ; copy fac1 to fac2
asmsub MOVMF (uword mflpt @ XY) clobbers(A,Y) = $bbd4 ; store fac1 to memory X/Y as 5-byte mflpt
romsub $bba2 = MOVFM(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac1
romsub $bba6 = FREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac1
romsub $ba8c = CONUPK(uword mflpt @ AY) clobbers(A,Y) ; load mflpt value from memory in A/Y into fac2
romsub $ba90 = FAREADMEM() clobbers(A,Y) ; load mflpt value from memory in $22/$23 into fac2
romsub $bbfc = MOVFA() clobbers(A,X) ; copy fac2 to fac1
romsub $bc0c = MOVAF() clobbers(A,X) ; copy fac1 to fac2 (rounded)
romsub $bc0f = MOVEF() clobbers(A,X) ; copy fac1 to fac2
romsub $bbd4 = MOVMF(uword mflpt @ XY) clobbers(A,Y) ; store fac1 to memory X/Y as 5-byte mflpt
; fac1-> signed word in Y/A (might throw ILLEGAL QUANTITY)
; (tip: use c64flt.FTOSWRDAY to get A/Y output; lo/hi switched to normal little endian order)
asmsub FTOSWORDYA () clobbers(X) -> ubyte @ Y, ubyte @ A = $b1aa ; note: calls AYINT.
romsub $b1aa = FTOSWORDYA() clobbers(X) -> ubyte @ Y, ubyte @ A ; note: calls AYINT.
; fac1 -> unsigned word in Y/A (might throw ILLEGAL QUANTITY) (result also in $14/15)
; (tip: use c64flt.GETADRAY to get A/Y output; lo/hi switched to normal little endian order)
asmsub GETADR () clobbers(X) -> ubyte @ Y, ubyte @ A = $b7f7
romsub $b7f7 = GETADR() clobbers(X) -> ubyte @ Y, ubyte @ A
asmsub QINT () clobbers(A,X,Y) = $bc9b ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
asmsub AYINT () clobbers(A,X,Y) = $b1bf ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
romsub $bc9b = QINT() clobbers(A,X,Y) ; fac1 -> 4-byte signed integer in 98-101 ($62-$65), with the MSB FIRST.
romsub $b1bf = AYINT() clobbers(A,X,Y) ; fac1-> signed word in 100-101 ($64-$65) MSB FIRST. (might throw ILLEGAL QUANTITY)
; GIVAYF: signed word in Y/A (note different lsb/msb order) -> float in fac1
; (tip: use c64flt.GIVAYFAY to use A/Y input; lo/hi switched to normal order)
@@ -67,50 +67,49 @@ asmsub AYINT () clobbers(A,X,Y) = $b1bf ; fac1-> signed word in 100-101 ($64
; there is also c64flt.FREADS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADUS32 that reads from 98-101 ($62-$65) MSB FIRST
; there is also c64flt.FREADS24AXY that reads signed int24 into fac1 from A/X/Y (lo/mid/hi bytes)
asmsub GIVAYF (ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y) = $b391
romsub $b391 = GIVAYF(ubyte lo @ Y, ubyte hi @ A) clobbers(A,X,Y)
asmsub FREADUY (ubyte value @ Y) clobbers(A,X,Y) = $b3a2 ; 8 bit unsigned Y -> float in fac1
asmsub FREADSA (byte value @ A) clobbers(A,X,Y) = $bc3c ; 8 bit signed A -> float in fac1
asmsub FREADSTR (ubyte length @ A) clobbers(A,X,Y) = $b7b5 ; str -> fac1, $22/23 must point to string, A=string length
asmsub FPRINTLN () clobbers(A,X,Y) = $aabc ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
asmsub FOUT () clobbers(X) -> uword @ AY = $bddd ; fac1 -> string, address returned in AY ($0100)
romsub $b3a2 = FREADUY(ubyte value @ Y) clobbers(A,X,Y) ; 8 bit unsigned Y -> float in fac1
romsub $bc3c = FREADSA(byte value @ A) clobbers(A,X,Y) ; 8 bit signed A -> float in fac1
romsub $b7b5 = FREADSTR(ubyte length @ A) clobbers(A,X,Y) ; str -> fac1, $22/23 must point to string, A=string length
romsub $aabc = FPRINTLN() clobbers(A,X,Y) ; print string of fac1, on one line (= with newline) destroys fac1. (consider FOUT + STROUT as well)
romsub $bddd = FOUT() clobbers(X) -> uword @ AY ; fac1 -> string, address returned in AY ($0100)
asmsub FADDH () clobbers(A,X,Y) = $b849 ; fac1 += 0.5, for rounding- call this before INT
asmsub MUL10 () clobbers(A,X,Y) = $bae2 ; fac1 *= 10
asmsub DIV10 () clobbers(A,X,Y) = $bafe ; fac1 /= 10 , CAUTION: result is always positive!
asmsub FCOMP (uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A = $bc5b ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
romsub $b849 = FADDH() clobbers(A,X,Y) ; fac1 += 0.5, for rounding- call this before INT
romsub $bae2 = MUL10() clobbers(A,X,Y) ; fac1 *= 10
romsub $bafe = DIV10() clobbers(A,X,Y) ; fac1 /= 10 , CAUTION: result is always positive!
romsub $bc5b = FCOMP(uword mflpt @ AY) clobbers(X,Y) -> ubyte @ A ; A = compare fac1 to mflpt in A/Y, 0=equal 1=fac1 is greater, 255=fac1 is less than
asmsub FADDT () clobbers(A,X,Y) = $b86a ; fac1 += fac2
asmsub FADD (uword mflpt @ AY) clobbers(A,X,Y) = $b867 ; fac1 += mflpt value from A/Y
asmsub FSUBT () clobbers(A,X,Y) = $b853 ; fac1 = fac2-fac1 mind the order of the operands
asmsub FSUB (uword mflpt @ AY) clobbers(A,X,Y) = $b850 ; fac1 = mflpt from A/Y - fac1
asmsub FMULTT () clobbers(A,X,Y) = $ba2b ; fac1 *= fac2
asmsub FMULT (uword mflpt @ AY) clobbers(A,X,Y) = $ba28 ; fac1 *= mflpt value from A/Y
asmsub FDIVT () clobbers(A,X,Y) = $bb12 ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
asmsub FDIV (uword mflpt @ AY) clobbers(A,X,Y) = $bb0f ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
asmsub FPWRT () clobbers(A,X,Y) = $bf7b ; fac1 = fac2 ** fac1
asmsub FPWR (uword mflpt @ AY) clobbers(A,X,Y) = $bf78 ; fac1 = fac2 ** mflpt from A/Y
romsub $b86a = FADDT() clobbers(A,X,Y) ; fac1 += fac2
romsub $b867 = FADD(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 += mflpt value from A/Y
romsub $b853 = FSUBT() clobbers(A,X,Y) ; fac1 = fac2-fac1 mind the order of the operands
romsub $b850 = FSUB(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt from A/Y - fac1
romsub $ba2b = FMULTT() clobbers(A,X,Y) ; fac1 *= fac2
romsub $ba28 = FMULT(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 *= mflpt value from A/Y
romsub $bb12 = FDIVT() clobbers(A,X,Y) ; fac1 = fac2/fac1 (remainder in fac2) mind the order of the operands
romsub $bb0f = FDIV(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = mflpt in A/Y / fac1 (remainder in fac2)
romsub $bf7b = FPWRT() clobbers(A,X,Y) ; fac1 = fac2 ** fac1
romsub $bf78 = FPWR(uword mflpt @ AY) clobbers(A,X,Y) ; fac1 = fac2 ** mflpt from A/Y
asmsub NOTOP () clobbers(A,X,Y) = $aed4 ; fac1 = NOT(fac1)
asmsub INT () clobbers(A,X,Y) = $bccc ; INT() truncates, use FADDH first to round instead of trunc
asmsub LOG () clobbers(A,X,Y) = $b9ea ; fac1 = LN(fac1) (natural log)
asmsub SGN () clobbers(A,X,Y) = $bc39 ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
asmsub SIGN () -> ubyte @ A = $bc2b ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
asmsub ABS () = $bc58 ; fac1 = ABS(fac1)
asmsub SQR () clobbers(A,X,Y) = $bf71 ; fac1 = SQRT(fac1)
asmsub SQRA () clobbers(A,X,Y) = $bf74 ; fac1 = SQRT(fac2)
asmsub EXP () clobbers(A,X,Y) = $bfed ; fac1 = EXP(fac1) (e ** fac1)
asmsub NEGOP () clobbers(A) = $bfb4 ; switch the sign of fac1
asmsub RND () clobbers(A,X,Y) = $e097 ; fac1 = RND(fac1) float random number generator
asmsub COS () clobbers(A,X,Y) = $e264 ; fac1 = COS(fac1)
asmsub SIN () clobbers(A,X,Y) = $e26b ; fac1 = SIN(fac1)
asmsub TAN () clobbers(A,X,Y) = $e2b4 ; fac1 = TAN(fac1)
asmsub ATN () clobbers(A,X,Y) = $e30e ; fac1 = ATN(fac1)
romsub $aed4 = NOTOP() clobbers(A,X,Y) ; fac1 = NOT(fac1)
romsub $bccc = INT() clobbers(A,X,Y) ; INT() truncates, use FADDH first to round instead of trunc
romsub $b9ea = LOG() clobbers(A,X,Y) ; fac1 = LN(fac1) (natural log)
romsub $bc39 = SGN() clobbers(A,X,Y) ; fac1 = SGN(fac1), result of SIGN (-1, 0 or 1)
romsub $bc2b = SIGN() -> ubyte @ A ; SIGN(fac1) to A, $ff, $0, $1 for negative, zero, positive
romsub $bc58 = ABS() ; fac1 = ABS(fac1)
romsub $bf71 = SQR() clobbers(A,X,Y) ; fac1 = SQRT(fac1)
romsub $bf74 = SQRA() clobbers(A,X,Y) ; fac1 = SQRT(fac2)
romsub $bfed = EXP() clobbers(A,X,Y) ; fac1 = EXP(fac1) (e ** fac1)
romsub $bfb4 = NEGOP() clobbers(A) ; switch the sign of fac1
romsub $e097 = RND() clobbers(A,X,Y) ; fac1 = RND(fac1) float random number generator
romsub $e264 = COS() clobbers(A,X,Y) ; fac1 = COS(fac1)
romsub $e26b = SIN() clobbers(A,X,Y) ; fac1 = SIN(fac1)
romsub $e2b4 = TAN() clobbers(A,X,Y) ; fac1 = TAN(fac1)
romsub $e30e = ATN() clobbers(A,X,Y) ; fac1 = ATN(fac1)
asmsub FREADS32 () clobbers(A,X,Y) {
asmsub FREADS32() clobbers(A,X,Y) {
; ---- fac1 = signed int32 from $62-$65 big endian (MSB FIRST)
%asm {{
lda $62
@@ -220,740 +219,6 @@ sub print_fln (float value) {
}
; --- low level floating point assembly routines
%asm {{
ub2float .proc
; -- convert ubyte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPB1
jsr FREADUY
_fac_to_mem ldx c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
b2float .proc
; -- convert byte in SCRATCH_ZPB1 to float at address A/Y
; clobbers A, Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPB1
jsr FREADSA
jmp ub2float._fac_to_mem
.pend
uw2float .proc
; -- convert uword in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr GIVUAYFAY
jmp ub2float._fac_to_mem
.pend
w2float .proc
; -- convert word in SCRATCH_ZPWORD1 to float at address A/Y
stx c64.SCRATCH_ZPREGX
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
jsr GIVAYF
jmp ub2float._fac_to_mem
.pend
stack_b2float .proc
; -- b2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
jsr FREADSA
jmp push_fac1_as_result
.pend
stack_w2float .proc
; -- w2float operating on the stack
inx
ldy c64.ESTACK_LO,x
lda c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr GIVAYF
jmp push_fac1_as_result
.pend
stack_ub2float .proc
; -- ub2float operating on the stack
inx
lda c64.ESTACK_LO,x
stx c64.SCRATCH_ZPREGX
tay
jsr FREADUY
jmp push_fac1_as_result
.pend
stack_uw2float .proc
; -- uw2float operating on the stack
inx
lda c64.ESTACK_LO,x
ldy c64.ESTACK_HI,x
stx c64.SCRATCH_ZPREGX
jsr GIVUAYFAY
jmp push_fac1_as_result
.pend
stack_float2w .proc ; also used for float2b
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr AYINT
ldx c64.SCRATCH_ZPREGX
lda $64
sta c64.ESTACK_HI,x
lda $65
sta c64.ESTACK_LO,x
dex
rts
.pend
stack_float2uw .proc ; also used for float2ub
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr GETADR
ldx c64.SCRATCH_ZPREGX
sta c64.ESTACK_HI,x
tya
sta c64.ESTACK_LO,x
dex
rts
.pend
push_float .proc
; ---- push mflpt5 in A/Y onto stack
; (taking 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_HI,x
dex
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_HI,x
dex
iny
lda (c64.SCRATCH_ZPWORD1),y
sta c64.ESTACK_LO,x
dex
rts
.pend
func_rndf .proc
; -- put a random floating point value on the stack
stx c64.SCRATCH_ZPREG
lda #1
jsr FREADSA
jsr RND ; rng into fac1
ldx #<_rndf_rnum5
ldy #>_rndf_rnum5
jsr MOVMF ; fac1 to mem X/Y
ldx c64.SCRATCH_ZPREG
lda #<_rndf_rnum5
ldy #>_rndf_rnum5
jmp push_float
_rndf_rnum5 .byte 0,0,0,0,0
.pend
push_float_from_indexed_var .proc
; -- push the float from the array at A/Y with index on stack, onto the stack.
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp push_float
.pend
pop_float .proc
; ---- pops mflpt5 from stack to memory A/Y
; (frees 3 stack positions = 6 bytes of which 1 is padding)
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
ldy #4
inx
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
dey
inx
lda c64.ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y
dey
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
dey
inx
lda c64.ESTACK_HI,x
sta (c64.SCRATCH_ZPWORD1),y
dey
lda c64.ESTACK_LO,x
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
pop_float_fac1 .proc
; -- pops float from stack into FAC1
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jmp MOVFM
.pend
pop_float_to_indexed_var .proc
; -- pop the float on the stack, to the memory in the array at A/Y indexed by the byte on stack
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
jsr prog8_lib.pop_index_times_5
jsr prog8_lib.add_a_to_zpword
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jmp pop_float
.pend
copy_float .proc
; -- copies the 5 bytes of the mflt value pointed to by SCRATCH_ZPWORD1,
; into the 5 bytes pointed to by A/Y. Clobbers A,Y.
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #0
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
iny
lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
rts
.pend
inc_var_f .proc
; -- add 1 to float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
jsr MOVFM
lda #<FL_FONE
ldy #>FL_FONE
jsr FADD
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
dec_var_f .proc
; -- subtract 1 from float pointed to by A/Y
sta c64.SCRATCH_ZPWORD1
sty c64.SCRATCH_ZPWORD1+1
stx c64.SCRATCH_ZPREGX
lda #<FL_FONE
ldy #>FL_FONE
jsr MOVFM
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FSUB
ldx c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVMF
ldx c64.SCRATCH_ZPREGX
rts
.pend
inc_indexed_var_f .proc
; -- add 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp inc_var_f
.pend
dec_indexed_var_f .proc
; -- subtract 1 to float in array pointed to by A/Y, at index X
pha
txa
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPB1
pla
clc
adc c64.SCRATCH_ZPB1
bcc +
iny
+ jmp dec_var_f
.pend
pop_2_floats_f2_in_fac1 .proc
; -- pop 2 floats from stack, load the second one in FAC1 as well
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float2
ldy #>fmath_float2
jmp MOVFM
.pend
fmath_float1 .byte 0,0,0,0,0 ; storage for a mflpt5 value
fmath_float2 .byte 0,0,0,0,0 ; storage for a mflpt5 value
push_fac1_as_result .proc
; -- push the float in FAC1 onto the stack, and return from calculation
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
lda #<fmath_float1
ldy #>fmath_float1
ldx c64.SCRATCH_ZPREGX
jmp push_float
.pend
pow_f .proc
; -- push f1 ** f2 on stack
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr CONUPK ; fac2 = float1
lda #<fmath_float2
ldy #>fmath_float2
jsr FPWR
ldx c64.SCRATCH_ZPREGX
jmp push_fac1_as_result
.pend
div_f .proc
; -- push f1/f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FDIV
jmp push_fac1_as_result
.pend
add_f .proc
; -- push f1+f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FADD
jmp push_fac1_as_result
.pend
sub_f .proc
; -- push f1-f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FSUB
jmp push_fac1_as_result
.pend
mul_f .proc
; -- push f1*f2 on stack
jsr pop_2_floats_f2_in_fac1
stx c64.SCRATCH_ZPREGX
lda #<fmath_float1
ldy #>fmath_float1
jsr FMULT
jmp push_fac1_as_result
.pend
neg_f .proc
; -- push -flt back on stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr NEGOP
jmp push_fac1_as_result
.pend
abs_f .proc
; -- push abs(float) on stack (as float)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr ABS
jmp push_fac1_as_result
.pend
equal_f .proc
; -- are the two mflpt5 numbers on the stack identical?
inx
inx
inx
inx
lda c64.ESTACK_LO-3,x
cmp c64.ESTACK_LO,x
bne _equals_false
lda c64.ESTACK_LO-2,x
cmp c64.ESTACK_LO+1,x
bne _equals_false
lda c64.ESTACK_LO-1,x
cmp c64.ESTACK_LO+2,x
bne _equals_false
lda c64.ESTACK_HI-2,x
cmp c64.ESTACK_HI+1,x
bne _equals_false
lda c64.ESTACK_HI-1,x
cmp c64.ESTACK_HI+2,x
bne _equals_false
_equals_true lda #1
_equals_store inx
sta c64.ESTACK_LO+1,x
rts
_equals_false lda #0
beq _equals_store
.pend
notequal_f .proc
; -- are the two mflpt5 numbers on the stack different?
jsr equal_f
eor #1 ; invert the result
sta c64.ESTACK_LO+1,x
rts
.pend
less_f .proc
; -- is f1 < f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
bne compare_floats._return_false
.pend
lesseq_f .proc
; -- is f1 <= f2?
jsr compare_floats
cmp #255
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greater_f .proc
; -- is f1 > f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
bne compare_floats._return_false
.pend
greatereq_f .proc
; -- is f1 >= f2?
jsr compare_floats
cmp #1
beq compare_floats._return_true
cmp #0
beq compare_floats._return_true
bne compare_floats._return_false
.pend
compare_floats .proc
lda #<fmath_float2
ldy #>fmath_float2
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr pop_float
lda #<fmath_float1
ldy #>fmath_float1
jsr MOVFM ; fac1 = flt1
lda #<fmath_float2
ldy #>fmath_float2
stx c64.SCRATCH_ZPREG
jsr FCOMP ; A = flt1 compared with flt2 (0=equal, 1=flt1>flt2, 255=flt1<flt2)
ldx c64.SCRATCH_ZPREG
rts
_return_false lda #0
_return_result sta c64.ESTACK_LO,x
dex
rts
_return_true lda #1
bne _return_result
.pend
func_sin .proc
; -- push sin(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr SIN
jmp push_fac1_as_result
.pend
func_cos .proc
; -- push cos(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr COS
jmp push_fac1_as_result
.pend
func_tan .proc
; -- push tan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr TAN
jmp push_fac1_as_result
.pend
func_atan .proc
; -- push atan(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr ATN
jmp push_fac1_as_result
.pend
func_ln .proc
; -- push ln(f) back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr LOG
jmp push_fac1_as_result
.pend
func_log2 .proc
; -- push log base 2, ln(f)/ln(2), back onto stack
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr LOG
jsr MOVEF
lda #<c64.FL_LOG2
ldy #>c64.FL_LOG2
jsr MOVFM
jsr FDIVT
jmp push_fac1_as_result
.pend
func_sqrt .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr SQR
jmp push_fac1_as_result
.pend
func_rad .proc
; -- convert degrees to radians (d * pi / 180)
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_pi_div_180
ldy #>_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_pi_div_180 .byte 123, 14, 250, 53, 18 ; pi / 180
.pend
func_deg .proc
; -- convert radians to degrees (d * (1/ pi * 180))
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
lda #<_one_over_pi_div_180
ldy #>_one_over_pi_div_180
jsr FMULT
jmp push_fac1_as_result
_one_over_pi_div_180 .byte 134, 101, 46, 224, 211 ; 1 / (pi * 180)
.pend
func_round .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr FADDH
jsr INT
jmp push_fac1_as_result
.pend
func_floor .proc
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
jsr INT
jmp push_fac1_as_result
.pend
func_ceil .proc
; -- ceil: tr = int(f); if tr==f -> return else return tr+1
jsr pop_float_fac1
stx c64.SCRATCH_ZPREGX
ldx #<fmath_float1
ldy #>fmath_float1
jsr MOVMF
jsr INT
lda #<fmath_float1
ldy #>fmath_float1
jsr FCOMP
cmp #0
beq +
lda #<FL_FONE
ldy #>FL_FONE
jsr FADD
+ jmp push_fac1_as_result
.pend
func_any_f .proc
inx
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
jmp prog8_lib.func_any_b._entry
.pend
func_all_f .proc
inx
jsr prog8_lib.peek_address
lda c64.ESTACK_LO,x ; array size
sta c64.SCRATCH_ZPB1
asl a
asl a
clc
adc c64.SCRATCH_ZPB1 ; times 5 because of float
tay
dey
- lda (c64.SCRATCH_ZPWORD1),y
clc
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
adc (c64.SCRATCH_ZPWORD1),y
dey
cmp #0
beq +
cpy #255
bne -
lda #1
sta c64.ESTACK_LO+1,x
rts
+ sta c64.ESTACK_LO+1,x
rts
.pend
func_max_f .proc
lda #255
sta _minmax_cmp+1
lda #<_largest_neg_float
ldy #>_largest_neg_float
_minmax_entry jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FCOMP
_minmax_cmp cmp #255 ; modified
bne +
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr MOVFM
+ lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc +
inc c64.SCRATCH_ZPWORD1+1
+ ldy c64.SCRATCH_ZPREG
dey
cpy #255
bne -
jmp push_fac1_as_result
_largest_neg_float .byte 255,255,255,255,255 ; largest negative float -1.7014118345e+38
.pend
func_min_f .proc
lda #1
sta func_max_f._minmax_cmp+1
lda #<_largest_pos_float
ldy #>_largest_pos_float
jmp func_max_f._minmax_entry
_largest_pos_float .byte 255,127,255,255,255 ; largest positive float
rts
.pend
func_sum_f .proc
lda #<FL_ZERO
ldy #>FL_ZERO
jsr MOVFM
jsr prog8_lib.pop_array_and_lengthmin1Y
stx c64.SCRATCH_ZPREGX
- sty c64.SCRATCH_ZPREG
lda c64.SCRATCH_ZPWORD1
ldy c64.SCRATCH_ZPWORD1+1
jsr FADD
ldy c64.SCRATCH_ZPREG
dey
cpy #255
beq +
lda c64.SCRATCH_ZPWORD1
clc
adc #5
sta c64.SCRATCH_ZPWORD1
bcc -
inc c64.SCRATCH_ZPWORD1+1
bne -
+ jmp push_fac1_as_result
.pend
}}
%asminclude "library:c64floats.asm", ""
} ; ------ end of block c64flt
+193 -193
View File
@@ -7,178 +7,178 @@
c64 {
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
const uword ESTACK_LO = $ce00 ; evaluation stack (lsb)
const uword ESTACK_HI = $cf00 ; evaluation stack (msb)
&ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
&ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
&ubyte SCRATCH_ZPREGX = $fa ; temp storage for X register (stack pointer)
&uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
&uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
&ubyte TIME_HI = $a0 ; software jiffy clock, hi byte
&ubyte TIME_MID = $a1 ; .. mid byte
&ubyte TIME_LO = $a2 ; .. lo byte. Updated by IRQ every 1/60 sec
&ubyte STKEY = $91 ; various keyboard statuses (updated by IRQ)
&ubyte SFDX = $cb ; current key pressed (matrix value) (updated by IRQ)
&ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword CINV = $0314 ; IRQ vector
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
&ubyte COLOR = $0286 ; cursor color
&ubyte HIBASE = $0288 ; screen base address / 256 (hi-byte of screen memory address)
&uword CINV = $0314 ; IRQ vector
&uword NMI_VEC = $FFFA ; 6502 nmi vector, determined by the kernal if banked in
&uword RESET_VEC = $FFFC ; 6502 reset vector, determined by the kernal if banked in
&uword IRQ_VEC = $FFFE ; 6502 interrupt vector, determined by the kernal if banked in
; the default addresses for the character screen chars and colors
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
; the default addresses for the character screen chars and colors
const uword Screen = $0400 ; to have this as an array[40*25] the compiler would have to support array size > 255
const uword Colors = $d800 ; to have this as an array[40*25] the compiler would have to support array size > 255
; the default locations of the 8 sprite pointers (store address of sprite / 64)
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
&ubyte SPRPTR2 = 2042
&ubyte SPRPTR3 = 2043
&ubyte SPRPTR4 = 2044
&ubyte SPRPTR5 = 2045
&ubyte SPRPTR6 = 2046
&ubyte SPRPTR7 = 2047
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
; the default locations of the 8 sprite pointers (store address of sprite / 64)
&ubyte SPRPTR0 = 2040
&ubyte SPRPTR1 = 2041
&ubyte SPRPTR2 = 2042
&ubyte SPRPTR3 = 2043
&ubyte SPRPTR4 = 2044
&ubyte SPRPTR5 = 2045
&ubyte SPRPTR6 = 2046
&ubyte SPRPTR7 = 2047
&ubyte[8] SPRPTR = 2040 ; the 8 sprite pointers as an array.
; ---- VIC-II 6567/6569/856x registers ----
&ubyte SP0X = $d000
&ubyte SP0Y = $d001
&ubyte SP1X = $d002
&ubyte SP1Y = $d003
&ubyte SP2X = $d004
&ubyte SP2Y = $d005
&ubyte SP3X = $d006
&ubyte SP3Y = $d007
&ubyte SP4X = $d008
&ubyte SP4Y = $d009
&ubyte SP5X = $d00a
&ubyte SP5Y = $d00b
&ubyte SP6X = $d00c
&ubyte SP6Y = $d00d
&ubyte SP7X = $d00e
&ubyte SP7Y = $d00f
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
&ubyte SP0X = $d000
&ubyte SP0Y = $d001
&ubyte SP1X = $d002
&ubyte SP1Y = $d003
&ubyte SP2X = $d004
&ubyte SP2Y = $d005
&ubyte SP3X = $d006
&ubyte SP3Y = $d007
&ubyte SP4X = $d008
&ubyte SP4Y = $d009
&ubyte SP5X = $d00a
&ubyte SP5Y = $d00b
&ubyte SP6X = $d00c
&ubyte SP6Y = $d00d
&ubyte SP7X = $d00e
&ubyte SP7Y = $d00f
&ubyte[16] SPXY = $d000 ; the 8 sprite X and Y registers as an array.
&uword[8] SPXYW = $d000 ; the 8 sprite X and Y registers as a combined xy word array.
&ubyte MSIGX = $d010
&ubyte SCROLY = $d011
&ubyte RASTER = $d012
&ubyte LPENX = $d013
&ubyte LPENY = $d014
&ubyte SPENA = $d015
&ubyte SCROLX = $d016
&ubyte YXPAND = $d017
&ubyte VMCSB = $d018
&ubyte VICIRQ = $d019
&ubyte IREQMASK = $d01a
&ubyte SPBGPR = $d01b
&ubyte SPMC = $d01c
&ubyte XXPAND = $d01d
&ubyte SPSPCL = $d01e
&ubyte SPBGCL = $d01f
&ubyte MSIGX = $d010
&ubyte SCROLY = $d011
&ubyte RASTER = $d012
&ubyte LPENX = $d013
&ubyte LPENY = $d014
&ubyte SPENA = $d015
&ubyte SCROLX = $d016
&ubyte YXPAND = $d017
&ubyte VMCSB = $d018
&ubyte VICIRQ = $d019
&ubyte IREQMASK = $d01a
&ubyte SPBGPR = $d01b
&ubyte SPMC = $d01c
&ubyte XXPAND = $d01d
&ubyte SPSPCL = $d01e
&ubyte SPBGCL = $d01f
&ubyte EXTCOL = $d020 ; border color
&ubyte BGCOL0 = $d021 ; screen color
&ubyte BGCOL1 = $d022
&ubyte BGCOL2 = $d023
&ubyte BGCOL4 = $d024
&ubyte SPMC0 = $d025
&ubyte SPMC1 = $d026
&ubyte SP0COL = $d027
&ubyte SP1COL = $d028
&ubyte SP2COL = $d029
&ubyte SP3COL = $d02a
&ubyte SP4COL = $d02b
&ubyte SP5COL = $d02c
&ubyte SP6COL = $d02d
&ubyte SP7COL = $d02e
&ubyte[8] SPCOL = $d027
&ubyte EXTCOL = $d020 ; border color
&ubyte BGCOL0 = $d021 ; screen color
&ubyte BGCOL1 = $d022
&ubyte BGCOL2 = $d023
&ubyte BGCOL4 = $d024
&ubyte SPMC0 = $d025
&ubyte SPMC1 = $d026
&ubyte SP0COL = $d027
&ubyte SP1COL = $d028
&ubyte SP2COL = $d029
&ubyte SP3COL = $d02a
&ubyte SP4COL = $d02b
&ubyte SP5COL = $d02c
&ubyte SP6COL = $d02d
&ubyte SP7COL = $d02e
&ubyte[8] SPCOL = $d027
; ---- end of VIC-II registers ----
; ---- CIA 6526 1 & 2 registers ----
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
&ubyte CIA1TODHR = $DC0B ; time of day, hours
&ubyte CIA1SDR = $DC0C ; Serial Data Register
&ubyte CIA1ICR = $DC0D
&ubyte CIA1CRA = $DC0E
&ubyte CIA1CRB = $DC0F
&ubyte CIA1PRA = $DC00 ; CIA 1 DRA, keyboard column drive (and joystick control port #2)
&ubyte CIA1PRB = $DC01 ; CIA 1 DRB, keyboard row port (and joystick control port #1)
&ubyte CIA1DDRA = $DC02 ; CIA 1 DDRA, keyboard column
&ubyte CIA1DDRB = $DC03 ; CIA 1 DDRB, keyboard row
&ubyte CIA1TAL = $DC04 ; CIA 1 timer A low byte
&ubyte CIA1TAH = $DC05 ; CIA 1 timer A high byte
&ubyte CIA1TBL = $DC06 ; CIA 1 timer B low byte
&ubyte CIA1TBH = $DC07 ; CIA 1 timer B high byte
&ubyte CIA1TOD10 = $DC08 ; time of day, 1/10 sec.
&ubyte CIA1TODSEC = $DC09 ; time of day, seconds
&ubyte CIA1TODMMIN = $DC0A ; time of day, minutes
&ubyte CIA1TODHR = $DC0B ; time of day, hours
&ubyte CIA1SDR = $DC0C ; Serial Data Register
&ubyte CIA1ICR = $DC0D
&ubyte CIA1CRA = $DC0E
&ubyte CIA1CRB = $DC0F
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
&ubyte CIA2TODHR = $DD0B ; time of day, hours
&ubyte CIA2SDR = $DD0C ; Serial Data Register
&ubyte CIA2ICR = $DD0D
&ubyte CIA2CRA = $DD0E
&ubyte CIA2CRB = $DD0F
&ubyte CIA2PRA = $DD00 ; CIA 2 DRA, serial port and video address
&ubyte CIA2PRB = $DD01 ; CIA 2 DRB, RS232 port / USERPORT
&ubyte CIA2DDRA = $DD02 ; CIA 2 DDRA, serial port and video address
&ubyte CIA2DDRB = $DD03 ; CIA 2 DDRB, RS232 port / USERPORT
&ubyte CIA2TAL = $DD04 ; CIA 2 timer A low byte
&ubyte CIA2TAH = $DD05 ; CIA 2 timer A high byte
&ubyte CIA2TBL = $DD06 ; CIA 2 timer B low byte
&ubyte CIA2TBH = $DD07 ; CIA 2 timer B high byte
&ubyte CIA2TOD10 = $DD08 ; time of day, 1/10 sec.
&ubyte CIA2TODSEC = $DD09 ; time of day, seconds
&ubyte CIA2TODMIN = $DD0A ; time of day, minutes
&ubyte CIA2TODHR = $DD0B ; time of day, hours
&ubyte CIA2SDR = $DD0C ; Serial Data Register
&ubyte CIA2ICR = $DD0D
&ubyte CIA2CRA = $DD0E
&ubyte CIA2CRB = $DD0F
; ---- end of CIA registers ----
; ---- SID 6581/8580 registers ----
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
&uword FREQ1 = $D400 ; channel 1 freq (word)
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
&uword PW1 = $D402 ; channel 1 pulse width (word)
&ubyte CR1 = $D404 ; channel 1 voice control register
&ubyte AD1 = $D405 ; channel 1 attack & decay
&ubyte SR1 = $D406 ; channel 1 sustain & release
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
&uword FREQ2 = $D407 ; channel 2 freq (word)
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
&uword PW2 = $D409 ; channel 2 pulse width (word)
&ubyte CR2 = $D40B ; channel 2 voice control register
&ubyte AD2 = $D40C ; channel 2 attack & decay
&ubyte SR2 = $D40D ; channel 2 sustain & release
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
&uword FREQ3 = $D40E ; channel 3 freq (word)
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
&uword PW3 = $D410 ; channel 3 pulse width (word)
&ubyte CR3 = $D412 ; channel 3 voice control register
&ubyte AD3 = $D413 ; channel 3 attack & decay
&ubyte SR3 = $D414 ; channel 3 sustain & release
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
&uword FC = $D415 ; filter cutoff (word)
&ubyte RESFILT = $D417 ; filter resonance and routing
&ubyte MVOL = $D418 ; filter mode and main volume control
&ubyte POTX = $D419 ; potentiometer X
&ubyte POTY = $D41A ; potentiometer Y
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
&ubyte ENV3 = $D41C ; channel 3 envelope value read
&ubyte FREQLO1 = $D400 ; channel 1 freq lo
&ubyte FREQHI1 = $D401 ; channel 1 freq hi
&uword FREQ1 = $D400 ; channel 1 freq (word)
&ubyte PWLO1 = $D402 ; channel 1 pulse width lo (7-0)
&ubyte PWHI1 = $D403 ; channel 1 pulse width hi (11-8)
&uword PW1 = $D402 ; channel 1 pulse width (word)
&ubyte CR1 = $D404 ; channel 1 voice control register
&ubyte AD1 = $D405 ; channel 1 attack & decay
&ubyte SR1 = $D406 ; channel 1 sustain & release
&ubyte FREQLO2 = $D407 ; channel 2 freq lo
&ubyte FREQHI2 = $D408 ; channel 2 freq hi
&uword FREQ2 = $D407 ; channel 2 freq (word)
&ubyte PWLO2 = $D409 ; channel 2 pulse width lo (7-0)
&ubyte PWHI2 = $D40A ; channel 2 pulse width hi (11-8)
&uword PW2 = $D409 ; channel 2 pulse width (word)
&ubyte CR2 = $D40B ; channel 2 voice control register
&ubyte AD2 = $D40C ; channel 2 attack & decay
&ubyte SR2 = $D40D ; channel 2 sustain & release
&ubyte FREQLO3 = $D40E ; channel 3 freq lo
&ubyte FREQHI3 = $D40F ; channel 3 freq hi
&uword FREQ3 = $D40E ; channel 3 freq (word)
&ubyte PWLO3 = $D410 ; channel 3 pulse width lo (7-0)
&ubyte PWHI3 = $D411 ; channel 3 pulse width hi (11-8)
&uword PW3 = $D410 ; channel 3 pulse width (word)
&ubyte CR3 = $D412 ; channel 3 voice control register
&ubyte AD3 = $D413 ; channel 3 attack & decay
&ubyte SR3 = $D414 ; channel 3 sustain & release
&ubyte FCLO = $D415 ; filter cutoff lo (2-0)
&ubyte FCHI = $D416 ; filter cutoff hi (10-3)
&uword FC = $D415 ; filter cutoff (word)
&ubyte RESFILT = $D417 ; filter resonance and routing
&ubyte MVOL = $D418 ; filter mode and main volume control
&ubyte POTX = $D419 ; potentiometer X
&ubyte POTY = $D41A ; potentiometer Y
&ubyte OSC3 = $D41B ; channel 3 oscillator value read
&ubyte ENV3 = $D41C ; channel 3 envelope value read
; ---- end of SID registers ----
@@ -186,8 +186,8 @@ c64 {
; ---- C64 basic routines ----
asmsub CLEARSCR () clobbers(A,X,Y) = $E544 ; clear the screen
asmsub HOMECRSR () clobbers(A,X,Y) = $E566 ; cursor to top left of screen
romsub $E544 = CLEARSCR() clobbers(A,X,Y) ; clear the screen
romsub $E566 = HOMECRSR() clobbers(A,X,Y) ; cursor to top left of screen
; ---- end of C64 basic routines ----
@@ -195,48 +195,48 @@ asmsub HOMECRSR () clobbers(A,X,Y) = $E566 ; cursor to top left of screen
; ---- C64 kernal routines ----
asmsub STROUT (uword strptr @ AY) clobbers(A, X, Y) = $AB1E ; print null-terminated string (use c64scr.print instead)
asmsub IRQDFRT () clobbers(A,X,Y) = $EA31 ; default IRQ routine
asmsub IRQDFEND () clobbers(A,X,Y) = $EA81 ; default IRQ end/cleanup
asmsub CINT () clobbers(A,X,Y) = $FF81 ; (alias: SCINIT) initialize screen editor and video chip
asmsub IOINIT () clobbers(A, X) = $FF84 ; initialize I/O devices (CIA, SID, IRQ)
asmsub RAMTAS () clobbers(A,X,Y) = $FF87 ; initialize RAM, tape buffer, screen
asmsub RESTOR () clobbers(A,X,Y) = $FF8A ; restore default I/O vectors
asmsub VECTOR (uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) = $FF8D ; read/set I/O vector table
asmsub SETMSG (ubyte value @ A) = $FF90 ; set Kernal message control flag
asmsub SECOND (ubyte address @ A) clobbers(A) = $FF93 ; (alias: LSTNSA) send secondary address after LISTEN
asmsub TKSA (ubyte address @ A) clobbers(A) = $FF96 ; (alias: TALKSA) send secondary address after TALK
asmsub MEMTOP (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF99 ; read/set top of memory pointer
asmsub MEMBOT (uword address @ XY, ubyte dir @ Pc) -> uword @ XY = $FF9C ; read/set bottom of memory pointer
asmsub SCNKEY () clobbers(A,X,Y) = $FF9F ; scan the keyboard
asmsub SETTMO (ubyte timeout @ A) = $FFA2 ; set time-out flag for IEEE bus
asmsub ACPTR () -> ubyte @ A = $FFA5 ; (alias: IECIN) input byte from serial bus
asmsub CIOUT (ubyte databyte @ A) = $FFA8 ; (alias: IECOUT) output byte to serial bus
asmsub UNTLK () clobbers(A) = $FFAB ; command serial bus device to UNTALK
asmsub UNLSN () clobbers(A) = $FFAE ; command serial bus device to UNLISTEN
asmsub LISTEN (ubyte device @ A) clobbers(A) = $FFB1 ; command serial bus device to LISTEN
asmsub TALK (ubyte device @ A) clobbers(A) = $FFB4 ; command serial bus device to TALK
asmsub READST () -> ubyte @ A = $FFB7 ; read I/O status word
asmsub SETLFS (ubyte logical @ A, ubyte device @ X, ubyte address @ Y) = $FFBA ; set logical file parameters
asmsub SETNAM (ubyte namelen @ A, str filename @ XY) = $FFBD ; set filename parameters
asmsub OPEN () clobbers(A,X,Y) = $FFC0 ; (via 794 ($31A)) open a logical file
asmsub CLOSE (ubyte logical @ A) clobbers(A,X,Y) = $FFC3 ; (via 796 ($31C)) close a logical file
asmsub CHKIN (ubyte logical @ X) clobbers(A,X) = $FFC6 ; (via 798 ($31E)) define an input channel
asmsub CHKOUT (ubyte logical @ X) clobbers(A,X) = $FFC9 ; (via 800 ($320)) define an output channel
asmsub CLRCHN () clobbers(A,X) = $FFCC ; (via 802 ($322)) restore default devices
asmsub CHRIN () clobbers(Y) -> ubyte @ A = $FFCF ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
asmsub CHROUT (ubyte char @ A) = $FFD2 ; (via 806 ($326)) output a character
asmsub LOAD (ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y = $FFD5 ; (via 816 ($330)) load from device
asmsub SAVE (ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A = $FFD8 ; (via 818 ($332)) save to a device
asmsub SETTIM (ubyte low @ A, ubyte middle @ X, ubyte high @ Y) = $FFDB ; set the software clock
asmsub RDTIM () -> ubyte @ A, ubyte @ X, ubyte @ Y = $FFDE ; read the software clock
asmsub STOP () clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc = $FFE1 ; (via 808 ($328)) check the STOP key
asmsub GETIN () clobbers(X,Y) -> ubyte @ A = $FFE4 ; (via 810 ($32A)) get a character
asmsub CLALL () clobbers(A,X) = $FFE7 ; (via 812 ($32C)) close all files
asmsub UDTIM () clobbers(A,X) = $FFEA ; update the software clock
asmsub SCREEN () -> ubyte @ X, ubyte @ Y = $FFED ; read number of screen rows and columns
asmsub PLOT (ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y = $FFF0 ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
asmsub IOBASE () -> uword @ XY = $FFF3 ; read base address of I/O devices
romsub $AB1E = STROUT(uword strptr @ AY) clobbers(A, X, Y) ; print null-terminated string (use c64scr.print instead)
romsub $EA31 = IRQDFRT() clobbers(A,X,Y) ; default IRQ routine
romsub $EA81 = IRQDFEND() clobbers(A,X,Y) ; default IRQ end/cleanup
romsub $FF81 = CINT() clobbers(A,X,Y) ; (alias: SCINIT) initialize screen editor and video chip
romsub $FF84 = IOINIT() clobbers(A, X) ; initialize I/O devices (CIA, SID, IRQ)
romsub $FF87 = RAMTAS() clobbers(A,X,Y) ; initialize RAM, tape buffer, screen
romsub $FF8A = RESTOR() clobbers(A,X,Y) ; restore default I/O vectors
romsub $FF8D = VECTOR(uword userptr @ XY, ubyte dir @ Pc) clobbers(A,Y) ; read/set I/O vector table
romsub $FF90 = SETMSG(ubyte value @ A) ; set Kernal message control flag
romsub $FF93 = SECOND(ubyte address @ A) clobbers(A) ; (alias: LSTNSA) send secondary address after LISTEN
romsub $FF96 = TKSA(ubyte address @ A) clobbers(A) ; (alias: TALKSA) send secondary address after TALK
romsub $FF99 = MEMTOP(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set top of memory pointer
romsub $FF9C = MEMBOT(uword address @ XY, ubyte dir @ Pc) -> uword @ XY ; read/set bottom of memory pointer
romsub $FF9F = SCNKEY() clobbers(A,X,Y) ; scan the keyboard
romsub $FFA2 = SETTMO(ubyte timeout @ A) ; set time-out flag for IEEE bus
romsub $FFA5 = ACPTR() -> ubyte @ A ; (alias: IECIN) input byte from serial bus
romsub $FFA8 = CIOUT(ubyte databyte @ A) ; (alias: IECOUT) output byte to serial bus
romsub $FFAB = UNTLK() clobbers(A) ; command serial bus device to UNTALK
romsub $FFAE = UNLSN() clobbers(A) ; command serial bus device to UNLISTEN
romsub $FFB1 = LISTEN(ubyte device @ A) clobbers(A) ; command serial bus device to LISTEN
romsub $FFB4 = TALK(ubyte device @ A) clobbers(A) ; command serial bus device to TALK
romsub $FFB7 = READST() -> ubyte @ A ; read I/O status word
romsub $FFBA = SETLFS(ubyte logical @ A, ubyte device @ X, ubyte address @ Y) ; set logical file parameters
romsub $FFBD = SETNAM(ubyte namelen @ A, str filename @ XY) ; set filename parameters
romsub $FFC0 = OPEN() clobbers(A,X,Y) ; (via 794 ($31A)) open a logical file
romsub $FFC3 = CLOSE(ubyte logical @ A) clobbers(A,X,Y) ; (via 796 ($31C)) close a logical file
romsub $FFC6 = CHKIN(ubyte logical @ X) clobbers(A,X) ; (via 798 ($31E)) define an input channel
romsub $FFC9 = CHKOUT(ubyte logical @ X) clobbers(A,X) ; (via 800 ($320)) define an output channel
romsub $FFCC = CLRCHN() clobbers(A,X) ; (via 802 ($322)) restore default devices
romsub $FFCF = CHRIN() clobbers(Y) -> ubyte @ A ; (via 804 ($324)) input a character (for keyboard, read a whole line from the screen) A=byte read.
romsub $FFD2 = CHROUT(ubyte char @ A) ; (via 806 ($326)) output a character
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y ; (via 816 ($330)) load from device
romsub $FFD8 = SAVE(ubyte zp_startaddr @ A, uword endaddr @ XY) -> ubyte @ Pc, ubyte @ A ; (via 818 ($332)) save to a device
romsub $FFDB = SETTIM(ubyte low @ A, ubyte middle @ X, ubyte high @ Y) ; set the software clock
romsub $FFDE = RDTIM() -> ubyte @ A, ubyte @ X, ubyte @ Y ; read the software clock
romsub $FFE1 = STOP() clobbers(A,X) -> ubyte @ Pz, ubyte @ Pc ; (via 808 ($328)) check the STOP key
romsub $FFE4 = GETIN() clobbers(X,Y) -> ubyte @ A ; (via 810 ($32A)) get a character
romsub $FFE7 = CLALL() clobbers(A,X) ; (via 812 ($32C)) close all files
romsub $FFEA = UDTIM() clobbers(A,X) ; update the software clock
romsub $FFED = SCREEN() -> ubyte @ X, ubyte @ Y ; read number of screen rows and columns
romsub $FFF0 = PLOT(ubyte col @ Y, ubyte row @ X, ubyte dir @ Pc) -> ubyte @ X, ubyte @ Y ; read/set position of cursor on screen. Use c64scr.plot for a 'safe' wrapper that preserves X.
romsub $FFF3 = IOBASE() -> uword @ XY ; read base address of I/O devices
; ---- end of C64 kernal routines ----
+216 -156
View File
@@ -15,28 +15,199 @@ c64utils {
const uword ESTACK_HI = $cf00
; ----- utility functions ----
; ----- number conversions to decimal strings
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A {
; ---- A to decimal string in Y/X/A (100s in Y, 10s in X, 1s in A)
%asm {{
ldy #$2f
ldx #$3a
sec
- iny
sbc #100
bcs -
- dex
adc #10
bmi -
adc #$2f
rts
asmsub ubyte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ A, ubyte @ X {
; ---- A to decimal string in Y/A/X (100s in Y, 10s in A, 1s in X)
%asm {{
ldy #uword2decimal.ASCII_0_OFFSET
bne uword2decimal.hex_try200
rts
}}
}
asmsub byte2decimal (ubyte value @ A) -> ubyte @ Y, ubyte @ X, ubyte @ A {
; ---- A (signed byte) to decimal string in Y/X/A (100s in Y, 10s in X, 1s in A)
; note: the '-' is not part of the conversion here if it's a negative number
asmsub uword2decimal (uword value @ AY) -> ubyte @Y, ubyte @A, ubyte @X {
; ---- convert 16 bit uword in A/Y to decimal
; 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 {{
cmp #0
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 {
; ---- 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 {{
stx c64.SCRATCH_ZPREGX
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) {
; ---- convert 16 bit uword in A/Y into 4-character hexadecimal string 'uword2hex.output' (0-terminated)
%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 {
; -- 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
@@ -227,7 +311,6 @@ _result_times_10 ; (W*4 + W)*2
}}
}
asmsub str2word(str string @ AY) -> word @ AY {
; -- returns the signed word value of the string number argument in AY
; the number may be preceded by a + or - sign but may NOT contain spaces
@@ -283,7 +366,6 @@ _negative .byte 0
}}
}
asmsub set_irqvec_excl() clobbers(A) {
%asm {{
sei
@@ -341,6 +423,7 @@ _irq_handler_init
dex
dex
dex
cld
rts
_irq_handler_end
@@ -372,7 +455,6 @@ IRQ_SCRATCH_ZPWORD2 .word 0
}}
}
asmsub restore_irqvec() {
%asm {{
sei
@@ -389,7 +471,6 @@ asmsub restore_irqvec() {
}}
}
asmsub set_rasterirq(uword rasterpos @ AY) clobbers(A) {
%asm {{
sei
@@ -454,13 +535,11 @@ _raster_irq_handler
}
} ; ------ end of block c64utils
c64scr {
; ---- 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) {
; ---- clear the character screen with the given fill character (leaves colors)
; (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) {
; ---- scroll the whole screen 1 character to the left
; 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) {
; ---- scroll the whole screen 1 character to the right
; 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) {
; ---- scroll the whole screen 1 character up
; 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) {
; ---- scroll the whole screen 1 character down
; 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) {
; ---- print null terminated string from A/Y
; 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) {
; ---- print the ubyte in A in decimal form, with left padding 0s (3 positions total)
%asm {{
@@ -770,16 +842,15 @@ asmsub print_ub0 (ubyte value @ A) clobbers(A,Y) {
pha
tya
jsr c64.CHROUT
txa
jsr c64.CHROUT
pla
jsr c64.CHROUT
txa
jsr c64.CHROUT
ldx c64.SCRATCH_ZPREGX
rts
}}
}
asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
; ---- print the ubyte in A in decimal form, without left padding 0s
%asm {{
@@ -788,15 +859,14 @@ asmsub print_ub (ubyte value @ A) clobbers(A,Y) {
_print_byte_digits
pha
cpy #'0'
bne _print_hundreds
cpx #'0'
bne _print_tens
jmp _end
_print_hundreds tya
beq +
tya
jsr c64.CHROUT
_print_tens txa
jsr c64.CHROUT
_end pla
+ pla
cmp #'0'
beq +
jsr c64.CHROUT
+ txa
jsr c64.CHROUT
ldx c64.SCRATCH_ZPREGX
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) {
; ---- print the ubyte in A in hex form (if Carry is set, a radix prefix '$' is printed as well)
%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) {
; ---- print the ubyte in A in binary form (if Carry is set, a radix prefix '%' is printed as well)
%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) {
; ---- print the uword in A/Y in binary form (if Carry is set, a radix prefix '%' is printed as well)
%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) {
; ---- print the uword in A/Y in hexadecimal form (4 digits)
; (if Carry is set, a radix prefix '$' is printed as well)
@@ -888,51 +954,45 @@ asmsub print_uwhex (uword value @ AY, ubyte prefix @ Pc) 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)
%asm {{
stx c64.SCRATCH_ZPREGX
jsr c64utils.uword2decimal
ldy #0
- lda c64utils.uword2decimal.output,y
- lda c64utils.uword2decimal.decTenThousands,y
beq +
jsr c64.CHROUT
iny
cpy #5
bne -
+ ldx c64.SCRATCH_ZPREGX
rts
}}
}
asmsub print_uw (uword value @ AY) clobbers(A,Y) {
; ---- print the uword in A/Y in decimal form, without left padding 0s
%asm {{
stx c64.SCRATCH_ZPREGX
jsr c64utils.uword2decimal
ldx c64.SCRATCH_ZPREGX
ldy #0
lda c64utils.uword2decimal.output
- lda c64utils.uword2decimal.decTenThousands,y
beq _allzero
cmp #'0'
bne _pr_decimal
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
bne _gotdigit
iny
bne -
_pr_decimal
lda c64utils.uword2decimal.output,y
_gotdigit
jsr c64.CHROUT
iny
cpy #5
bcc _pr_decimal
lda c64utils.uword2decimal.decTenThousands,y
bne _gotdigit
rts
_allzero
lda #'0'
jmp c64.CHROUT
}}
}
+37
View File
@@ -643,3 +643,40 @@ mul_word_40 .proc
sta c64.ESTACK_HI+1,x
rts
.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
+477 -4
View File
@@ -651,6 +651,18 @@ greatereq_w .proc
bmi equal_b._equal_b_false
.pend
orig_stackpointer .byte 0 ; stores the Stack pointer register at program start
func_exit .proc
; -- immediately exit the program with a return code in the A register
lda c64.ESTACK_LO+1,x
ldx orig_stackpointer
txs
rts ; return to original caller
.pend
func_read_flags .proc
; -- put the processor status register on the stack
php
@@ -716,7 +728,7 @@ func_sin8 .proc
lda _sinecos8,y
sta c64.ESTACK_LO+1,x
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
func_sin8u .proc
@@ -724,7 +736,7 @@ func_sin8u .proc
lda _sinecos8u,y
sta c64.ESTACK_LO+1,x
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
func_sin16 .proc
@@ -735,7 +747,7 @@ func_sin16 .proc
sta c64.ESTACK_HI+1,x
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 <_
_sinecos8hi .byte >_
.pend
@@ -748,7 +760,7 @@ func_sin16u .proc
sta c64.ESTACK_HI+1,x
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 <_
_sinecos8uhi .byte >_
.pend
@@ -1384,3 +1396,464 @@ _mod2b lda #0 ; self-modified
_done rts
.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
_index_right = c64.SCRATCH_ZPWORD2
_index_left = c64.SCRATCH_ZPWORD2+1
_loop_count = c64.SCRATCH_ZPREG
sta _loop_count
lsr _loop_count
sec
sbc #1
sta _index_right
lda #0
sta _index_left
_loop ldy _index_right
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _index_left
lda (c64.SCRATCH_ZPWORD1),y
ldy _index_right
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _index_left
sta (c64.SCRATCH_ZPWORD1),y
inc _index_left
dec _index_right
dec _loop_count
bne _loop
rts
.pend
reverse_f .proc
; --- reverse an array of floats
_left_index = c64.SCRATCH_ZPWORD2
_right_index = c64.SCRATCH_ZPWORD2+1
_loop_count = c64.SCRATCH_ZPREG
pha
sta c64.SCRATCH_ZPREG
asl a
asl a
clc
adc c64.SCRATCH_ZPREG ; *5 because float
sec
sbc #5
sta _right_index
lda #0
sta _left_index
pla
lsr a
sta _loop_count
_loop ; push the left indexed float on the stack
ldy _left_index
lda (c64.SCRATCH_ZPWORD1),y
pha
iny
lda (c64.SCRATCH_ZPWORD1),y
pha
iny
lda (c64.SCRATCH_ZPWORD1),y
pha
iny
lda (c64.SCRATCH_ZPWORD1),y
pha
iny
lda (c64.SCRATCH_ZPWORD1),y
pha
; copy right index float to left index float
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
inc _right_index
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
inc _right_index
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
inc _right_index
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
inc _right_index
ldy _right_index
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
sta (c64.SCRATCH_ZPWORD1),y
; pop the float off the stack into the right index float
ldy _right_index
pla
sta (c64.SCRATCH_ZPWORD1),y
dey
pla
sta (c64.SCRATCH_ZPWORD1),y
dey
pla
sta (c64.SCRATCH_ZPWORD1),y
dey
pla
sta (c64.SCRATCH_ZPWORD1),y
dey
pla
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
lda _right_index
sec
sbc #9
sta _right_index
dec _loop_count
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
_index_first = c64.SCRATCH_ZPWORD2
_index_second = c64.SCRATCH_ZPWORD2+1
_loop_count = c64.SCRATCH_ZPREG
pha
asl a ; *2 because words
sec
sbc #2
sta _index_first
lda #0
sta _index_second
pla
lsr a
pha
sta _loop_count
; first reverse the lsbs
_loop_lo ldy _index_first
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _index_second
lda (c64.SCRATCH_ZPWORD1),y
ldy _index_first
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _index_second
sta (c64.SCRATCH_ZPWORD1),y
inc _index_second
inc _index_second
dec _index_first
dec _index_first
dec _loop_count
bne _loop_lo
; now reverse the msbs
dec _index_second
inc _index_first
inc _index_first
inc _index_first
pla
sta _loop_count
_loop_hi ldy _index_first
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _index_second
lda (c64.SCRATCH_ZPWORD1),y
ldy _index_first
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _index_second
sta (c64.SCRATCH_ZPWORD1),y
dec _index_second
dec _index_second
inc _index_first
inc _index_first
dec _loop_count
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
+1 -1
View File
@@ -1 +1 @@
1.51
1.81
+95 -76
View File
@@ -1,24 +1,26 @@
package prog8
import kotlinx.cli.*
import prog8.ast.base.AstException
import prog8.compiler.CompilationResult
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.vm.astvm.AstVm
import java.io.IOException
import java.nio.file.FileSystems
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.StandardWatchEventKinds
import java.util.*
import java.time.LocalDateTime
import kotlin.system.exitProcess
fun main(args: Array<String>) {
printSoftwareHeader("compiler")
if (args.isEmpty())
usage()
compileMain(args)
}
@@ -29,55 +31,72 @@ internal fun printSoftwareHeader(what: String) {
}
fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDefault().getPath(stringPath, *rest)
private fun compileMain(args: Array<String>) {
var emulatorToStart = ""
var moduleFile = ""
var writeAssembly = true
var optimize = true
var launchAstVm = false
var watchMode = false
for (arg in args) {
if(arg=="-emu")
emulatorToStart = "x64"
else if(arg=="-emu2")
emulatorToStart = "x64sc"
else if(arg=="-noasm")
writeAssembly = false
else if(arg=="-noopt")
optimize = false
else if(arg=="-avm")
launchAstVm = true
else if(arg=="-watch")
watchMode = true
else if(!arg.startsWith("-"))
moduleFile = arg
else
usage()
val cli = CommandLineInterface("prog8compiler")
val startEmulator by cli.flagArgument("-emu", "auto-start the Vice C-64 emulator after successful compilation")
val outputDir by cli.flagValueArgument("-out", "directory", "directory for output files instead of current directory", ".")
val dontWriteAssembly by cli.flagArgument("-noasm", "don't create assembly code")
val dontOptimize by cli.flagArgument("-noopt", "don't perform any optimizations")
val launchSimulator by cli.flagArgument("-sim", "launch the builtin execution simulator after compilation")
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes), greatly increases compilation speed")
val compilationTarget by cli.flagValueArgument("-target", "compilertarget", "target output of the compiler, currently only 'c64' (C64 6502 assembly) available", "c64")
val moduleFiles by cli.positionalArgumentsList("modules", "main module file(s) to compile", minArgs = 1)
try {
cli.parse(args)
} catch (e: Exception) {
exitProcess(1)
}
if(watchMode) {
if(moduleFile.isBlank())
usage()
when(compilationTarget) {
"c64" -> {
with(CompilationTarget) {
name = "c64"
machine = C64MachineDefinition
encodeString = { str, altEncoding ->
if(altEncoding) Petscii.encodeScreencode(str, true) else Petscii.encodePetscii(str, true)
}
decodeString = { bytes, altEncoding ->
if(altEncoding) Petscii.decodeScreencode(bytes, true) else 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()
while(true) {
val filepath = Paths.get(moduleFile).normalize()
val filepath = pathFrom(moduleFiles.single()).normalize()
println("Continuous watch mode active. Main module: $filepath")
try {
val compilationResult = compileProgram(filepath, optimize, writeAssembly)
val compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
println("Imported files (now watching:)")
for (importedFile in compilationResult.importedFiles) {
print(" ")
println(importedFile)
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()
for(changed in event.pollEvents()) {
val changedPath = changed.context() as Path
println(" change detected: ${changedPath}")
println(" change detected: $changedPath")
}
event.reset()
println("\u001b[H\u001b[2J") // clear the screen
@@ -87,51 +106,51 @@ private fun compileMain(args: Array<String>) {
}
} else {
if(moduleFile.isBlank())
usage()
val filepath = Paths.get(moduleFile).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, optimize, writeAssembly)
if(!compilationResult.success)
for(filepathRaw in moduleFiles) {
val filepath = pathFrom(filepathRaw).normalize()
val compilationResult: CompilationResult
try {
compilationResult = compileProgram(filepath, !dontOptimize, !dontWriteAssembly, outputDir=outputPath)
if(!compilationResult.success)
exitProcess(1)
} catch (x: ParsingFailedError) {
exitProcess(1)
} catch (x: ParsingFailedError) {
exitProcess(1)
} catch (x: AstException) {
exitProcess(1)
}
} catch (x: AstException) {
exitProcess(1)
}
if (launchAstVm) {
println("\nLaunching AST-based vm...")
val vm = AstVm(compilationResult.programAst)
vm.run()
}
if (launchSimulator) {
// val c64 = razorvine.c64emu.C64Machine("C64 emulator launched from Prog8 compiler")
// c64.cpu.addBreakpoint(0xea31) { cpu, address ->
// println("zz")
// Cpu6502.BreakpointResultAction()
// }
// c64.start()
println("\nLaunching AST-based simulator...")
val vm = AstVm(compilationResult.programAst, compilationTarget)
vm.run()
}
if (emulatorToStart.isNotEmpty()) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else {
println("\nStarting C-64 emulator $emulatorToStart...")
val cmdline = listOf(emulatorToStart, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
val process = ProcessBuilder(cmdline).inheritIO().start()
process.waitFor()
if (startEmulator) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else if(startEmulator) {
for(emulator in listOf("x64sc", "x64")) {
println("\nStarting C-64 emulator $emulator...")
val cmdline = listOf(emulator, "-silent", "-moncommands", "${compilationResult.programName}.vice-mon-list",
"-autostartprgmode", "1", "-autostart-warp", "-autostart", compilationResult.programName + ".prg")
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(" [-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)
}
+15 -21
View File
@@ -3,7 +3,6 @@ package prog8.ast
import prog8.ast.antlr.escape
import prog8.ast.base.DataType
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.StringDatatypes
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
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 {
return when(dt) {
in NumericDatatypes -> dt.toString().toLowerCase()
in StringDatatypes -> dt.toString().toLowerCase()
DataType.STR -> dt.toString().toLowerCase()
DataType.ARRAY_UB -> "ubyte["
DataType.ARRAY_B -> "byte["
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)) {
val reg =
when {
true==param.second.stack -> "stack"
param.second.stack -> "stack"
param.second.registerOrPair!=null -> param.second.registerOrPair.toString()
param.second.statusflag!=null -> param.second.statusflag.toString()
else -> "?????1"
@@ -197,9 +196,9 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun printout(call: IFunctionCall) {
call.target.accept(this)
output("(")
for(arg in call.arglist) {
for(arg in call.args) {
arg.accept(this)
if(arg!==call.arglist.last())
if(arg!==call.args.last())
output(", ")
}
output(")")
@@ -256,15 +255,12 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
output(numLiteral.number.toString())
}
override fun visit(refLiteral: ReferenceLiteralValue) {
when {
refLiteral.isString -> output("\"${escape(refLiteral.str!!)}\"")
refLiteral.isArray -> {
if(refLiteral.array!=null) {
outputListMembers(refLiteral.array.asSequence(), '[', ']')
}
}
}
override fun visit(string: StringLiteralValue) {
output("\"${escape(string.value)}\"")
}
override fun visit(array: ArrayLiteralValue) {
outputListMembers(array.value.asSequence(), '[', ']')
}
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) {
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)
output(forLoop.loopRegister.toString())
else
@@ -342,6 +331,11 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
whileLoop.body.accept(this)
}
override fun visit(foreverLoop: ForeverLoop) {
output("forever ")
foreverLoop.body.accept(this)
}
override fun visit(repeatLoop: RepeatLoop) {
output("repeat ")
repeatLoop.body.accept(this)
+32 -7
View File
@@ -4,7 +4,6 @@ import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.functions.BuiltinFunctions
import java.nio.file.Path
@@ -38,7 +37,7 @@ interface Node {
interface IFunctionCall {
var target: IdentifierReference
var arglist: MutableList<Expression>
var args: MutableList<Expression>
}
interface INameScope {
@@ -159,6 +158,34 @@ interface INameScope {
if(!statements.remove(stmt))
throw FatalAstException("stmt to remove wasn't found in scope")
}
fun getAllLabels(label: String): List<Label> {
val result = mutableListOf<Label>()
fun find(scope: INameScope) {
scope.statements.forEach {
when(it) {
is Label -> result.add(it)
is INameScope -> find(it)
is IfStatement -> {
find(it.truepart)
find(it.elsepart)
}
is RepeatLoop -> find(it.body)
is ForeverLoop -> find(it.body)
is WhileLoop -> find(it.body)
is WhenStatement -> it.choices.forEach { choice->find(choice.statements) }
}
}
}
find(this)
return result
}
}
interface IAssignable {
// just a tag for now
}
@@ -166,7 +193,6 @@ interface INameScope {
class Program(val name: String, val modules: MutableList<Module>) {
val namespace = GlobalNamespace(modules)
val heap = HeapValues()
val definedLoadAddress: Int
get() = modules.first().loadAddress
@@ -174,7 +200,7 @@ class Program(val name: String, val modules: MutableList<Module>) {
var actualLoadAddress: Int = 0
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)
throw FatalAstException("more than one 'main' block")
return if(mainBlocks.isEmpty()) {
@@ -241,11 +267,10 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
}
}
// lookup something from the module.
val stmt = localContext.definingModule().lookup(scopedName, localContext)
return when (stmt) {
return when (val stmt = localContext.definingModule().lookup(scopedName, localContext)) {
is Label, is VarDecl, is Block, is Subroutine -> stmt
null -> null
else -> throw NameError("wrong identifier target: $stmt", stmt.position)
else -> throw SyntaxError("wrong identifier target: $stmt", stmt.position)
}
}
}
+104 -82
View File
@@ -7,7 +7,7 @@ import prog8.ast.Module
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.CompilationTarget
import prog8.parser.CustomLexer
import prog8.parser.prog8Parser
import java.io.CharConversionException
@@ -19,13 +19,13 @@ import java.nio.file.Path
private data class NumericLiteral(val number: Number, val datatype: DataType)
fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
internal fun prog8Parser.ModuleContext.toAst(name: String, isLibrary: Boolean, source: Path) : Module {
val nameWithoutSuffix = if(name.endsWith(".p8")) name.substringBeforeLast('.') else name
return Module(nameWithoutSuffix, modulestatement().asSequence().map { it.toAst(isLibrary) }.toMutableList(), toPosition(), isLibrary, source)
val directives = this.directive().map { it.toAst() }
val blocks = this.block().map { it.toAst(isLibrary) }
return Module(nameWithoutSuffix, (directives + blocks).toMutableList(), toPosition(), isLibrary, source)
}
private fun ParserRuleContext.toPosition() : Position {
val customTokensource = this.start.tokenSource as? CustomLexer
val filename =
@@ -38,27 +38,23 @@ private fun ParserRuleContext.toPosition() : Position {
return Position(filename, start.line, start.charPositionInLine, stop.charPositionInLine + stop.text.length)
}
private fun prog8Parser.ModulestatementContext.toAst(isInLibrary: Boolean) : Statement {
val directive = directive()?.toAst()
if(directive!=null) return directive
val block = block()?.toAst(isInLibrary)
if(block!=null) return block
throw FatalAstException(text)
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement {
val blockstatements = block_statement().map {
when {
it.variabledeclaration()!=null -> it.variabledeclaration().toAst()
it.subroutinedeclaration()!=null -> it.subroutinedeclaration().toAst()
it.directive()!=null -> it.directive().toAst()
it.inlineasm()!=null -> it.inlineasm().toAst()
else -> throw FatalAstException("weird block statement $it")
}
}
return Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), blockstatements.toMutableList(), isInLibrary, toPosition())
}
private fun prog8Parser.BlockContext.toAst(isInLibrary: Boolean) : Statement =
Block(identifier().text, integerliteral()?.toAst()?.number?.toInt(), statement_block().toAst(), isInLibrary, toPosition())
private fun prog8Parser.Statement_blockContext.toAst(): MutableList<Statement> =
statement().asSequence().map { it.toAst() }.toMutableList()
private fun prog8Parser.StatementContext.toAst() : Statement {
private fun prog8Parser.VariabledeclarationContext.toAst() : Statement {
vardecl()?.let { return it.toAst() }
varinitializer()?.let {
@@ -66,7 +62,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.VAR,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@@ -114,7 +110,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.CONST,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@@ -131,7 +127,7 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
return VarDecl(
VarDeclType.MEMORY,
vd.datatype()?.toAst() ?: DataType.STRUCT,
if(vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
if (vd.ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE,
vd.arrayindex()?.toAst(),
vd.varname.text,
null,
@@ -142,6 +138,28 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
)
}
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
toPosition())
}
throw FatalAstException("weird variable decl $this")
}
private fun prog8Parser.SubroutinedeclarationContext.toAst() : Subroutine {
return when {
subroutine()!=null -> subroutine().toAst()
asmsubroutine()!=null -> asmsubroutine().toAst()
romsubroutine()!=null -> romsubroutine().toAst()
else -> throw FatalAstException("weird subroutine decl $this")
}
}
private fun prog8Parser.StatementContext.toAst() : Statement {
val vardecl = variabledeclaration()?.toAst()
if(vardecl!=null) return vardecl
assignment()?.let {
return Assignment(it.assign_target().toAst(), null, it.expression().toAst(), it.toPosition())
}
@@ -175,8 +193,8 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val returnstmt = returnstmt()?.toAst()
if(returnstmt!=null) return returnstmt
val sub = subroutine()?.toAst()
if(sub!=null) return sub
val subroutine = subroutinedeclaration()?.toAst()
if(subroutine!=null) return subroutine
val asm = inlineasm()?.toAst()
if(asm!=null) return asm
@@ -193,40 +211,54 @@ private fun prog8Parser.StatementContext.toAst() : Statement {
val whileloop = whileloop()?.toAst()
if(whileloop!=null) return whileloop
val foreverloop = foreverloop()?.toAst()
if(foreverloop!=null) return foreverloop
val breakstmt = breakstmt()?.toAst()
if(breakstmt!=null) return breakstmt
val continuestmt = continuestmt()?.toAst()
if(continuestmt!=null) return continuestmt
val asmsubstmt = asmsubroutine()?.toAst()
if(asmsubstmt!=null) return asmsubstmt
val whenstmt = whenstmt()?.toAst()
if(whenstmt!=null) return whenstmt
structdecl()?.let {
return StructDecl(it.identifier().text,
it.vardecl().map { vd->vd.toAst() }.toMutableList(),
toPosition())
}
throw FatalAstException("unprocessed source text (are we missing ast conversion rules for parser elements?): $text")
}
private fun prog8Parser.AsmsubroutineContext.toAst(): Statement {
private fun prog8Parser.AsmsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, null, true, statements, toPosition())
}
private fun prog8Parser.RomsubroutineContext.toAst(): Subroutine {
val subdecl = asmsub_decl().toAst()
val address = integerliteral().toAst().number.toInt()
return Subroutine(subdecl.name, subdecl.parameters, subdecl.returntypes,
subdecl.asmParameterRegisters, subdecl.asmReturnvaluesRegisters,
subdecl.asmClobbers, address, true, mutableListOf(), toPosition())
}
private class AsmsubDecl(val name: String,
val parameters: List<SubroutineParameter>,
val returntypes: List<DataType>,
val asmParameterRegisters: List<RegisterOrStatusflag>,
val asmReturnvaluesRegisters: List<RegisterOrStatusflag>,
val asmClobbers: Set<Register>)
private fun prog8Parser.Asmsub_declContext.toAst(): AsmsubDecl {
val name = identifier().text
val address = asmsub_address()?.address?.toAst()?.number?.toInt()
val params = asmsub_params()?.toAst() ?: emptyList()
val returns = asmsub_returns()?.toAst() ?: emptyList()
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
val normalParameters = params.map { SubroutineParameter(it.name, it.type, it.position) }
val normalReturnvalues = returns.map { it.type }
val normalReturntypes = returns.map { it.type }
val paramRegisters = params.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val returnRegisters = returns.map { RegisterOrStatusflag(it.registerOrPair, it.statusflag, it.stack) }
val clobbers = asmsub_clobbers()?.clobber()?.toAst() ?: emptySet()
val statements = statement_block()?.toAst() ?: mutableListOf()
return Subroutine(name, normalParameters, normalReturnvalues,
paramRegisters, returnRegisters, clobbers, address, true, statements, toPosition())
return AsmsubDecl(name, normalParameters, normalReturntypes, paramRegisters, returnRegisters, clobbers)
}
private class AsmSubroutineParameter(name: String,
@@ -245,7 +277,6 @@ private class AsmSubroutineReturn(val type: DataType,
private fun prog8Parser.ClobberContext.toAst(): Set<Register>
= this.register().asSequence().map { it.toAst() }.toSet()
private fun prog8Parser.Asmsub_returnsContext.toAst(): List<AsmSubroutineReturn>
= asmsub_return().map { AsmSubroutineReturn(it.datatype().toAst(), it.registerorpair()?.toAst(), it.statusregister()?.toAst(), !it.stack?.text.isNullOrEmpty(), toPosition()) }
@@ -259,19 +290,17 @@ private fun prog8Parser.Asmsub_paramsContext.toAst(): List<AsmSubroutineParamete
!it.stack?.text.isNullOrEmpty(), toPosition())
}
private fun prog8Parser.StatusregisterContext.toAst() = Statusflag.valueOf(text)
private fun prog8Parser.Functioncall_stmtContext.toAst(): Statement {
val void = this.VOID() != null
val location = scoped_identifier().toAst()
return if(expression_list() == null)
FunctionCallStatement(location, mutableListOf(), toPosition())
FunctionCallStatement(location, mutableListOf(), void, toPosition())
else
FunctionCallStatement(location, expression_list().toAst().toMutableList(), toPosition())
FunctionCallStatement(location, expression_list().toAst().toMutableList(), void, toPosition())
}
private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
val location = scoped_identifier().toAst()
return if(expression_list() == null)
@@ -280,11 +309,9 @@ private fun prog8Parser.FunctioncallContext.toAst(): FunctionCall {
FunctionCall(location, expression_list().toAst().toMutableList(), toPosition())
}
private fun prog8Parser.InlineasmContext.toAst() =
InlineAssembly(INLINEASMBLOCK().text, toPosition())
private fun prog8Parser.ReturnstmtContext.toAst() : Return {
return Return(expression()?.toAst(), toPosition())
}
@@ -295,11 +322,9 @@ private fun prog8Parser.UnconditionaljumpContext.toAst(): Jump {
return Jump(address, identifier, null, toPosition())
}
private fun prog8Parser.LabeldefContext.toAst(): Statement =
Label(children[0].text, toPosition())
private fun prog8Parser.SubroutineContext.toAst() : Subroutine {
return Subroutine(identifier().text,
sub_params()?.toAst() ?: emptyList(),
@@ -318,14 +343,12 @@ private fun prog8Parser.Sub_return_partContext.toAst(): List<DataType> {
return returns.datatype().map { it.toAst() }
}
private fun prog8Parser.Sub_paramsContext.toAst(): List<SubroutineParameter> =
vardecl().map {
val datatype = it.datatype()?.toAst() ?: DataType.STRUCT
SubroutineParameter(it.varname.text, datatype, it.toPosition())
}
private fun prog8Parser.Assign_targetContext.toAst() : AssignTarget {
val register = register()?.toAst()
val identifier = scoped_identifier()
@@ -344,18 +367,19 @@ private fun prog8Parser.DatatypeContext.toAst() = DataType.valueOf(text.toUpperC
private fun prog8Parser.RegisterorpairContext.toAst() = RegisterOrPair.valueOf(text.toUpperCase())
private fun prog8Parser.ArrayindexContext.toAst() : ArrayIndex =
ArrayIndex(expression().toAst(), toPosition())
private fun prog8Parser.DirectiveContext.toAst() : Directive =
Directive(directivename.text, directivearg().map { it.toAst() }, toPosition())
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg {
val str = stringliteral()
if(str?.ALT_STRING_ENCODING() != null)
throw AstException("${toPosition()} can't use alternate string encodings for directive arguments")
private fun prog8Parser.DirectiveargContext.toAst() : DirectiveArg =
DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
return DirectiveArg(stringliteral()?.text, identifier()?.text, integerliteral()?.toAst()?.number?.toInt(), toPosition())
}
private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
fun makeLiteral(text: String, radix: Int, forceWord: Boolean): NumericLiteral {
@@ -408,7 +432,6 @@ private fun prog8Parser.IntegerliteralContext.toAst(): NumericLiteral {
}
}
private fun prog8Parser.ExpressionContext.toAst() : Expression {
val litval = literalvalue()
@@ -429,19 +452,22 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
else -> throw FatalAstException("invalid datatype for numeric literal")
}
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 -> litval.stringliteral().toAst()
litval.charliteral()!=null -> {
try {
NumericLiteralValue(DataType.UBYTE, Petscii.encodePetscii(unescape(litval.charliteral().text, litval.toPosition()), true)[0], litval.toPosition())
val cc=litval.charliteral()
NumericLiteralValue(DataType.UBYTE, CompilationTarget.encodeString(
unescape(litval.charliteral().SINGLECHAR().text, litval.toPosition()),
litval.charliteral().ALT_STRING_ENCODING()!=null)[0], litval.toPosition())
} catch (ce: CharConversionException) {
throw SyntaxError(ce.message ?: ce.toString(), litval.toPosition())
}
}
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 ConstantFold takes care of that and converts the type if needed.
ReferenceLiteralValue(DataType.ARRAY_UB, array = array, position = litval.toPosition())
ArrayLiteralValue(InferredTypes.InferredType.unknown(), array, position = litval.toPosition())
}
litval.structliteral()!=null -> {
val values = litval.structliteral().expression().map { it.toAst() }
@@ -468,7 +494,8 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
if(funcall!=null) return funcall
if (rangefrom!=null && rangeto!=null) {
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, 1, toPosition())
val defaultstep = if(rto.text == "to") 1 else -1
val step = rangestep?.toAst() ?: NumericLiteralValue(DataType.UBYTE, defaultstep, toPosition())
return RangeExpr(rangefrom.toAst(), rangeto.toAst(), step, toPosition())
}
@@ -490,6 +517,8 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
throw FatalAstException(text)
}
private fun prog8Parser.StringliteralContext.toAst(): StringLiteralValue =
StringLiteralValue(unescape(this.STRING().text, toPosition()), ALT_STRING_ENCODING()!=null, toPosition())
private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
return ArrayIndexedExpression(scoped_identifier().toAst(),
@@ -497,28 +526,22 @@ private fun prog8Parser.ArrayindexedContext.toAst(): ArrayIndexedExpression {
toPosition())
}
private fun prog8Parser.Expression_listContext.toAst() = expression().map{ it.toAst() }
private fun prog8Parser.IdentifierContext.toAst() : IdentifierReference =
IdentifierReference(listOf(text), toPosition())
private fun prog8Parser.Scoped_identifierContext.toAst() : IdentifierReference =
IdentifierReference(NAME().map { it.text }, toPosition())
private fun prog8Parser.FloatliteralContext.toAst() = text.toDouble()
private fun prog8Parser.BooleanliteralContext.toAst() = when(text) {
"true" -> true
"false" -> false
else -> throw FatalAstException(text)
}
private fun prog8Parser.ArrayliteralContext.toAst() : Array<Expression> =
expression().map { it.toAst() }.toTypedArray()
@@ -536,7 +559,6 @@ private fun prog8Parser.Else_partContext.toAst(): MutableList<Statement> {
return statement_block()?.toAst() ?: mutableListOf(statement().toAst())
}
private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
val branchcondition = branchcondition().toAst()
val trueStatements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@@ -549,11 +571,8 @@ private fun prog8Parser.Branch_stmtContext.toAst(): BranchStatement {
private fun prog8Parser.BranchconditionContext.toAst() = BranchCondition.valueOf(text.substringAfter('_').toUpperCase())
private fun prog8Parser.ForloopContext.toAst(): ForLoop {
val loopregister = register()?.toAst()
val datatype = datatype()?.toAst()
val zeropage = if(ZEROPAGE() != null) ZeropageWish.PREFER_ZEROPAGE else ZeropageWish.DONTCARE
val loopvar = identifier()?.toAst()
val iterable = expression()!!.toAst()
val scope =
@@ -561,15 +580,13 @@ private fun prog8Parser.ForloopContext.toAst(): ForLoop {
AnonymousScope(mutableListOf(statement().toAst()), statement().toPosition())
else
AnonymousScope(statement_block().toAst(), statement_block().toPosition())
return ForLoop(loopregister, datatype, zeropage, loopvar, iterable, scope, toPosition())
return ForLoop(loopregister, loopvar, iterable, scope, toPosition())
}
private fun prog8Parser.ContinuestmtContext.toAst() = Continue(toPosition())
private fun prog8Parser.BreakstmtContext.toAst() = Break(toPosition())
private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
val condition = expression().toAst()
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
@@ -578,6 +595,12 @@ private fun prog8Parser.WhileloopContext.toAst(): WhileLoop {
return WhileLoop(condition, scope, toPosition())
}
private fun prog8Parser.ForeverloopContext.toAst(): ForeverLoop {
val statements = statement_block()?.toAst() ?: mutableListOf(statement().toAst())
val scope = AnonymousScope(statements, statement_block()?.toPosition()
?: statement().toPosition())
return ForeverLoop(scope, toPosition())
}
private fun prog8Parser.RepeatloopContext.toAst(): RepeatLoop {
val untilCondition = expression().toAst()
@@ -596,10 +619,10 @@ private fun prog8Parser.WhenstmtContext.toAst(): WhenStatement {
private fun prog8Parser.When_choiceContext.toAst(): WhenChoice {
val values = expression_list()?.toAst()
val stmt = statement()?.toAst()
val stmt_block = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
val stmtBlock = statement_block()?.toAst()?.toMutableList() ?: mutableListOf()
if(stmt!=null)
stmt_block.add(stmt)
val scope = AnonymousScope(stmt_block, toPosition())
stmtBlock.add(stmt)
val scope = AnonymousScope(stmtBlock, toPosition())
return WhenChoice(values, scope, toPosition())
}
@@ -643,4 +666,3 @@ internal fun unescape(str: String, position: Position): String {
}
return result.joinToString("")
}
+12 -8
View File
@@ -1,7 +1,8 @@
package prog8.ast.base
import prog8.ast.Node
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.CompilationTarget
/**************************** AST Data classes ****************************/
@@ -12,7 +13,6 @@ enum class DataType {
WORD, // pass by value
FLOAT, // pass by value
STR, // pass by reference
STR_S, // pass by reference
ARRAY_UB, // pass by reference
ARRAY_B, // pass by reference
ARRAY_UW, // pass by reference
@@ -31,8 +31,7 @@ enum class DataType {
UWORD -> targetType in setOf(UWORD, FLOAT)
WORD -> targetType in setOf(WORD, FLOAT)
FLOAT -> targetType == FLOAT
STR -> targetType == STR || targetType==STR_S
STR_S -> targetType == STR || targetType==STR_S
STR -> targetType == STR
in ArrayDatatypes -> targetType == this
else -> false
}
@@ -58,7 +57,7 @@ enum class DataType {
return when(this) {
in ByteDatatypes -> 1
in WordDatatypes -> 2
FLOAT -> MachineDefinition.Mflpt5.MemorySize
FLOAT -> CompilationTarget.machine.FLOAT_MEM_SIZE
in PassByReferenceDatatypes -> 2
else -> -9999999
}
@@ -112,10 +111,9 @@ val ByteDatatypes = setOf(DataType.UBYTE, DataType.BYTE)
val WordDatatypes = setOf(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 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 IterableDatatypes = setOf(
DataType.STR, DataType.STR_S,
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B,
DataType.ARRAY_UW, DataType.ARRAY_W,
DataType.ARRAY_F)
@@ -123,12 +121,18 @@ val PassByValueDatatypes = NumericDatatypes
val PassByReferenceDatatypes = IterableDatatypes.plus(DataType.STRUCT)
val ArrayElementTypes = mapOf(
DataType.STR to DataType.UBYTE,
DataType.STR_S to DataType.UBYTE,
DataType.ARRAY_B to DataType.BYTE,
DataType.ARRAY_UB to DataType.UBYTE,
DataType.ARRAY_W to DataType.WORD,
DataType.ARRAY_UW to DataType.UWORD,
DataType.ARRAY_F to DataType.FLOAT)
val ElementArrayTypes = mapOf(
DataType.BYTE to DataType.ARRAY_B,
DataType.UBYTE to DataType.ARRAY_UB,
DataType.WORD to DataType.ARRAY_W,
DataType.UWORD to DataType.ARRAY_UW,
DataType.FLOAT to DataType.ARRAY_F
)
// find the parent node of a specific type or interface
// (useful to figure out in what namespace/block something is defined, etc)
+33 -26
View File
@@ -3,35 +3,42 @@ package prog8.ast.base
import prog8.parser.ParsingFailedError
fun printErrors(errors: List<Any>, moduleName: String) {
val reportedMessages = mutableSetOf<String>()
print("\u001b[91m") // bright red
errors.forEach {
val msg = it.toString()
if(msg !in reportedMessages) {
System.err.println(msg)
reportedMessages.add(msg)
}
class ErrorReporter {
private enum class MessageSeverity {
WARNING,
ERROR
}
print("\u001b[0m") // reset color
if(reportedMessages.isNotEmpty())
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
}
private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position?)
private val messages = mutableListOf<CompilerMessage>()
private val alreadyReportedMessages = mutableSetOf<String>()
fun printWarning(msg: String, position: Position, detailInfo: String?=null) {
print("\u001b[93m") // bright yellow
print("$position Warning: $msg")
if(detailInfo==null)
print("\n")
else
println(": $detailInfo\n")
print("\u001b[0m") // normal
}
fun err(msg: String, position: Position?) = messages.add(CompilerMessage(MessageSeverity.ERROR, msg, position))
fun warn(msg: String, position: Position?) = messages.add(CompilerMessage(MessageSeverity.WARNING, msg, position))
fun handle() {
var numErrors = 0
var numWarnings = 0
messages.forEach {
when(it.severity) {
MessageSeverity.ERROR -> System.err.print("\u001b[91m") // bright red
MessageSeverity.WARNING -> System.err.print("\u001b[93m") // bright yellow
}
val msg = "${it.position} ${it.severity} ${it.message}".trim()
if(msg !in alreadyReportedMessages) {
System.err.println(msg)
alreadyReportedMessages.add(msg)
when(it.severity) {
MessageSeverity.WARNING -> numWarnings++
MessageSeverity.ERROR -> numErrors++
}
}
System.err.print("\u001b[0m") // reset color
}
messages.clear()
if(numErrors>0)
throw ParsingFailedError("There are $numErrors errors and $numWarnings warnings.")
}
fun printWarning(msg: String) {
print("\u001b[93m") // bright yellow
print("Warning: $msg")
print("\u001b[0m\n") // normal
fun isEmpty() = messages.isEmpty()
}
+2 -6
View File
@@ -6,17 +6,13 @@ class FatalAstException (override var message: String) : Exception(message)
open class AstException (override var message: String) : Exception(message)
class SyntaxError(override var message: String, val position: Position) : AstException(message) {
open class SyntaxError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Syntax error: $message"
}
open class NameError(override var message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Name error: $message"
}
class ExpressionError(message: String, val position: Position) : AstException(message) {
override fun toString() = "$position Error: $message"
}
class UndefinedSymbolError(symbol: IdentifierReference)
: NameError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
: SyntaxError("undefined symbol: ${symbol.nameInSource.joinToString(".")}", symbol.position)
+22 -17
View File
@@ -4,7 +4,6 @@ import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.compiler.CompilationOptions
import prog8.compiler.target.c64.codegen2.AnonymousScopeVarsCleanup
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
@@ -18,17 +17,15 @@ internal fun Program.removeNopsFlattenAnonScopes() {
}
internal fun Program.checkValid(compilerOptions: CompilationOptions) {
val checker = AstChecker(this, compilerOptions)
internal fun Program.checkValid(compilerOptions: CompilationOptions, errors: ErrorReporter) {
val checker = AstChecker(this, compilerOptions, errors)
checker.visit(this)
printErrors(checker.result(), name)
}
internal fun Program.anonscopeVarsCleanup() {
val mover = AnonymousScopeVarsCleanup(this)
internal fun Program.anonscopeVarsCleanup(errors: ErrorReporter) {
val mover = AnonymousScopeVarsCleanup(errors)
mover.visit(this)
printErrors(mover.result(), name)
}
@@ -40,26 +37,34 @@ internal fun Program.reorderStatements() {
checker.visit(this)
}
internal fun Module.checkImportedValid() {
val checker = ImportedModuleDirectiveRemover()
checker.visit(this)
printErrors(checker.result(), name)
internal fun Program.addTypecasts(errors: ErrorReporter) {
val caster = TypecastsAdder(this, errors)
caster.visit(this)
}
internal fun Program.checkRecursion() {
val checker = AstRecursionChecker(namespace)
internal fun Module.checkImportedValid(errors: ErrorReporter) {
val checker = ImportedModuleDirectiveRemover(errors)
checker.visit(this)
printErrors(checker.result(), name)
}
internal fun Program.checkRecursion(errors: ErrorReporter) {
val checker = AstRecursionChecker(namespace, errors)
checker.visit(this)
checker.processMessages(name)
}
internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(this)
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
val checker = AstIdentifiersChecker(this, errors)
checker.visit(this)
if(modules.map {it.name}.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
}
}
printErrors(checker.result(), name)
internal fun Program.makeForeverLoops() {
val checker = MakeForeverLoops()
checker.visit(this)
}
@@ -1,20 +1,12 @@
package prog8.ast.expressions
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.*
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.ArrayIndex
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.compiler.HeapValues
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.Petscii
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
@@ -30,7 +22,7 @@ sealed class Expression: Node {
abstract fun accept(visitor: IAstModifyingVisitor): Expression
abstract fun accept(visitor: IAstVisitor)
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 {
if(this===other)
@@ -68,7 +60,27 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
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 {
return "Prefix($operator $expression)"
@@ -94,15 +106,22 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
override fun accept(visitor: IAstModifyingVisitor) = 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 inferType(program: Program): DataType? {
override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program)
val rightDt = right.inferType(program)
return when (operator) {
"+", "-", "*", "**", "%", "/" -> if (leftDt == null || rightDt == null) null else {
try {
commonDatatype(leftDt, rightDt, null, null).first
} catch (x: FatalAstException) {
null
"+", "-", "*", "**", "%", "/" -> {
if (!leftDt.isKnown || !rightDt.isKnown)
InferredTypes.unknown()
else {
try {
InferredTypes.knownFor(commonDatatype(
leftDt.typeOrElse(DataType.BYTE),
rightDt.typeOrElse(DataType.BYTE),
null, null).first)
} catch (x: FatalAstException) {
InferredTypes.unknown()
}
}
}
"&" -> leftDt
@@ -111,7 +130,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
"and", "or", "xor",
"<", ">",
"<=", ">=",
"==", "!=" -> DataType.UBYTE
"==", "!=" -> InferredTypes.knownFor(DataType.UBYTE)
"<<", ">>" -> leftDt
else -> throw FatalAstException("resulting datatype check for invalid operator $operator")
}
@@ -178,7 +197,7 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
class ArrayIndexedExpression(var identifier: IdentifierReference,
val arrayspec: ArrayIndex,
override val position: Position) : Expression() {
override val position: Position) : Expression(), IAssignable {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
@@ -191,17 +210,16 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
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)
if (target is VarDecl) {
return when (target.datatype) {
in NumericDatatypes -> null
in StringDatatypes -> DataType.UBYTE
in ArrayDatatypes -> ArrayElementTypes[target.datatype]
else -> throw FatalAstException("invalid dt")
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(target.datatype))
else -> InferredTypes.unknown()
}
}
return null
return InferredTypes.unknown()
}
override fun toString(): String {
@@ -220,7 +238,7 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
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? {
val cv = expression.constValue(program) ?: return null
return cv.cast(type)
@@ -243,12 +261,12 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
override fun constValue(program: Program): NumericLiteralValue? = null
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: 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 fun linkParents(parent: Node) {
@@ -259,7 +277,7 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
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 toString(): String {
@@ -301,7 +319,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) {
this.parent = parent
@@ -315,7 +333,7 @@ class NumericLiteralValue(val type: DataType, // only numerical types allowed
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 = Objects.hash(type, number)
@@ -399,144 +417,118 @@ class StructLiteralValue(var values: List<Expression>,
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
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 {
return "struct{ ${values.joinToString(", ")} }"
}
}
class ReferenceLiteralValue(val type: DataType, // only reference types allowed here
val str: String? = null,
val array: Array<Expression>? = null,
// actually, at the moment, we don't have struct literals in the language
initHeapId: Int? =null,
override val position: Position) : Expression() {
private var heapIdSequence = 0 // unique ids for strings and arrays "on the heap"
class StringLiteralValue(val value: String,
val altEncoding: Boolean, // such as: screencodes instead of Petscii for the C64
override val position: Position) : Expression() {
override lateinit var parent: Node
override fun referencesIdentifiers(vararg name: String) = array?.any { it.referencesIdentifiers(*name) } ?: false
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")
}
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) {
this.parent = parent
array?.forEach {it.linkParents(this)}
}
override fun constValue(program: Program): NumericLiteralValue? {
// 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 referencesIdentifiers(vararg name: String) = false
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 {
val valueStr = when(type) {
in StringDatatypes -> "'${escape(str!!)}'"
in ArrayDatatypes -> "$array"
else -> throw FatalAstException("weird ref type")
}
return "RefValueLit($type, $valueStr)"
}
override fun inferType(program: Program) = type
override fun hashCode(): Int = Objects.hash(str, array, type)
override fun toString(): String = "'${escape(value)}'"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STR)
operator fun compareTo(other: StringLiteralValue): Int = value.compareTo(other.value)
override fun hashCode(): Int = Objects.hash(value, altEncoding)
override fun equals(other: Any?): Boolean {
if(other==null || other !is ReferenceLiteralValue)
if(other==null || other !is StringLiteralValue)
return false
if(isArray && other.isArray)
return array!!.contentEquals(other.array!!) && heapId==other.heapId
if(isString && other.isString)
return str==other.str && heapId==other.heapId
return value==other.value && altEncoding == other.altEncoding
}
}
if(type!=other.type)
class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred because not all array literals hava a known type yet
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 = if(type.isUnknown) type else guessDatatype(program)
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 compareTo(other) == 0
return type==other.type && value.contentEquals(other.value)
}
operator fun compareTo(other: ReferenceLiteralValue): Int {
throw ExpressionError("cannot order compare type $type with ${other.type}", other.position)
fun guessDatatype(program: Program): InferredTypes.InferredType {
// Educated guess of the desired array literal's datatype.
// If it's inside a for loop, assume the data type of the loop variable is what we want.
val forloop = parent as? ForLoop
if(forloop != null) {
val loopvarDt = forloop.loopVarDt(program)
if(loopvarDt.isKnown) {
return if(loopvarDt.typeOrElse(DataType.STRUCT) !in ElementArrayTypes)
InferredTypes.InferredType.unknown()
else
InferredTypes.InferredType.known(ElementArrayTypes.getValue(loopvarDt.typeOrElse(DataType.STRUCT)))
}
}
// otherwise, select the "biggegst" datatype based on the elements in the array.
val datatypesInArray = value.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 -> InferredTypes.InferredType.known(DataType.ARRAY_F)
DataType.WORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_W)
DataType.UWORD in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UW)
DataType.BYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_B)
DataType.UBYTE in dts -> InferredTypes.InferredType.known(DataType.ARRAY_UB)
else -> InferredTypes.InferredType.unknown()
}
}
fun cast(targettype: DataType): ReferenceLiteralValue? {
if(type==targettype)
fun cast(targettype: DataType): ArrayLiteralValue? {
if(type.istype(targettype))
return this
when(type) {
in StringDatatypes -> {
if(targettype in StringDatatypes)
return ReferenceLiteralValue(targettype, str, position = position)
}
in ArrayDatatypes -> {
if(targettype in ArrayDatatypes) {
val elementType = ArrayElementTypes.getValue(targettype)
val castArray = array!!.map{
val num = it as? NumericLiteralValue
if(num==null) {
// an array of UWORDs could possibly also contain AddressOfs
if (elementType != DataType.UWORD || it !is AddressOf)
throw FatalAstException("weird array element $it")
it
} else {
num.cast(elementType) // TODO this can throw an exception
}
}.toTypedArray()
return ReferenceLiteralValue(targettype, null, array=castArray, position = position)
if(targettype in ArrayDatatypes) {
val elementType = ArrayElementTypes.getValue(targettype)
val castArray = value.map{
val num = it as? NumericLiteralValue
if(num==null) {
// an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted
if (elementType != DataType.UWORD || it !is AddressOf)
return null
it
} else {
try {
num.cast(elementType)
} catch(x: ExpressionError) {
return null
}
}
}
else -> {}
}.toTypedArray()
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
}
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,
@@ -556,18 +548,17 @@ class RangeExpr(var from: Expression,
override fun accept(visitor: IAstModifyingVisitor) = 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 inferType(program: Program): DataType? {
override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program)
val toDt=to.inferType(program)
return when {
fromDt==null || toDt==null -> null
fromDt== DataType.UBYTE && toDt== DataType.UBYTE -> DataType.ARRAY_UB
fromDt== DataType.UWORD && toDt== DataType.UWORD -> DataType.ARRAY_UW
fromDt== DataType.STR && toDt== DataType.STR -> DataType.STR
fromDt== DataType.STR_S && toDt== DataType.STR_S -> DataType.STR_S
fromDt== DataType.WORD || toDt== DataType.WORD -> DataType.ARRAY_W
fromDt== DataType.BYTE || toDt== DataType.BYTE -> DataType.ARRAY_B
else -> DataType.ARRAY_UB
!fromDt.isKnown || !toDt.isKnown -> InferredTypes.unknown()
fromDt istype DataType.UBYTE && toDt istype DataType.UBYTE -> InferredTypes.knownFor(DataType.ARRAY_UB)
fromDt istype DataType.UWORD && toDt istype DataType.UWORD -> InferredTypes.knownFor(DataType.ARRAY_UW)
fromDt istype DataType.STR && toDt istype DataType.STR -> InferredTypes.knownFor(DataType.STR)
fromDt istype DataType.WORD || toDt istype DataType.WORD -> InferredTypes.knownFor(DataType.ARRAY_W)
fromDt istype DataType.BYTE || toDt istype DataType.BYTE -> InferredTypes.knownFor(DataType.ARRAY_B)
else -> InferredTypes.knownFor(DataType.ARRAY_UB)
}
}
override fun toString(): String {
@@ -585,12 +576,12 @@ class RangeExpr(var from: Expression,
fun toConstantIntegerRange(): IntProgression? {
val fromVal: Int
val toVal: Int
val fromRlv = from as? ReferenceLiteralValue
val toRlv = to as? ReferenceLiteralValue
if(fromRlv!=null && fromRlv.isString && toRlv!=null && toRlv.isString) {
// string range -> int range over petscii values
fromVal = Petscii.encodePetscii(fromRlv.str!!, true)[0].toInt()
toVal = Petscii.encodePetscii(toRlv.str!!, true)[0].toInt()
val fromString = from as? StringLiteralValue
val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) {
// string range -> int range over character values
fromVal = CompilationTarget.encodeString(fromString.value, fromString.altEncoding)[0].toInt()
toVal = CompilationTarget.encodeString(toString.value, fromString.altEncoding)[0].toInt()
} else {
val fromLv = from as? NumericLiteralValue
val toLv = to as? NumericLiteralValue
@@ -601,22 +592,26 @@ class RangeExpr(var from: Expression,
toVal = toLv.number.toInt()
}
val stepVal = (step as? NumericLiteralValue)?.number?.toInt() ?: 1
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
return makeRange(fromVal, toVal, stepVal)
}
}
internal fun makeRange(fromVal: Int, toVal: Int, stepVal: Int): IntProgression {
return when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
}
}
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 fun linkParents(parent: Node) {
@@ -631,10 +626,10 @@ class RegisterExpr(val register: Register, override val position: Position) : Ex
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
fun targetStatement(namespace: INameScope) =
@@ -668,12 +663,12 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
override fun accept(visitor: IAstModifyingVisitor) = 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)
if(targetStmt is VarDecl) {
return targetStmt.datatype
return InferredTypes.knownFor(targetStmt.datatype)
} else {
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
}
@@ -686,28 +681,22 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
val value = (node as? VarDecl)?.value ?: throw FatalAstException("requires a reference value")
return when (value) {
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")
}
}
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,
override var arglist: MutableList<Expression>,
override var args: MutableList<Expression>,
override val position: Position) : Expression(), IFunctionCall {
override lateinit var parent: Node
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
arglist.forEach { it.linkParents(this) }
args.forEach { it.linkParents(this) }
}
override fun constValue(program: Program) = constValue(program, true)
@@ -722,14 +711,14 @@ class FunctionCall(override var target: IdentifierReference,
if(func!=null) {
val exprfunc = func.constExpressionFunc
if(exprfunc!=null)
resultValue = exprfunc(arglist, position, program)
resultValue = exprfunc(args, position, program)
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)
}
if(withDatatypeCheck) {
val resultDt = this.inferType(program)
if(resultValue==null || resultDt == resultValue.type)
if(resultValue==null || resultDt istype resultValue.type)
return resultValue
throw FatalAstException("evaluated const expression result value doesn't match expected datatype $resultDt, pos=$position")
} else {
@@ -748,29 +737,29 @@ class FunctionCall(override var target: IdentifierReference,
override fun accept(visitor: IAstModifyingVisitor) = 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)
if(constVal!=null)
return constVal.type
val stmt = target.targetStatement(program.namespace) ?: return null
return InferredTypes.knownFor(constVal.type)
val stmt = target.targetStatement(program.namespace) ?: return InferredTypes.unknown()
when (stmt) {
is BuiltinFunctionStatementPlaceholder -> {
if(target.nameInSource[0] == "set_carry" || target.nameInSource[0]=="set_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 -> {
if(stmt.returntypes.isEmpty())
return null // no return value
return InferredTypes.void() // no return value
if(stmt.returntypes.size==1)
return stmt.returntypes[0]
return null // has multiple return types... so not a single resulting datatype possible
return InferredTypes.knownFor(stmt.returntypes[0])
return InferredTypes.unknown() // has multiple return types... so not a single resulting datatype possible
}
else -> return null
else -> return InferredTypes.unknown()
}
}
}
@@ -0,0 +1,60 @@
package prog8.ast.expressions
import prog8.ast.base.DataType
import java.util.*
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)
}
@@ -1,32 +1,25 @@
package prog8.compiler.target.c64.codegen2
package prog8.ast.processing
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.NameError
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.base.ErrorReporter
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.Statement
import prog8.ast.statements.VarDecl
class AnonymousScopeVarsCleanup(val program: Program): IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
class AnonymousScopeVarsCleanup(private val errors: ErrorReporter): IAstModifyingVisitor {
private val varsToMove: MutableMap<AnonymousScope, List<VarDecl>> = mutableMapOf()
fun result(): List<AstException> {
return checkResult
}
override fun visit(program: Program) {
varsToMove.clear()
super.visit(program)
for((scope, decls) in varsToMove) {
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
decls.forEach {
val existing = existingVariables[it.name]
if (existing!=null) {
checkResult.add(NameError("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position))
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
conflicts = true
}
}
File diff suppressed because it is too large. Load diff
@@ -7,24 +7,17 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.AssemblyProgram
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
internal class AstIdentifiersChecker(private val program: Program) : IAstModifyingVisitor {
private val checkResult: MutableList<AstException> = mutableListOf()
internal class AstIdentifiersChecker(private val program: Program,
private val errors: ErrorReporter) : IAstModifyingVisitor {
private var blocks = mutableMapOf<String, Block>()
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
internal fun result(): List<AstException> {
return checkResult
}
private fun nameError(name: String, position: Position, existing: Statement) {
checkResult.add(NameError("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position))
errors.err("name conflict '$name', also defined in ${existing.position.file} line ${existing.position.line}", position)
}
override fun visit(module: Module) {
@@ -51,7 +44,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(functionCall: FunctionCall): Expression {
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, false, functionCall.position)
val typecast = TypecastExpression(functionCall.args.single(), DataType.UBYTE, false, functionCall.position)
typecast.linkParents(functionCall.parent)
return super.visit(typecast)
}
@@ -60,15 +53,15 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(decl: VarDecl): Statement {
// first, check if there are datatype errors on the vardecl
decl.datatypeErrors.forEach { checkResult.add(it) }
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
// now check the identifier
if(decl.name in BuiltinFunctions)
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", decl.position))
errors.err("builtin function cannot be redefined", decl.position)
if(decl.name in AssemblyProgram.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol", decl.position))
if(decl.name in CompilationTarget.machine.opcodeNames)
errors.err("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,
// and include the original decl as well.
@@ -77,7 +70,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
return super.visit(decl) // don't do this multiple times
if(decl.struct==null) {
checkResult.add(NameError("undefined struct type", decl.position))
errors.err("undefined struct type", decl.position)
return super.visit(decl)
}
@@ -85,7 +78,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
return super.visit(decl) // a non-numeric member, not supported. proper error is given by AstChecker later
if(decl.value is NumericLiteralValue) {
checkResult.add(ExpressionError("you cannot initialize a struct using a single value", decl.position))
errors.err("you cannot initialize a struct using a single value", decl.position)
return super.visit(decl)
}
@@ -104,11 +97,11 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
}
override fun visit(subroutine: Subroutine): Statement {
if(subroutine.name in AssemblyProgram.opcodeNames) {
checkResult.add(NameError("can't use a cpu opcode name as a symbol", subroutine.position))
if(subroutine.name in CompilationTarget.machine.opcodeNames) {
errors.err("can't use a cpu opcode name as a symbol: '${subroutine.name}'", subroutine.position)
} else if(subroutine.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", subroutine.position))
errors.err("builtin function cannot be redefined", subroutine.position)
} else {
// already reported elsewhere:
// if (subroutine.parameters.any { it.name in BuiltinFunctions })
@@ -144,8 +137,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
// NOTE:
// - numeric types BYTE and WORD and FLOAT are passed by value;
// - strings, arrays, matrices are passed by reference (their 16-bit address is passed as an uword parameter)
// - do NOT do this is the statement can be transformed into an asm subroutine later!
if(subroutine.asmAddress==null && !subroutine.canBeAsmSubroutine) {
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty()) {
subroutine.parameters
.filter { it.name !in namesInSub }
@@ -157,21 +149,31 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
}
}
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
}
}
return super.visit(subroutine)
}
override fun visit(label: Label): Statement {
if(label.name in AssemblyProgram.opcodeNames)
checkResult.add(NameError("can't use a cpu opcode name as a symbol", label.position))
if(label.name in CompilationTarget.machine.opcodeNames)
errors.err("can't use a cpu opcode name as a symbol: '${label.name}'", label.position)
if(label.name in BuiltinFunctions) {
// the builtin functions can't be redefined
checkResult.add(NameError("builtin function cannot be redefined", label.position))
errors.err("builtin function cannot be redefined", label.position)
} else {
val existing = program.namespace.lookup(listOf(label.name), label)
if (existing != null && existing !== label)
nameError(label.name, label.position, existing)
val existing = label.definingSubroutine()?.getAllLabels(label.name) ?: emptyList()
for(el in existing) {
if(el === label || el.name != label.name)
continue
else {
nameError(label.name, label.position, el)
break
}
}
}
return super.visit(label)
}
@@ -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
// additional interation count variable in their scope.
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)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
} else {
val loopVar = forLoop.loopVar
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 loopvarName = "prog8_loopvar_$validName"
if (forLoop.iterable !is RangeExpr) {
@@ -224,7 +209,7 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(assignTarget: AssignTarget): AssignTarget {
if(assignTarget.register== Register.X)
printWarning("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
return super.visit(assignTarget)
}
@@ -234,95 +219,91 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size!=1)
return returnStmt // mismatch in number of return values, error will be printed later.
val newValue: Expression
val lval = returnStmt.value as? NumericLiteralValue
if(lval!=null) {
newValue = lval.cast(subroutine.returntypes.single())
} else {
newValue = returnStmt.value!!
}
returnStmt.value = newValue
returnStmt.value = lval?.cast(subroutine.returntypes.single()) ?: returnStmt.value!!
}
return super.visit(returnStmt)
}
override fun visit(refLiteral: ReferenceLiteralValue): Expression {
val litval = super.visit(refLiteral)
if(litval is ReferenceLiteralValue) {
val vardecl = litval.parent as? VarDecl
if (litval.isString) {
// intern the string; move it into the heap
if (litval.str!!.length !in 1..255)
checkResult.add(ExpressionError("string literal length must be between 1 and 255", litval.position))
else {
litval.addToHeap(program.heap)
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
val vardecl = array.parent as? VarDecl
// adjust the datatype of the array (to an educated guess)
if(vardecl!=null) {
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
val cast = array.cast(vardecl.datatype)
if (cast != null) {
vardecl.value = cast
cast.linkParents(vardecl)
return cast
}
}
return if(vardecl!=null)
litval
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.
return makeIdentifierFromRefLv(litval2)
return array
}
else {
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
// this array literal is part of an expression, turn it into an identifier reference
val litval2 = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
return if (litval2 != null) {
litval2.parent = array.parent
makeIdentifierFromRefLv(litval2)
} else array
}
}
}
return litval
return array
}
private fun determineArrayDt(array: Array<Expression>): DataType? {
val datatypesInArray = array.mapNotNull { it.inferType(program) }
if(datatypesInArray.isEmpty())
return null
if(DataType.FLOAT in datatypesInArray)
return DataType.ARRAY_F
if(DataType.WORD in datatypesInArray)
return DataType.ARRAY_W
if(DataType.UWORD in datatypesInArray)
return DataType.ARRAY_UW
if(DataType.BYTE in datatypesInArray)
return DataType.ARRAY_B
if(DataType.UBYTE in datatypesInArray)
return DataType.ARRAY_UB
return null
override fun visit(stringLiteral: StringLiteralValue): Expression {
val string = super.visit(stringLiteral)
if(string is StringLiteralValue) {
val vardecl = string.parent as? VarDecl
// intern the string; move it into the heap
if (string.value.length !in 1..255)
errors.err("string literal length must be between 1 and 255", string.position)
return if (vardecl != null)
string
else
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
}
return string
}
private fun makeIdentifierFromRefLv(refLiteral: ReferenceLiteralValue): IdentifierReference {
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
// 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
// note: if the var references the same literal value, it is not yet de-duplicated here.
refLiteral.addToHeap(program.heap)
val scope = refLiteral.definingScope()
var variable = VarDecl.createAuto(refLiteral, program.heap)
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
val scope = array.definingScope()
val variable = VarDecl.createAuto(array)
return replaceWithIdentifier(variable, scope, array.parent)
}
override fun visit(addressOf: AddressOf): Expression {
// register the scoped name of the referenced identifier
val variable= addressOf.identifier.targetVarDecl(program.namespace) ?: return addressOf
return super.visit(addressOf)
private fun makeIdentifierFromRefLv(string: StringLiteralValue): IdentifierReference {
// 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
// note: if the var references the same literal value, it is not yet de-duplicated here.
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 {
for(member in structDecl.statements){
val decl = member as? VarDecl
if(decl!=null && decl.datatype !in NumericDatatypes)
checkResult.add(SyntaxError("structs can only contain numerical types", decl.position))
errors.err("structs can only contain numerical types", decl.position)
}
return super.visit(structDecl)
@@ -330,33 +311,26 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
override fun visit(expr: BinaryExpression): Expression {
return when {
expr.left is ReferenceLiteralValue ->
processBinaryExprWithReferenceVal(expr.left as ReferenceLiteralValue, expr.right, expr)
expr.right is ReferenceLiteralValue ->
processBinaryExprWithReferenceVal(expr.right as ReferenceLiteralValue, expr.left, expr)
expr.left is StringLiteralValue ->
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr)
expr.right is StringLiteralValue ->
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr)
else -> super.visit(expr)
}
}
private fun processBinaryExprWithReferenceVal(refLv: ReferenceLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
// expressions on strings or arrays
if(refLv.isString) {
val constvalue = operand.constValue(program)
if(constvalue!=null) {
if (expr.operator == "*") {
// 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)
}
private fun processBinaryExprWithString(string: StringLiteralValue, operand: Expression, expr: BinaryExpression): Expression {
val constvalue = operand.constValue(program)
if(constvalue!=null) {
if (expr.operator == "*") {
// repeat a string a number of times
return StringLiteralValue(string.value.repeat(constvalue.number.toInt()), string.altEncoding, expr.position)
}
}
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
return StringLiteralValue("${string.value}${operand.value}", string.altEncoding, expr.position)
}
return expr
}
@@ -374,27 +348,3 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
}
}
internal fun fixupArrayDatatype(array: ReferenceLiteralValue, vardecl: VarDecl, heap: HeapValues): ReferenceLiteralValue {
if(array.heapId!=null) {
val arrayDt = array.type
if(arrayDt!=vardecl.datatype) {
// fix the datatype of the array (also on the heap) to match the vardecl
val litval2 =
try {
array.cast(vardecl.datatype)!!
} catch(x: ExpressionError) {
// couldn't cast permanently.
// instead, simply adjust the array type and trust the AstChecker to report the exact error
ReferenceLiteralValue(vardecl.datatype, null, array.array, array.heapId, array.position)
}
vardecl.value = litval2
litval2.linkParents(vardecl)
litval2.addToHeap(heap) // TODO is the previous array discarded from the resulting asm code?
return litval2
}
} else {
array.addToHeap(heap)
}
return array
}
@@ -1,21 +1,22 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.base.AstException
import prog8.ast.base.ErrorReporter
import prog8.ast.expressions.FunctionCall
import prog8.ast.statements.FunctionCallStatement
import prog8.ast.statements.Subroutine
internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisitor {
internal class AstRecursionChecker(private val namespace: INameScope,
private val errors: ErrorReporter) : IAstVisitor {
private val callGraph = DirectedGraph<INameScope>()
internal fun result(): List<AstException> {
fun processMessages(modulename: String) {
val cycle = callGraph.checkForCycle()
if(cycle.isEmpty())
return emptyList()
return
val chain = cycle.joinToString(" <-- ") { "${it.name} at ${it.position}" }
return listOf(AstException("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain"))
errors.err("Program contains recursive subroutine calls, this is not supported. Recursive chain:\n (a subroutine call in) $chain", null)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
@@ -44,7 +45,6 @@ internal class AstRecursionChecker(private val namespace: INameScope) : IAstVisi
super.visit(functionCall)
}
private class DirectedGraph<VT> {
private val graph = mutableMapOf<VT, MutableSet<VT>>()
private var uniqueVertices = mutableSetOf<VT>()
@@ -6,6 +6,7 @@ import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
interface IAstModifyingVisitor {
fun visit(program: Program) {
program.modules.forEach { visit(it) }
@@ -52,7 +53,7 @@ interface IAstModifyingVisitor {
functionCall.target = newtarget
else
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
}
@@ -62,7 +63,7 @@ interface IAstModifyingVisitor {
functionCallStatement.target = newtarget
else
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
}
@@ -110,14 +111,16 @@ interface IAstModifyingVisitor {
return literalValue
}
fun visit(refLiteral: ReferenceLiteralValue): Expression {
if(refLiteral.array!=null) {
for(av in refLiteral.array.withIndex()) {
val newvalue = av.value.accept(this)
refLiteral.array[av.index] = newvalue
}
fun visit(stringLiteral: StringLiteralValue): Expression {
return stringLiteral
}
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 {
@@ -140,8 +143,7 @@ interface IAstModifyingVisitor {
}
fun visit(forLoop: ForLoop): Statement {
val newloopvar = forLoop.loopVar?.accept(this)
when(newloopvar) {
when(val newloopvar = forLoop.loopVar?.accept(this)) {
is IdentifierReference -> forLoop.loopVar = newloopvar
null -> forLoop.loopVar = null
else -> throw FatalAstException("can't change class of loopvar")
@@ -157,6 +159,11 @@ interface IAstModifyingVisitor {
return whileLoop
}
fun visit(foreverLoop: ForeverLoop): Statement {
foreverLoop.body = foreverLoop.body.accept(this) as AnonymousScope
return foreverLoop
}
fun visit(repeatLoop: RepeatLoop): Statement {
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
@@ -177,8 +184,7 @@ interface IAstModifyingVisitor {
}
fun visit(assignTarget: AssignTarget): AssignTarget {
val ident = assignTarget.identifier?.accept(this)
when (ident) {
when (val ident = assignTarget.identifier?.accept(this)) {
is IdentifierReference -> assignTarget.identifier = ident
null -> assignTarget.identifier = null
else -> throw FatalAstException("can't change class of assign target identifier")
@@ -41,12 +41,12 @@ interface IAstVisitor {
fun visit(functionCall: FunctionCall) {
functionCall.target.accept(this)
functionCall.arglist.forEach { it.accept(this) }
functionCall.args.forEach { it.accept(this) }
}
fun visit(functionCallStatement: FunctionCallStatement) {
functionCallStatement.target.accept(this)
functionCallStatement.arglist.forEach { it.accept(this) }
functionCallStatement.args.forEach { it.accept(this) }
}
fun visit(identifier: IdentifierReference) {
@@ -79,8 +79,11 @@ interface IAstVisitor {
fun visit(numLiteral: NumericLiteralValue) {
}
fun visit(refLiteral: ReferenceLiteralValue) {
refLiteral.array?.let { it.forEach { v->v.accept(this) }}
fun visit(string: StringLiteralValue) {
}
fun visit(array: ArrayLiteralValue) {
array.value.forEach { v->v.accept(this) }
}
fun visit(assignment: Assignment) {
@@ -109,6 +112,10 @@ interface IAstVisitor {
whileLoop.body.accept(this)
}
fun visit(foreverLoop: ForeverLoop) {
foreverLoop.body.accept(this)
}
fun visit(repeatLoop: RepeatLoop) {
repeatLoop.untilCondition.accept(this)
repeatLoop.body.accept(this)
@@ -1,18 +1,11 @@
package prog8.ast.processing
import prog8.ast.Module
import prog8.ast.base.SyntaxError
import prog8.ast.base.printWarning
import prog8.ast.base.ErrorReporter
import prog8.ast.statements.Directive
import prog8.ast.statements.Statement
internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
private val checkResult: MutableList<SyntaxError> = mutableListOf()
internal fun result(): List<SyntaxError> {
return checkResult
}
internal class ImportedModuleDirectiveRemover(private val errors: ErrorReporter) : IAstModifyingVisitor {
/**
* Most global directives don't apply for imported modules, so remove them
*/
@@ -25,7 +18,7 @@ internal class ImportedModuleDirectiveRemover : IAstModifyingVisitor {
val stmt = sourceStmt.accept(this)
if(stmt is Directive && stmt.parent is Module) {
if(stmt.directive in moduleLevelDirectives) {
printWarning("ignoring module directive because it was imported", stmt.position, stmt.directive)
errors.warn("ignoring module directive because it was imported", stmt.position)
continue
}
}
@@ -0,0 +1,24 @@
package prog8.ast.processing
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.ForeverLoop
import prog8.ast.statements.RepeatLoop
import prog8.ast.statements.Statement
import prog8.ast.statements.WhileLoop
internal class MakeForeverLoops : IAstModifyingVisitor {
override fun visit(whileLoop: WhileLoop): Statement {
val numeric = whileLoop.condition as? NumericLiteralValue
if(numeric!=null && numeric.number.toInt() != 0) {
return ForeverLoop(whileLoop.body, whileLoop.position)
}
return super.visit(whileLoop)
}
override fun visit(repeatLoop: RepeatLoop): Statement {
val numeric = repeatLoop.untilCondition as? NumericLiteralValue
if(numeric!=null && numeric.number.toInt() == 0)
return ForeverLoop(repeatLoop.body, repeatLoop.position)
return super.visit(repeatLoop)
}
}
@@ -4,19 +4,17 @@ import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName
import prog8.ast.base.printWarning
import prog8.ast.expressions.*
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 identifierName = identifier.nameInSource.single()
val targetVar = identifier.targetVarDecl(program.namespace)!!
val struct = targetVar.struct!!
when {
structAssignment.value is IdentifierReference -> {
when (structAssignment.value) {
is IdentifierReference -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null)
throw FatalAstException("can only assign arrays or structs to structs")
@@ -41,7 +39,7 @@ fun flattenStructAssignmentFromIdentifier(structAssignment: Assignment, program:
assign
}
}
structAssignment.value is StructLiteralValue -> {
is StructLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
}
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.
// - all other subroutines will be moved to the end of their block.
// - 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 addReturns = mutableListOf<Pair<INameScope, Int>>()
override fun visit(module: Module) {
addReturns.clear()
super.visit(module)
val (blocks, other) = module.statements.partition { it is Block }
@@ -95,6 +93,12 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
module.statements.removeAll(directives)
module.statements.addAll(0, directives)
for(pos in addReturns) {
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 {
@@ -164,6 +168,19 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
override fun visit(subroutine: Subroutine): Statement {
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>()
subroutine.statements.removeAll(varDecls)
subroutine.statements.addAll(0, varDecls)
@@ -186,64 +203,34 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
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 {
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 valuetype = assg.value.inferType(program)
val targettype = assg.target.inferType(program, assg)
if(targettype!=null && valuetype!=null) {
if(valuetype!=targettype) {
if (valuetype isAssignableTo targettype) {
assg.value = TypecastExpression(assg.value, targettype, true, assg.value.position)
assg.value.linkParents(assg)
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)
// 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 +254,4 @@ internal class StatementReorderer(private val program: Program): IAstModifyingVi
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 -> 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!=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
}
}
}
@@ -0,0 +1,199 @@
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.ErrorReporter
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
internal class TypecastsAdder(private val program: Program,
private val errors: ErrorReporter): 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)) {
errors.warn("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
}
}
}
@@ -9,7 +9,7 @@ import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature
import prog8.functions.FSignature
internal class VarInitValueAndAddressOfCreator(private val program: Program): IAstModifyingVisitor {
@@ -42,10 +42,9 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
if(decl.isArray && decl.value==null) {
// array datatype without initialization value, add list of zeros
val arraysize = decl.arraysize!!.size()!!
val array = ReferenceLiteralValue(decl.datatype, null,
val array = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
null, decl.position)
array.addToHeap(program.heap)
decl.position)
decl.value = array
}
@@ -79,11 +78,11 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
parentStatement = parentStatement.parent
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, parentStatement)
addAddressOfExprIfNeeded(targetStatement, functionCall.args, parentStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.arglist, parentStatement)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.args, parentStatement)
}
return functionCall
}
@@ -91,11 +90,11 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.args, functionCallStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.arglist, functionCallStatement)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.args, functionCallStatement)
}
return functionCallStatement
}
@@ -103,41 +102,39 @@ internal class VarInitValueAndAddressOfCreator(private val program: Program): IA
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.
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)
continue
val idref = argparam.second as? IdentifierReference
val strvalue = argparam.second as? ReferenceLiteralValue
val strvalue = argparam.second as? StringLiteralValue
if(idref!=null) {
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)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
}
else if(strvalue!=null) {
if(strvalue.isString) {
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl.createAuto(strvalue, program.heap)
addVarDecl(strvalue.definingScope(), variable)
// replace the argument with &autovar
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
// add a vardecl so that the autovar can be resolved in later lookups
val variable = VarDecl.createAuto(strvalue)
addVarDecl(strvalue.definingScope(), variable)
// replace the argument with &autovar
val autoHeapvarRef = IdentifierReference(listOf(variable.name), strvalue.position)
val pointerExpr = AddressOf(autoHeapvarRef, strvalue.position)
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: FSignature, args: MutableList<Expression>, parent: Statement) {
// val paramTypesForAddressOf = PassByReferenceDatatypes + DataType.UWORD
for(arg in args.withIndex().zip(signature.parameters)) {
val argvalue = arg.first.value
val argDt = argvalue.inferType(program)
if(argDt in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argDt.typeOrElse(DataType.UBYTE) in PassByReferenceDatatypes && DataType.UWORD in arg.second.possibleDatatypes) {
if(argvalue !is IdentifierReference)
throw CompilerException("pass-by-reference parameter isn't an identifier? $argvalue")
val addrOf = AddressOf(argvalue, argvalue.position)
@@ -5,7 +5,6 @@ import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.compiler.HeapValues
sealed class Statement : Node {
@@ -38,7 +37,6 @@ sealed class Statement : Node {
}
}
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
@@ -48,10 +46,8 @@ class BuiltinFunctionStatementPlaceholder(val name: String, override val positio
override val expensiveToInline = false
}
data class RegisterOrStatusflag(val registerOrPair: RegisterOrPair?, val statusflag: Statusflag?, val stack: Boolean)
class Block(override val name: String,
val address: Int?,
override var statements: MutableList<Statement>,
@@ -111,8 +107,6 @@ data class Label(val name: String, override val position: Position) : Statement(
override fun toString(): String {
return "Label(name=$name, pos=$position)"
}
val scopedname: String by lazy { makeScopedName(name) }
}
open class Return(var value: Expression?, override val position: Position) : Statement() {
@@ -197,20 +191,18 @@ class VarDecl(val type: VarDeclType,
companion object {
private var autoHeapValueSequenceNumber = 0
fun createAuto(refLv: ReferenceLiteralValue, heap: HeapValues): VarDecl {
if(refLv.heapId==null)
throw FatalAstException("can only create autovar for a ref lv that has a heapid $refLv")
fun createAuto(string: StringLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
return if(refLv.isArray) {
val declaredType = ArrayElementTypes.getValue(refLv.type)
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)
} else {
VarDecl(VarDeclType.VAR, refLv.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, refLv,
isArray = false, autogeneratedDontRemove = true, position = refLv.position)
}
return VarDecl(VarDeclType.VAR, DataType.STR, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string,
isArray = false, autogeneratedDontRemove = true, position = string.position)
}
fun createAuto(array: ArrayLiteralValue): VarDecl {
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val declaredType = ArrayElementTypes.getValue(array.type.typeOrElse(DataType.STRUCT))
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
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 {
@@ -301,9 +287,8 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
}
companion object {
fun forArray(v: ReferenceLiteralValue, heap: HeapValues): ArrayIndex {
val arraySize = v.array?.size ?: heap.get(v.heapId!!).arraysize
return ArrayIndex(NumericLiteralValue.optimalNumeric(arraySize, v.position), v.position)
fun forArray(v: ArrayLiteralValue): ArrayIndex {
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, 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)
return DataType.UBYTE
return InferredTypes.knownFor(DataType.UBYTE)
if(identifier!=null) {
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return null
if (symbol is VarDecl) return symbol.datatype
val symbol = program.namespace.lookup(identifier!!.nameInSource, stmt) ?: return InferredTypes.unknown()
if (symbol is VarDecl) return InferredTypes.knownFor(symbol.datatype)
}
if(arrayindexed!=null) {
val dt = arrayindexed!!.inferType(program)
if(dt!=null)
return dt
return arrayindexed!!.inferType(program)
}
if(memoryAddress!=null)
return DataType.UBYTE
return InferredTypes.knownFor(DataType.UBYTE)
return null
return InferredTypes.unknown()
}
infix fun isSameAs(value: Expression): Boolean {
@@ -489,16 +472,17 @@ class Jump(val address: Int?,
}
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 lateinit var parent: Node
override val expensiveToInline
get() = arglist.any { it !is NumericLiteralValue }
get() = args.any { it !is NumericLiteralValue }
override fun linkParents(parent: Node) {
this.parent = parent
target.linkParents(this)
arglist.forEach { it.linkParents(this) }
args.forEach { it.linkParents(this) }
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
@@ -608,42 +592,9 @@ class Subroutine(override val name: String,
.filter { it is InlineAssembly }
.map { (it as InlineAssembly).assembly }
.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,
val type: DataType,
override val position: Position) : Node {
@@ -692,8 +643,6 @@ class BranchStatement(var condition: BranchCondition,
}
class ForLoop(val loopRegister: Register?,
val decltype: DataType?,
val zeropage: ZeropageWish,
var loopVar: IdentifierReference?,
var iterable: Expression,
var body: AnonymousScope,
@@ -703,7 +652,7 @@ class ForLoop(val loopRegister: Register?,
override fun linkParents(parent: Node) {
this.parent=parent
loopVar?.linkParents(if(decltype==null) this else body)
loopVar?.linkParents(this)
iterable.linkParents(this)
body.linkParents(this)
}
@@ -714,6 +663,12 @@ class ForLoop(val loopRegister: Register?,
override fun toString(): String {
return "ForLoop(loopVar: $loopVar, loopReg: $loopRegister, iterable: $iterable, pos=$position)"
}
fun loopVarDt(program: Program): InferredTypes.InferredType {
val lv = loopVar
return if(loopRegister!=null) InferredTypes.InferredType.known(DataType.UBYTE)
else lv?.inferType(program) ?: InferredTypes.InferredType.unknown()
}
}
class WhileLoop(var condition: Expression,
@@ -732,6 +687,19 @@ class WhileLoop(var condition: Expression,
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
}
class ForeverLoop(var body: AnonymousScope, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline = true
override fun linkParents(parent: Node) {
this.parent = parent
body.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
}
class RepeatLoop(var body: AnonymousScope,
var untilCondition: Expression,
override val position: Position) : Statement() {
@@ -837,4 +805,3 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
}
@@ -0,0 +1,3 @@
package prog8.compiler
internal class AssemblyError(msg: String) : RuntimeException(msg)
-81
View File
@@ -1,13 +1,8 @@
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.InputStream
import java.nio.file.Path
import java.util.*
import kotlin.math.abs
enum class OutputType {
@@ -28,8 +23,6 @@ enum class ZeropageType {
DONTUSE
}
data class IntegerOrAddressOf(val integer: Int?, val addressOf: AddressOf?)
data class CompilationOptions(val output: OutputType,
val launcher: LauncherType,
val zeropage: ZeropageType,
@@ -73,77 +66,3 @@ fun loadAsmIncludeFile(filename: String, source: Path): String {
internal fun tryGetEmbeddedResource(name: String): InputStream? {
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 = Objects.hash(str, array, doubleArray)
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
}
+41 -22
View File
@@ -4,14 +4,12 @@ import prog8.ast.AstToSourceCode
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.statements.Directive
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.codegen2.AsmGen2
import prog8.compiler.target.CompilationTarget
import prog8.optimizer.constantFold
import prog8.optimizer.optimizeStatements
import prog8.optimizer.simplifyExpressions
import prog8.parser.ModuleImporter
import prog8.parser.ParsingFailedError
import prog8.parser.importLibraryModule
import prog8.parser.importModule
import prog8.parser.moduleName
import java.nio.file.Path
import kotlin.system.measureTimeMillis
@@ -25,19 +23,26 @@ class CompilationResult(val success: Boolean,
fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean): CompilationResult {
writeAssembly: Boolean,
outputDir: Path): CompilationResult {
lateinit var programAst: Program
var programName: String? = null
var importedFiles: List<Path> = emptyList()
var success=false
val errors = ErrorReporter()
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
val importer = ModuleImporter(errors)
errors.handle()
println("Parsing...")
programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath)
importer.importModule(programAst, filepath)
errors.handle()
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
@@ -47,60 +52,74 @@ fun compileProgram(filepath: Path,
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
importLibraryModule(programAst, "c64lib")
importLibraryModule(programAst, "c64utils")
importer.importLibraryModule(programAst, "c64lib")
importer.importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importLibraryModule(programAst, "math")
importLibraryModule(programAst, "prog8lib")
importer.importLibraryModule(programAst, "math")
importer.importLibraryModule(programAst, "prog8lib")
errors.handle()
// perform initial syntax checks and constant folding
println("Syntax check...")
val time1 = measureTimeMillis {
programAst.checkIdentifiers()
programAst.checkIdentifiers(errors)
errors.handle()
programAst.makeForeverLoops()
}
//println(" time1: $time1")
val time2 = measureTimeMillis {
programAst.constantFold()
programAst.constantFold(errors)
errors.handle()
}
//println(" time2: $time2")
val time3 = measureTimeMillis {
programAst.removeNopsFlattenAnonScopes()
programAst.reorderStatements() // reorder statements and add type casts, to please the compiler later
programAst.reorderStatements()
programAst.addTypecasts(errors)
errors.handle()
}
//println(" time3: $time3")
val time4 = measureTimeMillis {
programAst.checkValid(compilerOptions) // check if tree is valid
programAst.checkValid(compilerOptions, errors) // check if tree is valid
errors.handle()
}
//println(" time4: $time4")
programAst.checkIdentifiers()
programAst.checkIdentifiers(errors)
errors.handle()
if (optimize) {
// optimize the parse tree
println("Optimizing...")
while (true) {
// keep optimizing expressions and statements until no more steps remain
val optsDone1 = programAst.simplifyExpressions()
val optsDone2 = programAst.optimizeStatements()
val optsDone2 = programAst.optimizeStatements(errors)
errors.handle()
if (optsDone1 + optsDone2 == 0)
break
}
}
programAst.addTypecasts(errors)
errors.handle()
programAst.removeNopsFlattenAnonScopes()
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
programAst.checkValid(compilerOptions, errors) // check if final tree is valid
errors.handle()
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
errors.handle()
// printAst(programAst)
if(writeAssembly) {
// asm generation directly from the Ast, no need for intermediate code
val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
programAst.anonscopeVarsCleanup()
val assembly = AsmGen2(programAst, compilerOptions, zeropage).compileToAssembly(optimize)
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
programAst.anonscopeVarsCleanup(errors)
errors.handle()
val assembly = CompilationTarget.asmGenerator(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programName = assembly.name
}
+4 -4
View File
@@ -13,9 +13,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
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?, errors: ErrorReporter): Int {
assert(scopedname.isEmpty() || !allocations.values.any { it.first==scopedname } ) {"isSameAs scopedname can't be allocated twice"}
if(options.zeropage==ZeropageType.DONTUSE)
@@ -28,9 +28,9 @@ abstract class Zeropage(protected val options: CompilationOptions) {
DataType.FLOAT -> {
if (options.floats) {
if(position!=null)
printWarning("allocated a large value (float) in zeropage", position)
errors.warn("allocated a large value (float) in zeropage", position)
else
printWarning("$scopedname: allocated a large value (float) in zeropage")
errors.warn("$scopedname: allocated a large value (float) in zeropage", null)
5
} else throw CompilerException("floating point option not enabled")
}
@@ -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, altEncoding: Boolean) -> List<Short>
lateinit var decodeString: (bytes: List<Short>, altEncoding: Boolean) -> String
lateinit var asmGenerator: (Program, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
}
}
@@ -0,0 +1,14 @@
package prog8.compiler.target
import prog8.compiler.CompilationOptions
internal interface IAssemblyGenerator {
fun compileToAssembly(optimize: Boolean): IAssemblyProgram
}
internal const val generatedLabelPrefix = "_prog8_label_"
internal interface IAssemblyProgram {
val name: String
fun assemble(options: CompilationOptions)
}
@@ -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
}
@@ -2,70 +2,72 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import java.io.File
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path
import kotlin.system.exitProcess
class AssemblyProgram(val name: String) {
private val assemblyFile = "$name.asm"
private val viceMonListFile = "$name.vice-mon-list"
class AssemblyProgram(override val name: String, outputDir: Path) : IAssemblyProgram {
private val assemblyFile = outputDir.resolve("$name.asm")
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 {
// 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) {
override fun assemble(options: CompilationOptions) {
// add "-Wlong-branch" to see warnings about conversion of branch instructions to jumps
val command = mutableListOf("64tass", "--ascii", "--case-sensitive", "--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 -> {
command.add("--cbm-prg")
println("\nCreating C-64 prg.")
"$name.prg"
prgFile
}
OutputType.RAW -> {
command.add("--nostart")
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 result = proc.waitFor()
if(result!=0) {
if (result != 0) {
System.err.println("assembler failed with returncode $result")
exitProcess(result)
}
removeGeneratedLabelsFromMonlist()
generateBreakpointList()
}
private fun removeGeneratedLabelsFromMonlist() {
val pattern = Regex("""al (\w+) \S+${generatedLabelPrefix}.+?""")
val lines = viceMonListFile.toFile().readLines()
viceMonListFile.toFile().outputStream().bufferedWriter().use {
for (line in lines) {
if(pattern.matchEntire(line)==null)
it.write(line+"\n")
}
}
}
private fun generateBreakpointList() {
// builds list of breakpoints, appends to monitor list file
val breakpoints = mutableListOf<String>()
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()) {
val pattern = Regex("""al (\w+) \S+_prog8_breakpoint_\d+.?""") // gather breakpoints by the source label that's generated for them
for (line in viceMonListFile.toFile().readLines()) {
val match = pattern.matchEntire(line)
if(match!=null)
breakpoints.add("break \$" + match.groupValues[1])
if (match != null)
breakpoints.add("break \$" + match.groupValues[1])
}
val num = breakpoints.size
breakpoints.add(0, "; vice monitor breakpoint list now follows")
breakpoints.add(1, "; $num breakpoints have been defined")
breakpoints.add(2, "del")
File(viceMonListFile).appendText(breakpoints.joinToString("\n")+"\n")
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
}
}
@@ -4,18 +4,19 @@ import prog8.compiler.CompilationOptions
import prog8.compiler.CompilerException
import prog8.compiler.Zeropage
import prog8.compiler.ZeropageType
import prog8.compiler.target.IMachineDefinition
import java.awt.Color
import java.awt.image.BufferedImage
import javax.imageio.ImageIO
import kotlin.math.absoluteValue
import kotlin.math.pow
object MachineDefinition {
object C64MachineDefinition: IMachineDefinition {
// 5-byte cbm MFLPT format limitations:
const 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_POSITIVE = 1.7014118345e+38 // bytes: 255,127,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 RAW_LOAD_ADDRESS = 0xc000
@@ -30,6 +31,19 @@ object MachineDefinition {
const val ESTACK_HI_PLUS1_HEX = "\$cf01"
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) {
@@ -110,8 +124,6 @@ object MachineDefinition {
data class Mflpt5(val b0: Short, val b1: Short, val b2: Short, val b3: Short, val b4: Short) {
companion object {
const val MemorySize = 5
val zero = Mflpt5(0, 0, 0, 0, 0)
fun fromNumber(num: Number): Mflpt5 {
// see https://en.wikipedia.org/wiki/Microsoft_Binary_Format
@@ -232,7 +244,6 @@ object MachineDefinition {
return bcopy
}
val colorPalette = listOf( // this is Pepto's Commodore-64 palette http://www.pepto.de/projects/colorvic/
Color(0x000000), // 0 = black
Color(0xFFFFFF), // 1 = white
@@ -1058,7 +1058,7 @@ object Petscii {
0.toShort()
else {
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()
else {
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()})")
}
}
}
@@ -0,0 +1,902 @@
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_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.generatedLabelPrefix
import prog8.functions.BuiltinFunctions
import prog8.functions.FSignature
import java.math.RoundingMode
import java.nio.file.Path
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*
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(" tsx")
out(" stx prog8_lib.orig_stackpointer")
out(" jsr prog8_lib.init_system")
}
options.output == OutputType.PRG -> {
out("; ---- program without basic sys call ----")
out("* = ${program.actualLoadAddress.toHex()}\n")
out(" tsx")
out(" stx prog8_lib.orig_stackpointer")
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")
}
}
}
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 "${generatedLabelPrefix}${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 encode(str: String, altEncoding: Boolean): List<Short> {
val bytes = if(altEncoding) Petscii.encodeScreencode(str, true) else 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 errors = ErrorReporter()
val address = zeropage.allocate(fullName, variable.datatype, null, errors)
errors.handle()
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 str = decl.value as StringLiteralValue
outputStringvar(decl, encode(str.value, str.altEncoding))
}
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 {
val str = it.value as StringLiteralValue
it to encode(str.value, str.altEncoding)
}
.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 ForeverLoop -> 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")
else -> TODO("no translation for $stmt")
}
}
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: ForeverLoop) {
val foreverLabel = makeLabel("forever")
val endLabel = makeLabel("foreverend")
loopEndLabels.push(endLabel)
loopContinueLabels.push(foreverLabel)
out(foreverLabel)
translate(stmt.body)
out(" jmp $foreverLabel")
out(endLabel)
loopEndLabels.pop()
loopContinueLabels.pop()
}
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}") // underscore prefix to make sure it's a local label
}
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 -> {
val target = jmp.identifier.targetStatement(program.namespace)
val asmName = asmIdentifierName(jmp.identifier)
if(target is Label)
"_$asmName" // prefix with underscore to jump to local label
else
asmName
}
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: FSignature) =
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)
}
@@ -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.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_PLUS1_HEX
// note: see https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations
@@ -101,7 +101,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
// optimize sequential assignments of the isSameAs value to various targets (bytes, words, floats)
// the float one is the one that requires 2*7=14 lines of code to check...
// @todo a better place to do this is in the Compiler instead and transform the Ast, and never even create the inefficient asm in the first place...
// @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>()
for (pair in linesByFourteen) {
@@ -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.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
import prog8.compiler.toHex
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 ${'$'}ffff ; 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 ${'$'}ffff ; 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")
}
}
}
@@ -0,0 +1,612 @@
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.AssemblyError
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.functions.FSignature
internal class BuiltinFunctionsAsmGen(private val program: Program, private val asmgen: AsmGen) {
internal fun translateFunctioncallExpression(fcall: FunctionCall, func: FSignature) {
translateFunctioncall(fcall, func, false)
}
internal fun translateFunctioncallStatement(fcall: FunctionCallStatement, func: FSignature) {
translateFunctioncall(fcall, func, true)
}
private fun translateFunctioncall(fcall: IFunctionCall, func: FSignature, 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 ${'$'}ffff ; 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 ${'$'}ffff ; 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 ${'$'}ffff ; 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 ${'$'}ffff ; 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 -> throw AssemblyError("sorting of floating point array is not supported")
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 -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta ${C64Zeropage.SCRATCH_W1}
sty ${C64Zeropage.SCRATCH_W1 + 1}
lda #$numElements
jsr prog8_lib.reverse_f
""")
}
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: FSignature) {
args.forEach {
asmgen.translateExpression(it)
}
}
}
@@ -0,0 +1,436 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
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.compiler.toHex
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 -> asmgen.out(" txa | sta $ESTACK_LO_HEX,x | dex")
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()
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")
}
}
}
@@ -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.AssemblyError
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 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()
}
}
@@ -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.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.C64MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.toHex
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
}
}
@@ -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.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.toHex
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
@@ -1,305 +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_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.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)
}
"strlen" -> {
outputPushAddressOfIdentifier(fcall.arglist[0])
asmgen.out(" jsr prog8_lib.func_strlen")
}
"min", "max", "sum" -> {
outputPushAddressAndLenghtOfArray(fcall.arglist[0])
val dt = fcall.arglist.single().inferType(program)!!
when(dt) {
DataType.ARRAY_UB, DataType.STR_S, 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.arglist[0])
val dt = fcall.arglist.single().inferType(program)!!
when(dt) {
DataType.ARRAY_B, DataType.ARRAY_UB, DataType.STR_S, 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")
}
}
"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 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)
}
}
}
+133 -129
View File
@@ -7,130 +7,121 @@ import prog8.compiler.CompilerException
import kotlin.math.*
class BuiltinFunctionParam(val name: String, val possibleDatatypes: Set<DataType>)
class FParam(val name: String, val possibleDatatypes: Set<DataType>)
typealias ConstExpressionCaller = (args: List<Expression>, position: Position, program: Program) -> NumericLiteralValue
class FunctionSignature(val pure: Boolean, // does it have side effects?
val parameters: List<BuiltinFunctionParam>,
val returntype: DataType?,
val constExpressionFunc: ConstExpressionCaller? = null)
class FSignature(val pure: Boolean, // does it have side effects?
val parameters: List<FParam>,
val returntype: DataType?,
val constExpressionFunc: ConstExpressionCaller? = null)
val BuiltinFunctions = mapOf(
// this set of function have no return value and operate in-place:
"rol" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" 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),
"lsr" to FunctionSignature(false, listOf(BuiltinFunctionParam("item", IntegerDatatypes)), null),
"rol" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", setOf(DataType.UBYTE, DataType.UWORD))), null),
"lsl" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
"lsr" to FSignature(false, listOf(FParam("item", IntegerDatatypes)), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// 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
"min" 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, _ -> collectionArgNeverConst(a, p) }, // type depends on args
"abs" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
"max" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
"min" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
"sum" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // type depends on args
"abs" to FSignature(true, listOf(FParam("value", NumericDatatypes)), null, ::builtinAbs), // type depends on argument
"len" to FSignature(true, listOf(FParam("values", IterableDatatypes)), null, ::builtinLen), // type is UBYTE or UWORD depending on actual length
// normal functions follow:
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FunctionSignature(true, listOf(BuiltinFunctionParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
"sqrt16" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"round" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, _ -> collectionArgNeverConst(a, p) },
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, _ -> collectionArgNeverConst(a, p) },
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FunctionSignature(true, listOf(
BuiltinFunctionParam("lsb", setOf(DataType.UBYTE)),
BuiltinFunctionParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
"rnd" to FunctionSignature(true, emptyList(), DataType.UBYTE),
"rndw" to FunctionSignature(true, emptyList(), DataType.UWORD),
"rndf" to FunctionSignature(true, emptyList(), DataType.FLOAT),
"rsave" to FunctionSignature(false, emptyList(), null),
"rrestore" to FunctionSignature(false, emptyList(), null),
"set_carry" to FunctionSignature(false, emptyList(), null),
"clear_carry" to FunctionSignature(false, emptyList(), null),
"set_irqd" to FunctionSignature(false, emptyList(), null),
"clear_irqd" to FunctionSignature(false, emptyList(), null),
"read_flags" to FunctionSignature(false, emptyList(), DataType.UBYTE),
"swap" to FunctionSignature(false, listOf(BuiltinFunctionParam("first", NumericDatatypes), BuiltinFunctionParam("second", NumericDatatypes)), null),
"memcopy" to FunctionSignature(false, listOf(
BuiltinFunctionParam("from", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("to", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + setOf(DataType.UWORD)),
BuiltinFunctionParam("numwords", setOf(DataType.UWORD)),
BuiltinFunctionParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FunctionSignature(true, listOf(BuiltinFunctionParam("string", StringDatatypes)), DataType.UBYTE, ::builtinStrlen),
"vm_write_memchr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_memstr" to FunctionSignature(false, listOf(BuiltinFunctionParam("address", setOf(DataType.UWORD))), null),
"vm_write_num" to FunctionSignature(false, listOf(BuiltinFunctionParam("number", NumericDatatypes)), null),
"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)
"sgn" to FSignature(true, listOf(FParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"sin8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinSin8 ),
"sin8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinSin8u ),
"sin16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinSin16 ),
"sin16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinSin16u ),
"cos" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::cos) },
"cos8" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.BYTE, ::builtinCos8 ),
"cos8u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UBYTE, ::builtinCos8u ),
"cos16" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.WORD, ::builtinCos16 ),
"cos16u" to FSignature(true, listOf(FParam("angle8", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinCos16u ),
"tan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::tan) },
"atan" to FSignature(true, listOf(FParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::atan) },
"ln" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::log) },
"log2" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, ::log2) },
"sqrt16" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { sqrt(it.toDouble()).toInt() } },
"sqrt" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sqrt) },
"rad" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toRadians) },
"deg" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::toDegrees) },
"round" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::round) },
"floor" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::floor) },
"ceil" to FSignature(true, listOf(FParam("value", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArgOutputWord(a, p, prg, Math::ceil) },
"any" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to FSignature(true, listOf(FParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"msb" to FSignature(true, listOf(FParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x ushr 8 and 255}},
"mkword" to FSignature(true, listOf(FParam("lsb", setOf(DataType.UBYTE)), FParam("msb", setOf(DataType.UBYTE))), DataType.UWORD, ::builtinMkword),
"rnd" to FSignature(true, emptyList(), DataType.UBYTE),
"rndw" to FSignature(true, emptyList(), DataType.UWORD),
"rndf" to FSignature(true, emptyList(), DataType.FLOAT),
"exit" to FSignature(false, listOf(FParam("returnvalue", setOf(DataType.UBYTE))), null),
"rsave" to FSignature(false, emptyList(), null),
"rrestore" to FSignature(false, emptyList(), null),
"set_carry" to FSignature(false, emptyList(), null),
"clear_carry" to FSignature(false, emptyList(), null),
"set_irqd" to FSignature(false, emptyList(), null),
"clear_irqd" to FSignature(false, emptyList(), null),
"read_flags" to FSignature(false, emptyList(), DataType.UBYTE),
"swap" to FSignature(false, listOf(FParam("first", NumericDatatypes), FParam("second", NumericDatatypes)), null),
"memcopy" to FSignature(false, listOf(
FParam("from", IterableDatatypes + DataType.UWORD),
FParam("to", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FSignature(false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numbytes", setOf(DataType.UWORD)),
FParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FSignature(false, listOf(
FParam("address", IterableDatatypes + DataType.UWORD),
FParam("numwords", setOf(DataType.UWORD)),
FParam("wordvalue", setOf(DataType.UWORD, DataType.WORD))), null),
"strlen" to FSignature(true, listOf(FParam("string", setOf(DataType.STR))), DataType.UBYTE, ::builtinStrlen)
)
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 {
if(arglist is ReferenceLiteralValue) {
if(arglist.type== DataType.ARRAY_UB || arglist.type== DataType.ARRAY_UW || arglist.type== DataType.ARRAY_F) {
val dt = arglist.array!!.map {it.inferType(program)}
if(dt.any { it!= DataType.UBYTE && it!= DataType.UWORD && it!= DataType.FLOAT}) {
throw FatalAstException("fuction $function only accepts arraysize of numeric values")
}
if(dt.any { it== DataType.FLOAT }) return DataType.FLOAT
if(dt.any { it== DataType.UWORD }) return DataType.UWORD
return DataType.UBYTE
if(arglist is ArrayLiteralValue) {
val dt = arglist.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
if(dt.any { it !in NumericDatatypes }) {
throw FatalAstException("fuction $function only accepts array of numeric values")
}
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) {
return when(val dt = arglist.inferType(program)) {
in NumericDatatypes -> dt!!
in StringDatatypes -> dt!!
in ArrayDatatypes -> ArrayElementTypes.getValue(dt!!)
val idt = arglist.inferType(program)
if(!idt.isKnown)
throw FatalAstException("couldn't determine type of iterable $arglist")
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")
}
}
@@ -139,43 +130,43 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
val func = BuiltinFunctions.getValue(function)
if(func.returntype!=null)
return func.returntype
return InferredTypes.knownFor(func.returntype)
// function has return values, but the return type depends on the arguments
return when (function) {
"abs" -> {
val dt = args.single().inferType(program)
if(dt in NumericDatatypes)
if(dt.typeOrElse(DataType.STRUCT) in NumericDatatypes)
return dt
else
throw FatalAstException("weird datatype passed to abs $dt")
}
"max", "min" -> {
when(val dt = datatypeFromIterableArg(args.single())) {
in NumericDatatypes -> dt
in StringDatatypes -> DataType.UBYTE
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
else -> null
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in NumericDatatypes -> InferredTypes.knownFor(dt)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
else -> InferredTypes.unknown()
}
}
"sum" -> {
when(datatypeFromIterableArg(args.single())) {
DataType.UBYTE, DataType.UWORD -> DataType.UWORD
DataType.BYTE, DataType.WORD -> DataType.WORD
DataType.FLOAT -> DataType.FLOAT
DataType.ARRAY_UB, DataType.ARRAY_UW -> DataType.UWORD
DataType.ARRAY_B, DataType.ARRAY_W -> DataType.WORD
DataType.ARRAY_F -> DataType.FLOAT
in StringDatatypes -> DataType.UWORD
else -> null
DataType.UBYTE, DataType.UWORD -> InferredTypes.knownFor(DataType.UWORD)
DataType.BYTE, DataType.WORD -> InferredTypes.knownFor(DataType.WORD)
DataType.FLOAT -> InferredTypes.knownFor(DataType.FLOAT)
DataType.ARRAY_UB, DataType.ARRAY_UW -> InferredTypes.knownFor(DataType.UWORD)
DataType.ARRAY_B, DataType.ARRAY_W -> InferredTypes.knownFor(DataType.WORD)
DataType.ARRAY_F -> InferredTypes.knownFor(DataType.FLOAT)
DataType.STR -> InferredTypes.knownFor(DataType.UWORD)
else -> InferredTypes.unknown()
}
}
"len" -> {
// a length can be >255 so in that case, the result is an UWORD instead of an UBYTE
// but to avoid a lot of code duplication we simply assume UWORD in all cases for now
return DataType.UWORD
return InferredTypes.knownFor(DataType.UWORD)
}
else -> return null
else -> return InferredTypes.unknown()
}
}
@@ -210,12 +201,16 @@ private fun oneIntArgOutputInt(args: List<Expression>, position: Position, progr
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)
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
throw NotConstArgumentException()
val array= args[0] as? ArrayLiteralValue ?: 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 {
@@ -235,7 +230,7 @@ private fun builtinStrlen(args: List<Expression>, position: Position, program: P
if (args.size != 1)
throw SyntaxError("strlen requires one argument", position)
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 NotConstArgumentException() // this function is not considering the string argument a constant
@@ -253,6 +248,8 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
var arraySize = directMemVar?.arraysize?.size()
if(arraySize != null)
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)
throw SyntaxError("len argument should be an identifier, but is ${args[0]}", position)
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)!!
@@ -270,11 +267,11 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
in StringDatatypes -> {
val refLv = target.value as ReferenceLiteralValue
if(refLv.str!!.length>255)
DataType.STR -> {
val refLv = target.value as StringLiteralValue
if(refLv.value.length>255)
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)
else -> throw CompilerException("weird datatype")
@@ -355,6 +352,13 @@ private fun builtinCos16u(args: List<Expression>, position: Position, program: P
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 {
val floatNum=value.toDouble()
val tweakedValue: Number =
+31 -29
View File
@@ -14,13 +14,23 @@ import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.loadAsmIncludeFile
private val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq"),
Pair("prog8_lib", "init_system")
)
class CallGraph(private val program: Program): IAstVisitor {
private val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
private val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
class CallGraph(private val program: Program) : IAstVisitor {
val modulesImporting = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val modulesImportedBy = mutableMapOf<Module, List<Module>>().withDefault { mutableListOf() }
val subroutinesCalling = mutableMapOf<INameScope, List<Subroutine>>().withDefault { mutableListOf() }
val subroutinesCalledBy = mutableMapOf<Subroutine, List<Node>>().withDefault { mutableListOf() }
// TODO add dataflow graph: what statements use what variables
val usedSymbols = mutableSetOf<Statement>()
@@ -31,9 +41,9 @@ class CallGraph(private val program: Program): IAstVisitor {
fun forAllSubroutines(scope: INameScope, sub: (s: Subroutine) -> Unit) {
fun findSubs(scope: INameScope) {
scope.statements.forEach {
if(it is Subroutine)
if (it is Subroutine)
sub(it)
if(it is INameScope)
if (it is INameScope)
findSubs(it)
}
}
@@ -65,7 +75,7 @@ class CallGraph(private val program: Program): IAstVisitor {
}
override fun visit(block: Block) {
if(block.definingModule().isLibraryModule) {
if (block.definingModule().isLibraryModule) {
// make sure the block is not removed
addNodeAndParentScopes(block)
}
@@ -75,11 +85,11 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(directive: Directive) {
val thisModule = directive.definingModule()
if(directive.directive=="%import") {
val importedModule: Module = program.modules.single { it.name==directive.args[0].name }
if (directive.directive == "%import") {
val importedModule: Module = program.modules.single { it.name == directive.args[0].name }
modulesImporting[thisModule] = modulesImporting.getValue(thisModule).plus(importedModule)
modulesImportedBy[importedModule] = modulesImportedBy.getValue(importedModule).plus(thisModule)
} else if (directive.directive=="%asminclude") {
} else if (directive.directive == "%asminclude") {
val asm = loadAsmIncludeFile(directive.args[0].str!!, thisModule.source)
val scope = directive.definingScope()
scanAssemblyCode(asm, directive, scope)
@@ -91,7 +101,7 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(identifier: IdentifierReference) {
// track symbol usage
val target = identifier.targetStatement(this.program.namespace)
if(target!=null) {
if (target != null) {
addNodeAndParentScopes(target)
}
super.visit(identifier)
@@ -99,24 +109,18 @@ class CallGraph(private val program: Program): IAstVisitor {
private fun addNodeAndParentScopes(stmt: Statement) {
usedSymbols.add(stmt)
var node: Node=stmt
var node: Node = stmt
do {
if(node is INameScope && node is Statement) {
if (node is INameScope && node is Statement) {
usedSymbols.add(node)
}
node=node.parent
node = node.parent
} while (node !is Module && node !is ParentSentinel)
}
override fun visit(subroutine: Subroutine) {
val alwaysKeepSubroutines = setOf(
Pair("main", "start"),
Pair("irq", "irq"),
Pair("prog8_lib", "init_system")
)
if(Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|| subroutine.name== initvarsSubName || subroutine.definingModule().isLibraryModule) {
if (Pair(subroutine.definingScope().name, subroutine.name) in alwaysKeepSubroutines
|| subroutine.name == initvarsSubName || subroutine.definingModule().isLibraryModule) {
// make sure the entrypoint is mentioned in the used symbols
addNodeAndParentScopes(subroutine)
}
@@ -124,12 +128,12 @@ class CallGraph(private val program: Program): IAstVisitor {
}
override fun visit(decl: VarDecl) {
if(decl.autogeneratedDontRemove || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
if (decl.autogeneratedDontRemove || (decl.definingModule().isLibraryModule && decl.type != VarDeclType.VAR)) {
// make sure autogenerated vardecls are in the used symbols
addNodeAndParentScopes(decl)
}
if(decl.datatype==DataType.STRUCT)
if (decl.datatype == DataType.STRUCT)
addNodeAndParentScopes(decl)
super.visit(decl)
@@ -137,7 +141,7 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(functionCall: FunctionCall) {
val otherSub = functionCall.target.targetSubroutine(program.namespace)
if(otherSub!=null) {
if (otherSub != null) {
functionCall.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCall)
@@ -148,7 +152,7 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(functionCallStatement: FunctionCallStatement) {
val otherSub = functionCallStatement.target.targetSubroutine(program.namespace)
if(otherSub!=null) {
if (otherSub != null) {
functionCallStatement.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(functionCallStatement)
@@ -159,7 +163,7 @@ class CallGraph(private val program: Program): IAstVisitor {
override fun visit(jump: Jump) {
val otherSub = jump.identifier?.targetSubroutine(program.namespace)
if(otherSub!=null) {
if (otherSub != null) {
jump.definingSubroutine()?.let { thisSub ->
subroutinesCalling[thisSub] = subroutinesCalling.getValue(thisSub).plus(otherSub)
subroutinesCalledBy[otherSub] = subroutinesCalledBy.getValue(otherSub).plus(jump)
@@ -181,8 +185,6 @@ class CallGraph(private val program: Program): IAstVisitor {
}
private fun scanAssemblyCode(asm: String, context: Statement, scope: INameScope) {
val asmJumpRx = Regex("""[\-+a-zA-Z0-9_ \t]+(jmp|jsr)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
val asmRefRx = Regex("""[\-+a-zA-Z0-9_ \t]+(...)[ \t]+(\S+).*""", RegexOption.IGNORE_CASE)
asm.lines().forEach { line ->
val matches = asmJumpRx.matchEntire(line)
if (matches != null) {
@@ -192,7 +194,7 @@ class CallGraph(private val program: Program): IAstVisitor {
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
subroutinesCalledBy[node] = subroutinesCalledBy.getValue(node).plus(context)
} else if(jumptarget.contains('.')) {
} else if (jumptarget.contains('.')) {
// maybe only the first part already refers to a subroutine
val node2 = program.namespace.lookup(listOf(jumptarget.substringBefore('.')), context)
if (node2 is Subroutine) {
@@ -204,9 +206,9 @@ class CallGraph(private val program: Program): IAstVisitor {
} else {
val matches2 = asmRefRx.matchEntire(line)
if (matches2 != null) {
val target= matches2.groups[2]?.value
val target = matches2.groups[2]?.value
if (target != null && (target[0].isLetter() || target[0] == '_')) {
if(target.contains('.')) {
if (target.contains('.')) {
val node = program.namespace.lookup(listOf(target.substringBefore('.')), context)
if (node is Subroutine) {
subroutinesCalling[scope] = subroutinesCalling.getValue(scope).plus(node)
@@ -54,15 +54,15 @@ class ConstExprEvaluator {
private fun logicalxor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-bitxor $right"
return when {
left.type in IntegerDatatypes -> when {
right.type 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)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toInt() != 0) xor (right.number.toInt() != 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)
}
left.type == DataType.FLOAT -> when {
right.type 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 -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean((left.number.toDouble() != 0.0) xor (right.number.toInt() != 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)
@@ -71,15 +71,15 @@ class ConstExprEvaluator {
private fun logicalor(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-or $right"
return when {
left.type in IntegerDatatypes -> when {
right.type 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)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 || right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type 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 -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 || right.number.toInt() != 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)
@@ -88,15 +88,15 @@ class ConstExprEvaluator {
private fun logicaland(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot compute $left locical-and $right"
return when {
left.type in IntegerDatatypes -> when {
right.type 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)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toInt() != 0, left.position)
DataType.FLOAT -> NumericLiteralValue.fromBoolean(left.number.toInt() != 0 && right.number.toDouble() != 0.0, left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type 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 -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.fromBoolean(left.number.toDouble() != 0.0 && right.number.toInt() != 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)
@@ -144,15 +144,15 @@ class ConstExprEvaluator {
private fun power(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot calculate $left ** $right"
return when {
left.type in IntegerDatatypes -> when {
right.type 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)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt().toDouble().pow(right.number.toInt()), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt().toDouble().pow(right.number.toDouble()), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type 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 -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble().pow(right.number.toInt()), 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)
@@ -161,15 +161,15 @@ class ConstExprEvaluator {
private fun plus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot add $left and $right"
return when {
left.type in IntegerDatatypes -> when {
right.type 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)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() + right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() + right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type 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 -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() + right.number.toInt(), 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)
@@ -178,15 +178,15 @@ class ConstExprEvaluator {
private fun minus(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot subtract $left and $right"
return when {
left.type in IntegerDatatypes -> when {
right.type 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)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() - right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() - right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type 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 -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() - right.number.toInt(), 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)
@@ -195,15 +195,15 @@ class ConstExprEvaluator {
private fun multiply(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot multiply ${left.type} and ${right.type}"
return when {
left.type in IntegerDatatypes -> when {
right.type 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)
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue.optimalNumeric(left.number.toInt() * right.number.toInt(), left.position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, left.number.toInt() * right.number.toDouble(), left.position)
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type 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 -> when (right.type) {
in IntegerDatatypes -> NumericLiteralValue(DataType.FLOAT, left.number.toDouble() * right.number.toInt(), 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)
@@ -215,25 +215,25 @@ class ConstExprEvaluator {
private fun divide(left: NumericLiteralValue, right: NumericLiteralValue): NumericLiteralValue {
val error = "cannot divide $left by $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.position)
val result: Int = left.number.toInt() / right.number.toInt()
NumericLiteralValue.optimalNumeric(result, left.position)
}
right.type == DataType.FLOAT -> {
DataType.FLOAT -> {
if(right.number.toDouble()==0.0) divideByZeroError(right.position)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() / right.number.toDouble(), left.position)
}
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> {
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.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)
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 {
val error = "cannot compute remainder of $left by $right"
return when {
left.type in IntegerDatatypes -> when {
right.type in IntegerDatatypes -> {
return when (left.type) {
in IntegerDatatypes -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.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)
NumericLiteralValue(DataType.FLOAT, left.number.toInt() % right.number.toDouble(), left.position)
}
else -> throw ExpressionError(error, left.position)
}
left.type == DataType.FLOAT -> when {
right.type in IntegerDatatypes -> {
DataType.FLOAT -> when (right.type) {
in IntegerDatatypes -> {
if(right.number.toInt()==0) divideByZeroError(right.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)
NumericLiteralValue(DataType.FLOAT, left.number.toDouble() % right.number.toDouble(), left.position)
}
+123 -92
View File
@@ -5,33 +5,20 @@ import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.fixupArrayDatatype
import prog8.ast.statements.*
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.codegen2.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import kotlin.math.floor
class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
class ConstantFolding(private val program: Program, private val errors: ErrorReporter) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
var errors : MutableList<AstException> = mutableListOf()
private val reportedErrorMessages = mutableSetOf<String>()
fun addError(x: AstException) {
// check that we don't add the isSameAs error more than once
if(x.toString() !in reportedErrorMessages) {
reportedErrorMessages.add(x.toString())
errors.add(x)
}
}
override fun visit(decl: VarDecl): Statement {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call tree for this?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
errors.add(ExpressionError("recursive var declaration", decl.position))
errors.err("recursive var declaration", decl.position)
return decl
}
@@ -39,9 +26,9 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
if(decl.isArray){
if(decl.arraysize==null) {
// 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) {
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.size, decl.position), decl.position)
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position)
optimizationsDone++
}
}
@@ -65,12 +52,6 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
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 -> {
val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
@@ -78,18 +59,18 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.add(ExpressionError("range expression size doesn't match declared array size", decl.value?.position!!))
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program)!!
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
if(eltType in ByteDatatypes) {
decl.value = ReferenceLiteralValue(decl.datatype,
array = constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }
.toTypedArray(), position = decl.value!!.position)
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
decl.value = ReferenceLiteralValue(decl.datatype,
array = constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }
.toTypedArray(), position = decl.value!!.position)
decl.value = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
decl.value!!.linkParents(decl)
optimizationsDone++
@@ -97,7 +78,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
}
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.add(ExpressionError("arraysize requires only integers here", numericLv.position))
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.size() ?: return decl
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
@@ -105,26 +86,25 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.add(ExpressionError("ubyte value overflow", numericLv.position))
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.add(ExpressionError("byte value overflow", numericLv.position))
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.add(ExpressionError("uword value overflow", numericLv.position))
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.add(ExpressionError("word value overflow", numericLv.position))
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue.optimalInteger(it, numericLv.position) as Expression}.toTypedArray()
val refValue = ReferenceLiteralValue(decl.datatype, array = array, position = numericLv.position)
refValue.addToHeap(program.heap)
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
decl.value = refValue
refValue.parent=decl
optimizationsDone++
@@ -140,13 +120,12 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
} else {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
if (fillvalue < FLOAT_MAX_NEGATIVE || fillvalue > FLOAT_MAX_POSITIVE)
errors.add(ExpressionError("float value overflow", litval.position))
if (fillvalue < CompilationTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > CompilationTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", litval.position)
else {
// create the array itself, filled with the fillvalue.
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)
refValue.addToHeap(program.heap)
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
decl.value = refValue
refValue.parent=decl
optimizationsDone++
@@ -156,6 +135,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
else -> {
// nothing to do for this type
// this includes strings and structs
}
}
}
@@ -167,30 +147,39 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
* replace identifiers that refer to const value, with the value itself (if it's a simple type)
*/
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 {
val cval = identifier.constValue(program) ?: return identifier
return when {
cval.type in NumericDatatypes -> {
return when (cval.type) {
in NumericDatatypes -> {
val copy = NumericLiteralValue(cval.type, cval.number, identifier.position)
copy.parent = identifier.parent
copy
}
cval.type in PassByReferenceDatatypes -> throw AssemblyError("pass-by-reference type should not be considered a constant")
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> identifier
}
} catch (ax: AstException) {
addError(ax)
errors.err("unhandled AST error: $ax", identifier.position)
identifier
}
}
override fun visit(functionCall: FunctionCall): Expression {
super.visit(functionCall)
typeCastConstArguments(functionCall)
return try {
super.visit(functionCall)
typeCastConstArguments(functionCall)
functionCall.constValue(program) ?: functionCall
} catch (ax: AstException) {
addError(ax)
errors.err("unhandled AST error: $ax", functionCall.position)
functionCall
}
}
@@ -206,12 +195,12 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val builtinFunction = BuiltinFunctions[functionCall.target.nameInSource.single()]
if(builtinFunction!=null) {
// 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 argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type !in possibleDts) {
val convertedValue = argConst.cast(possibleDts.first()) // TODO can throw exception
functionCall.arglist[arg.first.index] = convertedValue
val convertedValue = argConst.cast(possibleDts.first())
functionCall.args[arg.first.index] = convertedValue
optimizationsDone++
}
}
@@ -222,12 +211,12 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
for(arg in functionCall.arglist.withIndex().zip(subroutine.parameters)) {
for(arg in functionCall.args.withIndex().zip(subroutine.parameters)) {
val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.cast(expectedDt) // TODO can throw exception
functionCall.arglist[arg.first.index] = convertedValue
val convertedValue = argConst.cast(expectedDt)
functionCall.args[arg.first.index] = convertedValue
optimizationsDone++
}
}
@@ -256,27 +245,27 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val subexpr = prefixExpr.expression
if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true)
return when {
prefixExpr.operator == "+" -> subexpr
prefixExpr.operator == "-" -> when {
subexpr.type in IntegerDatatypes -> {
return when (prefixExpr.operator) {
"+" -> subexpr
"-" -> when (subexpr.type) {
in IntegerDatatypes -> {
optimizationsDone++
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
}
subexpr.type == DataType.FLOAT -> {
DataType.FLOAT -> {
optimizationsDone++
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position)
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
}
prefixExpr.operator == "~" -> when {
subexpr.type in IntegerDatatypes -> {
"~" -> when (subexpr.type) {
in IntegerDatatypes -> {
optimizationsDone++
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
prefixExpr.operator == "not" -> {
"not" -> {
optimizationsDone++
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
}
@@ -285,7 +274,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
}
return prefixExpr
} catch (ax: AstException) {
addError(ax)
errors.err("unhandled AST error: $ax", expr.position)
expr
}
}
@@ -311,7 +300,8 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
return try {
super.visit(expr)
if(expr.left is ReferenceLiteralValue || expr.right is ReferenceLiteralValue)
if(expr.left is StringLiteralValue || expr.left is ArrayLiteralValue
|| expr.right is StringLiteralValue || expr.right is ArrayLiteralValue)
throw FatalAstException("binexpr with reference litval instead of numeric")
val leftconst = expr.left.constValue(program)
@@ -344,7 +334,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
else -> expr
}
} catch (ax: AstException) {
addError(ax)
errors.err("unhandled AST error: $ax", expr.position)
expr
}
}
@@ -356,7 +346,7 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
subleftIsConst: Boolean,
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) {
// both operators are the isSameAs.
// If + or *, we can simply swap the const of expr and Var in subexpr.
@@ -544,66 +534,104 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
override fun visit(forLoop: ForLoop): Statement {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
// TODO casts can throw exception
val newFrom = rangeFrom.cast(targetDt)
val newTo = rangeTo.cast(targetDt)
val newStep: Expression =
stepLiteral?.cast(targetDt) ?: range.step
val newFrom: NumericLiteralValue
val newTo: NumericLiteralValue
try {
newFrom = rangeFrom.cast(targetDt)
newTo = rangeTo.cast(targetDt)
} 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)
}
}
// adjust the datatype of a range expression in for loops to the loop variable.
val resultStmt = super.visit(forLoop) as ForLoop
val iterableRange = resultStmt.iterable as? RangeExpr ?: return resultStmt
val iterableRange = forLoop2.iterable as? RangeExpr ?: return forLoop2
val rangeFrom = iterableRange.from 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) {
val stepLiteral = iterableRange.step as? NumericLiteralValue
when(loopvar.datatype) {
DataType.UBYTE -> {
if(rangeFrom.type!= DataType.UBYTE) {
// 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 -> {
if(rangeFrom.type!= DataType.BYTE) {
// 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 -> {
if(rangeFrom.type!= DataType.UWORD) {
// 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 -> {
if(rangeFrom.type!= DataType.WORD) {
// 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")
}
}
return resultStmt
return forLoop2
}
override fun visit(refLiteral: ReferenceLiteralValue): Expression {
val litval = super.visit(refLiteral)
if(litval is ReferenceLiteralValue) {
if (litval.isArray) {
val vardecl = litval.parent as? VarDecl
if (vardecl!=null) {
return fixupArrayDatatype(litval, vardecl, program.heap)
}
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
// because constant folding can result in arrays that are now suddenly capable
// of telling the type of all their elements (for instance, when they contained -2 which
// was a prefix expression earlier), we recalculate the array's datatype.
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
if(array.type.isKnown)
return array
val arrayDt = array.guessDatatype(program)
if(arrayDt.isKnown) {
val newArray = arrayLiteral.cast(arrayDt.typeOrElse(DataType.STRUCT))
if(newArray!=null)
return newArray
}
}
return litval
return array
}
override fun visit(assignment: Assignment): Statement {
@@ -611,7 +639,10 @@ class ConstantFolding(private val program: Program) : IAstModifyingVisitor {
val lv = assignment.value as? NumericLiteralValue
if(lv!=null) {
// 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 -> {
// we can convert to UWORD: any UBYTE, BYTE/WORD that are >=0, FLOAT that's an integer 0..65535,
if(lv.type== DataType.UBYTE)
+8 -17
View File
@@ -1,34 +1,25 @@
package prog8.optimizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.parser.ParsingFailedError
import prog8.ast.base.ErrorReporter
internal fun Program.constantFold() {
val optimizer = ConstantFolding(this)
try {
optimizer.visit(this)
} catch (ax: AstException) {
optimizer.addError(ax)
}
internal fun Program.constantFold(errors: ErrorReporter) {
val optimizer = ConstantFolding(this, errors)
optimizer.visit(this)
while(optimizer.errors.isEmpty() && optimizer.optimizationsDone>0) {
while(errors.isEmpty() && optimizer.optimizationsDone>0) {
optimizer.optimizationsDone = 0
optimizer.visit(this)
}
if(optimizer.errors.isNotEmpty()) {
optimizer.errors.forEach { System.err.println(it) }
throw ParsingFailedError("There are ${optimizer.errors.size} errors.")
} else {
if(errors.isEmpty())
modules.forEach { it.linkParents(namespace) } // re-link in final configuration
}
}
internal fun Program.optimizeStatements(): Int {
val optimizer = StatementOptimizer(this)
internal fun Program.optimizeStatements(errors: ErrorReporter): Int {
val optimizer = StatementOptimizer(this, errors)
optimizer.visit(this)
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
@@ -1,21 +1,19 @@
package prog8.optimizer
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.DataType
import prog8.ast.base.IntegerDatatypes
import prog8.ast.base.NumericDatatypes
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.Assignment
import prog8.ast.statements.Statement
import kotlin.math.abs
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
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
*/
@@ -24,7 +22,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
override fun visit(assignment: Assignment): Statement {
if (assignment.aug_op != null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
return super.visit(assignment)
}
@@ -136,9 +134,14 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val leftDt = expr.left.inferType(program)
val rightDt = expr.right.inferType(program)
if (leftDt != null && rightDt != null && leftDt != rightDt) {
val leftIDt = expr.left.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)
if (leftDt != rightDt) {
// try to convert a datatype into the other (where ddd
if (adjustDatatypes(expr, leftVal, leftDt, rightVal, rightDt)) {
optimizationsDone++
@@ -226,7 +229,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.right
val y = determineY(x, leftBinExpr)
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)
}
} else {
@@ -235,7 +238,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
val x = expr.right
val y = determineY(x, leftBinExpr)
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)
}
}
@@ -338,6 +341,8 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
"-" -> return optimizeSub(expr, leftVal, rightVal)
"**" -> return optimizePower(expr, leftVal, rightVal)
"%" -> return optimizeRemainder(expr, leftVal, rightVal)
">>" -> return optimizeShiftRight(expr, rightVal)
"<<" -> return optimizeShiftLeft(expr, rightVal)
}
return expr
}
@@ -590,7 +595,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
"%" -> {
if (cv == 1.0) {
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) {
optimizationsDone++
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 {
if(leftVal==null && rightVal==null)
return expr
// cannot shuffle assiciativity with division!
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
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) {
-1.0 -> {
// '/' -> -left
@@ -629,7 +639,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
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) {
// divided by a power of two => shift right
optimizationsDone++
@@ -637,7 +647,7 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
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) {
// divided by a negative power of two => negate, then shift right
optimizationsDone++
@@ -700,16 +710,16 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
optimizationsDone++
return expr.left
}
2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 512.0, 1024.0, 2048.0, 4096.0, 8192.0, 16384.0, 32768.0, 65536.0 -> {
if(leftValue.inferType(program) in IntegerDatatypes) {
in powersOfTwo -> {
if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a power of two => shift left
optimizationsDone++
val numshifts = log2(cv).toInt()
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 -> {
if(leftValue.inferType(program) in IntegerDatatypes) {
in negativePowersOfTwo -> {
if(leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a negative power of two => negate, then shift left
optimizationsDone++
val numshifts = log2(-cv).toInt()
@@ -722,4 +732,97 @@ internal class SimplifyExpressions(private val program: Program) : IAstModifying
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
}
}
@@ -1,34 +1,41 @@
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.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
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 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 small subroutines (correctly renaming/relocating all variables in them and refs to those as well, or restrict to subs without variables?)
TODO: remove unreachable code after return and exit()
TODO: proper inlining of tiny subroutines (at first, restrict to subs without parameters and variables in them, and build it up from there: correctly renaming/relocating all variables in them and refs to those as well)
*/
internal class StatementOptimizer(private val program: Program) : IAstModifyingVisitor {
internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
private set
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
private val callgraph = CallGraph(program)
private val vardeclsToRemove = mutableListOf<VarDecl>()
override fun visit(program: Program) {
removeUnusedCode(callgraph)
super.visit(program)
for(decl in vardeclsToRemove) {
decl.definingScope().remove(decl)
}
}
private fun removeUnusedCode(callgraph: CallGraph) {
@@ -74,13 +81,13 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
optimizationsDone++
printWarning("removing empty block '${block.name}'", block.position)
errors.warn("removing empty block '${block.name}'", block.position)
return NopStatement.insteadOf(block)
}
if (block !in callgraph.usedSymbols) {
optimizationsDone++
printWarning("removing unused block '${block.name}'", block.position)
errors.warn("removing unused block '${block.name}'", block.position)
return NopStatement.insteadOf(block) // remove unused block
}
}
@@ -93,7 +100,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.containsNoCodeNorVars()) {
printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position)
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement.insteadOf(subroutine)
}
@@ -104,17 +111,13 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
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
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement.insteadOf(subroutine)
}
visitStatements(subroutine.statements)
return subroutine
}
@@ -122,7 +125,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
val forceOutput = "force_output" in decl.definingBlock().options()
if(decl !in callgraph.usedSymbols && !forceOutput) {
if(decl.type == VarDeclType.VAR)
printWarning("removing unused variable ${decl.type} '${decl.name}'", decl.position)
errors.warn("removing unused variable ${decl.type} '${decl.name}'", decl.position)
optimizationsDone++
return NopStatement.insteadOf(decl)
}
@@ -134,7 +137,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
// removes 'duplicate' assignments that assign the isSameAs target
val linesToRemove = mutableListOf<Int>()
var previousAssignmentLine: Int? = null
for (i in 0 until statements.size) {
for (i in statements.indices) {
val stmt = statements[i] as? Assignment
if (stmt != null && stmt.value is NumericLiteralValue) {
if (previousAssignmentLine == null) {
@@ -159,7 +162,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
printWarning("statement has no effect (function return value is discarded)", functionCallStatement.position)
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
optimizationsDone++
return NopStatement.insteadOf(functionCallStatement)
}
@@ -168,24 +171,34 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print") ||
functionCallStatement.target.nameInSource==listOf("c64scr", "print_p")) {
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
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) {
val heapId = stringVar.heapId(program.namespace)
val string = program.heap.get(heapId).str!!
if(string.length==1) {
val petscii = Petscii.encodePetscii(string, true)[0]
functionCallStatement.arglist.clear()
functionCallStatement.arglist.add(NumericLiteralValue.optimalInteger(petscii.toInt(), functionCallStatement.position))
val vardecl = stringVar.targetVarDecl(program.namespace)!!
val string = vardecl.value!! as StringLiteralValue
if(string.value.length==1) {
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
functionCallStatement.args.clear()
functionCallStatement.args.add(NumericLiteralValue.optimalInteger(firstCharEncoded.toInt(), functionCallStatement.position))
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
vardeclsToRemove.add(vardecl)
optimizationsDone++
return functionCallStatement
} else if(string.length==2) {
val petscii = Petscii.encodePetscii(string, true)
} else if(string.value.length==2) {
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
val scope = AnonymousScope(mutableListOf(), functionCallStatement.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),
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++
return scope
}
@@ -200,7 +213,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCallStatement(first.identifier, functionCallStatement.arglist, functionCallStatement.position)
return FunctionCallStatement(first.identifier, functionCallStatement.args, functionCallStatement.void, functionCallStatement.position)
}
if(first is ReturnFromIrq || first is Return) {
optimizationsDone++
@@ -220,7 +233,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCall(first.identifier, functionCall.arglist, functionCall.position)
return FunctionCall(first.identifier, functionCall.args, functionCall.position)
}
if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program)
@@ -252,12 +265,12 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
printWarning("condition is always true", ifStatement.position)
errors.warn("condition is always true", ifStatement.position)
optimizationsDone++
ifStatement.truepart
} else {
// always false -> keep only else-part
printWarning("condition is always false", ifStatement.position)
errors.warn("condition is always false", ifStatement.position)
optimizationsDone++
ifStatement.elsepart
}
@@ -300,20 +313,13 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
val constvalue = whileLoop.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> print a warning, and optimize into body + jump (if there are no continue and break statements)
printWarning("condition is always true", whileLoop.position)
if(hasContinueOrBreak(whileLoop.body))
return whileLoop
val label = Label("_prog8_back", whileLoop.condition.position)
whileLoop.body.statements.add(0, label)
whileLoop.body.statements.add(Jump(null,
IdentifierReference(listOf("_prog8_back"), whileLoop.condition.position),
null, whileLoop.condition.position))
// always true -> print a warning, and optimize into a forever-loop
errors.warn("condition is always true", whileLoop.condition.position)
optimizationsDone++
return whileLoop.body
ForeverLoop(whileLoop.body, whileLoop.position)
} else {
// always false -> ditch whole statement
printWarning("condition is always false", whileLoop.position)
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
optimizationsDone++
NopStatement.insteadOf(whileLoop)
}
@@ -327,7 +333,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only the statement block (if there are no continue and break statements)
printWarning("condition is always true", repeatLoop.position)
errors.warn("condition is always true", repeatLoop.untilCondition.position)
if(hasContinueOrBreak(repeatLoop.body))
repeatLoop
else {
@@ -335,17 +341,10 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
repeatLoop.body
}
} else {
// 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)
if(hasContinueOrBreak(repeatLoop.body))
return repeatLoop
val label = Label("__back", repeatLoop.untilCondition.position)
repeatLoop.body.statements.add(0, label)
repeatLoop.body.statements.add(Jump(null,
IdentifierReference(listOf("__back"), repeatLoop.untilCondition.position),
null, repeatLoop.untilCondition.position))
// always false -> print a warning, and optimize into a forever loop
errors.warn("condition is always false", repeatLoop.untilCondition.position)
optimizationsDone++
return repeatLoop.body
ForeverLoop(repeatLoop.body, repeatLoop.position)
}
}
return repeatLoop
@@ -411,7 +410,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
override fun visit(assignment: Assignment): Statement {
if(assignment.aug_op!=null)
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer")
throw AstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
if(assignment.target isSameAs assignment.value) {
if(assignment.target.isNotMemory(program.namespace)) {
@@ -419,7 +418,10 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
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
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.number?.toDouble()
@@ -504,7 +506,8 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
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--
}
optimizationsDone++
@@ -516,8 +519,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
if ((targetDt == DataType.UWORD && cv > 15.0) || (targetDt == DataType.UBYTE && cv > 7.0)) {
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment)
optimizationsDone++
@@ -526,7 +528,8 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
val scope = AnonymousScope(mutableListOf(), assignment.position)
var numshifts = cv.toInt()
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--
}
optimizationsDone++
@@ -547,6 +550,7 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
if(linesToRemove.isNotEmpty()) {
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
}
visitStatements(scope.statements)
return super.visit(scope)
}
@@ -554,11 +558,26 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
// remove duplicate labels
val stmts = label.definingScope().statements
val startIdx = stmts.indexOf(label)
if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label)
if(startIdx< stmts.lastIndex && stmts[startIdx+1] == label)
return NopStatement.insteadOf(label)
return super.visit(label)
}
private fun visitStatements(statements: MutableList<Statement>) {
// TODO remove all unreachable code statements after call to exit() or a return
// this is not yet correct because we still have nested subroutines
// val exitCallIndex = statements.indexOfFirst { it is FunctionCallStatement && it.target.nameInSource.last()=="exit" }
// if(exitCallIndex>=0) {
// while(exitCallIndex < statements.lastIndex) {
// val stmt = statements[exitCallIndex+1]
// println("after exit() removing: $stmt")
// statements.removeAt(exitCallIndex+1)
// }
// }
}
}
+105 -100
View File
@@ -4,11 +4,13 @@ import org.antlr.v4.runtime.*
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.antlr.toAst
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.base.SyntaxError
import prog8.ast.base.checkImportedValid
import prog8.ast.statements.Directive
import prog8.ast.statements.DirectiveArg
import prog8.pathFrom
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.Path
@@ -32,114 +34,117 @@ internal class CustomLexer(val modulePath: Path, input: CharStream?) : prog8Lexe
internal fun moduleName(fileName: Path) = fileName.toString().substringBeforeLast('.')
internal fun importModule(program: Program, filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString()
if(importloc.startsWith(curdir))
importloc = "." + importloc.substring(curdir.length)
println(" (from '$importloc')")
}
else
println("")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
internal class ModuleImporter(private val errors: ErrorReporter) {
val input = CharStreams.fromPath(filePath)
return importModule(program, input, filePath, false)
}
internal fun importModule(program: Program, filePath: Path): Module {
print("importing '${moduleName(filePath.fileName)}'")
if(filePath.parent!=null) {
var importloc = filePath.toString()
val curdir = Paths.get("").toAbsolutePath().toString()
if(importloc.startsWith(curdir))
importloc = "." + importloc.substring(curdir.length)
println(" (from '$importloc')")
}
else
println("")
if(!Files.isReadable(filePath))
throw ParsingFailedError("No such file: $filePath")
internal fun importLibraryModule(program: Program, name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(program, import, Paths.get(""))
}
internal fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener()
lexer.addErrorListener(lexerErrors)
val tokens = CommentHandlingTokenStream(lexer)
val parser = prog8Parser(tokens)
val parseTree = parser.module()
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
if(numberOfErrors > 0)
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
// You can do something with the parsed comments:
// tokens.commentTokens().forEach { println(it) }
// convert to Ast
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
moduleAst.program = program
moduleAst.linkParents(program.namespace)
program.modules.add(moduleAst)
// accept additional imports
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
moduleAst.statements = lines
return moduleAst
}
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8"
val locations = mutableListOf(Paths.get(source.parent.toString()))
val propPath = System.getProperty("prog8.libdir")
if(propPath!=null)
locations.add(Paths.get(propPath))
val envPath = System.getenv("PROG8_LIBDIR")
if(envPath!=null)
locations.add(Paths.get(envPath))
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
locations.forEach {
val file = Paths.get(it.toString(), fileName)
if (Files.isReadable(file)) return file
val input = CharStreams.fromPath(filePath)
return importModule(program, input, filePath, false)
}
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
}
internal fun importLibraryModule(program: Program, name: String): Module? {
val import = Directive("%import", listOf(
DirectiveArg("", name, 42, position = Position("<<<implicit-import>>>", 0, 0, 0))
), Position("<<<implicit-import>>>", 0, 0, 0))
return executeImportDirective(program, import, Paths.get(""))
}
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
private fun importModule(program: Program, stream: CharStream, modulePath: Path, isLibrary: Boolean): Module {
val moduleName = moduleName(modulePath.fileName)
val lexer = CustomLexer(modulePath, stream)
val lexerErrors = LexerErrorListener()
lexer.addErrorListener(lexerErrors)
val tokens = CommentHandlingTokenStream(lexer)
val parser = prog8Parser(tokens)
val parseTree = parser.module()
val numberOfErrors = parser.numberOfSyntaxErrors + lexerErrors.numberOfErrors
if(numberOfErrors > 0)
throw ParsingFailedError("There are $numberOfErrors errors in '$moduleName'.")
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null
// You can do something with the parsed comments:
// tokens.commentTokens().forEach { println(it) }
val resource = tryGetEmbeddedResource(moduleName+".p8")
val importedModule =
if(resource!=null) {
// load the module from the embedded resource
resource.use {
if(import.args[0].int==42)
println("importing '$moduleName' (library, auto)")
else
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
}
} else {
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
importModule(program, modulePath)
// convert to Ast
val moduleAst = parseTree.toAst(moduleName, isLibrary, modulePath)
moduleAst.program = program
moduleAst.linkParents(program.namespace)
program.modules.add(moduleAst)
// accept additional imports
val lines = moduleAst.statements.toMutableList()
lines.asSequence()
.mapIndexed { i, it -> Pair(i, it) }
.filter { (it.second as? Directive)?.directive == "%import" }
.forEach { executeImportDirective(program, it.second as Directive, modulePath) }
moduleAst.statements = lines
return moduleAst
}
private fun discoverImportedModuleFile(name: String, source: Path, position: Position?): Path {
val fileName = "$name.p8"
val locations = mutableListOf(source.parent)
val propPath = System.getProperty("prog8.libdir")
if(propPath!=null)
locations.add(pathFrom(propPath))
val envPath = System.getenv("PROG8_LIBDIR")
if(envPath!=null)
locations.add(pathFrom(envPath))
locations.add(Paths.get(Paths.get("").toAbsolutePath().toString(), "prog8lib"))
locations.forEach {
val file = pathFrom(it.toString(), fileName)
if (Files.isReadable(file)) return file
}
importedModule.checkImportedValid()
return importedModule
}
throw ParsingFailedError("$position Import: no module source file '$fileName' found (I've looked in: $locations)")
}
internal fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
private fun executeImportDirective(program: Program, import: Directive, source: Path): Module? {
if(import.directive!="%import" || import.args.size!=1 || import.args[0].name==null)
throw SyntaxError("invalid import directive", import.position)
val moduleName = import.args[0].name!!
if("$moduleName.p8" == import.position.file)
throw SyntaxError("cannot import self", import.position)
val existing = program.modules.singleOrNull { it.name == moduleName }
if(existing!=null)
return null
val resource = tryGetEmbeddedResource("$moduleName.p8")
val importedModule =
if(resource!=null) {
// load the module from the embedded resource
resource.use {
if(import.args[0].int==42)
println("importing '$moduleName' (library, auto)")
else
println("importing '$moduleName' (library)")
importModule(program, CharStreams.fromStream(it), Paths.get("@embedded@/$moduleName"), true)
}
} else {
val modulePath = discoverImportedModuleFile(moduleName, source, import.position)
importModule(program, modulePath)
}
importedModule.checkImportedValid(errors)
return importedModule
}
private fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
}
}
@@ -0,0 +1,22 @@
package prog8.server.dbus
//import org.freedesktop.dbus.interfaces.DBusInterface
//
//
//interface IrmenDbusTest: DBusInterface
//{
// fun Status(address: String): Map<Int, String>
//}
//
//
//internal class TestService: IrmenDbusTest {
// override fun Status(address: String): Map<Int, String> {
// return mapOf(
// 5 to "hello",
// 42 to address
// )
// }
//
// override fun isRemote() = true
// override fun getObjectPath() = "/razorvine/TestService"
//}
@@ -0,0 +1,17 @@
package prog8.server.dbus
//import org.freedesktop.dbus.connections.impl.DBusConnection
//
//
//fun main() {
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
// println(it.names.toList())
// println(it.uniqueName)
// println(it.address)
// println(it.machineId)
// val obj = it.getRemoteObject("local.net.razorvine.dbus.test", "/razorvine/TestService", IrmenDbusTest::class.java)
// println(obj.Status("irmen"))
// }
//}
//
@@ -0,0 +1,18 @@
package prog8.server.dbus
//import org.freedesktop.dbus.connections.impl.DBusConnection
//
//
//fun main() {
// DBusConnection.getConnection(DBusConnection.DBusBusType.SESSION).use {
// it.requestBusName("local.net.razorvine.dbus.test")
// println(it.names.toList())
// println(it.uniqueName)
// println(it.address)
// println(it.machineId)
// val service = TestService()
// it.exportObject(service.objectPath, service)
//
// Thread.sleep(100000)
// }
//}
+292 -263
View File
@@ -1,10 +1,12 @@
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.ReferenceLiteralValue
import prog8.compiler.HeapValues
import prog8.compiler.target.c64.Petscii
import prog8.ast.expressions.StringLiteralValue
import prog8.vm.astvm.VmExecutionException
import java.util.*
import kotlin.math.abs
import kotlin.math.pow
@@ -15,8 +17,14 @@ import kotlin.math.pow
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form)
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
*/
open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=null,
val array: Array<Number>?=null, val heapId: Int?=null) {
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 wordval: Int?
@@ -24,121 +32,78 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
val asBoolean: Boolean
companion object {
fun fromLv(literalValue: NumericLiteralValue): RuntimeValue {
return RuntimeValue(literalValue.type, num = literalValue.number)
fun fromLv(literalValue: NumericLiteralValue): RuntimeValueNumeric {
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 {
when(type) {
when (type) {
DataType.UBYTE -> {
val inum = num!!.toInt()
if(inum !in 0 .. 255)
throw IllegalArgumentException("invalid value for ubyte: $inum")
val inum = num.toInt()
require(inum in 0..255) { "invalid value for ubyte: $inum" }
byteval = inum.toShort()
wordval = null
floatval = null
asBoolean = byteval != 0.toShort()
}
DataType.BYTE -> {
val inum = num!!.toInt()
if(inum !in -128 .. 127)
throw IllegalArgumentException("invalid value for byte: $inum")
val inum = num.toInt()
require(inum in -128..127) { "invalid value for byte: $inum" }
byteval = inum.toShort()
wordval = null
floatval = null
asBoolean = byteval != 0.toShort()
}
DataType.UWORD -> {
val inum = num!!.toInt()
if(inum !in 0 .. 65535)
throw IllegalArgumentException("invalid value for uword: $inum")
val inum = num.toInt()
require(inum in 0..65535) { "invalid value for uword: $inum" }
wordval = inum
byteval = null
floatval = null
asBoolean = wordval != 0
}
DataType.WORD -> {
val inum = num!!.toInt()
if(inum !in -32768 .. 32767)
throw IllegalArgumentException("invalid value for word: $inum")
val inum = num.toInt()
require(inum in -32768..32767) { "invalid value for word: $inum" }
wordval = inum
byteval = null
floatval = null
asBoolean = wordval != 0
}
DataType.FLOAT -> {
floatval = num!!.toDouble()
floatval = num.toDouble()
byteval = null
wordval = null
asBoolean = floatval != 0.0
}
else -> {
byteval = null
wordval = null
floatval = null
asBoolean = true
}
else -> throw VmExecutionException("not a numeric value")
}
}
override fun toString(): String {
return when(type) {
return when (type) {
DataType.UBYTE -> "ub:%02x".format(byteval)
DataType.BYTE -> {
if(byteval!!<0)
if (byteval!! < 0)
"b:-%02x".format(abs(byteval.toInt()))
else
"b:%02x".format(byteval)
}
DataType.UWORD -> "uw:%04x".format(wordval)
DataType.WORD -> {
if(wordval!!<0)
if (wordval!! < 0)
"w:-%04x".format(abs(wordval))
else
"w:%04x".format(wordval)
}
DataType.FLOAT -> "f:$floatval"
else -> "heap:$heapId"
else -> "???"
}
}
fun numericValue(): Number {
return when(type) {
override fun numericValue(): Number {
return when (type) {
in ByteDatatypes -> byteval!!
in WordDatatypes -> wordval!!
DataType.FLOAT -> floatval!!
@@ -146,8 +111,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
}
}
fun integerValue(): Int {
return when(type) {
override fun integerValue(): Int {
return when (type) {
in ByteDatatypes -> byteval!!.toInt()
in WordDatatypes -> wordval!!
DataType.FLOAT -> throw ArithmeticException("float to integer loss of precision")
@@ -158,75 +123,69 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
override fun hashCode(): Int = Objects.hash(byteval, wordval, floatval, type)
override fun equals(other: Any?): Boolean {
if(other==null || other !is RuntimeValue)
if (other == null || other !is RuntimeValueNumeric)
return false
if(type==other.type)
return if (type in IterableDatatypes) heapId==other.heapId else compareTo(other)==0
return compareTo(other)==0 // note: datatype doesn't matter
return compareTo(other) == 0 // note: datatype doesn't matter
}
operator fun compareTo(other: RuntimeValue): Int {
return if (type in NumericDatatypes && other.type in NumericDatatypes)
numericValue().toDouble().compareTo(other.numericValue().toDouble())
else throw ArithmeticException("comparison can only be done between two numeric values")
}
operator fun compareTo(other: RuntimeValueNumeric): Int = numericValue().toDouble().compareTo(other.numericValue().toDouble())
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): RuntimeValue {
if(leftDt!=rightDt)
private fun arithResult(leftDt: DataType, result: Number, rightDt: DataType, op: String): RuntimeValueNumeric {
if (leftDt != rightDt)
throw ArithmeticException("left and right datatypes are not the same")
if(result.toDouble() < 0 ) {
return when(leftDt) {
if (result.toDouble() < 0) {
return when (leftDt) {
DataType.UBYTE, DataType.UWORD -> {
// storing a negative number in an unsigned one is done by storing the 2's complement instead
val number = abs(result.toDouble().toInt())
if(leftDt== DataType.UBYTE)
RuntimeValue(DataType.UBYTE, (number xor 255) + 1)
if (leftDt == DataType.UBYTE)
RuntimeValueNumeric(DataType.UBYTE, (number xor 255) + 1)
else
RuntimeValue(DataType.UWORD, (number xor 65535) + 1)
RuntimeValueNumeric(DataType.UWORD, (number xor 65535) + 1)
}
DataType.BYTE -> {
val v=result.toInt() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
val v = result.toInt() and 255
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
RuntimeValueNumeric(DataType.BYTE, v - 256)
}
DataType.WORD -> {
val v=result.toInt() and 65535
if(v<32768)
RuntimeValue(DataType.WORD, v)
val v = result.toInt() and 65535
if (v < 32768)
RuntimeValueNumeric(DataType.WORD, v)
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")
}
}
return when(leftDt) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result.toInt() and 255)
return when (leftDt) {
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result.toInt() and 255)
DataType.BYTE -> {
val v = result.toInt() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
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 -> {
val v = result.toInt() and 65535
if(v<32768)
RuntimeValue(DataType.WORD, v)
if (v < 32768)
RuntimeValueNumeric(DataType.WORD, v)
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")
}
}
fun add(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
fun add(other: RuntimeValueNumeric): RuntimeValueNumeric {
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
@@ -234,8 +193,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
return arithResult(type, result, other.type, "add")
}
fun sub(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
fun sub(other: RuntimeValueNumeric): RuntimeValueNumeric {
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
@@ -243,8 +202,8 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
return arithResult(type, result, other.type, "sub")
}
fun mul(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
fun mul(other: RuntimeValueNumeric): RuntimeValueNumeric {
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
@@ -252,317 +211,318 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
return arithResult(type, result, other.type, "mul")
}
fun div(other: RuntimeValue): RuntimeValue {
if(other.type == DataType.FLOAT && (type!= DataType.FLOAT))
fun div(other: RuntimeValueNumeric): RuntimeValueNumeric {
if (other.type == DataType.FLOAT && (type != DataType.FLOAT))
throw ArithmeticException("floating point loss of precision on type $type")
val v1 = numericValue()
val v2 = other.numericValue()
if(v2.toDouble()==0.0) {
if (v2.toDouble() == 0.0) {
when (type) {
DataType.UBYTE -> return RuntimeValue(DataType.UBYTE, 255)
DataType.BYTE -> return RuntimeValue(DataType.BYTE, 127)
DataType.UWORD -> return RuntimeValue(DataType.UWORD, 65535)
DataType.WORD -> return RuntimeValue(DataType.WORD, 32767)
else -> {}
DataType.UBYTE -> return RuntimeValueNumeric(DataType.UBYTE, 255)
DataType.BYTE -> return RuntimeValueNumeric(DataType.BYTE, 127)
DataType.UWORD -> return RuntimeValueNumeric(DataType.UWORD, 65535)
DataType.WORD -> return RuntimeValueNumeric(DataType.WORD, 32767)
else -> {
}
}
}
val result = v1.toDouble() / v2.toDouble()
// NOTE: integer division returns integer result!
return when(type) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, result)
DataType.BYTE -> RuntimeValue(DataType.BYTE, result)
DataType.UWORD -> RuntimeValue(DataType.UWORD, result)
DataType.WORD -> RuntimeValue(DataType.WORD, result)
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, result)
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result)
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, result)
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, result)
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, result)
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
else -> throw ArithmeticException("div on non-numeric type")
}
}
fun remainder(other: RuntimeValue): RuntimeValue {
fun remainder(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble() % v2.toDouble()
return arithResult(type, result, other.type, "remainder")
}
fun pow(other: RuntimeValue): RuntimeValue {
fun pow(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = numericValue()
val v2 = other.numericValue()
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()
return when (type) {
DataType.UBYTE -> RuntimeValue(type, (v shl 1) and 255)
DataType.UWORD -> RuntimeValue(type, (v shl 1) and 65535)
DataType.UBYTE -> RuntimeValueNumeric(type, (v shl 1) and 255)
DataType.UWORD -> RuntimeValueNumeric(type, (v shl 1) and 65535)
DataType.BYTE -> {
val value = v shl 1
if(value<128)
RuntimeValue(type, value)
if (value < 128)
RuntimeValueNumeric(type, value)
else
RuntimeValue(type, value-256)
RuntimeValueNumeric(type, value - 256)
}
DataType.WORD -> {
val value = v shl 1
if(value<32768)
RuntimeValue(type, value)
if (value < 32768)
RuntimeValueNumeric(type, value)
else
RuntimeValue(type, value-65536)
RuntimeValueNumeric(type, value - 65536)
}
else -> throw ArithmeticException("invalid type for shl: $type")
}
}
fun shr(): RuntimeValue {
fun shr(): RuntimeValueNumeric {
val v = integerValue()
return when(type){
DataType.UBYTE -> RuntimeValue(type, v ushr 1)
DataType.BYTE -> RuntimeValue(type, v shr 1)
DataType.UWORD -> RuntimeValue(type, v ushr 1)
DataType.WORD -> RuntimeValue(type, v shr 1)
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(type, v ushr 1)
DataType.BYTE -> RuntimeValueNumeric(type, v shr 1)
DataType.UWORD -> RuntimeValueNumeric(type, v ushr 1)
DataType.WORD -> RuntimeValueNumeric(type, v shr 1)
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))
return when(type) {
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if(carry) 1 else 0)
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry)
val newval = (v and 0x7f shl 1) or (if (carry) 1 else 0)
Pair(RuntimeValueNumeric(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val newCarry = (v and 0x8000) != 0
val newval = (v and 0x7fff shl 1) or (if(carry) 1 else 0)
Pair(RuntimeValue(DataType.UWORD, newval), newCarry)
val newval = (v and 0x7fff shl 1) or (if (carry) 1 else 0)
Pair(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
}
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)
return when(type) {
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x80 else 0)
Pair(RuntimeValue(DataType.UBYTE, newval), newCarry)
val newval = (v ushr 1) or (if (carry) 0x80 else 0)
Pair(RuntimeValueNumeric(DataType.UBYTE, newval), newCarry)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if(carry) 0x8000 else 0)
Pair(RuntimeValue(DataType.UWORD, newval), newCarry)
val newval = (v ushr 1) or (if (carry) 0x8000 else 0)
Pair(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun rol2(): RuntimeValue {
fun rol2(): RuntimeValueNumeric {
// 8 or 16 bit rotate left
return when(type) {
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry
RuntimeValue(DataType.UBYTE, newval)
RuntimeValueNumeric(DataType.UBYTE, newval)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val carry = (v and 0x8000) ushr 15
val newval = (v and 0x7fff shl 1) or carry
RuntimeValue(DataType.UWORD, newval)
RuntimeValueNumeric(DataType.UWORD, newval)
}
else -> throw ArithmeticException("rol2 can only work on byte/word")
}
}
fun ror2(): RuntimeValue {
fun ror2(): RuntimeValueNumeric {
// 8 or 16 bit rotate right
return when(type) {
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = v and 1 shl 7
val newval = (v ushr 1) or carry
RuntimeValue(DataType.UBYTE, newval)
RuntimeValueNumeric(DataType.UBYTE, newval)
}
DataType.UWORD, DataType.WORD -> {
val v = wordval!!
val carry = v and 1 shl 15
val newval = (v ushr 1) or carry
RuntimeValue(DataType.UWORD, newval)
RuntimeValueNumeric(DataType.UWORD, newval)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun neg(): RuntimeValue {
return when(type) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, -(byteval!!))
DataType.WORD -> RuntimeValue(DataType.WORD, -(wordval!!))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, -(floatval)!!)
fun neg(): RuntimeValueNumeric {
return when (type) {
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, -(byteval!!))
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, -(wordval!!))
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, -(floatval)!!)
else -> throw ArithmeticException("neg can only work on byte/word/float")
}
}
fun abs(): RuntimeValue {
return when(type) {
DataType.BYTE -> RuntimeValue(DataType.BYTE, abs(byteval!!.toInt()))
DataType.WORD -> RuntimeValue(DataType.WORD, abs(wordval!!))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(floatval!!))
fun abs(): RuntimeValueNumeric {
return when (type) {
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, abs(byteval!!.toInt()))
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, abs(wordval!!))
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, abs(floatval!!))
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 v2 = other.integerValue()
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 v2 = other.integerValue()
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 v2 = other.integerValue()
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 or(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean || other.asBoolean) 1 else 0)
fun xor(other: RuntimeValue) = RuntimeValue(DataType.UBYTE, if (this.asBoolean xor other.asBoolean) 1 else 0)
fun not() = RuntimeValue(DataType.UBYTE, if (this.asBoolean) 0 else 1)
fun and(other: RuntimeValueNumeric) = RuntimeValueNumeric(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: RuntimeValueNumeric) = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean xor other.asBoolean) 1 else 0)
fun not() = RuntimeValueNumeric(DataType.UBYTE, if (this.asBoolean) 0 else 1)
fun inv(): RuntimeValue {
return when(type) {
DataType.UBYTE -> RuntimeValue(type, byteval!!.toInt().inv() and 255)
DataType.UWORD -> RuntimeValue(type, wordval!!.inv() and 65535)
DataType.BYTE -> RuntimeValue(type, byteval!!.toInt().inv())
DataType.WORD -> RuntimeValue(type, wordval!!.inv())
fun inv(): RuntimeValueNumeric {
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(type, byteval!!.toInt().inv() and 255)
DataType.UWORD -> RuntimeValueNumeric(type, wordval!!.inv() and 65535)
DataType.BYTE -> RuntimeValueNumeric(type, byteval!!.toInt().inv())
DataType.WORD -> RuntimeValueNumeric(type, wordval!!.inv())
else -> throw ArithmeticException("inv can only work on byte/word")
}
}
fun inc(): RuntimeValue {
return when(type) {
DataType.UBYTE -> RuntimeValue(type, (byteval!! + 1) and 255)
DataType.UWORD -> RuntimeValue(type, (wordval!! + 1) and 65535)
fun inc(): RuntimeValueNumeric {
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(type, (byteval!! + 1) and 255)
DataType.UWORD -> RuntimeValueNumeric(type, (wordval!! + 1) and 65535)
DataType.BYTE -> {
val newval = byteval!! + 1
if(newval == 128)
RuntimeValue(type, -128)
if (newval == 128)
RuntimeValueNumeric(type, -128)
else
RuntimeValue(type, newval)
RuntimeValueNumeric(type, newval)
}
DataType.WORD -> {
val newval = wordval!! + 1
if(newval == 32768)
RuntimeValue(type, -32768)
if (newval == 32768)
RuntimeValueNumeric(type, -32768)
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")
}
}
fun dec(): RuntimeValue {
return when(type) {
DataType.UBYTE -> RuntimeValue(type, (byteval!! - 1) and 255)
DataType.UWORD -> RuntimeValue(type, (wordval!! - 1) and 65535)
fun dec(): RuntimeValueNumeric {
return when (type) {
DataType.UBYTE -> RuntimeValueNumeric(type, (byteval!! - 1) and 255)
DataType.UWORD -> RuntimeValueNumeric(type, (wordval!! - 1) and 65535)
DataType.BYTE -> {
val newval = byteval!! - 1
if(newval == -129)
RuntimeValue(type, 127)
if (newval == -129)
RuntimeValueNumeric(type, 127)
else
RuntimeValue(type, newval)
RuntimeValueNumeric(type, newval)
}
DataType.WORD -> {
val newval = wordval!! - 1
if(newval == -32769)
RuntimeValue(type, 32767)
if (newval == -32769)
RuntimeValueNumeric(type, 32767)
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")
}
}
fun msb(): RuntimeValue {
return when(type) {
in ByteDatatypes -> RuntimeValue(DataType.UBYTE, 0)
in WordDatatypes -> RuntimeValue(DataType.UBYTE, wordval!! ushr 8 and 255)
fun msb(): RuntimeValueNumeric {
return when (type) {
in ByteDatatypes -> RuntimeValueNumeric(DataType.UBYTE, 0)
in WordDatatypes -> RuntimeValueNumeric(DataType.UBYTE, wordval!! ushr 8 and 255)
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) {
DataType.UBYTE -> {
when (targetType) {
DataType.UBYTE -> this
DataType.BYTE -> {
val nval=byteval!!.toInt()
if(nval<128)
RuntimeValue(DataType.BYTE, nval)
val nval = byteval!!.toInt()
if (nval < 128)
RuntimeValueNumeric(DataType.BYTE, nval)
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 -> {
val nval = numericValue().toInt()
if(nval<32768)
RuntimeValue(DataType.WORD, nval)
if (nval < 32768)
RuntimeValueNumeric(DataType.WORD, nval)
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")
}
}
DataType.BYTE -> {
when (targetType) {
DataType.BYTE -> this
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> RuntimeValue(DataType.WORD, integerValue())
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, numericValue())
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue() and 65535)
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, integerValue())
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.UWORD -> {
when (targetType) {
DataType.BYTE -> {
val v=integerValue()
if(v<128)
RuntimeValue(DataType.BYTE, v)
val v = integerValue()
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
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.WORD -> {
val v=integerValue()
if(v<32768)
RuntimeValue(DataType.WORD, v)
val v = integerValue()
if (v < 32768)
RuntimeValueNumeric(DataType.WORD, v)
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")
}
}
@@ -570,33 +530,33 @@ open class RuntimeValue(val type: DataType, num: Number?=null, val str: String?=
when (targetType) {
DataType.BYTE -> {
val v = integerValue() and 255
if(v<128)
RuntimeValue(DataType.BYTE, v)
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
else
RuntimeValue(DataType.BYTE, v-256)
RuntimeValueNumeric(DataType.BYTE, v - 256)
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, integerValue() and 65535)
DataType.UWORD -> RuntimeValue(DataType.UWORD, integerValue())
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 65535)
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue())
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")
}
}
DataType.FLOAT -> {
when (targetType) {
DataType.BYTE -> {
val integer=numericValue().toInt()
if(integer in -128..127)
RuntimeValue(DataType.BYTE, integer)
val integer = numericValue().toInt()
if (integer in -128..127)
RuntimeValueNumeric(DataType.BYTE, integer)
else
throw ArithmeticException("overflow when casting float to byte: $this")
}
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, numericValue().toInt())
DataType.UWORD -> RuntimeValue(DataType.UWORD, numericValue().toInt())
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, numericValue().toInt())
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, numericValue().toInt())
DataType.WORD -> {
val integer=numericValue().toInt()
if(integer in -32768..32767)
RuntimeValue(DataType.WORD, integer)
val integer = numericValue().toInt()
if (integer in -32768..32767)
RuntimeValueNumeric(DataType.WORD, integer)
else
throw ArithmeticException("overflow when casting float to word: $this")
}
@@ -607,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")
}
}
}
open fun iterator(): Iterator<Number> {
return when (type) {
in StringDatatypes -> {
Petscii.encodePetscii(str!!, true).iterator()
}
in ArrayDatatypes -> {
array!!.iterator()
}
else -> throw IllegalArgumentException("cannot iterate over $this")
class RuntimeValueString(val str: String, val altEncoding: Boolean, val heapId: Int?): RuntimeValueBase(DataType.STR) {
companion object {
fun fromLv(string: StringLiteralValue): RuntimeValueString {
return RuntimeValueString(string.value, string.altEncoding, string.heapId)
}
}
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.istype(DataType.ARRAY_F)) {
val doubleArray = array.value.map { (it as NumericLiteralValue).number }.toTypedArray()
RuntimeValueArray(DataType.ARRAY_F, 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.typeOrElse(DataType.STRUCT), 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> {
return range.iterator()
}
+251 -201
View File
@@ -7,13 +7,9 @@ import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.statements.*
import prog8.compiler.IntegerOrAddressOf
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.vm.*
import java.awt.EventQueue
import java.io.CharConversionException
import java.util.*
import kotlin.NoSuchElementException
import kotlin.concurrent.fixedRateTimer
@@ -72,10 +68,14 @@ class StatusFlags {
class RuntimeVariables {
fun define(scope: INameScope, name: String, initialValue: RuntimeValue) {
fun define(scope: INameScope, name: String, initialValue: RuntimeValueBase) {
val where = vars.getValue(scope)
where[name] = initialValue
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) {
@@ -84,7 +84,7 @@ class RuntimeVariables {
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 existing = where[name]
if(existing==null) {
@@ -96,9 +96,15 @@ class RuntimeVariables {
throw VmExecutionException("new value is of different datatype ${value.type} expected ${existing.type} for $name")
where[name] = value
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)
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")
}
fun swap(a1: VarDecl, a2: VarDecl) = swap(a1.definingScope(), a1.name, a2.definingScope(), a2.name)
fun swap(scope1: INameScope, name1: String, scope2: INameScope, name2: String) {
val v1 = get(scope1, name1)
val v2 = get(scope2, name2)
@@ -117,12 +121,13 @@ class RuntimeVariables {
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 byHeapId = mutableMapOf<Int, RuntimeValueBase>()
}
class AstVm(val program: Program) {
class AstVm(val program: Program, compilationTarget: String) {
val mem = Memory(::memread, ::memwrite)
val statusflags = StatusFlags()
@@ -133,13 +138,15 @@ class AstVm(val program: Program) {
var rtcOffset = bootTime
private val rnd = Random(0)
private val statusFlagsSave = Stack<StatusFlags>()
private val registerXsave = Stack<RuntimeValue>()
private val registerYsave = Stack<RuntimeValue>()
private val registerAsave = Stack<RuntimeValue>()
private val statusFlagsSave = ArrayDeque<StatusFlags>()
private val registerXsave = ArrayDeque<RuntimeValueNumeric>()
private val registerYsave = ArrayDeque<RuntimeValueNumeric>()
private val registerAsave = ArrayDeque<RuntimeValueNumeric>()
init {
require(compilationTarget == "c64") {"using the AstVm only works for the C64 compiler target"}
// observe the jiffyclock and screen matrix
mem.observe(0xa0, 0xa1, 0xa2)
for(i in 1024..2023)
@@ -166,23 +173,23 @@ class AstVm(val program: Program) {
fun memwrite(address: Int, value: Short): Short {
if(address==0xa0 || address==0xa1 || address==0xa2) {
// 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 time_mid = if(address==0xa1) value else mem.getUByte_DMA(0xa1)
val time_lo = if(address==0xa2) value else mem.getUByte_DMA(0xa2)
val jiffies = (time_hi.toInt() shl 16) + (time_mid.toInt() shl 8) + time_lo
val timeHi = if(address==0xa0) value else mem.getUByteDirectly(0xa0)
val timeMid = if(address==0xa1) value else mem.getUByteDirectly(0xa1)
val timeLo = if(address==0xa2) value else mem.getUByteDirectly(0xa2)
val jiffies = (timeHi.toInt() shl 16) + (timeMid.toInt() shl 8) + timeLo
rtcOffset = bootTime - (jiffies*1000/60)
}
if(address in 1024..2023) {
// write to the screen matrix
val scraddr = address-1024
dialog.canvas.setChar(scraddr % 40, scraddr / 40, value, 1)
dialog.canvas.setScreenChar(scraddr % 40, scraddr / 40, value, 1)
}
return value
}
fun run() {
try {
val init = VariablesCreator(runtimeVariables, program.heap)
val init = VariablesCreator(runtimeVariables)
init.visit(program)
// initialize all global variables
@@ -215,7 +222,7 @@ class AstVm(val program: Program) {
when {
jx.generatedLabel != null -> {
val label = entrypoint.getLabelOrVariable(jx.generatedLabel) as Label
TODO("generatedlabel $label")
TODO("astvm handle generatedlabel $label")
}
jx.identifier != null -> {
when (val jumptarget = entrypoint.lookup(jx.identifier.nameInSource, jx.identifier.parent)) {
@@ -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!")
dialog.title = "PROGRAM EXITED"
} catch (tx: VmTerminationException) {
@@ -253,9 +260,9 @@ class AstVm(val program: Program) {
rtcOffset = timeStamp
}
// update the C-64 60hz jiffy clock in the ZP addresses:
mem.setUByte_DMA(0x00a0, (jiffies ushr 16).toShort())
mem.setUByte_DMA(0x00a1, (jiffies ushr 8 and 255).toShort())
mem.setUByte_DMA(0x00a2, (jiffies and 255).toShort())
mem.setUByteDirectly(0x00a0, (jiffies ushr 16).toShort())
mem.setUByteDirectly(0x00a1, (jiffies ushr 8 and 255).toShort())
mem.setUByteDirectly(0x00a2, (jiffies and 255).toShort())
}
private val runtimeVariables = RuntimeVariables()
@@ -263,11 +270,11 @@ class AstVm(val program: Program) {
class LoopControlBreak : 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()
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) {
return performSyscall(sub, arguments)
}
@@ -332,10 +339,9 @@ class AstVm(val program: Program) {
// should have been defined already when the program started
}
is FunctionCallStatement -> {
val target = stmt.target.targetStatement(program.namespace)
when (target) {
when (val target = stmt.target.targetStatement(program.namespace)) {
is Subroutine -> {
val args = evaluate(stmt.arglist)
val args = evaluate(stmt.args).map { it as RuntimeValueNumeric }
if (target.isAsmSubroutine) {
performSyscall(target, args)
} else {
@@ -348,12 +354,12 @@ class AstVm(val program: Program) {
// swap cannot be implemented as a function, so inline it here
executeSwap(stmt)
} else {
val args = evaluate(stmt.arglist)
val args = evaluate(stmt.args)
performBuiltinFunction(target.name, args, statusflags)
}
}
else -> {
TODO("weird call $target")
throw VmExecutionException("weird call target $target")
}
}
}
@@ -362,7 +368,7 @@ class AstVm(val program: Program) {
if(stmt.value==null)
null
else
evaluate(stmt.value!!, evalCtx)
evaluate(stmt.value!!, evalCtx) as RuntimeValueNumeric
throw LoopControlReturn(value)
}
is Continue -> throw LoopControlContinue()
@@ -380,19 +386,19 @@ class AstVm(val program: Program) {
val identScope = ident.definingScope()
when(ident.type){
VarDeclType.VAR -> {
var value = runtimeVariables.get(identScope, ident.name)
value = when {
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1))
stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1))
var value = runtimeVariables.get(identScope, ident.name) as RuntimeValueNumeric
value = when (stmt.operator) {
"++" -> value.add(RuntimeValueNumeric(value.type, 1))
"--" -> value.sub(RuntimeValueNumeric(value.type, 1))
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
runtimeVariables.set(identScope, ident.name, value)
}
VarDeclType.MEMORY -> {
val addr=ident.value!!.constValue(program)!!.number.toInt()
val newval = when {
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
val newval = when (stmt.operator) {
"++" -> mem.getUByte(addr)+1 and 255
"--" -> mem.getUByte(addr)-1 and 255
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
mem.setUByte(addr,newval.toShort())
@@ -401,32 +407,34 @@ class AstVm(val program: Program) {
}
}
stmt.target.memoryAddress != null -> {
val addr = evaluate(stmt.target.memoryAddress!!.addressExpression, evalCtx).integerValue()
val newval = when {
stmt.operator == "++" -> mem.getUByte(addr)+1 and 255
stmt.operator == "--" -> mem.getUByte(addr)-1 and 255
val addr = (evaluate(stmt.target.memoryAddress!!.addressExpression, evalCtx) as RuntimeValueNumeric).integerValue()
val newval = when (stmt.operator) {
"++" -> mem.getUByte(addr)+1 and 255
"--" -> mem.getUByte(addr)-1 and 255
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
mem.setUByte(addr,newval.toShort())
}
stmt.target.arrayindexed != null -> {
val arrayvar = stmt.target.arrayindexed!!.identifier.targetVarDecl(program.namespace)!!
val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name)
val elementType = stmt.target.arrayindexed!!.inferType(program)!!
val index = evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx).integerValue()
var value = RuntimeValue(elementType, arrayvalue.array!![index].toInt())
value = when {
stmt.operator == "++" -> value.inc()
stmt.operator == "--" -> value.dec()
val arrayvalue = runtimeVariables.get(arrayvar.definingScope(), arrayvar.name) as RuntimeValueArray
val index = (evaluate(stmt.target.arrayindexed!!.arrayspec.index, evalCtx) as RuntimeValueNumeric).integerValue()
val elementType = stmt.target.arrayindexed!!.inferType(program)
if(!elementType.isKnown)
throw VmExecutionException("unknown/void elt type")
var value = RuntimeValueNumeric(elementType.typeOrElse(DataType.BYTE), arrayvalue.array[index].toInt())
value = when (stmt.operator) {
"++" -> value.inc()
"--" -> value.dec()
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
arrayvalue.array[index] = value.numericValue()
}
stmt.target.register != null -> {
var value = runtimeVariables.get(program.namespace, stmt.target.register!!.name)
value = when {
stmt.operator == "++" -> value.add(RuntimeValue(value.type, 1))
stmt.operator == "--" -> value.sub(RuntimeValue(value.type, 1))
var value = runtimeVariables.get(program.namespace, stmt.target.register!!.name) as RuntimeValueNumeric
value = when (stmt.operator) {
"++" -> value.add(RuntimeValueNumeric(value.type, 1))
"--" -> value.sub(RuntimeValueNumeric(value.type, 1))
else -> throw VmExecutionException("strange postincdec operator $stmt")
}
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 InlineAssembly -> {
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)
throw LoopControlReturn(null)
}
@@ -445,7 +453,7 @@ class AstVm(val program: Program) {
}
is AnonymousScope -> executeAnonymousScope(stmt)
is IfStatement -> {
val condition = evaluate(stmt.condition, evalCtx)
val condition = evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
if (condition.asBoolean)
executeAnonymousScope(stmt.truepart)
else
@@ -459,7 +467,7 @@ class AstVm(val program: Program) {
BranchCondition.NE, BranchCondition.NZ -> if(statusflags.zero) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.MI, BranchCondition.NEG -> if(statusflags.negative) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.PL, BranchCondition.POS -> if(statusflags.negative) executeAnonymousScope(stmt.truepart) else executeAnonymousScope(stmt.elsepart)
BranchCondition.VS, BranchCondition.VC -> TODO("overflow status")
BranchCondition.VS, BranchCondition.VC -> TODO("astvm branch on overflow status")
}
}
is ForLoop -> {
@@ -472,10 +480,17 @@ class AstVm(val program: Program) {
loopvarDt = DataType.UBYTE
loopvar = IdentifierReference(listOf(stmt.loopRegister.name), stmt.position)
} else {
loopvarDt = stmt.loopVar!!.inferType(program)!!
val dt = stmt.loopVar!!.inferType(program)
loopvarDt = dt.typeOrElse(DataType.UBYTE)
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) {
try {
oneForCycle(stmt, loopvarDt, loopvalue, loopvar)
@@ -487,11 +502,11 @@ class AstVm(val program: Program) {
}
}
is WhileLoop -> {
var condition = evaluate(stmt.condition, evalCtx)
var condition = evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
while (condition.asBoolean) {
try {
executeAnonymousScope(stmt.body)
condition = evaluate(stmt.condition, evalCtx)
condition = evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
} catch (b: LoopControlBreak) {
break
} catch (c: LoopControlContinue) {
@@ -501,7 +516,7 @@ class AstVm(val program: Program) {
}
is RepeatLoop -> {
do {
val condition = evaluate(stmt.untilCondition, evalCtx)
val condition = evaluate(stmt.untilCondition, evalCtx) as RuntimeValueNumeric
try {
executeAnonymousScope(stmt.body)
} catch (b: LoopControlBreak) {
@@ -512,7 +527,7 @@ class AstVm(val program: Program) {
} while (!condition.asBoolean)
}
is WhenStatement -> {
val condition=evaluate(stmt.condition, evalCtx)
val condition=evaluate(stmt.condition, evalCtx) as RuntimeValueNumeric
for(choice in stmt.choices) {
if(choice.values==null) {
// the 'else' choice
@@ -520,7 +535,7 @@ class AstVm(val program: Program) {
break
} else {
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) {
executeAnonymousScope(choice.statements)
break
@@ -529,14 +544,14 @@ class AstVm(val program: Program) {
}
}
else -> {
TODO("implement $stmt")
TODO("astvm implement statement $stmt")
}
}
}
private fun executeSwap(swap: FunctionCallStatement) {
val v1 = swap.arglist[0]
val v2 = swap.arglist[1]
val v1 = swap.args[0]
val v2 = swap.args[1]
val value1 = evaluate(v1, evalCtx)
val value2 = evaluate(v2, evalCtx)
val target1 = AssignTarget.fromExpr(v1)
@@ -545,7 +560,7 @@ class AstVm(val program: Program) {
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 targetArrayIndexed = target.arrayindexed
when {
@@ -555,27 +570,26 @@ class AstVm(val program: Program) {
if (decl.type == VarDeclType.MEMORY) {
val address = runtimeVariables.getMemoryAddress(decl.definingScope(), decl.name)
when (decl.datatype) {
DataType.UBYTE -> mem.setUByte(address, value.byteval!!)
DataType.BYTE -> mem.setSByte(address, value.byteval!!)
DataType.UWORD -> mem.setUWord(address, value.wordval!!)
DataType.WORD -> mem.setSWord(address, value.wordval!!)
DataType.FLOAT -> mem.setFloat(address, value.floatval!!)
DataType.STR -> mem.setString(address, value.str!!)
DataType.STR_S -> mem.setScreencodeString(address, value.str!!)
DataType.UBYTE -> mem.setUByte(address, (value as RuntimeValueNumeric).byteval!!)
DataType.BYTE -> mem.setSByte(address, (value as RuntimeValueNumeric).byteval!!)
DataType.UWORD -> mem.setUWord(address, (value as RuntimeValueNumeric).wordval!!)
DataType.WORD -> mem.setSWord(address, (value as RuntimeValueNumeric).wordval!!)
DataType.FLOAT -> mem.setFloat(address, (value as RuntimeValueNumeric).floatval!!)
DataType.STR -> mem.setString(address, (value as RuntimeValueString).str, value.altEncoding)
else -> throw VmExecutionException("weird memaddress type $decl")
}
} else
runtimeVariables.set(decl.definingScope(), decl.name, value)
}
target.memoryAddress != null -> {
val address = evaluate(target.memoryAddress.addressExpression, evalCtx).wordval!!
evalCtx.mem.setUByte(address, value.byteval!!)
val address = (evaluate(target.memoryAddress.addressExpression, evalCtx) as RuntimeValueNumeric).wordval!!
evalCtx.mem.setUByte(address, (value as RuntimeValueNumeric).byteval!!)
}
targetArrayIndexed != null -> {
val vardecl = targetArrayIndexed.identifier.targetVarDecl(program.namespace)!!
if(vardecl.type==VarDeclType.VAR) {
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) {
DataType.ARRAY_UB -> {
if (value.type != DataType.UBYTE)
@@ -597,35 +611,37 @@ class AstVm(val program: Program) {
if (value.type != DataType.FLOAT)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
DataType.STR, DataType.STR_S -> {
DataType.STR -> {
if (value.type !in ByteDatatypes)
throw VmExecutionException("new value is of different datatype ${value.type} for $array")
}
else -> throw VmExecutionException("strange array type ${array.type}")
}
if (array.type in ArrayDatatypes)
array.array!![index.integerValue()] = value.numericValue()
else if (array.type in StringDatatypes) {
(array as RuntimeValueArray).array[index.integerValue()] = value.numericValue()
else if (array.type == DataType.STR) {
val indexInt = index.integerValue()
val newchr = Petscii.decodePetscii(listOf(value.numericValue().toShort()), true)
val newstr = array.str!!.replaceRange(indexInt, indexInt + 1, newchr)
val newchr = value.numericValue().toChar().toString()
val newstr = (array as RuntimeValueString).str.replaceRange(indexInt, indexInt + 1, newchr)
val ident = contextStmt.definingScope().lookup(targetArrayIndexed.identifier.nameInSource, contextStmt) as? VarDecl
?: throw VmExecutionException("can't find assignment target ${target.identifier}")
val identScope = ident.definingScope()
program.heap.update(array.heapId!!, newstr)
runtimeVariables.set(identScope, ident.name, RuntimeValue(array.type, str = newstr, heapId = array.heapId))
runtimeVariables.set(identScope, ident.name, RuntimeValueString(newstr, false, array.heapId))
}
}
else {
value as RuntimeValueNumeric
val address = (vardecl.value as NumericLiteralValue).number.toInt()
val index = evaluate(targetArrayIndexed.arrayspec.index, evalCtx).integerValue()
val elementType = targetArrayIndexed.inferType(program)!!
when(elementType) {
val index = (evaluate(targetArrayIndexed.arrayspec.index, evalCtx) as RuntimeValueNumeric).integerValue()
val elementType = targetArrayIndexed.inferType(program)
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.BYTE -> mem.setSByte(address+index, value.byteval!!)
DataType.UWORD -> mem.setUWord(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")
}
}
@@ -633,67 +649,67 @@ class AstVm(val program: Program) {
target.register != null -> {
runtimeVariables.set(program.namespace, target.register.name, value)
}
else -> TODO("assign $target")
else -> TODO("assign weird target $target")
}
}
private fun oneForCycle(stmt: ForLoop, loopvarDt: DataType, loopValue: Number, loopVar: IdentifierReference) {
// assign the new loop value to the loopvar, and run the code
performAssignment(AssignTarget(null, loopVar, null, null, loopVar.position),
RuntimeValue(loopvarDt, loopValue), stmt.body.statements.first(), evalCtx)
RuntimeValueNumeric(loopvarDt, loopValue), stmt.body.statements.first(), evalCtx)
executeAnonymousScope(stmt.body)
}
private fun evaluate(args: List<Expression>) = args.map { evaluate(it, evalCtx) }
private fun performSyscall(sub: Subroutine, args: List<RuntimeValue>): RuntimeValue? {
var result: RuntimeValue? = null
private fun performSyscall(sub: Subroutine, args: List<RuntimeValueNumeric>): RuntimeValueNumeric? {
var result: RuntimeValueNumeric? = null
when (sub.scopedname) {
"c64scr.print" -> {
// if the argument is an UWORD, consider it to be the "address" of the string (=heapId)
if (args[0].wordval != null) {
val str = program.heap.get(args[0].wordval!!).str!!
dialog.canvas.printText(str, true)
val string = getAsciiStringFromRuntimeVars(args[0].wordval!!)
dialog.canvas.printAsciiText(string)
} else
dialog.canvas.printText(args[0].str!!, true)
throw VmExecutionException("print non-heap string")
}
"c64scr.print_ub" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), true)
dialog.canvas.printAsciiText(args[0].byteval!!.toString())
}
"c64scr.print_ub0" -> {
dialog.canvas.printText("%03d".format(args[0].byteval!!), true)
dialog.canvas.printAsciiText("%03d".format(args[0].byteval!!))
}
"c64scr.print_b" -> {
dialog.canvas.printText(args[0].byteval!!.toString(), true)
dialog.canvas.printAsciiText(args[0].byteval!!.toString())
}
"c64scr.print_uw" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), true)
dialog.canvas.printAsciiText(args[0].wordval!!.toString())
}
"c64scr.print_uw0" -> {
dialog.canvas.printText("%05d".format(args[0].wordval!!), true)
dialog.canvas.printAsciiText("%05d".format(args[0].wordval!!))
}
"c64scr.print_w" -> {
dialog.canvas.printText(args[0].wordval!!.toString(), true)
dialog.canvas.printAsciiText(args[0].wordval!!.toString())
}
"c64scr.print_ubhex" -> {
val number = args[0].byteval!!
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" -> {
val number = args[0].wordval!!
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" -> {
val number = args[0].wordval!!
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" -> {
val number = args[0].byteval!!
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" -> {
dialog.canvas.clearScreen(6)
@@ -702,7 +718,7 @@ class AstVm(val program: Program) {
dialog.canvas.clearScreen(args[0].integerValue().toShort())
}
"c64scr.setcc" -> {
dialog.canvas.setChar(args[0].integerValue(), args[1].integerValue(), args[2].integerValue().toShort(), args[3].integerValue().toShort())
dialog.canvas.setScreenChar(args[0].integerValue(), args[1].integerValue(), args[2].integerValue().toShort(), args[3].integerValue().toShort())
}
"c64scr.plot" -> {
dialog.canvas.setCursorPos(args[0].integerValue(), args[1].integerValue())
@@ -718,26 +734,23 @@ class AstVm(val program: Program) {
break
else {
input.add(char)
val printChar = try {
Petscii.encodePetscii("" + char, true).first()
} catch (cv: CharConversionException) {
0x3f.toShort()
}
dialog.canvas.printPetscii(printChar)
dialog.canvas.printAsciiText(char.toString())
}
}
val inputStr = input.joinToString("")
var inputStr = input.joinToString("")
val inputLength = inputStr.length
val heapId = args[0].wordval!!
val origStr = program.heap.get(heapId).str!!
val paddedStr=inputStr.padEnd(origStr.length+1, '\u0000').substring(0, origStr.length)
program.heap.update(heapId, paddedStr)
result = RuntimeValue(DataType.UBYTE, paddedStr.indexOf('\u0000'))
val origStrLength = getAsciiStringFromRuntimeVars(heapId).length
while(inputStr.length < origStrLength) {
inputStr += '\u0000'
}
result = RuntimeValueNumeric(DataType.UBYTE, inputLength)
}
"c64flt.print_f" -> {
dialog.canvas.printText(args[0].floatval.toString(), false)
dialog.canvas.printAsciiText(args[0].floatval.toString())
}
"c64.CHROUT" -> {
dialog.canvas.printPetscii(args[0].byteval!!)
dialog.canvas.printPetsciiChar(args[0].byteval!!)
}
"c64.CLEARSCR" -> {
dialog.canvas.clearScreen(6)
@@ -747,158 +760,173 @@ class AstVm(val program: Program) {
Thread.sleep(10)
}
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" -> {
val heapId = args[0].wordval!!
val argString = program.heap.get(heapId).str!!
val argString = getAsciiStringFromRuntimeVars(heapId)
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("astvm implement syscall ${sub.scopedname} $sub")
}
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) {
"rnd" -> RuntimeValue(DataType.UBYTE, rnd.nextInt() and 255)
"rndw" -> RuntimeValue(DataType.UWORD, rnd.nextInt() and 65535)
"rndf" -> RuntimeValue(DataType.FLOAT, rnd.nextDouble())
"lsb" -> RuntimeValue(DataType.UBYTE, args[0].integerValue() and 255)
"msb" -> RuntimeValue(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
"sin" -> RuntimeValue(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
"rnd" -> RuntimeValueNumeric(DataType.UBYTE, rnd.nextInt() and 255)
"rndw" -> RuntimeValueNumeric(DataType.UWORD, rnd.nextInt() and 65535)
"rndf" -> RuntimeValueNumeric(DataType.FLOAT, rnd.nextDouble())
"lsb" -> RuntimeValueNumeric(DataType.UBYTE, args[0].integerValue() and 255)
"msb" -> RuntimeValueNumeric(DataType.UBYTE, (args[0].integerValue() ushr 8) and 255)
"sin" -> RuntimeValueNumeric(DataType.FLOAT, sin(args[0].numericValue().toDouble()))
"sin8" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (127.0 * sin(rad)).toShort())
RuntimeValueNumeric(DataType.BYTE, (127.0 * sin(rad)).toShort())
}
"sin8u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
RuntimeValueNumeric(DataType.UBYTE, (128.0 + 127.5 * sin(rad)).toShort())
}
"sin16" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.BYTE, (32767.0 * sin(rad)).toShort())
RuntimeValueNumeric(DataType.BYTE, (32767.0 * sin(rad)).toShort())
}
"sin16u" -> {
val rad = args[0].numericValue().toDouble() / 256.0 * 2.0 * PI
RuntimeValue(DataType.UBYTE, (32768.0 + 32767.5 * sin(rad)).toShort())
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" -> {
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" -> {
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" -> {
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" -> {
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()))
"atan" -> RuntimeValue(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
"ln" -> RuntimeValue(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
"log2" -> RuntimeValue(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
"sqrt" -> RuntimeValue(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
"sqrt16" -> RuntimeValue(DataType.UBYTE, sqrt(args[0].wordval!!.toDouble()).toInt())
"rad" -> RuntimeValue(DataType.FLOAT, Math.toRadians(args[0].numericValue().toDouble()))
"deg" -> RuntimeValue(DataType.FLOAT, Math.toDegrees(args[0].numericValue().toDouble()))
"round" -> RuntimeValue(DataType.FLOAT, round(args[0].numericValue().toDouble()))
"floor" -> RuntimeValue(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
"ceil" -> RuntimeValue(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
"tan" -> RuntimeValueNumeric(DataType.FLOAT, tan(args[0].numericValue().toDouble()))
"atan" -> RuntimeValueNumeric(DataType.FLOAT, atan(args[0].numericValue().toDouble()))
"ln" -> RuntimeValueNumeric(DataType.FLOAT, ln(args[0].numericValue().toDouble()))
"log2" -> RuntimeValueNumeric(DataType.FLOAT, log2(args[0].numericValue().toDouble()))
"sqrt" -> RuntimeValueNumeric(DataType.FLOAT, sqrt(args[0].numericValue().toDouble()))
"sqrt16" -> RuntimeValueNumeric(DataType.UBYTE, sqrt((args[0] as RuntimeValueNumeric).wordval!!.toDouble()).toInt())
"rad" -> RuntimeValueNumeric(DataType.FLOAT, Math.toRadians(args[0].numericValue().toDouble()))
"deg" -> RuntimeValueNumeric(DataType.FLOAT, Math.toDegrees(args[0].numericValue().toDouble()))
"round" -> RuntimeValueNumeric(DataType.FLOAT, round(args[0].numericValue().toDouble()))
"floor" -> RuntimeValueNumeric(DataType.FLOAT, floor(args[0].numericValue().toDouble()))
"ceil" -> RuntimeValueNumeric(DataType.FLOAT, ceil(args[0].numericValue().toDouble()))
"rol" -> {
val (result, newCarry) = args[0].rol(statusflags.carry)
val (result, newCarry) = (args[0] as RuntimeValueNumeric).rol(statusflags.carry)
statusflags.carry = newCarry
return result
}
"rol2" -> args[0].rol2()
"rol2" -> (args[0] as RuntimeValueNumeric).rol2()
"ror" -> {
val (result, newCarry) = args[0].ror(statusflags.carry)
val (result, newCarry) = (args[0] as RuntimeValueNumeric).ror(statusflags.carry)
statusflags.carry = newCarry
return result
}
"ror2" -> args[0].ror2()
"lsl" -> args[0].shl()
"lsr" -> args[0].shr()
"ror2" -> (args[0] as RuntimeValueNumeric).ror2()
"lsl" -> (args[0] as RuntimeValueNumeric).shl()
"lsr" -> (args[0] as RuntimeValueNumeric).shr()
"abs" -> {
when (args[0].type) {
DataType.UBYTE -> args[0]
DataType.BYTE -> RuntimeValue(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
DataType.UWORD -> args[0]
DataType.WORD -> RuntimeValue(DataType.UWORD, abs(args[0].numericValue().toDouble()))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
DataType.UBYTE -> (args[0] as RuntimeValueNumeric)
DataType.BYTE -> RuntimeValueNumeric(DataType.UBYTE, abs(args[0].numericValue().toDouble()))
DataType.UWORD -> (args[0] as RuntimeValueNumeric)
DataType.WORD -> RuntimeValueNumeric(DataType.UWORD, abs(args[0].numericValue().toDouble()))
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, abs(args[0].numericValue().toDouble()))
else -> throw VmExecutionException("strange abs type ${args[0]}")
}
}
"max" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.max())
val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
RuntimeValueNumeric(ArrayElementTypes.getValue(args[0].type), numbers.max()!!)
}
"min" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(ArrayElementTypes.getValue(args[0].type), numbers.min())
val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
RuntimeValueNumeric(ArrayElementTypes.getValue(args[0].type), numbers.min()!!)
}
"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) {
DataType.ARRAY_UB -> RuntimeValue(DataType.UWORD, sum)
DataType.ARRAY_B -> RuntimeValue(DataType.WORD, sum)
DataType.ARRAY_UW -> RuntimeValue(DataType.UWORD, sum)
DataType.ARRAY_W -> RuntimeValue(DataType.WORD, sum)
DataType.ARRAY_F -> RuntimeValue(DataType.FLOAT, sum)
DataType.ARRAY_UB -> RuntimeValueNumeric(DataType.UWORD, sum)
DataType.ARRAY_B -> RuntimeValueNumeric(DataType.WORD, sum)
DataType.ARRAY_UW -> RuntimeValueNumeric(DataType.UWORD, sum)
DataType.ARRAY_W -> RuntimeValueNumeric(DataType.WORD, sum)
DataType.ARRAY_F -> RuntimeValueNumeric(DataType.FLOAT, sum)
else -> throw VmExecutionException("weird sum type ${args[0]}")
}
}
"any" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
RuntimeValueNumeric(DataType.UBYTE, if (numbers.any { it != 0.0 }) 1 else 0)
}
"all" -> {
val numbers = args.single().array!!.map { it.toDouble() }
RuntimeValue(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
val numbers = (args.single() as RuntimeValueArray).array.map { it.toDouble() }
RuntimeValueNumeric(DataType.UBYTE, if (numbers.all { it != 0.0 }) 1 else 0)
}
"swap" ->
throw VmExecutionException("swap() cannot be implemented as a function")
"strlen" -> {
val zeroIndex = args[0].str!!.indexOf(0.toChar())
val zeroIndex = (args[0] as RuntimeValueString).str.indexOf(0.toChar())
if (zeroIndex >= 0)
RuntimeValue(DataType.UBYTE, zeroIndex)
RuntimeValueNumeric(DataType.UBYTE, zeroIndex)
else
RuntimeValue(DataType.UBYTE, args[0].str!!.length)
RuntimeValueNumeric(DataType.UBYTE, (args[0] as RuntimeValueString).str.length)
}
"memset" -> {
val heapId = args[0].wordval!!
val target = program.heap.get(heapId).array ?: throw VmExecutionException("memset target is not an array")
val heapId = (args[0] as RuntimeValueNumeric).wordval!!
val target = getArrayFromRuntimeVars(heapId)
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount) {
target[i] = IntegerOrAddressOf(value, null)
target[i] = value
}
null
}
"memsetw" -> {
val heapId = args[0].wordval!!
val target = program.heap.get(heapId).array ?: throw VmExecutionException("memset target is not an array")
val heapId = (args[0] as RuntimeValueNumeric).wordval!!
val target = getArrayFromRuntimeVars(heapId)
val amount = args[1].integerValue()
val value = args[2].integerValue()
for (i in 0 until amount step 2) {
target[i * 2] = IntegerOrAddressOf(value and 255, null)
target[i * 2 + 1] = IntegerOrAddressOf(value ushr 8, null)
target[i * 2] = value and 255
target[i * 2 + 1] = value ushr 8
}
null
}
"memcopy" -> {
val sourceHeapId = args[0].wordval!!
val destHeapId = args[1].wordval!!
val source = program.heap.get(sourceHeapId).array!!
val dest = program.heap.get(destHeapId).array!!
val sourceHeapId = (args[0] as RuntimeValueNumeric).wordval!!
val destHeapId = (args[1] as RuntimeValueNumeric).wordval!!
val source = getArrayFromRuntimeVars(sourceHeapId)
val dest = getArrayFromRuntimeVars(destHeapId)
val amount = args[2].integerValue()
for(i in 0 until amount) {
dest[i] = source[i]
@@ -907,7 +935,7 @@ class AstVm(val program: Program) {
}
"mkword" -> {
val result = (args[1].integerValue() shl 8) or args[0].integerValue()
RuntimeValue(DataType.UWORD, result)
RuntimeValueNumeric(DataType.UWORD, result)
}
"set_carry" -> {
statusflags.carry=true
@@ -930,13 +958,13 @@ class AstVm(val program: Program) {
val zero = if(statusflags.zero) 2 else 0
val irqd = if(statusflags.irqd) 4 else 0
val negative = if(statusflags.negative) 128 else 0
RuntimeValue(DataType.UBYTE, carry or zero or irqd or negative)
RuntimeValueNumeric(DataType.UBYTE, carry or zero or irqd or negative)
}
"rsave" -> {
statusFlagsSave.push(statusflags)
registerAsave.push(runtimeVariables.get(program.namespace, Register.A.name))
registerXsave.push(runtimeVariables.get(program.namespace, Register.X.name))
registerYsave.push(runtimeVariables.get(program.namespace, Register.Y.name))
registerAsave.push(runtimeVariables.get(program.namespace, Register.A.name) as RuntimeValueNumeric)
registerXsave.push(runtimeVariables.get(program.namespace, Register.X.name) as RuntimeValueNumeric)
registerYsave.push(runtimeVariables.get(program.namespace, Register.Y.name) as RuntimeValueNumeric)
null
}
"rrestore" -> {
@@ -950,8 +978,30 @@ class AstVm(val program: Program) {
runtimeVariables.set(program.namespace, Register.Y.name, registerYsave.pop())
null
}
else -> TODO("builtin function $name")
"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)
}
}
"exit" -> {
val rv = args.single().numericValue()
dialog.canvas.printAsciiText("\n<program ended with exit($rv)>")
throw VmTerminationException("program ended with exit($rv)")
}
else -> TODO("astvm implement builtin function $name")
}
}
}
+58 -57
View File
@@ -10,13 +10,11 @@ import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueRange
import kotlin.math.abs
import prog8.vm.*
typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValue>, flags: StatusFlags) -> RuntimeValue?
typealias SubroutineCaller = (sub: Subroutine, args: List<RuntimeValue>, startAtLabel: Label?) -> RuntimeValue?
typealias BuiltinfunctionCaller = (name: String, args: List<RuntimeValueNumeric>, flags: StatusFlags) -> RuntimeValueNumeric?
typealias SubroutineCaller = (sub: Subroutine, args: List<RuntimeValueNumeric>, startAtLabel: Label?) -> RuntimeValueNumeric?
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 executeSubroutine: SubroutineCaller)
fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValueBase {
val constval = expr.constValue(ctx.program)
if(constval!=null)
return RuntimeValue.fromLv(constval)
return RuntimeValueNumeric.fromLv(constval)
when(expr) {
is NumericLiteralValue -> {
return RuntimeValue.fromLv(expr)
}
is ReferenceLiteralValue -> {
return RuntimeValue.fromLv(expr, ctx.program.heap)
}
is NumericLiteralValue -> return RuntimeValueNumeric.fromLv(expr)
is StringLiteralValue -> return RuntimeValueString.fromLv(expr)
is ArrayLiteralValue -> return RuntimeValueArray.fromLv(expr)
is PrefixExpression -> {
return when(expr.operator) {
"-" -> evaluate(expr.expression, ctx).neg()
"~" -> evaluate(expr.expression, ctx).inv()
"not" -> evaluate(expr.expression, ctx).not()
"-" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).neg()
"~" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).inv()
"not" -> (evaluate(expr.expression, ctx) as RuntimeValueNumeric).not()
// unary '+' should have been optimized away
else -> throw VmExecutionException("unsupported prefix operator "+expr.operator)
}
}
is BinaryExpression -> {
val left = evaluate(expr.left, ctx)
val right = evaluate(expr.right, ctx)
val left = evaluate(expr.left, ctx) as RuntimeValueNumeric
val right = evaluate(expr.right, ctx) as RuntimeValueNumeric
return when(expr.operator) {
"<" -> RuntimeValue(DataType.UBYTE, if (left < right) 1 else 0)
"<=" -> RuntimeValue(DataType.UBYTE, if (left <= right) 1 else 0)
">" -> RuntimeValue(DataType.UBYTE, if (left > right) 1 else 0)
">=" -> RuntimeValue(DataType.UBYTE, if (left >= right) 1 else 0)
"==" -> RuntimeValue(DataType.UBYTE, if (left == right) 1 else 0)
"!=" -> RuntimeValue(DataType.UBYTE, if (left != right) 1 else 0)
"<" -> RuntimeValueNumeric(DataType.UBYTE, if (left < right) 1 else 0)
"<=" -> RuntimeValueNumeric(DataType.UBYTE, if (left <= right) 1 else 0)
">" -> RuntimeValueNumeric(DataType.UBYTE, if (left > right) 1 else 0)
">=" -> RuntimeValueNumeric(DataType.UBYTE, if (left >= right) 1 else 0)
"==" -> RuntimeValueNumeric(DataType.UBYTE, if (left == right) 1 else 0)
"!=" -> RuntimeValueNumeric(DataType.UBYTE, if (left != right) 1 else 0)
"+" -> left.add(right)
"-" -> left.sub(right)
"*" -> left.mul(right)
@@ -82,27 +77,36 @@ fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
}
is ArrayIndexedExpression -> {
val array = evaluate(expr.identifier, ctx)
val index = evaluate(expr.arrayspec.index, ctx)
val value = array.array!![index.integerValue()]
return RuntimeValue(ArrayElementTypes.getValue(array.type), value)
val index = evaluate(expr.arrayspec.index, ctx) as RuntimeValueNumeric
return when (array) {
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 -> {
return evaluate(expr.expression, ctx).cast(expr.type)
return (evaluate(expr.expression, ctx) as RuntimeValueNumeric).cast(expr.type)
}
is AddressOf -> {
// we support: address of heap var -> the heap id
return try {
val heapId = expr.identifier.heapId(ctx.program.namespace)
RuntimeValue(DataType.UWORD, heapId)
RuntimeValueNumeric(DataType.UWORD, heapId)
} catch( f: FatalAstException) {
// fallback: use the hash of the name, so we have at least *a* value...
val address = expr.identifier.hashCode() and 65535
RuntimeValue(DataType.UWORD, address)
RuntimeValueNumeric(DataType.UWORD, address)
}
}
is DirectMemoryRead -> {
val address = evaluate(expr.addressExpression, ctx).wordval!!
return RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
val address = (evaluate(expr.addressExpression, ctx) as RuntimeValueNumeric).wordval!!
return RuntimeValueNumeric(DataType.UBYTE, ctx.mem.getUByte(address))
}
is RegisterExpr -> return ctx.runtimeVars.get(ctx.program.namespace, expr.register.name)
is IdentifierReference -> {
@@ -115,13 +119,12 @@ fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
else -> {
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
return when(variable.datatype) {
DataType.UBYTE -> RuntimeValue(DataType.UBYTE, ctx.mem.getUByte(address))
DataType.BYTE -> RuntimeValue(DataType.BYTE, ctx.mem.getSByte(address))
DataType.UWORD -> RuntimeValue(DataType.UWORD, ctx.mem.getUWord(address))
DataType.WORD -> RuntimeValue(DataType.WORD, ctx.mem.getSWord(address))
DataType.FLOAT -> RuntimeValue(DataType.FLOAT, ctx.mem.getFloat(address))
DataType.STR -> RuntimeValue(DataType.STR, str = ctx.mem.getString(address))
DataType.STR_S -> RuntimeValue(DataType.STR_S, str = ctx.mem.getScreencodeString(address))
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, ctx.mem.getUByte(address))
DataType.BYTE -> RuntimeValueNumeric(DataType.BYTE, ctx.mem.getSByte(address))
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, ctx.mem.getUWord(address))
DataType.WORD -> RuntimeValueNumeric(DataType.WORD, ctx.mem.getSWord(address))
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, ctx.mem.getFloat(address))
DataType.STR -> RuntimeValueString(ctx.mem.getString(address, false), false, null)
else -> throw VmExecutionException("unexpected datatype $variable")
}
}
@@ -131,7 +134,7 @@ fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
}
is FunctionCall -> {
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) {
is Subroutine -> {
val result = ctx.executeSubroutine(sub, args, null)
@@ -150,24 +153,22 @@ fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValue {
}
is RangeExpr -> {
val cRange = expr.toConstantIntegerRange()
if(cRange!=null)
return RuntimeValueRange(expr.inferType(ctx.program)!!, cRange)
val fromVal = evaluate(expr.from, ctx).integerValue()
val toVal = evaluate(expr.to, ctx).integerValue()
val stepVal = evaluate(expr.step, ctx).integerValue()
val range = when {
fromVal <= toVal -> when {
stepVal <= 0 -> IntRange.EMPTY
stepVal == 1 -> fromVal..toVal
else -> fromVal..toVal step stepVal
}
else -> when {
stepVal >= 0 -> IntRange.EMPTY
stepVal == -1 -> fromVal downTo toVal
else -> fromVal downTo toVal step abs(stepVal)
}
if(cRange!=null) {
val dt = expr.inferType(ctx.program)
if(dt.isKnown)
return RuntimeValueRange(dt.typeOrElse(DataType.UBYTE), cRange)
else
throw VmExecutionException("couldn't determine datatype")
}
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 -> {
throw VmExecutionException("unimplemented expression node $expr")
+13 -35
View File
@@ -1,7 +1,7 @@
package prog8.vm.astvm
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.CompilationTarget
import prog8.compiler.target.c64.C64MachineDefinition
import kotlin.math.abs
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]
}
fun getUByte_DMA(address: Int): Short {
fun getUByteDirectly(address: Int): Short {
return mem[address]
}
@@ -39,7 +39,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
else value
}
fun setUByte_DMA(address: Int, value: Short) {
fun setUByteDirectly(address: Int, value: Short) {
if(value !in 0..255)
throw VmExecutionException("ubyte value out of range $value")
mem[address] = value
@@ -80,7 +80,7 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
}
fun setFloat(address: Int, value: Double) {
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(value)
val mflpt5 = C64MachineDefinition.Mflpt5.fromNumber(value)
setUByte(address, mflpt5.b0)
setUByte(address+1, mflpt5.b1)
setUByte(address+2, mflpt5.b2)
@@ -89,28 +89,26 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
}
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()
}
fun setString(address: Int, str: String) {
// lowercase PETSCII
val petscii = Petscii.encodePetscii(str, true)
fun setString(address: Int, str: String, altEncoding: Boolean) {
val encoded = CompilationTarget.encodeString(str, altEncoding)
var addr = address
for (c in petscii) setUByte(addr++, c)
for (c in encoded) setUByte(addr++, c)
setUByte(addr, 0)
}
fun getString(strAddress: Int): String {
// lowercase PETSCII
val petscii = mutableListOf<Short>()
fun getString(strAddress: Int, altEncoding: Boolean): String {
val encoded = mutableListOf<Short>()
var addr = strAddress
while(true) {
val byte = getUByte(addr++)
if(byte==0.toShort()) break
petscii.add(byte)
encoded.add(byte)
}
return Petscii.decodePetscii(petscii, true)
return CompilationTarget.decodeString(encoded, altEncoding)
}
fun clear() {
@@ -121,24 +119,4 @@ class Memory(private val readObserver: (address: Int, value: Short) -> Short,
for(i in 0 until numbytes)
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)
}
}
+21 -46
View File
@@ -1,6 +1,6 @@
package prog8.vm.astvm
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.C64MachineDefinition
import prog8.compiler.target.c64.Petscii
import java.awt.*
import java.awt.event.KeyEvent
@@ -18,7 +18,7 @@ class BitmapScreenPanel : KeyListener, JPanel() {
private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0
private var cursorY: Int=0
val keyboardBuffer: Deque<Char> = LinkedList()
val keyboardBuffer = ArrayDeque<Char>()
init {
val size = Dimension(image.width * SCALING, image.height * SCALING)
@@ -50,37 +50,33 @@ class BitmapScreenPanel : KeyListener, JPanel() {
}
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)
cursorX = 0
cursorY = 0
}
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) {
g2d.color = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size]
g2d.color = C64MachineDefinition.colorPalette[color % C64MachineDefinition.colorPalette.size]
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 lines = t2.split('\n')
for(line in lines.withIndex()) {
val petscii = Petscii.encodePetscii(line.value, lowercase)
petscii.forEach { printPetscii(it, inverseVideo) }
if(line.index<lines.size-1) {
printPetscii(13) // newline
}
}
val petscii = Petscii.encodePetscii(t2, true)
petscii.forEach { printPetsciiChar(it) }
}
fun printPetscii(char: Short, inverseVideo: Boolean=false) {
if(char==13.toShort() || char==141.toShort()) {
fun printPetsciiChar(petscii: Short) {
if(petscii in listOf(0x0d.toShort(), 0x8d.toShort())) {
// Return and shift-Return
cursorX=0
cursorY++
} else {
setPetscii(cursorX, cursorY, char, 1, inverseVideo)
val scr = Petscii.petscii2scr(petscii, false)
setScreenChar(cursorX, cursorY, scr, 1)
cursorX++
if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++
@@ -94,38 +90,17 @@ class BitmapScreenPanel : KeyListener, JPanel() {
val graphics = image.graphics as Graphics2D
graphics.drawImage(screen, 0, -8, null)
val color = graphics.color
graphics.color = MachineDefinition.colorPalette[6]
graphics.color = C64MachineDefinition.colorPalette[6]
graphics.fillRect(0, 24*8, SCREENWIDTH, 25*8)
graphics.color=color
cursorY--
}
}
fun writeTextAt(x: Int, y: Int, text: String, color: Short, lowercase: Boolean, inverseVideo: Boolean=false) {
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) {
fun setScreenChar(x: Int, y: Int, screencode: Short, color: Short) {
g2d.clearRect(8*x, 8*y, 8, 8)
val colorIdx = (color % MachineDefinition.colorPalette.size).toShort()
val screencode = Petscii.petscii2scr(petscii, inverseVideo)
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)
val colorIdx = (color % C64MachineDefinition.colorPalette.size).toShort()
val coloredImage = C64MachineDefinition.Charset.getColoredChar(screencode, colorIdx)
g2d.drawImage(coloredImage, 8*x, 8*y , null)
}
@@ -159,19 +134,19 @@ class ScreenDialog(title: String) : JFrame(title) {
// the borders (top, left, right, bottom)
val borderTop = JPanel().apply {
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = MachineDefinition.colorPalette[14]
background = C64MachineDefinition.colorPalette[14]
}
val borderBottom = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = MachineDefinition.colorPalette[14]
background = C64MachineDefinition.colorPalette[14]
}
val borderLeft = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = MachineDefinition.colorPalette[14]
background = C64MachineDefinition.colorPalette[14]
}
val borderRight = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = MachineDefinition.colorPalette[14]
background = C64MachineDefinition.colorPalette[14]
}
var c = GridBagConstraints()
c.gridx=0; c.gridy=1; c.gridwidth=3
+21 -18
View File
@@ -5,23 +5,25 @@ import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.base.Register
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.statements.Statement
import prog8.ast.statements.StructDecl
import prog8.ast.statements.VarDecl
import prog8.ast.statements.ZeropageWish
import prog8.compiler.HeapValues
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueArray
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) {
// define the three registers as global variables
runtimeVariables.define(program.namespace, Register.A.name, RuntimeValue(DataType.UBYTE, 0))
runtimeVariables.define(program.namespace, Register.X.name, RuntimeValue(DataType.UBYTE, 255))
runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValue(DataType.UBYTE, 0))
runtimeVariables.define(program.namespace, Register.A.name, RuntimeValueNumeric(DataType.UBYTE, 0))
runtimeVariables.define(program.namespace, Register.X.name, RuntimeValueNumeric(DataType.UBYTE, 255))
runtimeVariables.define(program.namespace, Register.Y.name, RuntimeValueNumeric(DataType.UBYTE, 0))
val globalpos = Position("<<global>>", 0, 0, 0)
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) {
val numericLv = decl.value as? NumericLiteralValue
val value = if(numericLv!=null) {
RuntimeValue.fromLv(numericLv)
RuntimeValueNumeric.fromLv(numericLv)
} else {
val referenceLv = decl.value as ReferenceLiteralValue
RuntimeValue.fromLv(referenceLv, heap)
val strLv = decl.value as? StringLiteralValue
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)
}
@@ -66,12 +77,4 @@ class VariablesCreator(private val runtimeVariables: RuntimeVariables, private v
}
return super.visit(decl)
}
// override fun accept(assignment: Assignment): Statement {
// if(assignment is VariableInitializationAssignment) {
// println("INIT VAR $assignment")
// }
// return super.accept(assignment)
// }
}
+11 -10
View File
@@ -4,8 +4,10 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.base.Position
import prog8.ast.expressions.ArrayLiteralValue
import prog8.ast.expressions.InferredTypes
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.ast.expressions.StringLiteralValue
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertNotEquals
@@ -16,10 +18,6 @@ private fun sameValueAndType(lv1: NumericLiteralValue, lv2: NumericLiteralValue)
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)
class TestParserNumericLiteralValue {
@@ -86,8 +84,11 @@ class TestParserNumericLiteralValue {
@Test
fun testEqualsRef() {
assertTrue(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos)))
assertFalse(sameValueAndType(ReferenceLiteralValue(DataType.STR, str = "hello", position = dummyPos), ReferenceLiteralValue(DataType.STR, str = "bye", position = dummyPos)))
assertEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("hello", false, dummyPos))
assertNotEquals(StringLiteralValue("hello", false, dummyPos), StringLiteralValue("bye", false, dummyPos))
assertEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", true, dummyPos))
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("bye", true, dummyPos))
assertNotEquals(StringLiteralValue("hello", true, dummyPos), StringLiteralValue("hello", false, dummyPos))
val lvOne = NumericLiteralValue(DataType.UBYTE, 1, dummyPos)
val lvTwo = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
@@ -96,9 +97,9 @@ class TestParserNumericLiteralValue {
val lvTwoR = NumericLiteralValue(DataType.UBYTE, 2, dummyPos)
val lvThreeR = NumericLiteralValue(DataType.UBYTE, 3, dummyPos)
val lvFour= NumericLiteralValue(DataType.UBYTE, 4, dummyPos)
val lv1 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOne, lvTwo, lvThree), position = dummyPos)
val lv2 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOneR, lvTwoR, lvThreeR), position = dummyPos)
val lv3 = ReferenceLiteralValue(DataType.ARRAY_UB, array = arrayOf(lvOneR, lvTwoR, lvFour), position = dummyPos)
val lv1 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOne, lvTwo, lvThree), dummyPos)
val lv2 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvThreeR), dummyPos)
val lv3 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_UB), arrayOf(lvOneR, lvTwoR, lvFour), dummyPos)
assertEquals(lv1, lv2)
assertNotEquals(lv1, lv3)
}
+220 -237
View File
@@ -3,66 +3,66 @@ package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueNumeric
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
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestRuntimeValue {
class TestRuntimeValueNumeric {
@Test
fun testValueRanges() {
assertEquals(0, RuntimeValue(DataType.UBYTE, 0).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 255).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UBYTE, -1)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UBYTE, 256)}
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).integerValue())
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 255).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UBYTE, -1)}
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UBYTE, 256)}
assertEquals(0, RuntimeValue(DataType.BYTE, 0).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -128).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, 127).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.BYTE, -129)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.BYTE, 128)}
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 0).integerValue())
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -128).integerValue())
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 127).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.BYTE, -129)}
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.BYTE, 128)}
assertEquals(0, RuntimeValue(DataType.UWORD, 0).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 65535).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UWORD, -1)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.UWORD, 65536)}
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).integerValue())
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 65535).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UWORD, -1)}
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.UWORD, 65536)}
assertEquals(0, RuntimeValue(DataType.WORD, 0).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32768).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, 32767).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.WORD, -32769)}
assertFailsWith<IllegalArgumentException> { RuntimeValue(DataType.WORD, 32768)}
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 0).integerValue())
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32768).integerValue())
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32767).integerValue())
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.WORD, -32769)}
assertFailsWith<IllegalArgumentException> { RuntimeValueNumeric(DataType.WORD, 32768)}
}
@Test
fun testTruthiness()
{
assertFalse(RuntimeValue(DataType.BYTE, 0).asBoolean)
assertFalse(RuntimeValue(DataType.UBYTE, 0).asBoolean)
assertFalse(RuntimeValue(DataType.WORD, 0).asBoolean)
assertFalse(RuntimeValue(DataType.UWORD, 0).asBoolean)
assertFalse(RuntimeValue(DataType.FLOAT, 0.0).asBoolean)
assertFalse(RuntimeValueNumeric(DataType.BYTE, 0).asBoolean)
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 0).asBoolean)
assertFalse(RuntimeValueNumeric(DataType.WORD, 0).asBoolean)
assertFalse(RuntimeValueNumeric(DataType.UWORD, 0).asBoolean)
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 0.0).asBoolean)
assertTrue(RuntimeValue(DataType.BYTE, 42).asBoolean)
assertTrue(RuntimeValue(DataType.UBYTE, 42).asBoolean)
assertTrue(RuntimeValue(DataType.WORD, 42).asBoolean)
assertTrue(RuntimeValue(DataType.UWORD, 42).asBoolean)
assertTrue(RuntimeValue(DataType.FLOAT, 42.0).asBoolean)
assertTrue(RuntimeValue(DataType.BYTE, -42).asBoolean)
assertTrue(RuntimeValue(DataType.WORD, -42).asBoolean)
assertTrue(RuntimeValue(DataType.FLOAT, -42.0).asBoolean)
assertTrue(RuntimeValueNumeric(DataType.BYTE, 42).asBoolean)
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 42).asBoolean)
assertTrue(RuntimeValueNumeric(DataType.WORD, 42).asBoolean)
assertTrue(RuntimeValueNumeric(DataType.UWORD, 42).asBoolean)
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 42.0).asBoolean)
assertTrue(RuntimeValueNumeric(DataType.BYTE, -42).asBoolean)
assertTrue(RuntimeValueNumeric(DataType.WORD, -42).asBoolean)
assertTrue(RuntimeValueNumeric(DataType.FLOAT, -42.0).asBoolean)
}
@Test
fun testIdentity() {
val v = RuntimeValue(DataType.UWORD, 12345)
val v = RuntimeValueNumeric(DataType.UWORD, 12345)
assertEquals(v, v)
assertFalse(v != v)
assertTrue(v<=v)
@@ -70,300 +70,283 @@ class TestRuntimeValue {
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
fun testEqualsAndNotEquals() {
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100))
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 100))
assertEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 100))
assertEquals(RuntimeValue(DataType.UWORD, 254), RuntimeValue(DataType.UBYTE, 254))
assertEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12345))
assertEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12345))
assertEquals(RuntimeValue(DataType.FLOAT, 100.0), RuntimeValue(DataType.UBYTE, 100))
assertEquals(RuntimeValue(DataType.FLOAT, 22239.0), RuntimeValue(DataType.UWORD, 22239))
assertEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.99))
assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100))
assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 100))
assertEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 100))
assertEquals(RuntimeValueNumeric(DataType.UWORD, 254), RuntimeValueNumeric(DataType.UBYTE, 254))
assertEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12345))
assertEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12345))
assertEquals(RuntimeValueNumeric(DataType.FLOAT, 100.0), RuntimeValueNumeric(DataType.UBYTE, 100))
assertEquals(RuntimeValueNumeric(DataType.FLOAT, 22239.0), RuntimeValueNumeric(DataType.UWORD, 22239))
assertEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.99))
assertTrue(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 254), RuntimeValue(DataType.UBYTE, 254)))
assertTrue(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12345)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12345)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 100.0), RuntimeValue(DataType.UBYTE, 100)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 22239.0), RuntimeValue(DataType.UWORD, 22239)))
assertTrue(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.99)))
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 100)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 100)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 254), RuntimeValueNumeric(DataType.UBYTE, 254)))
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12345)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12345)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 100.0), RuntimeValueNumeric(DataType.UBYTE, 100)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 22239.0), RuntimeValueNumeric(DataType.UWORD, 22239)))
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.99)))
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 101))
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 101))
assertNotEquals(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 101))
assertNotEquals(RuntimeValue(DataType.UWORD, 245), RuntimeValue(DataType.UBYTE, 246))
assertNotEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12346))
assertNotEquals(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12346))
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UBYTE, 9))
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UWORD, 9))
assertNotEquals(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.0))
assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 101))
assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 101))
assertNotEquals(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 101))
assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 245), RuntimeValueNumeric(DataType.UBYTE, 246))
assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12346))
assertNotEquals(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12346))
assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UBYTE, 9))
assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UWORD, 9))
assertNotEquals(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.0))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UBYTE, 101)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.UWORD, 101)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UBYTE, 100), RuntimeValue(DataType.FLOAT, 101)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 245), RuntimeValue(DataType.UBYTE, 246)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.UWORD, 12346)))
assertFalse(sameValueAndType(RuntimeValue(DataType.UWORD, 12345), RuntimeValue(DataType.FLOAT, 12346)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UBYTE, 9)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.UWORD, 9)))
assertFalse(sameValueAndType(RuntimeValue(DataType.FLOAT, 9.99), RuntimeValue(DataType.FLOAT, 9.0)))
}
@Test
fun 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)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 101)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UWORD, 101)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.FLOAT, 101)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 245), RuntimeValueNumeric(DataType.UBYTE, 246)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.UWORD, 12346)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.UWORD, 12345), RuntimeValueNumeric(DataType.FLOAT, 12346)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UBYTE, 9)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.UWORD, 9)))
assertFalse(sameValueAndType(RuntimeValueNumeric(DataType.FLOAT, 9.99), RuntimeValueNumeric(DataType.FLOAT, 9.0)))
}
@Test
fun testGreaterThan(){
assertTrue(RuntimeValue(DataType.UBYTE, 100) > RuntimeValue(DataType.UBYTE, 99))
assertTrue(RuntimeValue(DataType.UWORD, 254) > RuntimeValue(DataType.UWORD, 253))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) > RuntimeValue(DataType.FLOAT, 99.9))
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) > RuntimeValueNumeric(DataType.UBYTE, 99))
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) > RuntimeValueNumeric(DataType.UWORD, 253))
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) > RuntimeValueNumeric(DataType.FLOAT, 99.9))
assertTrue(RuntimeValue(DataType.UBYTE, 100) >= RuntimeValue(DataType.UBYTE, 100))
assertTrue(RuntimeValue(DataType.UWORD, 254) >= RuntimeValue(DataType.UWORD, 254))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) >= RuntimeValue(DataType.FLOAT, 100.0))
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) >= RuntimeValueNumeric(DataType.UBYTE, 100))
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) >= RuntimeValueNumeric(DataType.UWORD, 254))
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) >= RuntimeValueNumeric(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) > RuntimeValue(DataType.UBYTE, 100))
assertFalse(RuntimeValue(DataType.UWORD, 254) > RuntimeValue(DataType.UWORD, 254))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) > RuntimeValue(DataType.FLOAT, 100.0))
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) > RuntimeValueNumeric(DataType.UBYTE, 100))
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) > RuntimeValueNumeric(DataType.UWORD, 254))
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) > RuntimeValueNumeric(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) >= RuntimeValue(DataType.UBYTE, 101))
assertFalse(RuntimeValue(DataType.UWORD, 254) >= RuntimeValue(DataType.UWORD, 255))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) >= RuntimeValue(DataType.FLOAT, 100.1))
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) >= RuntimeValueNumeric(DataType.UBYTE, 101))
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) >= RuntimeValueNumeric(DataType.UWORD, 255))
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) >= RuntimeValueNumeric(DataType.FLOAT, 100.1))
}
@Test
fun testLessThan() {
assertTrue(RuntimeValue(DataType.UBYTE, 100) < RuntimeValue(DataType.UBYTE, 101))
assertTrue(RuntimeValue(DataType.UWORD, 254) < RuntimeValue(DataType.UWORD, 255))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) < RuntimeValue(DataType.FLOAT, 100.1))
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) < RuntimeValueNumeric(DataType.UBYTE, 101))
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) < RuntimeValueNumeric(DataType.UWORD, 255))
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) < RuntimeValueNumeric(DataType.FLOAT, 100.1))
assertTrue(RuntimeValue(DataType.UBYTE, 100) <= RuntimeValue(DataType.UBYTE, 100))
assertTrue(RuntimeValue(DataType.UWORD, 254) <= RuntimeValue(DataType.UWORD, 254))
assertTrue(RuntimeValue(DataType.FLOAT, 100.0) <= RuntimeValue(DataType.FLOAT, 100.0))
assertTrue(RuntimeValueNumeric(DataType.UBYTE, 100) <= RuntimeValueNumeric(DataType.UBYTE, 100))
assertTrue(RuntimeValueNumeric(DataType.UWORD, 254) <= RuntimeValueNumeric(DataType.UWORD, 254))
assertTrue(RuntimeValueNumeric(DataType.FLOAT, 100.0) <= RuntimeValueNumeric(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) < RuntimeValue(DataType.UBYTE, 100))
assertFalse(RuntimeValue(DataType.UWORD, 254) < RuntimeValue(DataType.UWORD, 254))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) < RuntimeValue(DataType.FLOAT, 100.0))
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) < RuntimeValueNumeric(DataType.UBYTE, 100))
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) < RuntimeValueNumeric(DataType.UWORD, 254))
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) < RuntimeValueNumeric(DataType.FLOAT, 100.0))
assertFalse(RuntimeValue(DataType.UBYTE, 100) <= RuntimeValue(DataType.UBYTE, 99))
assertFalse(RuntimeValue(DataType.UWORD, 254) <= RuntimeValue(DataType.UWORD, 253))
assertFalse(RuntimeValue(DataType.FLOAT, 100.0) <= RuntimeValue(DataType.FLOAT, 99.9))
assertFalse(RuntimeValueNumeric(DataType.UBYTE, 100) <= RuntimeValueNumeric(DataType.UBYTE, 99))
assertFalse(RuntimeValueNumeric(DataType.UWORD, 254) <= RuntimeValueNumeric(DataType.UWORD, 253))
assertFalse(RuntimeValueNumeric(DataType.FLOAT, 100.0) <= RuntimeValueNumeric(DataType.FLOAT, 99.9))
}
@Test
fun testNoDtConversion() {
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 100).add(RuntimeValue(DataType.UBYTE, 120))
RuntimeValueNumeric(DataType.UWORD, 100).add(RuntimeValueNumeric(DataType.UBYTE, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 100).add(RuntimeValue(DataType.UWORD, 120))
RuntimeValueNumeric(DataType.UBYTE, 100).add(RuntimeValueNumeric(DataType.UWORD, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.FLOAT, 100.22).add(RuntimeValue(DataType.UWORD, 120))
RuntimeValueNumeric(DataType.FLOAT, 100.22).add(RuntimeValueNumeric(DataType.UWORD, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 1002).add(RuntimeValue(DataType.FLOAT, 120.22))
RuntimeValueNumeric(DataType.UWORD, 1002).add(RuntimeValueNumeric(DataType.FLOAT, 120.22))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.FLOAT, 100.22).add(RuntimeValue(DataType.UBYTE, 120))
RuntimeValueNumeric(DataType.FLOAT, 100.22).add(RuntimeValueNumeric(DataType.UBYTE, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 12).add(RuntimeValue(DataType.FLOAT, 120.22))
RuntimeValueNumeric(DataType.UBYTE, 12).add(RuntimeValueNumeric(DataType.FLOAT, 120.22))
}
}
@Test
fun testNoAutoFloatConversion() {
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 233).add(RuntimeValue(DataType.FLOAT, 1.234))
RuntimeValueNumeric(DataType.UBYTE, 233).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 233).add(RuntimeValue(DataType.FLOAT, 1.234))
RuntimeValueNumeric(DataType.UWORD, 233).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 233).mul(RuntimeValue(DataType.FLOAT, 1.234))
RuntimeValueNumeric(DataType.UBYTE, 233).mul(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UWORD, 233).mul(RuntimeValue(DataType.FLOAT, 1.234))
RuntimeValueNumeric(DataType.UWORD, 233).mul(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValue(DataType.UBYTE, 233).div(RuntimeValue(DataType.FLOAT, 1.234))
RuntimeValueNumeric(DataType.UBYTE, 233).div(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
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
fun arithmetictestUbyte() {
assertEquals(255, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 55)).integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 56)).integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 200).add(RuntimeValue(DataType.UBYTE, 57)).integerValue())
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 55)).integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 200).add(RuntimeValueNumeric(DataType.UBYTE, 56)).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(0, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 2)).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 2).sub(RuntimeValue(DataType.UBYTE, 3)).integerValue())
assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 1)).integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 2)).integerValue())
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 2).sub(RuntimeValueNumeric(DataType.UBYTE, 3)).integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 254).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 255).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 1).dec().integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 0).dec().integerValue())
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 254).inc().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 255).inc().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 1).dec().integerValue())
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 0).dec().integerValue())
assertEquals(255, RuntimeValue(DataType.UBYTE, 0).inv().integerValue())
assertEquals(0b00110011, RuntimeValue(DataType.UBYTE, 0b11001100).inv().integerValue())
// assertEquals(0, RuntimeValue(DataType.UBYTE, 0).neg().integerValue())
// assertEquals(0, RuntimeValue(DataType.UBYTE, 0).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.UBYTE, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UBYTE, 255).not().integerValue())
assertEquals(255, RuntimeValueNumeric(DataType.UBYTE, 0).inv().integerValue())
assertEquals(0b00110011, RuntimeValueNumeric(DataType.UBYTE, 0b11001100).inv().integerValue())
// assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).neg().integerValue())
// assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 0).neg().integerValue())
assertEquals(1, RuntimeValueNumeric(DataType.UBYTE, 0).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 1).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 111).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UBYTE, 255).not().integerValue())
assertEquals(200, RuntimeValue(DataType.UBYTE, 20).mul(RuntimeValue(DataType.UBYTE, 10)).integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 20).mul(RuntimeValue(DataType.UBYTE, 20)).integerValue())
assertEquals(200, RuntimeValueNumeric(DataType.UBYTE, 20).mul(RuntimeValueNumeric(DataType.UBYTE, 10)).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(125, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 3)).integerValue())
assertEquals(113, RuntimeValue(DataType.UBYTE, 5).pow(RuntimeValue(DataType.UBYTE, 4)).integerValue())
assertEquals(25, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 2)).integerValue())
assertEquals(125, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 3)).integerValue())
assertEquals(113, RuntimeValueNumeric(DataType.UBYTE, 5).pow(RuntimeValueNumeric(DataType.UBYTE, 4)).integerValue())
assertEquals(100, RuntimeValue(DataType.UBYTE, 50).shl().integerValue())
assertEquals(200, RuntimeValue(DataType.UBYTE, 100).shl().integerValue())
assertEquals(144, RuntimeValue(DataType.UBYTE, 200).shl().integerValue())
assertEquals(100, RuntimeValueNumeric(DataType.UBYTE, 50).shl().integerValue())
assertEquals(200, RuntimeValueNumeric(DataType.UBYTE, 100).shl().integerValue())
assertEquals(144, RuntimeValueNumeric(DataType.UBYTE, 200).shl().integerValue())
}
@Test
fun arithmetictestUWord() {
assertEquals(65535, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5535)).integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5536)).integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 60000).add(RuntimeValue(DataType.UWORD, 5537)).integerValue())
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5535)).integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 60000).add(RuntimeValueNumeric(DataType.UWORD, 5536)).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(0, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 2)).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 2).sub(RuntimeValue(DataType.UWORD, 3)).integerValue())
assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 1)).integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 2)).integerValue())
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 2).sub(RuntimeValueNumeric(DataType.UWORD, 3)).integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 65534).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 65535).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 1).dec().integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 0).dec().integerValue())
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 65534).inc().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 65535).inc().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 1).dec().integerValue())
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 0).dec().integerValue())
assertEquals(65535, RuntimeValue(DataType.UWORD, 0).inv().integerValue())
assertEquals(0b0011001101010101, RuntimeValue(DataType.UWORD, 0b1100110010101010).inv().integerValue())
// assertEquals(0, RuntimeValue(DataType.UWORD, 0).neg().integerValue())
// assertEquals(0, RuntimeValue(DataType.UWORD, 0).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.UWORD, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 11111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.UWORD, 65535).not().integerValue())
assertEquals(65535, RuntimeValueNumeric(DataType.UWORD, 0).inv().integerValue())
assertEquals(0b0011001101010101, RuntimeValueNumeric(DataType.UWORD, 0b1100110010101010).inv().integerValue())
// assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).neg().integerValue())
// assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 0).neg().integerValue())
assertEquals(1, RuntimeValueNumeric(DataType.UWORD, 0).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 1).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 11111).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.UWORD, 65535).not().integerValue())
assertEquals(2000, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 10)).integerValue())
assertEquals(40000, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 200)).integerValue())
assertEquals(14464, RuntimeValue(DataType.UWORD, 200).mul(RuntimeValue(DataType.UWORD, 400)).integerValue())
assertEquals(2000, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 10)).integerValue())
assertEquals(40000, RuntimeValueNumeric(DataType.UWORD, 200).mul(RuntimeValueNumeric(DataType.UWORD, 200)).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(12589, RuntimeValue(DataType.UWORD, 5).pow(RuntimeValue(DataType.UWORD, 7)).integerValue())
assertEquals(15625, RuntimeValueNumeric(DataType.UWORD, 5).pow(RuntimeValueNumeric(DataType.UWORD, 6)).integerValue())
assertEquals(12589, RuntimeValueNumeric(DataType.UWORD, 5).pow(RuntimeValueNumeric(DataType.UWORD, 7)).integerValue())
assertEquals(10000, RuntimeValue(DataType.UWORD, 5000).shl().integerValue())
assertEquals(60000, RuntimeValue(DataType.UWORD, 30000).shl().integerValue())
assertEquals(14464, RuntimeValue(DataType.UWORD, 40000).shl().integerValue())
assertEquals(10000, RuntimeValueNumeric(DataType.UWORD, 5000).shl().integerValue())
assertEquals(60000, RuntimeValueNumeric(DataType.UWORD, 30000).shl().integerValue())
assertEquals(14464, RuntimeValueNumeric(DataType.UWORD, 40000).shl().integerValue())
}
@Test
fun arithmetictestByte() {
assertEquals(127, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(DataType.BYTE, 27)).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, 100).add(RuntimeValue(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, 27)).integerValue())
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, 100).add(RuntimeValueNumeric(DataType.BYTE, 28)).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(0, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 2)).integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 2).sub(RuntimeValue(DataType.BYTE, 3)).integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -100).sub(RuntimeValue(DataType.BYTE, 28)).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, -100).sub(RuntimeValue(DataType.BYTE, 29)).integerValue())
assertEquals(1, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 1)).integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 2)).integerValue())
assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 2).sub(RuntimeValueNumeric(DataType.BYTE, 3)).integerValue())
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -100).sub(RuntimeValueNumeric(DataType.BYTE, 28)).integerValue())
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, -100).sub(RuntimeValueNumeric(DataType.BYTE, 29)).integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, 126).inc().integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, 127).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 1).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 0).dec().integerValue())
assertEquals(-128, RuntimeValue(DataType.BYTE, -127).dec().integerValue())
assertEquals(127, RuntimeValue(DataType.BYTE, -128).dec().integerValue())
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, 126).inc().integerValue())
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, 127).inc().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 1).dec().integerValue())
assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 0).dec().integerValue())
assertEquals(-128, RuntimeValueNumeric(DataType.BYTE, -127).dec().integerValue())
assertEquals(127, RuntimeValueNumeric(DataType.BYTE, -128).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.BYTE, 0).inv().integerValue())
assertEquals(-103, RuntimeValue(DataType.BYTE, 0b01100110).inv().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 0).neg().integerValue())
assertEquals(-2, RuntimeValue(DataType.BYTE, 2).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.BYTE, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.BYTE, -33).not().integerValue())
assertEquals(-1, RuntimeValueNumeric(DataType.BYTE, 0).inv().integerValue())
assertEquals(-103, RuntimeValueNumeric(DataType.BYTE, 0b01100110).inv().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 0).neg().integerValue())
assertEquals(-2, RuntimeValueNumeric(DataType.BYTE, 2).neg().integerValue())
assertEquals(1, RuntimeValueNumeric(DataType.BYTE, 0).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 1).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, 111).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.BYTE, -33).not().integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 10).mul(RuntimeValue(DataType.BYTE, 10)).integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 20).mul(RuntimeValue(DataType.BYTE, 10)).integerValue())
assertEquals(100, RuntimeValueNumeric(DataType.BYTE, 10).mul(RuntimeValueNumeric(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(125, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 3)).integerValue())
assertEquals(113, RuntimeValue(DataType.BYTE, 5).pow(RuntimeValue(DataType.BYTE, 4)).integerValue())
assertEquals(25, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 2)).integerValue())
assertEquals(125, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 3)).integerValue())
assertEquals(113, RuntimeValueNumeric(DataType.BYTE, 5).pow(RuntimeValueNumeric(DataType.BYTE, 4)).integerValue())
assertEquals(100, RuntimeValue(DataType.BYTE, 50).shl().integerValue())
assertEquals(-56, RuntimeValue(DataType.BYTE, 100).shl().integerValue())
assertEquals(-2, RuntimeValue(DataType.BYTE, -1).shl().integerValue())
assertEquals(100, RuntimeValueNumeric(DataType.BYTE, 50).shl().integerValue())
assertEquals(-56, RuntimeValueNumeric(DataType.BYTE, 100).shl().integerValue())
assertEquals(-2, RuntimeValueNumeric(DataType.BYTE, -1).shl().integerValue())
}
@Test
fun arithmetictestWorrd() {
assertEquals(32767, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(DataType.WORD, 67)).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, 32700).add(RuntimeValue(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, 67)).integerValue())
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, 32700).add(RuntimeValueNumeric(DataType.WORD, 68)).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(0, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 2)).integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 2).sub(RuntimeValue(DataType.WORD, 3)).integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32700).sub(RuntimeValue(DataType.WORD, 68)).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, -32700).sub(RuntimeValue(DataType.WORD, 69)).integerValue())
assertEquals(1, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 1)).integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 2)).integerValue())
assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 2).sub(RuntimeValueNumeric(DataType.WORD, 3)).integerValue())
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32700).sub(RuntimeValueNumeric(DataType.WORD, 68)).integerValue())
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, -32700).sub(RuntimeValueNumeric(DataType.WORD, 69)).integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, 32766).inc().integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, 32767).inc().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 1).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 0).dec().integerValue())
assertEquals(-32768, RuntimeValue(DataType.WORD, -32767).dec().integerValue())
assertEquals(32767, RuntimeValue(DataType.WORD, -32768).dec().integerValue())
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, 32766).inc().integerValue())
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, 32767).inc().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 1).dec().integerValue())
assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 0).dec().integerValue())
assertEquals(-32768, RuntimeValueNumeric(DataType.WORD, -32767).dec().integerValue())
assertEquals(32767, RuntimeValueNumeric(DataType.WORD, -32768).dec().integerValue())
assertEquals(-1, RuntimeValue(DataType.WORD, 0).inv().integerValue())
assertEquals(-103, RuntimeValue(DataType.WORD, 0b01100110).inv().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 0).neg().integerValue())
assertEquals(-2, RuntimeValue(DataType.WORD, 2).neg().integerValue())
assertEquals(1, RuntimeValue(DataType.WORD, 0).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 1).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, 111).not().integerValue())
assertEquals(0, RuntimeValue(DataType.WORD, -33).not().integerValue())
assertEquals(-1, RuntimeValueNumeric(DataType.WORD, 0).inv().integerValue())
assertEquals(-103, RuntimeValueNumeric(DataType.WORD, 0b01100110).inv().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 0).neg().integerValue())
assertEquals(-2, RuntimeValueNumeric(DataType.WORD, 2).neg().integerValue())
assertEquals(1, RuntimeValueNumeric(DataType.WORD, 0).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 1).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.WORD, 111).not().integerValue())
assertEquals(0, RuntimeValueNumeric(DataType.WORD, -33).not().integerValue())
assertEquals(10000, RuntimeValue(DataType.WORD, 100).mul(RuntimeValue(DataType.WORD, 100)).integerValue())
assertEquals(-25536, RuntimeValue(DataType.WORD, 200).mul(RuntimeValue(DataType.WORD, 200)).integerValue())
assertEquals(10000, RuntimeValueNumeric(DataType.WORD, 100).mul(RuntimeValueNumeric(DataType.WORD, 100)).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(-6487, RuntimeValue(DataType.WORD, 9).pow(RuntimeValue(DataType.WORD, 5)).integerValue())
assertEquals(15625, RuntimeValueNumeric(DataType.WORD, 5).pow(RuntimeValueNumeric(DataType.WORD, 6)).integerValue())
assertEquals(-6487, RuntimeValueNumeric(DataType.WORD, 9).pow(RuntimeValueNumeric(DataType.WORD, 5)).integerValue())
assertEquals(18000, RuntimeValue(DataType.WORD, 9000).shl().integerValue())
assertEquals(-25536, RuntimeValue(DataType.WORD, 20000).shl().integerValue())
assertEquals(-2, RuntimeValue(DataType.WORD, -1).shl().integerValue())
assertEquals(18000, RuntimeValueNumeric(DataType.WORD, 9000).shl().integerValue())
assertEquals(-25536, RuntimeValueNumeric(DataType.WORD, 20000).shl().integerValue())
assertEquals(-2, RuntimeValueNumeric(DataType.WORD, -1).shl().integerValue())
}
}
+63 -53
View File
@@ -6,20 +6,20 @@ import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.ReferenceLiteralValue
import prog8.ast.expressions.StringLiteralValue
import prog8.compiler.*
import prog8.compiler.target.c64.MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.MachineDefinition.Mflpt5
import prog8.compiler.target.c64.C64MachineDefinition.C64Zeropage
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_NEGATIVE
import prog8.compiler.target.c64.C64MachineDefinition.FLOAT_MAX_POSITIVE
import prog8.compiler.target.c64.C64MachineDefinition.Mflpt5
import prog8.compiler.target.c64.Petscii
import prog8.vm.RuntimeValue
import prog8.vm.RuntimeValueNumeric
import java.io.CharConversionException
import kotlin.test.*
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestCompiler {
@Test
@@ -54,7 +54,6 @@ class TestCompiler {
assertFailsWith<CompilerException> { 65536L.toHex() }
}
@Test
fun testFloatToMflpt5() {
assertThat(Mflpt5.fromNumber(0), equalTo(Mflpt5(0x00, 0x00, 0x00, 0x00, 0x00)))
@@ -97,60 +96,63 @@ class TestCompiler {
@Test
fun testMflpt5ToFloat() {
val PRECISION=0.000000001
val epsilon=0.000000001
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, 0xA2).toDouble(), closeTo(3.141592653589793, PRECISION))
assertThat(Mflpt5(0x82, 0x49, 0x0F, 0xDA, 0xA1).toDouble(), closeTo(3.141592653, epsilon))
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, 0x80, 0x00, 0x00, 0x00).toDouble(), equalTo(-32768.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, 0x33).toDouble(), closeTo(0.7071067811865476, PRECISION))
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, PRECISION))
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(1.4142135623730951, PRECISION))
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(0.7071067812, epsilon))
assertThat(Mflpt5(0x80, 0x35, 0x04, 0xF3, 0x33).toDouble(), closeTo(0.7071067811865476, epsilon))
assertThat(Mflpt5(0x81, 0x35, 0x04, 0xF3, 0x34).toDouble(), closeTo(1.4142135624, epsilon))
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, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, PRECISION))
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF7).toDouble(), closeTo(0.6931471805599453, PRECISION))
assertThat(Mflpt5(0x80, 0x31, 0x72, 0x17, 0xF8).toDouble(), closeTo(0.69314718061, epsilon))
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(0x9E, 0x6E, 0x6B, 0x28, 0x00).toDouble(), equalTo(1000000000.0))
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, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, PRECISION))
assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, PRECISION))
assertThat(Mflpt5(0x81, 0x38, 0xAA, 0x3B, 0x29).toDouble(), closeTo(1.4426950408889634, epsilon))
assertThat(Mflpt5(0x81, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(1.5707963267948966, epsilon))
assertThat(Mflpt5(0x83, 0x49, 0x0F, 0xDA, 0xA2).toDouble(), closeTo(6.283185307179586, epsilon))
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(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, PRECISION))
assertThat(Mflpt5(0x3e, 0xe9, 0x34, 0x09, 0x1b).toDouble(), closeTo(-123.45678e-22, epsilon))
}
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestZeropage {
private val errors = ErrorReporter()
@Test
fun testNames() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), false))
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null)
zp.allocate("varname", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("", DataType.UBYTE, null, errors)
zp.allocate("varname", DataType.UBYTE, null, errors)
assertFailsWith<AssertionError> {
zp.allocate("varname", DataType.UBYTE, null)
zp.allocate("varname", DataType.UBYTE, null, errors)
}
zp.allocate("varname2", DataType.UBYTE, null)
zp.allocate("varname2", DataType.UBYTE, null, errors)
}
@Test
fun testZpFloatEnable() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
assertFailsWith<CompilerException> {
zp.allocate("", DataType.FLOAT, null)
zp.allocate("", DataType.FLOAT, null, errors)
}
val zp2 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.DONTUSE, emptyList(), true))
assertFailsWith<CompilerException> {
zp2.allocate("", DataType.FLOAT, null)
zp2.allocate("", DataType.FLOAT, null, errors)
}
val zp3 = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FLOATSAFE, emptyList(), true))
zp3.allocate("", DataType.FLOAT, null)
zp3.allocate("", DataType.FLOAT, null, errors)
}
@Test
@@ -169,7 +171,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, errors)
}
}
@Test
fun testFreeSpaces() {
@@ -212,19 +222,19 @@ class TestZeropage {
assertFailsWith<ZeropageDepletedError> {
// in regular zp there aren't 5 sequential bytes free
zp.allocate("", DataType.FLOAT, null)
zp.allocate("", DataType.FLOAT, null, errors)
}
for (i in 0 until zp.available()) {
val loc = zp.allocate("", DataType.UBYTE, null)
val loc = zp.allocate("", DataType.UBYTE, null, errors)
assertTrue(loc > 0)
}
assertEquals(0, zp.available())
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null, errors)
}
assertFailsWith<ZeropageDepletedError> {
zp.allocate("", DataType.UWORD, null)
zp.allocate("", DataType.UWORD, null, errors)
}
}
@@ -232,29 +242,29 @@ class TestZeropage {
fun testFullAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, emptyList(), false))
assertEquals(238, zp.available())
val loc = zp.allocate("", DataType.UWORD, null)
val loc = zp.allocate("", DataType.UWORD, null, errors)
assertTrue(loc > 3)
assertFalse(loc in zp.free)
val num = zp.available() / 2
for(i in 0..num-4) {
zp.allocate("", DataType.UWORD, null)
zp.allocate("", DataType.UWORD, null, errors)
}
assertEquals(6,zp.available())
assertFailsWith<ZeropageDepletedError> {
// can't allocate because no more sequential bytes, only fragmented
zp.allocate("", DataType.UWORD, null)
zp.allocate("", DataType.UWORD, null, errors)
}
for(i in 0..5) {
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null, errors)
}
assertEquals(0, zp.available())
assertFailsWith<ZeropageDepletedError> {
// no more space
zp.allocate("", DataType.UBYTE, null)
zp.allocate("", DataType.UBYTE, null, errors)
}
}
@@ -262,16 +272,16 @@ class TestZeropage {
fun testEfficientAllocation() {
val zp = C64Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, emptyList(), true))
assertEquals(16, zp.available())
assertEquals(0x04, zp.allocate("", DataType.WORD, null))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x94, zp.allocate("", DataType.UWORD, null))
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null))
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null))
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null))
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null))
assertEquals(0x04, zp.allocate("", DataType.WORD, null, errors))
assertEquals(0x06, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x0a, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0x94, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa7, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xa9, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xb5, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0xf7, zp.allocate("", DataType.UWORD, null, errors))
assertEquals(0x0e, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0xf9, zp.allocate("", DataType.UBYTE, null, errors))
assertEquals(0, zp.available())
}
}
@@ -363,8 +373,8 @@ class TestPetscii {
assertTrue(ten <= ten)
assertFalse(ten < ten)
val abc = ReferenceLiteralValue(DataType.STR, str = "abc", position = Position("", 0, 0, 0))
val abd = ReferenceLiteralValue(DataType.STR, str = "abd", position = Position("", 0, 0, 0))
val abc = StringLiteralValue("abc", false, Position("", 0, 0, 0))
val abd = StringLiteralValue("abd", false, Position("", 0, 0, 0))
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
@@ -372,8 +382,8 @@ class TestPetscii {
@Test
fun testStackvmValueComparisons() {
val ten = RuntimeValue(DataType.FLOAT, 10)
val nine = RuntimeValue(DataType.UWORD, 9)
val ten = RuntimeValueNumeric(DataType.FLOAT, 10)
val nine = RuntimeValueNumeric(DataType.UWORD, 9)
assertEquals(ten, ten)
assertNotEquals(ten, nine)
assertFalse(ten != ten)
+1 -1
View File
@@ -5,7 +5,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/build" />
</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" />
</component>
</module>
+12 -11
View File
@@ -94,6 +94,13 @@ 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.
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
------------------------
@@ -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
Launches the "AST virtual machine" that directly executes the parsed program.
-sim
Launches the "AST virtual machine Simulator" that directly executes the parsed program.
No compilation steps will be performed.
Allows for very fast testing and debugging before actually compiling programs
to machine code.
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
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.
+14 -13
View File
@@ -143,6 +143,7 @@ Design principles and features
still able to directly use memory addresses, CPU registers and ROM subroutines,
and inline assembly to have full control when every cycle or byte matters
- Arbitrary number of subroutine parameters (constrained only by available memory)
- Nested subroutines can access variables from outer scopes, this avoids the need and overhead to pass everything via parameters
- Complex nested expressions are possible
- Values are typed. Types supported include signed and unsigned bytes and words, arrays, strings and floats.
- No dynamic memory allocation or sizing! All variables stay fixed size as determined at compile time.
@@ -158,6 +159,7 @@ Design principles and features
- 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
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:
@@ -166,26 +168,25 @@ Required tools
--------------
`64tass <https://sourceforge.net/projects/tass64/>`_ - cross assembler. Install this on your shell path.
A recent .exe version of this tool for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
For other platforms it is very easy to compile it yourself (make ; make install).
It's very easy to compile yourself.
A recent precompiled .exe for Windows can be obtained from my `clone <https://github.com/irmen/64tass/releases>`_ of this project.
A **Java runtime (jre or jdk), version 8 or newer** is required to run the packaged compiler.
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead
and for Windows it's possible to get that as well. Check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ for
downloads.
A **Java runtime (jre or jdk), version 8 or newer** is required to run the prog8 compiler itself.
If you're scared of Oracle's licensing terms, most Linux distributions ship OpenJDK instead.
Fnd for Windows it's possible to get that as well. Check out `AdoptOpenJDK <https://adoptopenjdk.net/>`_ .
Finally: a **C-64 emulator** (or a real C-64 ofcourse) to run the programs on. The compiler assumes the presence
of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
Finally: a **C-64 emulator** (or a real C-64 ofcourse) can be nice to test and run your programs on.
The compiler assumes the presence of the `Vice emulator <http://vice-emu.sourceforge.net/>`_.
.. important::
**Building the compiler itself:** (*Only needed if you have not downloaded a pre-built 'fat-jar'*)
(re)building the compiler itself requires a recent Kotlin SDK.
The compiler is developed using the `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_
IDE from Jetbrains, with the Kotlin plugin (free community edition of this IDE is available).
But a bare Kotlin SDK installation should work just as well.
(Re)building the compiler itself requires a recent Kotlin SDK.
The compiler is developed using `IntelliJ IDEA <https://www.jetbrains.com/idea/>`_ ,
but only a Kotlin SDK installation should work as well, because the gradle tool is
used to compile everything from the commandline.
Instructions on how to obtain a working compiler are in :ref:`building_compiler`.
Instructions on how to obtain a prebuilt compiler are in :ref:`building_compiler`.
.. toctree::
Loaded 100 of 184 files, more files were not shown because too many files have changed in this diff. Show more