Compare commits

...

175 Commits
v1.62 ... v2.2

Author SHA1 Message Date
76cda82e23 v2.2 2020-06-16 01:43:44 +02:00
37b61d9e6b v2.2 2020-06-16 01:39:11 +02:00
52f0222a6d Got rid of old Ast transformer Api, some compiler error fixes 2020-06-16 01:25:49 +02:00
75ccac2f2c refactoring last of old Ast modification Api 2020-06-16 00:36:02 +02:00
5c771a91f7 refactoring last of old Ast modification Api 2020-06-14 16:56:48 +02:00
a242ad10e6 fix double printing of sub param vardecl 2020-06-14 13:46:46 +02:00
b5086b6a8f refactoring last of old Ast modification Api 2020-06-14 03:17:42 +02:00
3e47dad12a clearer no modifications 2020-06-14 02:54:29 +02:00
235610f40c refactored StatementOptimizer 2020-06-14 02:41:23 +02:00
6b59559c65 memory address assignment codegen 2020-06-14 02:12:40 +02:00
23e954f716 refactoring StatementOptimizer 2020-06-14 02:00:32 +02:00
983c899cad refactor AstIdentifierChecker 2020-06-13 00:14:19 +02:00
c2f9385965 refactor AstIdentifierChecker 2020-06-12 21:34:27 +02:00
ceb2c9e4f8 added string value assignment, leftstr, rightstr, substr functions 2020-06-06 00:05:39 +02:00
68a7f9c665 version 2.1 2020-06-04 23:03:18 +02:00
ffd8d9c7c1 more assignment expression optimizations 2020-06-04 22:57:32 +02:00
c66fc8630c fixed missing repeated constant folding in expression optimization 2020-06-04 20:22:37 +02:00
9ca1c66f2b added some optimizations for >= 0 and <0 comparisons for integers 2020-06-04 01:43:37 +02:00
33647a29d0 be smarter about certain implicit type casts 2020-06-03 23:55:41 +02:00
02b12cc762 optimized swap() for byte and word vars, optimized graphics line routine 2020-06-03 23:27:50 +02:00
3280993e2a stricter type checking in assignments (less implicit typecasts) 2020-06-02 22:36:57 +02:00
3723c22054 fix string param type 2020-06-02 02:09:52 +02:00
0a2c4ea0c4 improved ast printing 2020-06-02 01:51:27 +02:00
58a83c0439 improved code gen for passing string and array types. 2020-06-02 01:44:42 +02:00
d665489054 implemented asm for addressof-assignment 2020-06-02 00:31:56 +02:00
9200992024 slightly improved asm gen error messages 2020-06-02 00:31:20 +02:00
6408cc46a8 cmdrx16 github ref 2020-05-15 00:32:45 +02:00
961bcdb7ae some more todo's noted down 2020-05-15 00:24:25 +02:00
edee70cf31 use new api for ast mods in unused code remover 2020-05-15 00:16:53 +02:00
1978a9815a version 2.0 2020-05-14 23:59:18 +02:00
f5e6db9d66 big compiler speedup due to optimized scope lookups 2020-05-14 23:59:02 +02:00
a94bc40ab0 performance todo's 2020-05-08 20:41:10 +02:00
534b5ced8f updated the compiled examples 2020-04-10 23:36:29 +02:00
5ebd9b54e4 added some more optimized array assignments 2020-04-10 23:30:19 +02:00
cc4e272526 the new assignment code (once complete) really is a big enough change to bump the version to 2.0 2020-04-09 00:24:37 +02:00
295e199bfa optimized asm output for unneeded typecasts, fixed parent node linking issues with replaceChildNode, Assignment aug_op field is now mutable to avoid having to recreate many Assignment nodes 2020-04-09 00:12:50 +02:00
df3371b0f0 slight gfx optimizations 2020-04-08 22:53:23 +02:00
e4fe1d2b8d attempts to optimize in-place assignments 2020-04-08 03:11:38 +02:00
b8b9244ffa merged AddressOfInserter into StatementReorderer 2020-04-06 15:23:54 +02:00
3be3989e1c version 2020-04-06 14:31:23 +02:00
ed54cf680a fixed ast parent link bug in AstWalker, rewrote StatementReorderer using new API, when labels are sorted. 2020-04-06 14:31:02 +02:00
95e76058d3 version 2020-04-03 23:55:29 +02:00
a6bee6a860 some slight tweaks to asm for setting float value in array 2020-04-03 22:44:10 +02:00
d22780ee44 implemented asm for lsl array values 2020-04-03 21:45:52 +02:00
f8b0b9575d implemented asm for rol array values 2020-04-03 21:31:39 +02:00
4274fd168e implemented asm for rol2 array values 2020-04-03 21:24:55 +02:00
be7f5957f3 implemented asm for ror2 array values 2020-04-03 21:04:42 +02:00
f2e5d987a9 implemented asm for ror array values 2020-04-03 00:03:42 +02:00
f01173d8db fixed compilation of clear/set_carry() and clear/set_irqd() functions 2020-04-03 00:00:58 +02:00
15e8e0bf6d implemented asm for lsr array values 2020-04-02 23:38:45 +02:00
2c59cbdece fixed a crash in astchecking of array init values 2020-04-02 18:40:04 +02:00
b73da4ed02 some more obvious optimizations for X+X and X-X 2020-03-31 23:54:01 +02:00
267adb4612 doc 2020-03-29 03:06:51 +02:00
05c73fa8bc version 2020-03-28 17:06:59 +01:00
bfe9f442e6 balloon 2020-03-28 17:06:17 +01:00
0deadb694b updated the compiled examples 2020-03-28 14:31:31 +01:00
bed34378be doc 2020-03-28 14:24:00 +01:00
5927cf2d43 added turtle graphics example 2020-03-28 14:17:35 +01:00
fffe36e358 fix bresenham line 2020-03-28 13:42:24 +01:00
fac2a2d7cb fast asm plot 2020-03-28 00:36:44 +01:00
0af5582ca7 fix compiler crash for undefined symbol in expression 2020-03-27 23:09:46 +01:00
582d31263c better lines and circles 2020-03-27 00:09:17 +01:00
4108a528e1 proepr compiler error when there's no main module 2020-03-26 23:22:01 +01:00
ab7d7c2907 fix comparison of memory expressions (this error prevented some optimizations) 2020-03-26 22:59:42 +01:00
152888ee93 fix direcetmemoryread invalid asm 2020-03-26 22:46:05 +01:00
22f8f4f359 fixed memory pointer access asm code for direct reads and direct assignments 2020-03-26 19:20:39 +01:00
5f3a9e189a doc 2020-03-26 01:20:04 +01:00
b734dc44fd fix invalid assembly for @(address)++/-- 2020-03-26 01:13:20 +01:00
fab224f509 fix compiler crashing with invalid array initializer built from single integer 2020-03-25 01:23:54 +01:00
2f05ebb966 bitmap lines and circles 2020-03-25 01:07:42 +01:00
a335ba519a fix warnings about unreachable code 2020-03-24 22:37:42 +01:00
8805693ed2 cleaned up the way return statements are added to avoid code falling through in/out of subroutines 2020-03-24 22:02:50 +01:00
f2bb238e9b cleaned up various ast checks/mutations 2020-03-24 19:37:54 +01:00
131fe670a4 optimized scroll routines by removing needless twin loops 2020-03-24 17:33:47 +01:00
11e9539416 smooth scroll 2020-03-24 02:42:32 +01:00
3881ebe429 begun skramble clone 2020-03-24 01:47:02 +01:00
29d1b8802e whitespace 2020-03-24 00:24:51 +01:00
bcc75732e9 optimize asm jsr+rts into jmp 2020-03-23 23:51:27 +01:00
50a85ee6b0 attempt to optimize asm for bitshifts more. 2020-03-23 22:59:29 +01:00
2c7424fd43 fix: datatype mismatch in optimized bitshift const value 2020-03-23 22:35:23 +01:00
7426587c38 fix: add proper return statement type cast if needed, now also for non constant values 2020-03-23 19:49:11 +01:00
1f39749a5e tweak bitshift asm 2020-03-23 17:35:58 +01:00
ca63051c71 replaced todo's that aren't real todo's with regular exception 2020-03-23 13:00:44 +01:00
6dd44aaf0d compiler main cleanup 2020-03-23 02:54:04 +01:00
f89457ba68 fixed var initialization bug in anonymous scopes 2020-03-23 02:09:30 +01:00
efef205fcf doc 2020-03-23 01:24:54 +01:00
0c561d8528 fixed subroutine parameter value issue 2020-03-23 00:13:46 +01:00
8bfa2c4c02 proper initialization of block-level global variables 2020-03-22 22:47:05 +01:00
f0d4c3aba9 moved initialvalues to asmgen, fixed sgn bug and internal float 0.0 variable disappearing bug 2020-03-22 18:17:12 +01:00
3a99115070 Initial variable values semantics changed: now always sets value at program (re)start (except strings/arrays).
This may change later by introducing a compiler option to choose a strategy, perhaps.
2020-03-22 15:12:26 +01:00
7232134931 fix some compiler errors 2020-03-22 13:47:13 +01:00
954e911eb3 optimized zeros array initializer 2020-03-22 02:58:51 +01:00
63c073c93f got rid of the Simulator / AST VM 2020-03-22 02:50:34 +01:00
78feef9d59 simplified handling of initial vardecl values in codegeneration 2020-03-22 02:45:42 +01:00
4fbdd6d570 fix ubyte number print bug for 100-109 and 200-209 missing the tens digit 2020-03-22 01:49:05 +01:00
4929c198ba tweak error reporting, expanded lines and circles example 2020-03-22 00:43:46 +01:00
9409f17372 bugfixes in new optimization routines 2020-03-21 23:09:18 +01:00
43781c02d0 tweaked ast modifications 2020-03-21 18:42:40 +01:00
824f06e17f new var init values 2020-03-21 14:54:19 +01:00
21dbc6da97 doc 2020-03-21 12:51:32 +01:00
270ea54ff7 now properly compile assignment of struct literal value to struct variable (outside of vardecl) 2020-03-21 00:57:20 +01:00
771ac7aba7 error when struct literal value element count doesn't match struct members in assignment 2020-03-20 23:14:03 +01:00
97d36243f2 don't include the generated parser java files in git 2020-03-20 22:53:56 +01:00
511b47bac4 fix compiler crash when initializing struct var with something other than a struct literal 2020-03-20 22:48:33 +01:00
f265199fbe replaced typecastsAdder with version based on astwalker 2020-03-20 22:28:18 +01:00
a191ec71a4 this is not modifying the ast 2020-03-19 23:16:58 +01:00
82dce2dd53 added Foreverloop statement to the ast simulator 2020-03-19 22:45:27 +01:00
29ac160811 applying new astwalker for modifications 2020-03-19 22:40:49 +01:00
5e50ea14f8 applying new astwalker for modifications 2020-03-19 21:30:01 +01:00
40e6091506 new astvisitor tryout 2020-03-19 00:01:57 +01:00
0ee4d420b1 slight tweaks on the Ast, Program (the top level) is now a Node as well 2020-03-18 22:29:30 +01:00
66acce9e8e doc 2020-03-15 01:49:16 +01:00
6c23ae14ab ver 2020-03-15 01:37:01 +01:00
6f000d0d26 fix datatype warning 2020-03-15 01:14:44 +01:00
9d7eb3be5a fix error reporting of constantfolding, and number of errors printed 2020-03-15 01:10:08 +01:00
835555171e fix function call arg type mismatch crash 2020-03-15 00:50:59 +01:00
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
a995867deb added check for duplicate label definitions 2020-03-15 00:16:50 +01:00
6bd99d63b4 cleanup of error reporting 2020-03-14 23:47:26 +01:00
baf5d3041a cleanup of error reporting 2020-03-14 23:15:44 +01:00
a326ffa00a added warning about sgn() of unsigned type 2020-03-14 21:09:34 +01:00
d28dd92b47 refreshed examples 2020-03-14 18:11:38 +01:00
1de328b2e8 added forever-loop and optimizer 2020-03-14 18:11:04 +01:00
51bb902162 added bresenham and circle example 2020-03-14 17:11:10 +01:00
4fd14f1366 doc updates 2020-03-14 15:20:04 +01:00
91d9559f79 avoid pulling in the dbus libraries for now 2020-03-14 14:40:39 +01:00
3245a9b157 restricted block to only directive/subroutine/vardecl/inlineasm 2020-03-14 14:20:55 +01:00
2b28493bba simplified module grammar rules 2020-03-14 13:44:13 +01:00
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
0422ad080a added exit function to astvm simulator 2020-03-13 02:44:01 +01:00
64d682bfde todo 2020-03-13 02:33:02 +01:00
b182f7e693 optimizer removes unreachable code following call to exit() 2020-03-13 02:31:53 +01:00
e6be428589 compiler warning for unreachable code following a call to exit() 2020-03-13 02:21:37 +01:00
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
796d07a7f8 fix crash in asm code generated for bitshift operation with memory address operand 2020-03-13 01:26:53 +01:00
2af86a10b2 remove stack error comments 2020-03-13 00:52:52 +01:00
7fbe486dff fix eval stack register X error in print_uw 2020-03-13 00:50:30 +01:00
87e5a9859a remove autogenerated labels from vice mon list, fixes #17 2020-03-12 22:33:58 +01:00
b036e5ed72 refreshed the compiled examples 2020-03-12 01:14:10 +01:00
5f1ec80ae0 improved array literal datatype handling, fixed some datatype compiler errors related to this 2020-03-12 01:10:19 +01:00
fbecedaf41 added error for unsupported sort(floatarray) 2020-03-11 23:33:06 +01:00
aa36acd65a implemented reverse(floatarray) builtin function 2020-03-11 23:18:03 +01:00
8d1a4588d3 added 'downto' range expression 2020-03-11 20:59:14 +01:00
66d2af4453 added '@' alternative string/char encoding 2020-03-11 00:41:58 +01:00
ef6c731bb3 added '@' alternative string/char encoding 2020-03-11 00:32:50 +01:00
98a638a2f3 split asmsub and romsub declarations 2020-03-10 23:09:31 +01:00
96d8a7f0d7 float assembly code moved to separate library file 2020-03-10 22:03:24 +01:00
3162b10392 optimize callgraph 2020-03-10 21:47:15 +01:00
e2358de27c ver 2020-03-10 20:39:30 +01:00
7facb4f372 correct version 1.70 2020-02-09 01:41:05 +01:00
ee90fed489 readme 2020-02-09 01:33:20 +01:00
4796c56c35 antlr code back 2020-02-09 01:29:58 +01:00
e2cb031386 added 'void' keyword to explicitly ignore subroutine return values (and no longer get a warning) 2020-02-09 01:29:09 +01:00
a0bc97b90c fix byte array iteration for bb in [1,2,3]
improved array literal datatype detection
2020-02-09 00:45:53 +01:00
fd240899bd fix CHROUT in simulator 2020-02-09 00:12:50 +01:00
885b22df40 fixed while and repeat warning messages line number
fixed invalid while and repeat asm label names
fixed boolean checking of numbers
2020-02-08 19:45:30 +01:00
11de3db25f simplified heapId for arrayvalues 2020-02-08 18:49:48 +01:00
14a13da7ec simplified heapId for stringvalue 2020-02-08 15:54:03 +01:00
875a71c786 removed datatype from StringValue classes (is always STR now) 2020-02-08 02:21:18 +01:00
0ff5b79353 code inspection cleanups 2020-02-08 01:31:41 +01:00
8c4d276810 improvements to string encoding/decoding and text output in the simulator 2020-02-08 01:12:30 +01:00
3dd38c0ac8 antlr library updated to 4.8 2020-02-07 23:58:07 +01:00
b8816a0e2f got rid of separate str_s datatype 2020-02-07 20:47:38 +01:00
a01a9e76f9 removed bogus clang target
fixed various simulator bugs regarding strings and chars
2020-02-07 01:22:07 +01:00
357d704aec clean up version specifier 2020-02-02 19:33:40 +01:00
868df1865c got rid of obsolete code 2020-02-02 19:18:40 +01:00
654d74da1e automatic selection of best Vice C64 emulator executable 2020-02-02 13:39:56 +01:00
59939c727a gradle updated 2020-02-02 13:39:25 +01:00
fbcf190324 sync gradle version with my manjaro packaged gradle 2020-01-27 21:32:42 +01:00
b9922a90cc update gradle wrapper to 6.1.1 2020-01-26 18:36:51 +01:00
66e0b07428 gradle updates 2020-01-07 01:29:25 +01:00
01e617ae8f new kotlin version 2019-12-09 16:17:20 +01:00
52769decd4 fix assembler float truncation warning 2019-11-27 22:36:59 +01:00
165eec4054 started a c++ language compiler code target
(meant to be an intermediate step before direct Wasm/binaryen, via clang compilation to wasm)
2019-10-30 00:15:03 +01:00
8c2e602cc7 preparing for multiple compiler backends/targets 2019-10-26 23:41:15 +02:00
179 changed files with 11472 additions and 19442 deletions

13
.gitignore vendored
View File

@ -1,8 +1,8 @@
.idea/workspace.xml
.idea/discord.xml
/build/
/dist/
/output/
build/
dist/
output/
.*cache/
*.directory
*.prg
@ -12,9 +12,9 @@
*.vice-mon-list
docs/build
out/
**/*.interp
**/*.tokens
parser/**/*.interp
parser/**/*.tokens
parser/**/*.java
*.py[cod]
*.egg
*.egg-info
@ -28,5 +28,4 @@ parsetab.py
.attach_pid*
.gradle
build/
/prog8compiler.jar

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

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

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

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

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

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

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

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

16
.idea/misc.xml generated
View File

@ -1,5 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<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
.idea/modules.xml generated
View File

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

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>

View File

@ -1,762 +0,0 @@
package oldcodegen
/** OLD STACK-VM CODE GEN -- NO LONGER USED **/
// 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 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\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

View File

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

View File

@ -1,5 +1,6 @@
[![saythanks](https://img.shields.io/badge/say-thanks-ff69b4.svg)](https://saythanks.io/to/irmen)
[![Build Status](https://travis-ci.org/irmen/prog8.svg?branch=master)](https://travis-ci.org/irmen/prog8)
[![Documentation](https://readthedocs.org/projects/prog8/badge/?version=latest)](https://prog8.readthedocs.io/)
Prog8 - Structured Programming Language for 8-bit 6502/6510 microprocessors
===========================================================================
@ -30,21 +31,18 @@ which aims to provide many conveniences over raw assembly code (even when using
Rapid edit-compile-run-debug cycle:
- use modern PC to work on
- quick compilation times (seconds)
- option to automatically run the program in the Vice emulator
- use a modern PC to do the work on
- very quick compilation times
- can automatically run the program in the Vice emulator after succesful compilation
- 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
- 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.
Prog8 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/
https://prog8.readthedocs.io/
Required tools
--------------

View File

@ -1,14 +1,14 @@
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
}
}
plugins {
// id "org.jetbrains.kotlin.jvm" version $kotlinVersion
// id "org.jetbrains.kotlin.jvm" version "1.3.72"
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'
}
@ -29,17 +29,18 @@ 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'
compile 'org.jetbrains.kotlinx:kotlinx-cli-jvm:0.1.0-dev-5'
}
compileKotlin {
@ -85,8 +86,8 @@ artifacts {
shadowJar {
baseName = 'prog8compiler'
version = prog8version
archiveBaseName = 'prog8compiler'
archiveVersion = prog8version
// minimize()
}
@ -100,7 +101,7 @@ test {
// Show test results.
testLogging {
events "passed", "skipped", "failed"
events "skipped", "failed"
}
}

View File

@ -11,9 +11,9 @@
<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.

View File

@ -0,0 +1,783 @@
; --- 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
set_0_array_float .proc
; -- set a float in an array to zero (index on stack, array in SCRATCH_ZPWORD1)
inx
lda c64.ESTACK_LO,x
asl a
asl a
clc
adc c64.ESTACK_LO,x
tay
lda #0
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
iny
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
set_array_float .proc
; -- set a float in an array to a value (index on stack, float in SCRATCH_ZPWORD1, array in SCRATCH_ZPWORD2)
inx
lda c64.ESTACK_LO,x
asl a
asl a
clc
adc c64.ESTACK_LO,x
clc
adc c64.SCRATCH_ZPWORD2
ldy c64.SCRATCH_ZPWORD2+1
bcc +
iny
+ jmp copy_float
; -- 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.
.pend

View File

@ -10,8 +10,8 @@
c64flt {
; ---- this block contains C-64 floating point related functions ----
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
const float PI = 3.141592653589793
const float TWOPI = 6.283185307179586
; ---- C64 basic and kernal ROM float constants and functions ----
@ -34,32 +34,33 @@ c64flt {
&float FL_PIHALF = $e2e0 ; PI / 2
&float FL_TWOPI = $e2e5 ; 2 * PI
&float FL_FR4 = $e2ea ; .25
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
; oddly enough, 0.0 isn't available in the kernel.
float FL_ZERO = 0.0 ; oddly enough 0.0 isn't available in the kernel
; note: fac1/2 might get clobbered even if not mentioned in the function's name.
; 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 +68,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
@ -210,8 +210,8 @@ sub print_fln (float value) {
; ---- prints the floating point value (with a newline at the end) using basic rom routines
%asm {{
stx c64.SCRATCH_ZPREGX
lda #<print_fln_value
ldy #>print_fln_value
lda #<value
ldy #>value
jsr MOVFM ; load float into fac1
jsr FPRINTLN ; print fac1 with newline
ldx c64.SCRATCH_ZPREGX
@ -220,750 +220,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
sign_f .proc
jsr pop_float_fac1
jsr SIGN
sta c64.ESTACK_LO,x
dex
rts
.pend
}}
%asminclude "library:c64floats.asm", ""
} ; ------ end of block c64flt

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

View File

@ -565,14 +565,9 @@ asmsub clear_screenchars (ubyte char @ A) clobbers(Y) {
%asm {{
ldy #0
_loop sta c64.Screen,y
sta c64.Screen+1,y
sta c64.Screen+$0100,y
sta c64.Screen+$0101,y
sta c64.Screen+$0200,y
sta c64.Screen+$0201,y
sta c64.Screen+$02e8,y
sta c64.Screen+$02e9,y
iny
iny
bne _loop
rts
@ -585,14 +580,9 @@ asmsub clear_screencolors (ubyte color @ A) clobbers(Y) {
%asm {{
ldy #0
_loop sta c64.Colors,y
sta c64.Colors+1,y
sta c64.Colors+$0100,y
sta c64.Colors+$0101,y
sta c64.Colors+$0200,y
sta c64.Colors+$0201,y
sta c64.Colors+$02e8,y
sta c64.Colors+$02e9,y
iny
iny
bne _loop
rts
@ -603,6 +593,7 @@ 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
; Carry flag determines if screen color data must be scrolled too
%asm {{
stx c64.SCRATCH_ZPREGX
bcs +
@ -612,18 +603,7 @@ asmsub scroll_left_full (ubyte alsocolors @ Pc) clobbers(A, Y) {
ldx #0
ldy #38
-
.for row=0, row<=12, row+=1
lda c64.Colors + 40*row + 1,x
sta c64.Colors + 40*row,x
.next
inx
dey
bpl -
ldx #0
ldy #38
-
.for row=13, row<=24, row+=1
.for row=0, row<=24, row+=1
lda c64.Colors + 40*row + 1,x
sta c64.Colors + 40*row,x
.next
@ -635,18 +615,7 @@ _scroll_screen ; scroll the screen memory
ldx #0
ldy #38
-
.for row=0, row<=12, row+=1
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row,x
.next
inx
dey
bpl -
ldx #0
ldy #38
-
.for row=13, row<=24, row+=1
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 1,x
sta c64.Screen + 40*row,x
.next
@ -671,41 +640,23 @@ asmsub scroll_right_full (ubyte alsocolors @ Pc) clobbers(A) {
+ ; scroll the color memory
ldx #38
-
.for row=0, row<=12, row+=1
.for row=0, row<=24, row+=1
lda c64.Colors + 40*row + 0,x
sta c64.Colors + 40*row + 1,x
.next
dex
bpl -
ldx #38
-
.for row=13, row<=24, row+=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*row + 1,x
.next
dex
bpl -
_scroll_screen ; scroll the screen memory
ldx #38
-
.for row=0, row<=12, row+=1
.for row=0, row<=24, row+=1
lda c64.Screen + 40*row + 0,x
sta c64.Screen + 40*row + 1,x
.next
dex
bpl -
ldx #38
-
.for row=13, row<=24, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*row + 1,x
.next
dex
bpl -
ldx c64.SCRATCH_ZPREGX
rts
}}
@ -723,16 +674,7 @@ asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
+ ; scroll the color memory
ldx #39
-
.for row=1, row<=11, row+=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row-1),x
.next
dex
bpl -
ldx #39
-
.for row=12, row<=24, row+=1
.for row=1, row<=24, row+=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row-1),x
.next
@ -742,16 +684,7 @@ asmsub scroll_up_full (ubyte alsocolors @ Pc) clobbers(A) {
_scroll_screen ; scroll the screen memory
ldx #39
-
.for row=1, row<=11, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
.next
dex
bpl -
ldx #39
-
.for row=12, row<=24, row+=1
.for row=1, row<=24, row+=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row-1),x
.next
@ -775,16 +708,7 @@ asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
+ ; scroll the color memory
ldx #39
-
.for row=23, row>=12, row-=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row+1),x
.next
dex
bpl -
ldx #39
-
.for row=11, row>=0, row-=1
.for row=23, row>=0, row-=1
lda c64.Colors + 40*row,x
sta c64.Colors + 40*(row+1),x
.next
@ -794,16 +718,7 @@ asmsub scroll_down_full (ubyte alsocolors @ Pc) clobbers(A) {
_scroll_screen ; scroll the screen memory
ldx #39
-
.for row=23, row>=12, row-=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
dex
bpl -
ldx #39
-
.for row=11, row>=0, row-=1
.for row=23, row>=0, row-=1
lda c64.Screen + 40*row,x
sta c64.Screen + 40*(row+1),x
.next
@ -862,11 +777,14 @@ _print_byte_digits
beq +
tya
jsr c64.CHROUT
pla
jsr c64.CHROUT
jmp _ones
+ pla
cmp #'0'
beq +
beq _ones
jsr c64.CHROUT
+ txa
_ones txa
jsr c64.CHROUT
ldx c64.SCRATCH_ZPREGX
rts
@ -975,6 +893,7 @@ asmsub print_uw (uword value @ AY) clobbers(A,Y) {
%asm {{
stx c64.SCRATCH_ZPREGX
jsr c64utils.uword2decimal
ldx c64.SCRATCH_ZPREGX
ldy #0
- lda c64utils.uword2decimal.decTenThousands,y
beq _allzero

View File

@ -239,7 +239,7 @@ mul_byte_3 .proc
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_3 .proc
; W*2 + W
lda c64.ESTACK_HI+1,x
@ -255,7 +255,7 @@ mul_word_3 .proc
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_5 .proc
; X*4 + X
@ -286,7 +286,7 @@ mul_word_5 .proc
rts
.pend
mul_byte_6 .proc
; (X*2 + X)*2
lda c64.ESTACK_LO+1,x
@ -327,7 +327,7 @@ mul_byte_7 .proc
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_7 .proc
; W*8 - W
lda c64.ESTACK_HI+1,x
@ -411,7 +411,7 @@ mul_word_10 .proc
sta c64.ESTACK_HI+1,x
rts
.pend
mul_byte_11 .proc
; (X*2 + X)*4 - X
lda c64.ESTACK_LO+1,x
@ -488,7 +488,7 @@ mul_byte_14 .proc
sta c64.ESTACK_LO+1,x
rts
.pend
; mul_word_14 is skipped (too much code)
mul_byte_15 .proc
@ -604,7 +604,7 @@ mul_word_25 .proc
adc c64.ESTACK_HI+1,x
sta c64.ESTACK_HI+1,x
rts
.pend
.pend
mul_byte_40 .proc
; (X*4 + X)*8
@ -619,7 +619,7 @@ mul_byte_40 .proc
sta c64.ESTACK_LO+1,x
rts
.pend
mul_word_40 .proc
; (W*4 + W)*8
lda c64.ESTACK_HI+1,x
@ -680,3 +680,192 @@ _sign_possibly_zero lda c64.ESTACK_LO+1,x
sta c64.ESTACK_LO+1,x
rts
.pend
; bit shifts.
; anything below 3 is done inline. anything above 7 is done via other optimizations.
shift_left_w_7 .proc
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
asl a
rol c64.SCRATCH_ZPB1
_shift6 asl a
rol c64.SCRATCH_ZPB1
_shift5 asl a
rol c64.SCRATCH_ZPB1
_shift4 asl a
rol c64.SCRATCH_ZPB1
_shift3 asl a
rol c64.SCRATCH_ZPB1
asl a
rol c64.SCRATCH_ZPB1
asl a
rol c64.SCRATCH_ZPB1
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPB1
sta c64.ESTACK_HI+1,x
rts
.pend
shift_left_w_6 .proc
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
jmp shift_left_w_7._shift6
.pend
shift_left_w_5 .proc
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
jmp shift_left_w_7._shift5
.pend
shift_left_w_4 .proc
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
jmp shift_left_w_7._shift4
.pend
shift_left_w_3 .proc
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_LO+1,x
jmp shift_left_w_7._shift3
.pend
shift_right_uw_7 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_HI+1,x
lsr a
ror c64.SCRATCH_ZPB1
_shift6 lsr a
ror c64.SCRATCH_ZPB1
_shift5 lsr a
ror c64.SCRATCH_ZPB1
_shift4 lsr a
ror c64.SCRATCH_ZPB1
_shift3 lsr a
ror c64.SCRATCH_ZPB1
lsr a
ror c64.SCRATCH_ZPB1
lsr a
ror c64.SCRATCH_ZPB1
sta c64.ESTACK_HI+1,x
lda c64.SCRATCH_ZPB1
sta c64.ESTACK_LO+1,x
rts
.pend
shift_right_uw_6 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_HI+1,x
jmp shift_right_uw_7._shift6
.pend
shift_right_uw_5 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_HI+1,x
jmp shift_right_uw_7._shift5
.pend
shift_right_uw_4 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_HI+1,x
jmp shift_right_uw_7._shift4
.pend
shift_right_uw_3 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPB1
lda c64.ESTACK_HI+1,x
jmp shift_right_uw_7._shift3
.pend
shift_right_w_7 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
_shift6 asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
_shift5 asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
_shift4 asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
_shift3 asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1+1
asl a
ror c64.SCRATCH_ZPWORD1+1
ror c64.SCRATCH_ZPWORD1
lda c64.SCRATCH_ZPWORD1
sta c64.ESTACK_LO+1,x
lda c64.SCRATCH_ZPWORD1+1
sta c64.ESTACK_HI+1,x
rts
.pend
shift_right_w_6 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
jmp shift_right_w_7._shift6
.pend
shift_right_w_5 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
jmp shift_right_w_7._shift5
.pend
shift_right_w_4 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
jmp shift_right_w_7._shift4
.pend
shift_right_w_3 .proc
lda c64.ESTACK_LO+1,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+1,x
sta c64.SCRATCH_ZPWORD1+1
jmp shift_right_w_7._shift3
.pend

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
@ -1577,96 +1589,182 @@ _work3 .word 0
reverse_b .proc
; --- reverse an array of bytes (in-place)
; inputs: pointer to array in c64.SCRATCH_ZPWORD1, length in A
_left_index = c64.SCRATCH_ZPWORD2
_right_index = c64.SCRATCH_ZPWORD2+1
pha
_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 _left_index
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
tay
_loop sty c64.SCRATCH_ZPREG
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
pla
ldy _right_index
sta (c64.SCRATCH_ZPWORD1),y
inc _left_index
inc _right_index
dec _left_index
ldy c64.SCRATCH_ZPREG
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
_left_index = c64.SCRATCH_ZPWORD2
_right_index = c64.SCRATCH_ZPWORD2+1
_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 _left_index
sta _index_first
lda #0
sta _right_index
sta _index_second
pla
lsr a
pha
tay
sta _loop_count
; first reverse the lsbs
_loop_lo sty c64.SCRATCH_ZPREG
ldy _left_index
_loop_lo ldy _index_first
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _right_index
ldy _index_second
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
ldy _index_first
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _right_index
ldy _index_second
sta (c64.SCRATCH_ZPWORD1),y
inc _right_index
inc _right_index
dec _left_index
dec _left_index
ldy c64.SCRATCH_ZPREG
dey
inc _index_second
inc _index_second
dec _index_first
dec _index_first
dec _loop_count
bne _loop_lo
; now reverse the msbs
dec _right_index
inc _left_index
inc _left_index
inc _left_index
dec _index_second
inc _index_first
inc _index_first
inc _index_first
pla
tay
_loop_hi sty c64.SCRATCH_ZPREG
ldy _left_index
sta _loop_count
_loop_hi ldy _index_first
lda (c64.SCRATCH_ZPWORD1),y
pha
ldy _right_index
ldy _index_second
lda (c64.SCRATCH_ZPWORD1),y
ldy _left_index
ldy _index_first
sta (c64.SCRATCH_ZPWORD1),y
pla
ldy _right_index
ldy _index_second
sta (c64.SCRATCH_ZPWORD1),y
dec _right_index
dec _right_index
inc _left_index
inc _left_index
ldy c64.SCRATCH_ZPREG
dey
dec _index_second
dec _index_second
inc _index_first
inc _index_first
dec _loop_count
bne _loop_hi
rts
@ -1690,7 +1788,6 @@ ror2_mem_ub .proc
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
@ -1705,57 +1802,403 @@ rol2_mem_ub .proc
.pend
lsl_array_b .proc
.warn "lsl_array_b" ; TODO
.pend
lsl_array_w .proc
.warn "lsl_array_w" ; TODO
; -- lsl a (u)byte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsr_array_ub .proc
.warn "lsr_array_ub" ; TODO
; -- lsr a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
lsr a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsr_array_b .proc
.warn "lsr_array_b" ; TODO
; -- lsr a byte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsl_array_w .proc
; -- lsl a (u)word in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
sta (c64.SCRATCH_ZPWORD1),y
iny
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsr_array_uw .proc
.warn "lsr_array_uw" ; TODO
; -- lsr a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
lsr a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
lsr_array_w .proc
.warn "lsr_array_w" ; TODO
; -- lsr a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
asl a
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol_array_ub .proc
.warn "rol_array_ub" ; TODO
; -- rol a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.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
; -- ror a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
ror2_array_ub .proc
.warn "ror2_array_ub" ; TODO
; -- ror2 (8-bit ror) a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
lsr a
bcc +
ora #$80
+ sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol2_array_ub .proc
; -- rol2 (8-bit rol) a ubyte in an array (index and array address on stack)
inx
ldy c64.ESTACK_LO,x
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
cmp #$80
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
ror_array_uw .proc
; -- ror a uword in an array (index and array address on stack)
php
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
plp
ror a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol_array_uw .proc
; -- rol a uword in an array (index and array address on stack)
php
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
plp
rol a
sta (c64.SCRATCH_ZPWORD1),y
iny
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
rts
.pend
rol2_array_uw .proc
; -- rol2 (16-bit rol) a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda (c64.SCRATCH_ZPWORD1),y
asl a
sta (c64.SCRATCH_ZPWORD1),y
iny
lda (c64.SCRATCH_ZPWORD1),y
rol a
sta (c64.SCRATCH_ZPWORD1),y
bcc +
dey
lda (c64.SCRATCH_ZPWORD1),y
adc #0
sta (c64.SCRATCH_ZPWORD1),y
+ rts
.pend
ror2_array_uw .proc
.warn "ror2_array_uw" ; TODO
; -- ror2 (16-bit ror) a uword in an array (index and array address on stack)
inx
lda c64.ESTACK_LO,x
asl a
tay
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
iny
lda (c64.SCRATCH_ZPWORD1),y
lsr a
sta (c64.SCRATCH_ZPWORD1),y
dey
lda (c64.SCRATCH_ZPWORD1),y
ror a
sta (c64.SCRATCH_ZPWORD1),y
bcc +
iny
lda (c64.SCRATCH_ZPWORD1),y
ora #$80
sta (c64.SCRATCH_ZPWORD1),y
+ rts
.pend
strcpy .proc
; copy a string (0-terminated) from A/Y to (ZPWORD1)
; it is assumed the target string is large enough.
sta c64.SCRATCH_ZPWORD2
sty c64.SCRATCH_ZPWORD2+1
ldy #$ff
- iny
lda (c64.SCRATCH_ZPWORD2),y
sta (c64.SCRATCH_ZPWORD1),y
bne -
rts
.pend
func_leftstr .proc
; leftstr(source, target, length) with params on stack
inx
lda c64.ESTACK_LO,x
tay ; length
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD2+1
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
lda #0
sta (c64.SCRATCH_ZPWORD2),y
- dey
cpy #$ff
bne +
rts
+ lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
jmp -
.pend
func_rightstr .proc
; rightstr(source, target, length) with params on stack
; make place for the 4 parameters for substr()
dex
dex
dex
dex
; X-> .
; x+1 -> length of segment
; x+2 -> start index
; X+3 -> target LO+HI
; X+4 -> source LO+HI
; original parameters:
; x+5 -> original length LO
; x+6 -> original targetLO + HI
; x+7 -> original sourceLO + HI
; replicate paramters:
lda c64.ESTACK_LO+5,x
sta c64.ESTACK_LO+1,x
lda c64.ESTACK_LO+6,x
sta c64.ESTACK_LO+3,x
lda c64.ESTACK_HI+6,x
sta c64.ESTACK_HI+3,x
lda c64.ESTACK_LO+7,x
sta c64.ESTACK_LO+4,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI+7,x
sta c64.ESTACK_HI+4,x
sta c64.SCRATCH_ZPWORD1+1
; determine string length
ldy #0
- lda (c64.SCRATCH_ZPWORD1),y
beq +
iny
bne -
+ tya
sec
sbc c64.ESTACK_LO+1,x ; start index = strlen - segment length
sta c64.ESTACK_LO+2,x
jsr func_substr
; unwind original params
inx
inx
inx
rts
.pend
func_substr .proc
; substr(source, target, start, length) with params on stack
inx
ldy c64.ESTACK_LO,x ; length
inx
lda c64.ESTACK_LO,x ; start
sta c64.SCRATCH_ZPB1
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD2
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD2+1
inx
lda c64.ESTACK_LO,x
sta c64.SCRATCH_ZPWORD1
lda c64.ESTACK_HI,x
sta c64.SCRATCH_ZPWORD1+1
; adjust src location
clc
lda c64.SCRATCH_ZPWORD1
adc c64.SCRATCH_ZPB1
sta c64.SCRATCH_ZPWORD1
bcc +
inc c64.SCRATCH_ZPWORD1+1
+ lda #0
sta (c64.SCRATCH_ZPWORD2),y
jmp _startloop
- lda (c64.SCRATCH_ZPWORD1),y
sta (c64.SCRATCH_ZPWORD2),y
_startloop dey
cpy #$ff
bne -
rts
.pend

View File

@ -1 +1 @@
1.62
2.2

View File

@ -4,8 +4,12 @@ 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.StandardWatchEventKinds
@ -31,13 +35,12 @@ fun pathFrom(stringPath: String, vararg rest: String): Path = FileSystems.getDe
private fun compileMain(args: Array<String>) {
val cli = CommandLineInterface("prog8compiler")
val startEmulator1 by cli.flagArgument("-emu", "auto-start the 'x64' C-64 emulator after successful compilation")
val startEmulator2 by cli.flagArgument("-emu2", "auto-start the 'x64sc' C-64 emulator after successful compilation")
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 prog8 virtual machine/simulator after compilation")
val watchMode by cli.flagArgument("-watch", "continuous compilation mode (watches for file changes)")
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 {
@ -46,6 +49,26 @@ private fun compileMain(args: Array<String>) {
exitProcess(1)
}
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")
@ -94,22 +117,24 @@ private fun compileMain(args: Array<String>) {
exitProcess(1)
}
if (launchSimulator) {
println("\nLaunching AST-based simulator...")
val vm = AstVm(compilationResult.programAst)
vm.run()
}
if (startEmulator1 || startEmulator2) {
if (startEmulator) {
if (compilationResult.programName.isEmpty())
println("\nCan't start emulator because no program was assembled.")
else {
val emulator = if(startEmulator1) "x64" else "x64sc"
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 process = ProcessBuilder(cmdline).inheritIO().start()
process.waitFor()
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
}
}
}
}

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["
@ -103,6 +102,12 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(decl: VarDecl) {
// if the vardecl is a parameter of a subroutine, don't output it again
val paramNames = (decl.definingScope() as? Subroutine)?.parameters?.map { it.name }
if(paramNames!=null && decl.name in paramNames)
return
when(decl.type) {
VarDeclType.VAR -> {}
VarDeclType.CONST -> output("const ")
@ -178,8 +183,6 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
private fun outputStatements(statements: List<Statement>) {
for(stmt in statements) {
if(stmt is VarDecl && stmt.autogeneratedDontRemove)
continue // skip autogenerated decls (to avoid generating a newline)
outputi("")
stmt.accept(this)
output("\n")
@ -197,9 +200,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(")")
@ -284,16 +287,8 @@ class AstToSourceCode(val output: (text: String) -> Unit, val program: Program):
}
override fun visit(assignment: Assignment) {
if(assignment is VariableInitializationAssignment) {
val targetVar = assignment.target.identifier?.targetVarDecl(program.namespace)
if(targetVar?.struct != null) {
// skip STRUCT init assignments
return
}
}
assignment.target.accept(this)
if (assignment.aug_op != null)
if (assignment.aug_op != null && assignment.aug_op != "setvalue")
output(" ${assignment.aug_op} ")
else
output(" = ")
@ -332,6 +327,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)

View File

@ -3,6 +3,8 @@ package prog8.ast
import prog8.ast.base.*
import prog8.ast.expressions.Expression
import prog8.ast.expressions.IdentifierReference
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
import java.nio.file.Path
@ -33,11 +35,13 @@ interface Node {
return this
throw FatalAstException("scope missing from $this")
}
fun replaceChildNode(node: Node, replacement: Node)
}
interface IFunctionCall {
var target: IdentifierReference
var arglist: MutableList<Expression>
var args: MutableList<Expression>
}
interface INameScope {
@ -48,32 +52,31 @@ interface INameScope {
fun linkParents(parent: Node)
fun subScopes(): Map<String, INameScope> {
val subscopes = mutableMapOf<String, INameScope>()
fun subScope(name: String): INameScope? {
for(stmt in statements) {
when(stmt) {
// NOTE: if other nodes are introduced that are a scope, or contain subscopes, they must be added here!
is ForLoop -> subscopes[stmt.body.name] = stmt.body
is RepeatLoop -> subscopes[stmt.body.name] = stmt.body
is WhileLoop -> subscopes[stmt.body.name] = stmt.body
is ForLoop -> if(stmt.body.name==name) return stmt.body
is RepeatLoop -> if(stmt.body.name==name) return stmt.body
is WhileLoop -> if(stmt.body.name==name) return stmt.body
is BranchStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
}
is IfStatement -> {
subscopes[stmt.truepart.name] = stmt.truepart
if(stmt.elsepart.containsCodeOrVars())
subscopes[stmt.elsepart.name] = stmt.elsepart
if(stmt.truepart.name==name) return stmt.truepart
if(stmt.elsepart.containsCodeOrVars() && stmt.elsepart.name==name) return stmt.elsepart
}
is WhenStatement -> {
stmt.choices.forEach { subscopes[it.statements.name] = it.statements }
val scope = stmt.choices.firstOrNull { it.statements.name==name }
if(scope!=null)
return scope.statements
}
is INameScope -> subscopes[stmt.name] = stmt
is INameScope -> if(stmt.name==name) return stmt
else -> {}
}
}
return subscopes
return null
}
fun getLabelOrVariable(name: String): Statement? {
@ -121,7 +124,7 @@ interface INameScope {
for(module in localContext.definingModule().program.modules) {
var scope: INameScope? = module
for(name in scopedName.dropLast(1)) {
scope = scope?.subScopes()?.get(name)
scope = scope?.subScope(name)
if(scope==null)
break
}
@ -129,7 +132,7 @@ interface INameScope {
val result = scope.getLabelOrVariable(scopedName.last())
if(result!=null)
return result
return scope.subScopes()[scopedName.last()] as Statement?
return scope.subScope(scopedName.last()) as Statement?
}
}
return null
@ -141,7 +144,7 @@ interface INameScope {
val result = localScope.getLabelOrVariable(scopedName[0])
if (result != null)
return result
val subscope = localScope.subScopes()[scopedName[0]] as Statement?
val subscope = localScope.subScope(scopedName[0]) as Statement?
if (subscope != null)
return subscope
// not found in this scope, look one higher up
@ -158,6 +161,31 @@ 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) }
else -> { /* do nothing */ }
}
}
}
find(this)
return result
}
}
interface IAssignable {
@ -167,7 +195,7 @@ interface IAssignable {
/*********** Everything starts from here, the Program; zero or more modules *************/
class Program(val name: String, val modules: MutableList<Module>) {
class Program(val name: String, val modules: MutableList<Module>): Node {
val namespace = GlobalNamespace(modules)
val definedLoadAddress: Int
@ -182,11 +210,29 @@ class Program(val name: String, val modules: MutableList<Module>) {
return if(mainBlocks.isEmpty()) {
null
} else {
mainBlocks[0].subScopes()["start"] as Subroutine?
mainBlocks[0].subScope("start") as Subroutine?
}
}
fun allBlocks(): List<Block> = modules.flatMap { it.statements.filterIsInstance<Block>() }
override val position: Position = Position.DUMMY
override var parent: Node
get() = throw FatalAstException("program has no parent")
set(value) = throw FatalAstException("can't set parent of program")
override fun linkParents(parent: Node) {
modules.forEach {
it.linkParents(this)
}
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Module && replacement is Module)
val idx = modules.indexOf(node)
modules[idx] = replacement
replacement.parent = this
}
}
class Module(override val name: String,
@ -194,6 +240,7 @@ class Module(override val name: String,
override val position: Position,
val isLibraryModule: Boolean,
val source: Path) : Node, INameScope {
override lateinit var parent: Node
lateinit var program: Program
val importedBy = mutableListOf<Module>()
@ -207,10 +254,20 @@ class Module(override val name: String,
}
override fun definingScope(): INameScope = program.namespace
override fun replaceChildNode(node: Node, replacement: Node) {
require(node is Statement && replacement is Statement)
val idx = statements.indexOf(node)
statements[idx] = replacement
replacement.parent = this
}
override fun toString() = "Module(name=$name, pos=$position, lib=$isLibraryModule)"
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class GlobalNamespace(val modules: List<Module>): Node, INameScope {
override val name = "<<<global>>>"
override val position = Position("<<<global>>>", 0, 0, 0)
@ -221,6 +278,10 @@ class GlobalNamespace(val modules: List<Module>): Node, INameScope {
modules.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("cannot replace anything in the namespace")
}
override fun lookup(scopedName: List<String>, localContext: Node): Statement? {
if (scopedName.size == 1 && scopedName[0] in BuiltinFunctions) {
// builtin functions always exist, return a dummy localContext for them
@ -243,11 +304,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 for $scopedName: $stmt", stmt.position)
}
}
}

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,10 +452,13 @@ 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 -> StringLiteralValue(DataType.STR, unescape(litval.stringliteral().text, litval.toPosition()), 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())
}
@ -441,7 +467,7 @@ private fun prog8Parser.ExpressionContext.toAst() : Expression {
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.
ArrayLiteralValue(DataType.ARRAY_UB, 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,7 +571,6 @@ 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 loopvar = identifier()?.toAst()
@ -562,12 +583,10 @@ private fun prog8Parser.ForloopContext.toAst(): ForLoop {
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())
@ -576,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()
@ -641,4 +666,3 @@ internal fun unescape(str: String, position: Position): String {
}
return result.joinToString("")
}

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
@ -24,15 +24,14 @@ enum class DataType {
* is the type assignable to the given other type?
*/
infix fun isAssignableTo(targetType: DataType) =
// what types are assignable to others without loss of precision?
// what types are assignable to others, perhaps via a typecast, without loss of precision?
when(this) {
UBYTE -> targetType in setOf(UBYTE, WORD, UWORD, FLOAT)
BYTE -> targetType in setOf(BYTE, WORD, FLOAT)
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)
@ -146,8 +150,15 @@ object ParentSentinel : Node {
override val position = Position("<<sentinel>>", 0, 0, 0)
override var parent: Node = this
override fun linkParents(parent: Node) {}
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
}
data class Position(val file: String, val line: Int, val startCol: Int, val endCol: Int) {
override fun toString(): String = "[$file: line $line col ${startCol+1}-${endCol+1}]"
companion object {
val DUMMY = Position("<dummy>", 0, 0, 0)
}
}

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>()
System.err.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
}
System.err.print("\u001b[0m") // reset color
if(reportedMessages.isNotEmpty())
throw ParsingFailedError("There are ${reportedMessages.size} errors in module '$moduleName'.")
}
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 printWarning(msg: String) {
print("\u001b[93m") // bright yellow
print("Warning: $msg")
print("\u001b[0m\n") // normal
private class CompilerMessage(val severity: MessageSeverity, val message: String, val position: Position)
private val messages = mutableListOf<CompilerMessage>()
private val alreadyReportedMessages = mutableSetOf<String>()
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 isEmpty() = messages.isEmpty()
}

View File

@ -2,21 +2,17 @@ package prog8.ast.base
import prog8.ast.expressions.IdentifierReference
class FatalAstException (override var message: String) : Exception(message)
open 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)

View File

@ -4,67 +4,74 @@ import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.processing.*
import prog8.compiler.CompilationOptions
import prog8.compiler.target.c64.codegen.AnonymousScopeVarsCleanup
import prog8.optimizer.FlattenAnonymousScopesAndRemoveNops
import prog8.compiler.BeforeAsmGenerationAstChanger
import prog8.optimizer.AssignmentTransformer
import prog8.optimizer.FlattenAnonymousScopesAndNopRemover
// the name of the subroutine that should be called for every block to initialize its variables
internal const val initvarsSubName="prog8_init_vars"
internal fun Program.removeNopsFlattenAnonScopes() {
val flattener = FlattenAnonymousScopesAndRemoveNops()
flattener.visit(this)
}
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)
mover.visit(this)
printErrors(mover.result(), name)
internal fun Program.processAstBeforeAsmGeneration(errors: ErrorReporter) {
val fixer = BeforeAsmGenerationAstChanger(this, errors)
fixer.visit(this)
fixer.applyModifications()
}
internal fun Program.reorderStatements() {
val initvalueCreator = VarInitValueAndAddressOfCreator(this)
initvalueCreator.visit(this)
val checker = StatementReorderer(this)
checker.visit(this)
val reorder = StatementReorderer(this)
reorder.visit(this)
reorder.applyModifications()
}
internal fun Program.addTypecasts() {
val caster = TypecastsAdder(this)
internal fun Program.addTypecasts(errors: ErrorReporter) {
val caster = TypecastsAdder(this, errors)
caster.visit(this)
caster.applyModifications()
}
internal fun Program.transformAssignments(errors: ErrorReporter) {
val transform = AssignmentTransformer(this, errors)
transform.visit(this)
while(transform.optimizationsDone>0 && errors.isEmpty()) {
transform.applyModifications()
transform.optimizationsDone = 0
transform.visit(this)
}
transform.applyModifications()
}
internal fun Module.checkImportedValid() {
val checker = ImportedModuleDirectiveRemover()
checker.visit(this)
printErrors(checker.result(), name)
val imr = ImportedModuleDirectiveRemover()
imr.visit(this, this.parent)
imr.applyModifications()
}
internal fun Program.checkRecursion() {
val checker = AstRecursionChecker(namespace)
internal fun Program.checkRecursion(errors: ErrorReporter) {
val checker = AstRecursionChecker(namespace, errors)
checker.visit(this)
printErrors(checker.result(), name)
checker.processMessages(name)
}
internal fun Program.checkIdentifiers(errors: ErrorReporter) {
internal fun Program.checkIdentifiers() {
val checker = AstIdentifiersChecker(this)
checker.visit(this)
val checker2 = AstIdentifiersChecker(this, errors)
checker2.visit(this)
if(modules.map {it.name}.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
if(errors.isEmpty()) {
val transforms = AstVariousTransforms(this)
transforms.visit(this)
transforms.applyModifications()
}
printErrors(checker.result(), name)
if (modules.map { it.name }.toSet().size != modules.size) {
throw FatalAstException("modules should all be unique")
}
}
internal fun Program.removeNopsFlattenAnonScopes() {
val flattener = FlattenAnonymousScopesAndNopRemover()
flattener.visit(this)
}

View File

@ -3,17 +3,15 @@ package prog8.ast.expressions
import prog8.ast.*
import prog8.ast.antlr.escape
import prog8.ast.base.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.AstWalker
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.target.c64.Petscii
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import prog8.functions.CannotEvaluateException
import prog8.functions.NotConstArgumentException
import prog8.functions.builtinFunctionReturnType
import java.util.Objects
import java.util.*
import kotlin.math.abs
@ -22,8 +20,8 @@ val associativeOperators = setOf("+", "*", "&", "|", "^", "or", "and", "xor", "=
sealed class Expression: Node {
abstract fun constValue(program: Program): NumericLiteralValue?
abstract fun accept(visitor: IAstModifyingVisitor): Expression
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
abstract fun referencesIdentifiers(vararg name: String): Boolean // todo: remove this and add identifier usage tracking into CallGraph instead
abstract fun inferType(program: Program): InferredTypes.InferredType
@ -59,11 +57,38 @@ class PrefixExpression(val operator: String, var expression: Expression, overrid
expression.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(node === expression && replacement is Expression)
expression = replacement
replacement.parent = this
}
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 accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType = 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)"
@ -79,6 +104,16 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
right.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
when {
node===left -> left = replacement
node===right -> right = replacement
else -> throw FatalAstException("invalid replace, no child $node")
}
replacement.parent = this
}
override fun toString(): String {
return "[$left $operator $right]"
}
@ -86,8 +121,9 @@ class BinaryExpression(var left: Expression, var operator: String, var right: Ex
// binary expression should actually have been optimized away into a single value, before const value was requested...
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 accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = left.referencesIdentifiers(*name) || right.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType {
val leftDt = left.inferType(program)
@ -188,16 +224,26 @@ class ArrayIndexedExpression(var identifier: IdentifierReference,
arrayspec.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===identifier -> identifier = replacement as IdentifierReference
node===arrayspec.index -> arrayspec.index = replacement as Expression
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
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 accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = identifier.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType {
val target = identifier.targetStatement(program.namespace)
if (target is VarDecl) {
return when (target.datatype) {
in StringDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(target.datatype))
else -> InferredTypes.unknown()
}
@ -218,8 +264,15 @@ class TypecastExpression(var expression: Expression, var type: DataType, val imp
expression.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===expression)
expression = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = expression.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun constValue(program: Program): NumericLiteralValue? {
@ -242,11 +295,17 @@ data class AddressOf(var identifier: IdentifierReference, override val position:
identifier.parent=this
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is IdentifierReference && node===identifier)
identifier = replacement
replacement.parent = this
}
override fun constValue(program: Program): NumericLiteralValue? = null
override fun referencesIdentifiers(vararg name: String) = false
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)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
}
class DirectMemoryRead(var addressExpression: Expression, override val position: Position) : Expression(), IAssignable {
@ -257,8 +316,15 @@ class DirectMemoryRead(var addressExpression: Expression, override val position:
this.addressExpression.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression)
addressExpression = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = false
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.UBYTE)
override fun constValue(program: Program): NumericLiteralValue? = null
@ -302,17 +368,21 @@ 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
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun referencesIdentifiers(vararg name: String) = false
override fun constValue(program: Program) = this
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "NumericLiteral(${type.name}:$number)"
@ -396,9 +466,14 @@ class StructLiteralValue(var values: List<Expression>,
values.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
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 accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String) = values.any { it.referencesIdentifiers(*name) }
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(DataType.STRUCT)
@ -407,59 +482,66 @@ class StructLiteralValue(var values: List<Expression>,
}
}
class StringLiteralValue(val type: DataType, // only string types
val value: String,
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
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
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 accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "'${escape(value)}'"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
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, type)
override fun hashCode(): Int = Objects.hash(value, altEncoding)
override fun equals(other: Any?): Boolean {
if(other==null || other !is StringLiteralValue)
return false
return value==other.value && type==other.type
}
var heapId: Int? = null
private set
fun addToHeap() {
if(heapId==null)
heapId = ++heapIdSequence
return value==other.value && altEncoding == other.altEncoding
}
}
private var heapIdSequence = 0
class ArrayLiteralValue(val type: DataType, // only array types
class ArrayLiteralValue(val type: InferredTypes.InferredType, // inferred because not all array literals hava a known type yet
val value: Array<Expression>,
initHeapId: Int? =null,
override val position: Position) : Expression() {
override lateinit var parent: Node
var heapId = initHeapId
private set
val heapId = ++heapIdSequence
override fun linkParents(parent: Node) {
this.parent = parent
value.forEach {it.linkParents(this)}
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
val idx = value.indexOf(node)
value[idx] = replacement
replacement.parent = 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 accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun toString(): String = "$value"
override fun inferType(program: Program): InferredTypes.InferredType = InferredTypes.knownFor(type)
override fun inferType(program: Program): InferredTypes.InferredType = if(type.isKnown) 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 {
@ -468,17 +550,45 @@ class ArrayLiteralValue(val type: DataType, // only array types
return type==other.type && value.contentEquals(other.value)
}
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): ArrayLiteralValue? {
if(type==targettype)
if(type.istype(targettype))
return this
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
// an array of UWORDs could possibly also contain AddressOfs, other stuff can't be casted
if (elementType != DataType.UWORD || it !is AddressOf)
throw FatalAstException("weird array element $it")
return null
it
} else {
try {
@ -488,15 +598,10 @@ class ArrayLiteralValue(val type: DataType, // only array types
}
}
}.toTypedArray()
return ArrayLiteralValue(targettype, castArray, position = position)
return ArrayLiteralValue(InferredTypes.InferredType.known(targettype), castArray, position = position)
}
return null // invalid type conversion from $this to $targettype
}
fun addToHeap() {
if(heapId==null)
heapId = ++heapIdSequence
}
}
class RangeExpr(var from: Expression,
@ -512,9 +617,21 @@ class RangeExpr(var from: Expression,
step.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
when {
from===node -> from=replacement
to===node -> to=replacement
step===node -> step=replacement
else -> throw FatalAstException("invalid replacement")
}
replacement.parent = this
}
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 accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = from.referencesIdentifiers(*name) || to.referencesIdentifiers(*name)
override fun inferType(program: Program): InferredTypes.InferredType {
val fromDt=from.inferType(program)
@ -524,7 +641,6 @@ class RangeExpr(var from: Expression,
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.STR_S && toDt istype DataType.STR_S -> InferredTypes.knownFor(DataType.STR_S)
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)
@ -548,9 +664,9 @@ class RangeExpr(var from: Expression,
val fromString = from as? StringLiteralValue
val toString = to as? StringLiteralValue
if(fromString!=null && toString!=null ) {
// string range -> int range over petscii values
fromVal = Petscii.encodePetscii(fromString.value, true)[0].toInt()
toVal = Petscii.encodePetscii(toString.value, true)[0].toInt()
// 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
@ -587,9 +703,14 @@ class RegisterExpr(val register: Register, override val position: Position) : Ex
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
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 accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = register.name in name
override fun toString(): String {
return "RegisterExpr(register=$register, pos=$position)"
@ -614,6 +735,10 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace here")
}
override fun constValue(program: Program): NumericLiteralValue? {
val node = program.namespace.lookup(nameInSource, this)
?: throw UndefinedSymbolError(this)
@ -630,16 +755,17 @@ data class IdentifierReference(val nameInSource: List<String>, override val posi
return "IdentifierRef($nameInSource)"
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = nameInSource.last() in name
override fun inferType(program: Program): InferredTypes.InferredType {
val targetStmt = targetStatement(program.namespace)
if(targetStmt is VarDecl) {
return InferredTypes.knownFor(targetStmt.datatype)
return if(targetStmt is VarDecl) {
InferredTypes.knownFor(targetStmt.datatype)
} else {
throw FatalAstException("cannot get datatype from identifier reference ${this}, pos=$position")
InferredTypes.InferredType.unknown()
}
}
@ -650,22 +776,32 @@ 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 StringLiteralValue -> value.heapId ?: throw FatalAstException("string is not on the heap: $value")
is ArrayLiteralValue -> value.heapId ?: throw FatalAstException("array is not on the heap: $value")
is StringLiteralValue -> value.heapId
is ArrayLiteralValue -> value.heapId
else -> throw FatalAstException("requires a reference value")
}
}
}
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 replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target=replacement as IdentifierReference
else {
val idx = args.indexOf(node)
args[idx] = replacement as Expression
}
replacement.parent = this
}
override fun constValue(program: Program) = constValue(program, true)
@ -680,7 +816,7 @@ 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)
}
@ -698,15 +834,20 @@ class FunctionCall(override var target: IdentifierReference,
// const-evaluating the builtin function call failed.
return null
}
catch(x: CannotEvaluateException) {
// const-evaluating the builtin function call failed.
return null
}
}
override fun toString(): String {
return "FunctionCall(target=$target, pos=$position)"
}
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 accept(visitor: AstWalker, parent: Node)= visitor.visit(this, parent)
override fun referencesIdentifiers(vararg name: String): Boolean = target.referencesIdentifiers(*name) || args.any{it.referencesIdentifiers(*name)}
override fun inferType(program: Program): InferredTypes.InferredType {
val constVal = constValue(program ,false)
@ -719,7 +860,7 @@ class FunctionCall(override var target: IdentifierReference,
target.nameInSource[0] == "clear_carry" || target.nameInSource[0]=="clear_irqd") {
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())

View File

@ -1,7 +1,7 @@
package prog8.ast.expressions
import java.util.Objects
import prog8.ast.base.DataType
import java.util.*
object InferredTypes {
@ -46,7 +46,6 @@ object InferredTypes {
DataType.WORD to InferredType.known(DataType.WORD),
DataType.FLOAT to InferredType.known(DataType.FLOAT),
DataType.STR to InferredType.known(DataType.STR),
DataType.STR_S to InferredType.known(DataType.STR_S),
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),

File diff suppressed because it is too large Load Diff

View File

@ -1,113 +1,81 @@
package prog8.ast.processing
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.statements.*
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) : IAstVisitor {
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) {
vardeclsToAdd.clear()
blocks.clear() // blocks may be redefined within a different module
super.visit(module)
// add any new vardecls to the various scopes
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
}
override fun visit(block: Block): Statement {
override fun visit(block: Block) {
val existing = blocks[block.name]
if(existing!=null)
nameError(block.name, block.position, existing)
else
blocks[block.name] = block
return super.visit(block)
super.visit(block)
}
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)
typecast.linkParents(functionCall.parent)
return super.visit(typecast)
}
return super.visit(functionCall)
}
override fun visit(decl: VarDecl) {
decl.datatypeErrors.forEach { errors.err(it.message, it.position) }
override fun visit(decl: VarDecl): Statement {
// first, check if there are datatype errors on the vardecl
decl.datatypeErrors.forEach { checkResult.add(it) }
// 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.name}'", 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.
if(decl.datatype==DataType.STRUCT) {
if(decl.structHasBeenFlattened)
if (decl.structHasBeenFlattened)
return super.visit(decl) // don't do this multiple times
if(decl.struct==null) {
checkResult.add(NameError("undefined struct type", decl.position))
if (decl.struct == null) {
errors.err("undefined struct type", decl.position)
return super.visit(decl)
}
if(decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes})
if (decl.struct!!.statements.any { (it as VarDecl).datatype !in NumericDatatypes })
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))
if (decl.value is NumericLiteralValue) {
errors.err("you cannot initialize a struct using a single value", decl.position)
return super.visit(decl)
}
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
result.linkParents(decl.parent)
return result
if (decl.value != null && decl.value !is StructLiteralValue) {
errors.err("initializing requires struct literal value", decl.value?.position ?: decl.position)
return super.visit(decl)
}
}
val existing = program.namespace.lookup(listOf(decl.name), decl)
if (existing != null && existing !== decl)
nameError(decl.name, decl.position, existing)
return super.visit(decl)
super.visit(decl)
}
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.name}'", subroutine.position))
override fun visit(subroutine: Subroutine) {
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 })
@ -139,289 +107,65 @@ internal class AstIdentifiersChecker(private val program: Program) : IAstModifyi
nameError(name, sub.position, subroutine)
}
// inject subroutine params as local variables (if they're not there yet) (for non-kernel subroutines and non-asm parameters)
// 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)
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty()) {
subroutine.parameters
.filter { it.name !in namesInSub }
.forEach {
val vardecl = VarDecl(VarDeclType.VAR, it.type, ZeropageWish.NOT_IN_ZEROPAGE, null, it.name, null, null,
isArray = false, autogeneratedDontRemove = true, position = subroutine.position)
vardecl.linkParents(subroutine)
subroutine.statements.add(0, vardecl)
}
}
}
if(subroutine.isAsmSubroutine && subroutine.statements.any{it !is InlineAssembly}) {
checkResult.add(SyntaxError("asmsub can only contain inline assembly (%asm)", subroutine.position))
errors.err("asmsub can only contain inline assembly (%asm)", subroutine.position)
}
}
return super.visit(subroutine)
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.name}'", label.position))
override fun visit(label: Label) {
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)
}
return super.visit(label)
}
override fun visit(forLoop: ForLoop): Statement {
// If the for loop has a decltype, it means to declare the loopvar inside the loop body
// rather than reusing an already declared loopvar from an outer scope.
// 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.loopRegister == Register.X)
printWarning("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 validName = forLoop.body.name.replace("<", "").replace(">", "").replace("-", "")
val loopvarName = "prog8_loopvar_$validName"
if (forLoop.iterable !is RangeExpr) {
val existing = if (forLoop.body.containsNoCodeNorVars()) null else forLoop.body.lookup(listOf(loopvarName), forLoop.body.statements.first())
if (existing == null) {
// create loop iteration counter variable (without value, to avoid an assignment)
val vardecl = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.PREFER_ZEROPAGE, null, loopvarName, 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'
}
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(forLoop)
super.visit(label)
}
override fun visit(assignTarget: AssignTarget): AssignTarget {
override fun visit(forLoop: ForLoop) {
if (forLoop.loopRegister != null) {
if (forLoop.loopRegister == Register.X)
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", forLoop.position)
}
super.visit(forLoop)
}
override fun visit(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)
return super.visit(assignTarget)
errors.warn("writing to the X register is dangerous, because it's used as an internal pointer", assignTarget.position)
super.visit(assignTarget)
}
override fun visit(returnStmt: Return): Statement {
if(returnStmt.value!=null) {
// possibly adjust any literal values returned, into the desired returning data type
val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size!=1)
return returnStmt // mismatch in number of return values, error will be printed later.
val lval = returnStmt.value as? NumericLiteralValue
returnStmt.value = lval?.cast(subroutine.returntypes.single()) ?: returnStmt.value!!
}
return super.visit(returnStmt)
override fun visit(string: StringLiteralValue) {
if (string.value.length !in 1..255)
errors.err("string literal length must be between 1 and 255", string.position)
super.visit(string)
}
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
val vardecl = array.parent as? VarDecl
return when {
vardecl!=null -> fixupArrayDatatype(array, vardecl, program)
array.heapId!=null -> {
// 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(array.value)
val litval2 = array.cast(datatype)!!
litval2.parent = array.parent
// finally, replace the literal array by a identifier reference.
makeIdentifierFromRefLv(litval2)
}
else -> array
}
}
return array
}
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)
checkResult.add(ExpressionError("string literal length must be between 1 and 255", string.position))
else {
string.addToHeap()
}
return if (vardecl != null)
string
else
makeIdentifierFromRefLv(string) // replace the literal string by a identifier reference.
}
return string
}
private fun determineArrayDt(array: Array<Expression>): DataType {
val datatypesInArray = array.map { it.inferType(program) }
require(datatypesInArray.isNotEmpty() && datatypesInArray.all { it.isKnown }) { "can't determine type of empty array" }
val dts = datatypesInArray.map { it.typeOrElse(DataType.STRUCT) }
return when {
DataType.FLOAT in dts -> DataType.ARRAY_F
DataType.WORD in dts -> DataType.ARRAY_W
DataType.UWORD in dts -> DataType.ARRAY_UW
DataType.BYTE in dts -> DataType.ARRAY_B
DataType.UBYTE in dts -> DataType.ARRAY_UB
else -> throw IllegalArgumentException("can't determine type of array")
}
}
private fun makeIdentifierFromRefLv(array: ArrayLiteralValue): IdentifierReference {
// a referencetype literal value that's not declared as a variable
// 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.
array.addToHeap()
val scope = array.definingScope()
val variable = VarDecl.createAuto(array)
return replaceWithIdentifier(variable, scope, array.parent)
}
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.
string.addToHeap()
val scope = string.definingScope()
val variable = VarDecl.createAuto(string)
return replaceWithIdentifier(variable, scope, string.parent)
}
private fun replaceWithIdentifier(variable: VarDecl, scope: INameScope, parent: Node): IdentifierReference {
val variable1 = addVarDecl(scope, variable)
// replace the reference literal by a identifier reference
val identifier = IdentifierReference(listOf(variable1.name), variable1.position)
identifier.parent = parent
return identifier
}
override fun visit(structDecl: StructDecl): Statement {
override fun visit(structDecl: StructDecl) {
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)
super.visit(structDecl)
}
override fun visit(expr: BinaryExpression): Expression {
return when {
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 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
val idt = string.inferType(program)
return StringLiteralValue(idt.typeOrElse(DataType.STR),
string.value.repeat(constvalue.number.toInt()), expr.position)
}
}
if(expr.operator == "+" && operand is StringLiteralValue) {
// concatenate two strings
val idt = string.inferType(program)
return StringLiteralValue(idt.typeOrElse(DataType.STR),
"${string.value}${operand.value}", expr.position)
}
return expr
}
private fun addVarDecl(scope: INameScope, variable: VarDecl): VarDecl {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
val existing = declList.singleOrNull { it.name==variable.name }
return if(existing!=null) {
existing
} else {
declList.add(variable)
variable
}
}
}
internal fun fixupArrayDatatype(array: ArrayLiteralValue, program: Program): ArrayLiteralValue {
val dts = array.value.map {it.inferType(program).typeOrElse(DataType.STRUCT)}.toSet()
if(dts.any { it !in NumericDatatypes }) {
return array
}
val dt = when {
DataType.FLOAT in dts -> DataType.ARRAY_F
DataType.WORD in dts -> DataType.ARRAY_W
DataType.UWORD in dts -> DataType.ARRAY_UW
DataType.BYTE in dts -> DataType.ARRAY_B
else -> DataType.ARRAY_UB
}
if(dt==array.type)
return array
// convert values and array type
val elementType = ArrayElementTypes.getValue(dt)
val values = array.value.map { (it as NumericLiteralValue).cast(elementType) as Expression}.toTypedArray()
val array2 = ArrayLiteralValue(dt, values, array.heapId, array.position)
array2.linkParents(array.parent)
return array2
}
internal fun fixupArrayDatatype(array: ArrayLiteralValue, vardecl: VarDecl, program: Program): ArrayLiteralValue {
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 {
val result = array.cast(vardecl.datatype)
if(result==null) {
val constElements = array.value.mapNotNull { it.constValue(program) }
val elementDts = constElements.map { it.type }
if(DataType.FLOAT in elementDts) {
array.cast(DataType.ARRAY_F) ?: ArrayLiteralValue(DataType.ARRAY_F, array.value, array.heapId, array.position)
} else {
val numbers = constElements.map { it.number.toInt() }
val minValue = numbers.min()!!
val maxValue = numbers.max()!!
if (minValue >= 0) {
// only positive values, so uword or ubyte
val dt = if(maxValue<256) DataType.ARRAY_UB else DataType.ARRAY_UW
array.cast(dt) ?: ArrayLiteralValue(dt, array.value, array.heapId, array.position)
} else {
// negative value present, so word or byte
val dt = if(minValue >= -128 && maxValue<=127) DataType.ARRAY_B else DataType.ARRAY_W
array.cast(dt) ?: ArrayLiteralValue(dt, array.value, array.heapId, array.position)
}
}
}
else result
} catch(x: ExpressionError) {
// couldn't cast permanently.
// instead, simply adjust the array type and trust the AstChecker to report the exact error
ArrayLiteralValue(vardecl.datatype, array.value, array.heapId, array.position)
}
vardecl.value = litval2
litval2.linkParents(vardecl)
litval2.addToHeap()
return litval2
}
}
return array
}

View File

@ -1,21 +1,23 @@
package prog8.ast.processing
import prog8.ast.INameScope
import prog8.ast.base.AstException
import prog8.ast.base.ErrorReporter
import prog8.ast.base.Position
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", Position.DUMMY)
}
override fun visit(functionCallStatement: FunctionCallStatement) {
@ -44,7 +46,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>()

View File

@ -0,0 +1,133 @@
package prog8.ast.processing
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
internal class AstVariousTransforms(private val program: Program) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
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.args.single(), DataType.UBYTE, false, functionCall.position)
return listOf(IAstModification.ReplaceNode(
functionCall, typecast, parent
))
}
return noModifications
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// is it a struct variable? then define all its struct members as mangled names,
// and include the original decl as well.
if(decl.datatype==DataType.STRUCT && !decl.structHasBeenFlattened) {
val decls = decl.flattenStructMembers()
decls.add(decl)
val result = AnonymousScope(decls, decl.position)
return listOf(IAstModification.ReplaceNode(
decl, result, parent
))
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// For non-kernel subroutines and non-asm parameters:
// inject subroutine params as local variables (if they're not there yet).
val symbolsInSub = subroutine.allDefinedSymbols()
val namesInSub = symbolsInSub.map{ it.first }.toSet()
if(subroutine.asmAddress==null) {
if(subroutine.asmParameterRegisters.isEmpty()) {
return subroutine.parameters
.filter { it.name !in namesInSub }
.map {
val vardecl = ParameterVarDecl(it.name, it.type, subroutine.position)
IAstModification.InsertFirst(vardecl, subroutine)
}
}
}
return noModifications
}
override fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
when {
expr.left is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.left as StringLiteralValue, expr.right, expr),
parent
))
expr.right is StringLiteralValue ->
return listOf(IAstModification.ReplaceNode(
expr,
processBinaryExprWithString(expr.right as StringLiteralValue, expr.left, expr),
parent
))
}
return noModifications
}
override fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> {
if(string.parent !is VarDecl) {
// replace the literal string by a identifier reference to a new local vardecl
val vardecl = VarDecl.createAuto(string)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(string, identifier, parent),
IAstModification.InsertFirst(vardecl, string.definingScope() as Node)
)
}
return noModifications
}
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
val vardecl = array.parent as? VarDecl
if(vardecl!=null) {
// adjust the datatype of the array (to an educated guess)
val arrayDt = array.type
if(!arrayDt.istype(vardecl.datatype)) {
val cast = array.cast(vardecl.datatype)
if (cast != null && cast!=array)
return listOf(IAstModification.ReplaceNode(vardecl.value!!, cast, vardecl))
}
} 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))
if(litval2!=null && litval2!=array) {
val vardecl = VarDecl.createAuto(litval2)
val identifier = IdentifierReference(listOf(vardecl.name), vardecl.position)
return listOf(
IAstModification.ReplaceNode(array, identifier, parent),
IAstModification.InsertFirst(vardecl, array.definingScope() as Node)
)
}
}
}
return noModifications
}
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
}
}

View File

@ -0,0 +1,451 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
interface IAstModification {
fun perform()
class Remove(val node: Node, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
if (!parent.statements.remove(node) && parent !is GlobalNamespace)
throw FatalAstException("attempt to remove non-existing node $node")
} else {
throw FatalAstException("parent of a remove modification is not an INameScope")
}
}
}
class SetExpression(val setter: (newExpr: Expression) -> Unit, val newExpr: Expression, val parent: Node) : IAstModification {
override fun perform() {
setter(newExpr)
newExpr.linkParents(parent)
}
}
class InsertFirst(val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(0, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
}
}
class InsertLast(val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
parent.statements.add(stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
}
}
class InsertAfter(val after: Statement, val stmt: Statement, val parent: Node) : IAstModification {
override fun perform() {
if(parent is INameScope) {
val idx = parent.statements.indexOf(after)+1
parent.statements.add(idx, stmt)
stmt.linkParents(parent)
} else {
throw FatalAstException("parent of an insert modification is not an INameScope")
}
}
}
class ReplaceNode(val node: Node, val replacement: Node, val parent: Node) : IAstModification {
override fun perform() {
parent.replaceChildNode(node, replacement)
replacement.linkParents(parent)
}
}
class SwapOperands(val expr: BinaryExpression): IAstModification {
override fun perform() {
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
}
}
}
abstract class AstWalker {
open fun before(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(block: Block, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(foreverLoop: ForeverLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(jump: Jump, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(label: Label, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(module: Module, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(addressOf: AddressOf, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(arrayIndexedExpression: ArrayIndexedExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(assignTarget: AssignTarget, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(block: Block, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(branchStatement: BranchStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(breakStmt: Break, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(contStmt: Continue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(directive: Directive, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(foreverLoop: ForeverLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(inlineAssembly: InlineAssembly, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(jump: Jump, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(label: Label, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(module: Module, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(nopStatement: NopStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(numLiteral: NumericLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(postIncrDecr: PostIncrDecr, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(program: Program, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(range: RangeExpr, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(registerExpr: RegisterExpr, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(string: StringLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(structDecl: StructDecl, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(whenChoice: WhenChoice, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> = emptyList()
open fun after(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> = emptyList()
private val modifications = mutableListOf<Triple<IAstModification, Node, Node>>()
private fun track(mods: Iterable<IAstModification>, node: Node, parent: Node) {
for (it in mods) modifications += Triple(it, node, parent)
}
fun applyModifications(): Int {
modifications.forEach {
it.first.perform()
}
val amount = modifications.size
modifications.clear()
return amount
}
fun visit(program: Program) {
track(before(program, program), program, program)
program.modules.forEach { it.accept(this, program) }
track(after(program, program), program, program)
}
fun visit(module: Module, parent: Node) {
track(before(module, parent), module, parent)
module.statements.forEach{ it.accept(this, module) }
track(after(module, parent), module, parent)
}
fun visit(expr: PrefixExpression, parent: Node) {
track(before(expr, parent), expr, parent)
expr.expression.accept(this, expr)
track(after(expr, parent), expr, parent)
}
fun visit(expr: BinaryExpression, parent: Node) {
track(before(expr, parent), expr, parent)
expr.left.accept(this, expr)
expr.right.accept(this, expr)
track(after(expr, parent), expr, parent)
}
fun visit(directive: Directive, parent: Node) {
track(before(directive, parent), directive, parent)
track(after(directive, parent), directive, parent)
}
fun visit(block: Block, parent: Node) {
track(before(block, parent), block, parent)
block.statements.forEach { it.accept(this, block) }
track(after(block, parent), block, parent)
}
fun visit(decl: VarDecl, parent: Node) {
track(before(decl, parent), decl, parent)
decl.value?.accept(this, decl)
decl.arraysize?.accept(this, decl)
track(after(decl, parent), decl, parent)
}
fun visit(subroutine: Subroutine, parent: Node) {
track(before(subroutine, parent), subroutine, parent)
subroutine.statements.forEach { it.accept(this, subroutine) }
track(after(subroutine, parent), subroutine, parent)
}
fun visit(functionCall: FunctionCall, parent: Node) {
track(before(functionCall, parent), functionCall, parent)
functionCall.target.accept(this, functionCall)
functionCall.args.forEach { it.accept(this, functionCall) }
track(after(functionCall, parent), functionCall, parent)
}
fun visit(functionCallStatement: FunctionCallStatement, parent: Node) {
track(before(functionCallStatement, parent), functionCallStatement, parent)
functionCallStatement.target.accept(this, functionCallStatement)
functionCallStatement.args.forEach { it.accept(this, functionCallStatement) }
track(after(functionCallStatement, parent), functionCallStatement, parent)
}
fun visit(identifier: IdentifierReference, parent: Node) {
track(before(identifier, parent), identifier, parent)
track(after(identifier, parent), identifier, parent)
}
fun visit(jump: Jump, parent: Node) {
track(before(jump, parent), jump, parent)
jump.identifier?.accept(this, jump)
track(after(jump, parent), jump, parent)
}
fun visit(ifStatement: IfStatement, parent: Node) {
track(before(ifStatement, parent), ifStatement, parent)
ifStatement.condition.accept(this, ifStatement)
ifStatement.truepart.accept(this, ifStatement)
ifStatement.elsepart.accept(this, ifStatement)
track(after(ifStatement, parent), ifStatement, parent)
}
fun visit(branchStatement: BranchStatement, parent: Node) {
track(before(branchStatement, parent), branchStatement, parent)
branchStatement.truepart.accept(this, branchStatement)
branchStatement.elsepart.accept(this, branchStatement)
track(after(branchStatement, parent), branchStatement, parent)
}
fun visit(range: RangeExpr, parent: Node) {
track(before(range, parent), range, parent)
range.from.accept(this, range)
range.to.accept(this, range)
range.step.accept(this, range)
track(after(range, parent), range, parent)
}
fun visit(label: Label, parent: Node) {
track(before(label, parent), label, parent)
track(after(label, parent), label, parent)
}
fun visit(numLiteral: NumericLiteralValue, parent: Node) {
track(before(numLiteral, parent), numLiteral, parent)
track(after(numLiteral, parent), numLiteral, parent)
}
fun visit(string: StringLiteralValue, parent: Node) {
track(before(string, parent), string, parent)
track(after(string, parent), string, parent)
}
fun visit(array: ArrayLiteralValue, parent: Node) {
track(before(array, parent), array, parent)
array.value.forEach { v->v.accept(this, array) }
track(after(array, parent), array, parent)
}
fun visit(assignment: Assignment, parent: Node) {
track(before(assignment, parent), assignment, parent)
assignment.target.accept(this, assignment)
assignment.value.accept(this, assignment)
track(after(assignment, parent), assignment, parent)
}
fun visit(postIncrDecr: PostIncrDecr, parent: Node) {
track(before(postIncrDecr, parent), postIncrDecr, parent)
postIncrDecr.target.accept(this, postIncrDecr)
track(after(postIncrDecr, parent), postIncrDecr, parent)
}
fun visit(contStmt: Continue, parent: Node) {
track(before(contStmt, parent), contStmt, parent)
track(after(contStmt, parent), contStmt, parent)
}
fun visit(breakStmt: Break, parent: Node) {
track(before(breakStmt, parent), breakStmt, parent)
track(after(breakStmt, parent), breakStmt, parent)
}
fun visit(forLoop: ForLoop, parent: Node) {
track(before(forLoop, parent), forLoop, parent)
forLoop.loopVar?.accept(this, forLoop)
forLoop.iterable.accept(this, forLoop)
forLoop.body.accept(this, forLoop)
track(after(forLoop, parent), forLoop, parent)
}
fun visit(whileLoop: WhileLoop, parent: Node) {
track(before(whileLoop, parent), whileLoop, parent)
whileLoop.condition.accept(this, whileLoop)
whileLoop.body.accept(this, whileLoop)
track(after(whileLoop, parent), whileLoop, parent)
}
fun visit(foreverLoop: ForeverLoop, parent: Node) {
track(before(foreverLoop, parent), foreverLoop, parent)
foreverLoop.body.accept(this, foreverLoop)
track(after(foreverLoop, parent), foreverLoop, parent)
}
fun visit(repeatLoop: RepeatLoop, parent: Node) {
track(before(repeatLoop, parent), repeatLoop, parent)
repeatLoop.untilCondition.accept(this, repeatLoop)
repeatLoop.body.accept(this, repeatLoop)
track(after(repeatLoop, parent), repeatLoop, parent)
}
fun visit(returnStmt: Return, parent: Node) {
track(before(returnStmt, parent), returnStmt, parent)
returnStmt.value?.accept(this, returnStmt)
track(after(returnStmt, parent), returnStmt, parent)
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression, parent: Node) {
track(before(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
arrayIndexedExpression.identifier.accept(this, arrayIndexedExpression)
arrayIndexedExpression.arrayspec.accept(this, arrayIndexedExpression)
track(after(arrayIndexedExpression, parent), arrayIndexedExpression, parent)
}
fun visit(assignTarget: AssignTarget, parent: Node) {
track(before(assignTarget, parent), assignTarget, parent)
assignTarget.arrayindexed?.accept(this, assignTarget)
assignTarget.identifier?.accept(this, assignTarget)
assignTarget.memoryAddress?.accept(this, assignTarget)
track(after(assignTarget, parent), assignTarget, parent)
}
fun visit(scope: AnonymousScope, parent: Node) {
track(before(scope, parent), scope, parent)
scope.statements.forEach { it.accept(this, scope) }
track(after(scope, parent), scope, parent)
}
fun visit(typecast: TypecastExpression, parent: Node) {
track(before(typecast, parent), typecast, parent)
typecast.expression.accept(this, typecast)
track(after(typecast, parent), typecast, parent)
}
fun visit(memread: DirectMemoryRead, parent: Node) {
track(before(memread, parent), memread, parent)
memread.addressExpression.accept(this, memread)
track(after(memread, parent), memread, parent)
}
fun visit(memwrite: DirectMemoryWrite, parent: Node) {
track(before(memwrite, parent), memwrite, parent)
memwrite.addressExpression.accept(this, memwrite)
track(after(memwrite, parent), memwrite, parent)
}
fun visit(addressOf: AddressOf, parent: Node) {
track(before(addressOf, parent), addressOf, parent)
addressOf.identifier.accept(this, addressOf)
track(after(addressOf, parent), addressOf, parent)
}
fun visit(inlineAssembly: InlineAssembly, parent: Node) {
track(before(inlineAssembly, parent), inlineAssembly, parent)
track(after(inlineAssembly, parent), inlineAssembly, parent)
}
fun visit(registerExpr: RegisterExpr, parent: Node) {
track(before(registerExpr, parent), registerExpr, parent)
track(after(registerExpr, parent), registerExpr, parent)
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder, parent: Node) {
track(before(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
track(after(builtinFunctionStatementPlaceholder, parent), builtinFunctionStatementPlaceholder, parent)
}
fun visit(nopStatement: NopStatement, parent: Node) {
track(before(nopStatement, parent), nopStatement, parent)
track(after(nopStatement, parent), nopStatement, parent)
}
fun visit(whenStatement: WhenStatement, parent: Node) {
track(before(whenStatement, parent), whenStatement, parent)
whenStatement.condition.accept(this, whenStatement)
whenStatement.choices.forEach { it.accept(this, whenStatement) }
track(after(whenStatement, parent), whenStatement, parent)
}
fun visit(whenChoice: WhenChoice, parent: Node) {
track(before(whenChoice, parent), whenChoice, parent)
whenChoice.values?.forEach { it.accept(this, whenChoice) }
whenChoice.statements.accept(this, whenChoice)
track(after(whenChoice, parent), whenChoice, parent)
}
fun visit(structDecl: StructDecl, parent: Node) {
track(before(structDecl, parent), structDecl, parent)
structDecl.statements.forEach { it.accept(this, structDecl) }
track(after(structDecl, parent), structDecl, parent)
}
fun visit(structLv: StructLiteralValue, parent: Node) {
track(before(structLv, parent), structLv, parent)
structLv.values.forEach { it.accept(this, structLv) }
track(after(structLv, parent), structLv, parent)
}
}

View File

@ -1,261 +0,0 @@
package prog8.ast.processing
import prog8.ast.Module
import prog8.ast.Program
import prog8.ast.base.FatalAstException
import prog8.ast.expressions.*
import prog8.ast.statements.*
interface IAstModifyingVisitor {
fun visit(program: Program) {
program.modules.forEach { visit(it) }
}
fun visit(module: Module) {
module.statements = module.statements.map { it.accept(this) }.toMutableList()
}
fun visit(expr: PrefixExpression): Expression {
expr.expression = expr.expression.accept(this)
return expr
}
fun visit(expr: BinaryExpression): Expression {
expr.left = expr.left.accept(this)
expr.right = expr.right.accept(this)
return expr
}
fun visit(directive: Directive): Statement {
return directive
}
fun visit(block: Block): Statement {
block.statements = block.statements.map { it.accept(this) }.toMutableList()
return block
}
fun visit(decl: VarDecl): Statement {
decl.value = decl.value?.accept(this)
decl.arraysize?.accept(this)
return decl
}
fun visit(subroutine: Subroutine): Statement {
subroutine.statements = subroutine.statements.map { it.accept(this) }.toMutableList()
return subroutine
}
fun visit(functionCall: FunctionCall): Expression {
val newtarget = functionCall.target.accept(this)
if(newtarget is IdentifierReference)
functionCall.target = newtarget
else
throw FatalAstException("cannot change class of function call target")
functionCall.arglist = functionCall.arglist.map { it.accept(this) }.toMutableList()
return functionCall
}
fun visit(functionCallStatement: FunctionCallStatement): Statement {
val newtarget = functionCallStatement.target.accept(this)
if(newtarget is IdentifierReference)
functionCallStatement.target = newtarget
else
throw FatalAstException("cannot change class of function call target")
functionCallStatement.arglist = functionCallStatement.arglist.map { it.accept(this) }.toMutableList()
return functionCallStatement
}
fun visit(identifier: IdentifierReference): Expression {
// note: this is an identifier that is used in an expression.
// other identifiers are simply part of the other statements (such as jumps, subroutine defs etc)
return identifier
}
fun visit(jump: Jump): Statement {
if(jump.identifier!=null) {
val ident = jump.identifier.accept(this)
if(ident is IdentifierReference && ident!==jump.identifier) {
return Jump(null, ident, null, jump.position)
}
}
return jump
}
fun visit(ifStatement: IfStatement): Statement {
ifStatement.condition = ifStatement.condition.accept(this)
ifStatement.truepart = ifStatement.truepart.accept(this) as AnonymousScope
ifStatement.elsepart = ifStatement.elsepart.accept(this) as AnonymousScope
return ifStatement
}
fun visit(branchStatement: BranchStatement): Statement {
branchStatement.truepart = branchStatement.truepart.accept(this) as AnonymousScope
branchStatement.elsepart = branchStatement.elsepart.accept(this) as AnonymousScope
return branchStatement
}
fun visit(range: RangeExpr): Expression {
range.from = range.from.accept(this)
range.to = range.to.accept(this)
range.step = range.step.accept(this)
return range
}
fun visit(label: Label): Statement {
return label
}
fun visit(literalValue: NumericLiteralValue): NumericLiteralValue {
return literalValue
}
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 arrayLiteral
}
fun visit(assignment: Assignment): Statement {
assignment.target = assignment.target.accept(this)
assignment.value = assignment.value.accept(this)
return assignment
}
fun visit(postIncrDecr: PostIncrDecr): Statement {
postIncrDecr.target = postIncrDecr.target.accept(this)
return postIncrDecr
}
fun visit(contStmt: Continue): Statement {
return contStmt
}
fun visit(breakStmt: Break): Statement {
return breakStmt
}
fun visit(forLoop: ForLoop): Statement {
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")
}
forLoop.iterable = forLoop.iterable.accept(this)
forLoop.body = forLoop.body.accept(this) as AnonymousScope
return forLoop
}
fun visit(whileLoop: WhileLoop): Statement {
whileLoop.condition = whileLoop.condition.accept(this)
whileLoop.body = whileLoop.body.accept(this) as AnonymousScope
return whileLoop
}
fun visit(repeatLoop: RepeatLoop): Statement {
repeatLoop.untilCondition = repeatLoop.untilCondition.accept(this)
repeatLoop.body = repeatLoop.body.accept(this) as AnonymousScope
return repeatLoop
}
fun visit(returnStmt: Return): Statement {
returnStmt.value = returnStmt.value?.accept(this)
return returnStmt
}
fun visit(arrayIndexedExpression: ArrayIndexedExpression): ArrayIndexedExpression {
val ident = arrayIndexedExpression.identifier.accept(this)
if(ident is IdentifierReference)
arrayIndexedExpression.identifier = ident
arrayIndexedExpression.arrayspec.accept(this)
return arrayIndexedExpression
}
fun visit(assignTarget: AssignTarget): AssignTarget {
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")
}
assignTarget.arrayindexed = assignTarget.arrayindexed?.accept(this)
assignTarget.memoryAddress?.let { visit(it) }
return assignTarget
}
fun visit(scope: AnonymousScope): Statement {
scope.statements = scope.statements.map { it.accept(this) }.toMutableList()
return scope
}
fun visit(typecast: TypecastExpression): Expression {
typecast.expression = typecast.expression.accept(this)
return typecast
}
fun visit(memread: DirectMemoryRead): Expression {
memread.addressExpression = memread.addressExpression.accept(this)
return memread
}
fun visit(memwrite: DirectMemoryWrite) {
memwrite.addressExpression = memwrite.addressExpression.accept(this)
}
fun visit(addressOf: AddressOf): Expression {
val ident = addressOf.identifier.accept(this)
if(ident is IdentifierReference)
addressOf.identifier = ident
else
throw FatalAstException("can't change class of addressof identifier")
return addressOf
}
fun visit(inlineAssembly: InlineAssembly): Statement {
return inlineAssembly
}
fun visit(registerExpr: RegisterExpr): Expression {
return registerExpr
}
fun visit(builtinFunctionStatementPlaceholder: BuiltinFunctionStatementPlaceholder): Statement {
return builtinFunctionStatementPlaceholder
}
fun visit(nopStatement: NopStatement): Statement {
return nopStatement
}
fun visit(whenStatement: WhenStatement): Statement {
whenStatement.condition = whenStatement.condition.accept(this)
whenStatement.choices.forEach { it.accept(this) }
return whenStatement
}
fun visit(whenChoice: WhenChoice) {
whenChoice.values = whenChoice.values?.map { it.accept(this) }
val stmt = whenChoice.statements.accept(this)
if(stmt is AnonymousScope)
whenChoice.statements = stmt
else {
whenChoice.statements = AnonymousScope(mutableListOf(stmt), stmt.position)
whenChoice.statements.linkParents(whenChoice)
}
}
fun visit(structDecl: StructDecl): Statement {
structDecl.statements = structDecl.statements.map{ it.accept(this) }.toMutableList()
return structDecl
}
fun visit(structLv: StructLiteralValue): Expression {
structLv.values = structLv.values.map { it.accept(this) }
return structLv
}
}

View File

@ -7,7 +7,7 @@ import prog8.ast.statements.*
interface IAstVisitor {
fun visit(program: Program) {
program.modules.forEach { visit(it) }
program.modules.forEach { it.accept(this) }
}
fun visit(module: Module) {
@ -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) {
@ -112,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)

View File

@ -1,36 +1,21 @@
package prog8.ast.processing
import prog8.ast.Module
import prog8.ast.base.SyntaxError
import prog8.ast.base.printWarning
import prog8.ast.Node
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: AstWalker() {
/**
* Most global directives don't apply for imported modules, so remove them
*/
override fun visit(module: Module) {
super.visit(module)
val newStatements : MutableList<Statement> = mutableListOf()
val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
for (sourceStmt in module.statements) {
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)
continue
}
}
newStatements.add(stmt)
private val moduleLevelDirectives = listOf("%output", "%launcher", "%zeropage", "%zpreserved", "%address")
private val noModifications = emptyList<IAstModification>()
override fun before(directive: Directive, parent: Node): Iterable<IAstModification> {
if(directive.directive in moduleLevelDirectives) {
return listOf(IAstModification.Remove(directive, parent))
}
module.statements = newStatements
return noModifications
}
}

View File

@ -0,0 +1,71 @@
package prog8.ast.processing
/*
This is here for reference only, reflection based ast walking is very slow
when compared to the more verbose visitor pattern interfaces.
Too bad, because the code is very small
*/
//import prog8.ast.NoAstWalk
//import prog8.ast.Node
//import prog8.ast.Program
//import prog8.ast.base.Position
//import prog8.ast.expressions.BinaryExpression
//import prog8.ast.expressions.NumericLiteralValue
//import kotlin.reflect.KClass
//import kotlin.reflect.KVisibility
//import kotlin.reflect.full.declaredMemberProperties
//import kotlin.reflect.full.isSubtypeOf
//import kotlin.reflect.full.starProjectedType
//
//
//class ReflectionAstWalker {
// private val nodeType = Node::class.starProjectedType
// private val collectionType = Collection::class.starProjectedType
//
//
// fun walk(node: Node, nesting: Int) {
// val nodetype: KClass<out Node> = node::class
// val indent = " ".repeat(nesting)
// //println("$indent VISITING ${nodetype.simpleName}")
// val visibleAstMembers = nodetype.declaredMemberProperties.filter {
// it.visibility!=KVisibility.PRIVATE && !it.isLateinit &&
// !(it.annotations.any{a->a is NoAstWalk})
// }
// for(prop in visibleAstMembers) {
// if(prop.returnType.isSubtypeOf(nodeType)) {
// // println("$indent +PROP: ${prop.name}")
// walk(prop.call(node) as Node, nesting + 1)
// }
// else if(prop.returnType.isSubtypeOf(collectionType)) {
// val elementType = prop.returnType.arguments.single().type
// if(elementType!=null && elementType.isSubtypeOf(nodeType)) {
// val nodes = prop.call(node) as Collection<Node>
// nodes.forEach { walk(it, nesting+1) }
// }
// }
// }
// }
// fun walk(program: Program) {
// for(module in program.modules) {
// println("---MODULE $module---")
// walk(module, 0)
// }
// }
//}
//
//
//fun main() {
// val ast = BinaryExpression(
// NumericLiteralValue.optimalInteger(100, Position.DUMMY),
// "+",
// NumericLiteralValue.optimalInteger(200, Position.DUMMY),
// Position.DUMMY
// )
//
// val walker = ReflectionAstWalker()
// walker.walk(ast,0)
//
//}

View File

@ -1,258 +1,184 @@
package prog8.ast.processing
import prog8.ast.*
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.initvarsSubName
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
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 -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null)
throw FatalAstException("can only assign arrays or structs to structs")
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
null, sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
structAssignment.value is StructLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}
}
internal class StatementReorderer(private val program: Program): IAstModifyingVisitor {
internal class StatementReorderer(val program: Program) : AstWalker() {
// Reorders the statements in a way the compiler needs.
// - 'main' block must be the very first statement UNLESS it has an address set.
// - blocks are ordered by address, where blocks without address are put at the end.
// - in every scope:
// -- the directives '%output', '%launcher', '%zeropage', '%zpreserved', '%address' and '%option' will come first.
// -- all vardecls then follow.
// -- the remaining statements then follow in their original order.
//
// - 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.
// - library blocks are put last.
// - blocks are ordered by address, where blocks without address are placed last.
// - in every scope, most directives and vardecls are moved to the top.
// - the 'start' subroutine is moved to the top.
// - (syntax desugaring) a vardecl with a non-const initializer value is split into a regular vardecl and an assignment statement.
// - (syntax desugaring) augmented assignment is turned into regular assignment.
// - (syntax desugaring) struct value assignment is expanded into several struct member assignments.
// - sorts the choices in when statement.
// - insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val noModifications = emptyList<IAstModification>()
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)
override fun after(module: Module, parent: Node): Iterable<IAstModification> {
val (blocks, other) = module.statements.partition { it is Block }
module.statements = other.asSequence().plus(blocks.sortedBy { (it as Block).address ?: Int.MAX_VALUE }).toMutableList()
// make sure user-defined blocks come BEFORE library blocks, and move the "main" block to the top of everything
val nonLibraryBlocks = module.statements.withIndex()
.filter { it.value is Block && !(it.value as Block).isInLibrary }
.map { it.index to it.value }
.reversed()
for(nonLibBlock in nonLibraryBlocks)
module.statements.removeAt(nonLibBlock.first)
for(nonLibBlock in nonLibraryBlocks)
module.statements.add(0, nonLibBlock.second)
val mainBlock = module.statements.singleOrNull { it is Block && it.name=="main" }
if(mainBlock!=null && (mainBlock as Block).address==null) {
module.remove(mainBlock)
val mainBlock = module.statements.filterIsInstance<Block>().firstOrNull { it.name=="main" }
if(mainBlock!=null && mainBlock.address==null) {
module.statements.remove(mainBlock)
module.statements.add(0, mainBlock)
}
val varDecls = module.statements.filterIsInstance<VarDecl>()
module.statements.removeAll(varDecls)
module.statements.addAll(0, varDecls)
reorderVardeclsAndDirectives(module.statements)
return noModifications
}
val directives = module.statements.filter {it is Directive && it.directive in directivesToMove}
module.statements.removeAll(directives)
module.statements.addAll(0, directives)
private fun reorderVardeclsAndDirectives(statements: MutableList<Statement>) {
val varDecls = statements.filterIsInstance<VarDecl>()
statements.removeAll(varDecls)
statements.addAll(0, varDecls)
for(pos in addReturns) {
println(pos)
val returnStmt = Return(null, pos.first.position)
returnStmt.linkParents(pos.first as Node)
pos.first.statements.add(pos.second, returnStmt)
val directives = statements.filterIsInstance<Directive>().filter {it.directive in directivesToMove}
statements.removeAll(directives)
statements.addAll(0, directives)
}
override fun before(block: Block, parent: Node): Iterable<IAstModification> {
parent as Module
if(block.isInLibrary) {
return listOf(
IAstModification.Remove(block, parent),
IAstModification.InsertLast(block, parent)
)
}
reorderVardeclsAndDirectives(block.statements)
return noModifications
}
override fun before(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
if(subroutine.name=="start" && parent is Block) {
if(parent.statements.filterIsInstance<Subroutine>().first().name!="start") {
return listOf(
IAstModification.Remove(subroutine, parent),
IAstModification.InsertFirst(subroutine, parent)
)
}
}
return noModifications
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
val declValue = decl.value
if(declValue!=null && decl.type== VarDeclType.VAR && decl.datatype in NumericDatatypes) {
val declConstValue = declValue.constValue(program)
if(declConstValue==null) {
// move the vardecl (without value) to the scope and replace this with a regular assignment
decl.value = null
val target = AssignTarget(null, IdentifierReference(listOf(decl.name), decl.position), null, null, decl.position)
val assign = Assignment(target, null, declValue, decl.position)
return listOf(
IAstModification.ReplaceNode(decl, assign, parent),
IAstModification.InsertFirst(decl, decl.definingScope() as Node)
)
}
}
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
val choices = whenStatement.choiceValues(program).sortedBy {
it.first?.first() ?: Int.MAX_VALUE
}
whenStatement.choices.clear()
choices.mapTo(whenStatement.choices) { it.second }
return noModifications
}
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.aug_op!=null) {
return listOf(IAstModification.ReplaceNode(assignment, assignment.asDesugaredNonaugmented(), parent))
}
val valueType = assignment.value.inferType(program)
val targetType = assignment.target.inferType(program, assignment)
if(valueType.istype(DataType.STRUCT) && targetType.istype(DataType.STRUCT)) {
val assignments = if (assignment.value is StructLiteralValue) {
flattenStructAssignmentFromStructLiteral(assignment, program) // 'structvar = { ..... } '
} else {
flattenStructAssignmentFromIdentifier(assignment, program) // 'structvar1 = structvar2'
}
if(assignments.isNotEmpty()) {
val modifications = mutableListOf<IAstModification>()
assignments.reversed().mapTo(modifications) { IAstModification.InsertAfter(assignment, it, parent) }
modifications.add(IAstModification.Remove(assignment, parent))
return modifications
}
}
return noModifications
}
private fun flattenStructAssignmentFromStructLiteral(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!!
val slv = structAssignment.value as? StructLiteralValue
if(slv==null || slv.values.size != struct.numberOfElements)
throw FatalAstException("element count mismatch")
return struct.statements.zip(slv.values).map { (targetDecl, sourceValue) ->
targetDecl as VarDecl
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
null, sourceValue, sourceValue.position)
assign.linkParents(structAssignment)
assign
}
}
override fun visit(block: Block): Statement {
val subroutines = block.statements.filterIsInstance<Subroutine>()
var numSubroutinesAtEnd = 0
// move all subroutines to the end of the block
for (subroutine in subroutines) {
if(subroutine.name!="start" || block.name!="main") {
block.remove(subroutine)
block.statements.add(subroutine)
}
numSubroutinesAtEnd++
}
// move the "start" subroutine to the top
if(block.name=="main") {
block.statements.singleOrNull { it is Subroutine && it.name == "start" } ?.let {
block.remove(it)
block.statements.add(0, it)
numSubroutinesAtEnd--
}
}
// make sure there is a 'return' in front of the first subroutine
// (if it isn't the first statement in the block itself, and isn't the program's entrypoint)
if(numSubroutinesAtEnd>0 && block.statements.size > (numSubroutinesAtEnd+1)) {
val firstSub = block.statements[block.statements.size - numSubroutinesAtEnd] as Subroutine
if(firstSub.name != "start" && block.name != "main") {
val stmtBeforeFirstSub = block.statements[block.statements.size - numSubroutinesAtEnd - 1]
if (stmtBeforeFirstSub !is Return
&& stmtBeforeFirstSub !is Jump
&& stmtBeforeFirstSub !is Subroutine
&& stmtBeforeFirstSub !is BuiltinFunctionStatementPlaceholder) {
val ret = Return(null, stmtBeforeFirstSub.position)
ret.linkParents(block)
block.statements.add(block.statements.size - numSubroutinesAtEnd, ret)
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 -> {
val sourceVar = (structAssignment.value as IdentifierReference).targetVarDecl(program.namespace)!!
if (sourceVar.struct == null)
throw FatalAstException("can only assign arrays or structs to structs")
// struct memberwise copy
val sourceStruct = sourceVar.struct!!
if(sourceStruct!==targetVar.struct) {
// structs are not the same in assignment
return listOf() // error will be printed elsewhere
}
return struct.statements.zip(sourceStruct.statements).map { member ->
val targetDecl = member.first as VarDecl
val sourceDecl = member.second as VarDecl
if(targetDecl.name != sourceDecl.name)
throw FatalAstException("struct member mismatch")
val mangled = mangledStructMemberName(identifierName, targetDecl.name)
val idref = IdentifierReference(listOf(mangled), structAssignment.position)
val sourcemangled = mangledStructMemberName(sourceVar.name, sourceDecl.name)
val sourceIdref = IdentifierReference(listOf(sourcemangled), structAssignment.position)
val assign = Assignment(AssignTarget(null, idref, null, null, structAssignment.position),
null, sourceIdref, member.second.position)
assign.linkParents(structAssignment)
assign
}
}
is StructLiteralValue -> {
throw IllegalArgumentException("not going to flatten a structLv assignment here")
}
else -> throw FatalAstException("strange struct value")
}
val varDecls = block.statements.filterIsInstance<VarDecl>()
block.statements.removeAll(varDecls)
block.statements.addAll(0, varDecls)
val directives = block.statements.filter {it is Directive && it.directive in directivesToMove}
block.statements.removeAll(directives)
block.statements.addAll(0, directives)
block.linkParents(block.parent)
// create subroutine that initializes the block's variables (if any)
val varInits = block.statements.withIndex().filter { it.value is VariableInitializationAssignment }
if(varInits.isNotEmpty()) {
val statements = varInits.map{it.value}.toMutableList()
val varInitSub = Subroutine(initvarsSubName, emptyList(), emptyList(), emptyList(), emptyList(),
emptySet(), null, false, statements, block.position)
varInitSub.keepAlways = true
varInitSub.linkParents(block)
block.statements.add(varInitSub)
// remove the varinits from the block's statements
for(index in varInits.map{it.index}.reversed())
block.statements.removeAt(index)
}
return super.visit(block)
}
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)
val directives = subroutine.statements.filter {it is Directive && it.directive in directivesToMove}
subroutine.statements.removeAll(directives)
subroutine.statements.addAll(0, directives)
if(subroutine.returntypes.isEmpty()) {
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti
if(subroutine.asmAddress==null && subroutine.amountOfRtsInAsm()==0) {
if (subroutine.statements.lastOrNull {it !is VarDecl } !is Return) {
val returnStmt = Return(null, subroutine.position)
returnStmt.linkParents(subroutine)
subroutine.statements.add(returnStmt)
}
}
}
return subroutine
}
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)
// 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(assg.aug_op!=null) {
// transform augmented assg into normal assg so we have one case less to deal with later
val newTarget: Expression =
when {
assg.target.register != null -> RegisterExpr(assg.target.register!!, assg.target.position)
assg.target.identifier != null -> assg.target.identifier!!
assg.target.arrayindexed != null -> assg.target.arrayindexed!!
assg.target.memoryAddress != null -> DirectMemoryRead(assg.target.memoryAddress!!.addressExpression, assg.value.position)
else -> throw FatalAstException("strange assg")
}
val expression = BinaryExpression(newTarget, assg.aug_op.substringBeforeLast('='), assg.value, assg.position)
expression.linkParents(assg.parent)
val convertedAssignment = Assignment(assg.target, null, expression, assg.position)
convertedAssignment.linkParents(assg.parent)
return super.visit(convertedAssignment)
}
return assg
}
}

View File

@ -2,103 +2,121 @@ package prog8.ast.processing
import prog8.ast.IFunctionCall
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.printWarning
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.statements.*
import prog8.functions.BuiltinFunctions
internal class TypecastsAdder(private val program: Program): IAstModifyingVisitor {
// Make sure any value assignments get the proper type casts if needed to cast them into the target variable's type.
// (this includes function call arguments)
class TypecastsAdder(val program: Program, val errors: ErrorReporter) : AstWalker() {
/*
* 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)
private val noModifications = emptyList<IAstModification>()
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftDt = expr.left.inferType(program)
val rightDt = expr.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)
val (commonDt, toFix) = BinaryExpression.commonDatatype(leftDt.typeOrElse(DataType.STRUCT), rightDt.typeOrElse(DataType.STRUCT), expr.left, expr.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)
}
return when {
toFix===expr.left -> listOf(IAstModification.ReplaceNode(
expr.left, TypecastExpression(expr.left, commonDt, true, expr.left.position), expr))
toFix===expr.right -> listOf(IAstModification.ReplaceNode(
expr.right, TypecastExpression(expr.right, commonDt, true, expr.right.position), expr))
else -> throw FatalAstException("confused binary expression side")
}
}
}
return expr2
return noModifications
}
override fun visit(assignment: Assignment): Statement {
val assg = super.visit(assignment)
if(assg !is Assignment)
return assg
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// 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)
val valueItype = assignment.value.inferType(program)
val targetItype = assignment.target.inferType(program, assignment)
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)
return listOf(IAstModification.ReplaceNode(
assignment.value,
TypecastExpression(assignment.value, targettype, true, assignment.value.position),
assignment))
} else {
fun castLiteral(cvalue: NumericLiteralValue): List<IAstModification.ReplaceNode> =
listOf(IAstModification.ReplaceNode(cvalue, cvalue.cast(targettype), cvalue.parent))
val cvalue = assignment.value.constValue(program)
if(cvalue!=null) {
val number = cvalue.number.toDouble()
// more complex comparisons if the type is different, but the constant value is compatible
if (valuetype == DataType.BYTE && targettype == DataType.UBYTE) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.WORD && targettype == DataType.UWORD) {
if(number>0)
return castLiteral(cvalue)
} else if (valuetype == DataType.UBYTE && targettype == DataType.BYTE) {
if(number<0x80)
return castLiteral(cvalue)
} else if (valuetype == DataType.UWORD && targettype == DataType.WORD) {
if(number<0x8000)
return castLiteral(cvalue)
}
}
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
return assg
return noModifications
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
checkFunctionCallArguments(functionCallStatement, functionCallStatement.definingScope())
return super.visit(functionCallStatement)
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCallStatement, functionCallStatement.definingScope())
}
override fun visit(functionCall: FunctionCall): Expression {
checkFunctionCallArguments(functionCall, functionCall.definingScope())
return super.visit(functionCall)
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
return afterFunctionCallArgs(functionCall, functionCall.definingScope())
}
private fun checkFunctionCallArguments(call: IFunctionCall, scope: INameScope) {
private fun afterFunctionCallArgs(call: IFunctionCall, scope: INameScope): Iterable<IAstModification> {
// see if a typecast is needed to convert the arguments into the required parameter's type
when(val sub = call.target.targetStatement(scope)) {
return when(val sub = call.target.targetStatement(scope)) {
is Subroutine -> {
for(arg in sub.parameters.zip(call.arglist.withIndex())) {
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.arglist[arg.second.index] = typecasted
return listOf(IAstModification.ReplaceNode(
call.args[arg.second.index],
TypecastExpression(arg.second.value, requiredType, true, arg.second.value.position),
call as Node))
} else if(requiredType == DataType.UWORD && argtype in PassByReferenceDatatypes) {
// we allow STR/ARRAY values in place of UWORD parameters. Take their address instead.
return listOf(IAstModification.ReplaceNode(
call.args[arg.second.index],
AddressOf(arg.second.value as IdentifierReference, arg.second.value.position),
call as Node))
}
// if they're not assignable, we'll get a proper error later from the AstChecker
}
}
}
emptyList()
}
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())) {
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)
@ -106,93 +124,116 @@ internal class TypecastsAdder(private val program: Program): IAstModifyingVisito
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
return listOf(IAstModification.ReplaceNode(
call.args[arg.second.index],
TypecastExpression(arg.second.value, possibleType, true, arg.second.value.position),
call as Node))
}
}
}
}
}
emptyList()
}
null -> {}
null -> emptyList()
else -> throw FatalAstException("call to something weird $sub ${call.target}")
}
}
override fun visit(typecast: TypecastExpression): Expression {
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// 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)
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)
return noModifications
}
override fun visit(memread: DirectMemoryRead): Expression {
override fun after(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// 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
}
val typecast = (memread.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
?: TypecastExpression(memread.addressExpression, DataType.UWORD, true, memread.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memread.addressExpression, typecast, memread))
}
return super.visit(memread)
return noModifications
}
override fun visit(memwrite: DirectMemoryWrite) {
override fun after(memwrite: DirectMemoryWrite, parent: Node): Iterable<IAstModification> {
// make sure the memory address is an uword
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
}
val typecast = (memwrite.addressExpression as? NumericLiteralValue)?.cast(DataType.UWORD)
?: TypecastExpression(memwrite.addressExpression, DataType.UWORD, true, memwrite.addressExpression.position)
return listOf(IAstModification.ReplaceNode(memwrite.addressExpression, typecast, memwrite))
}
super.visit(memwrite)
return noModifications
}
override fun visit(structLv: StructLiteralValue): Expression {
val litval = super.visit(structLv)
if(litval !is StructLiteralValue)
return litval
override fun after(structLv: StructLiteralValue, parent: Node): Iterable<IAstModification> {
// assignment of a struct literal value, some member values may need proper typecast
val decl = litval.parent as? VarDecl
fun addTypecastsIfNeeded(struct: StructDecl): Iterable<IAstModification> {
val newValues = struct.statements.zip(structLv.values).map { (structMemberDecl, memberValue) ->
val memberDt = (structMemberDecl as VarDecl).datatype
val valueDt = memberValue.inferType(program)
if (valueDt.typeOrElse(memberDt) != memberDt)
TypecastExpression(memberValue, memberDt, true, memberValue.position)
else
memberValue
}
class StructLvValueReplacer(val targetStructLv: StructLiteralValue, val typecastValues: List<Expression>) : IAstModification {
override fun perform() {
targetStructLv.values = typecastValues
typecastValues.forEach { it.linkParents(targetStructLv) }
}
}
return if(structLv.values.zip(newValues).any { (v1, v2) -> v1 !== v2})
listOf(StructLvValueReplacer(structLv, newValues))
else
emptyList()
}
val decl = structLv.parent as? VarDecl
if(decl != null) {
val struct = decl.struct
if(struct != null) {
addTypecastsIfNeeded(litval, struct)
}
if(struct != null)
return addTypecastsIfNeeded(struct)
} else {
val assign = litval.parent as? Assignment
val assign = structLv.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)
}
if(struct != null)
return addTypecastsIfNeeded(struct)
}
}
}
return litval
return noModifications
}
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
override fun after(returnStmt: Return, parent: Node): Iterable<IAstModification> {
// add a typecast to the return type if it doesn't match the subroutine's signature
val returnValue = returnStmt.value
if(returnValue!=null) {
val subroutine = returnStmt.definingSubroutine()!!
if(subroutine.returntypes.size==1) {
val subReturnType = subroutine.returntypes.first()
if (returnValue.inferType(program).istype(subReturnType))
return noModifications
if (returnValue is NumericLiteralValue) {
returnStmt.value = returnValue.cast(subroutine.returntypes.single())
} else {
return listOf(IAstModification.ReplaceNode(
returnValue,
TypecastExpression(returnValue, subReturnType, true, returnValue.position),
returnStmt))
}
}
}
return noModifications
}
}

View File

@ -1,157 +0,0 @@
package prog8.ast.processing
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.statements.*
import prog8.compiler.CompilerException
import prog8.functions.BuiltinFunctions
import prog8.functions.FunctionSignature
internal class VarInitValueAndAddressOfCreator(private val program: Program): IAstModifyingVisitor {
// For VarDecls that declare an initialization value:
// Replace the vardecl with an assignment (to set the initial value),
// and add a new vardecl with the default constant value of that type (usually zero) to the scope.
// This makes sure the variables get reset to the intended value on a next run of the program.
// Variable decls without a value don't get this treatment, which means they retain the last
// value they had when restarting the program.
// This is done in a separate step because it interferes with the namespace lookup of symbols
// in other ast processors.
// Also takes care to insert AddressOf (&) expression where required (string params to a UWORD function param etc).
private val vardeclsToAdd = mutableMapOf<INameScope, MutableList<VarDecl>>()
override fun visit(module: Module) {
vardeclsToAdd.clear()
super.visit(module)
// add any new vardecls to the various scopes
for((where, decls) in vardeclsToAdd) {
where.statements.addAll(0, decls)
decls.forEach { it.linkParents(where as Node) }
}
}
override fun visit(decl: VarDecl): Statement {
super.visit(decl)
if(decl.isArray && decl.value==null) {
// array datatype without initialization value, add list of zeros
val arraysize = decl.arraysize!!.size()!!
val array = ArrayLiteralValue(decl.datatype,
Array(arraysize) { NumericLiteralValue.optimalInteger(0, decl.position) },
null, decl.position)
array.addToHeap()
decl.value = array
}
if(decl.type!= VarDeclType.VAR || decl.value==null)
return decl
if(decl.datatype in NumericDatatypes) {
val scope = decl.definingScope()
addVarDecl(scope, decl.asDefaultValueDecl(null))
val declvalue = decl.value!!
val value =
if(declvalue is NumericLiteralValue)
declvalue.cast(decl.datatype)
else
declvalue
val identifierName = listOf(decl.name) // this was: (scoped name) decl.scopedname.split(".")
return VariableInitializationAssignment(
AssignTarget(null, IdentifierReference(identifierName, decl.position), null, null, decl.position),
null,
value,
decl.position
)
}
return decl
}
override fun visit(functionCall: FunctionCall): Expression {
var parentStatement: Node = functionCall
while(parentStatement !is Statement)
parentStatement = parentStatement.parent
val targetStatement = functionCall.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCall.arglist, parentStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCall.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCall.arglist, parentStatement)
}
return functionCall
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
val targetStatement = functionCallStatement.target.targetSubroutine(program.namespace)
if(targetStatement!=null) {
addAddressOfExprIfNeeded(targetStatement, functionCallStatement.arglist, functionCallStatement)
} else {
val builtinFunc = BuiltinFunctions[functionCallStatement.target.nameInSource.joinToString (".")]
if(builtinFunc!=null)
addAddressOfExprIfNeededForBuiltinFuncs(builtinFunc, functionCallStatement.arglist, functionCallStatement)
}
return functionCallStatement
}
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.second is AddressOf)
continue
val idref = argparam.second as? IdentifierReference
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)) {
val pointerExpr = AddressOf(idref, idref.position)
pointerExpr.linkParents(arglist[argparam.first.index].parent)
arglist[argparam.first.index] = pointerExpr
}
}
else if(strvalue!=null) {
// 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) {
// 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.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)
args[arg.first.index] = addrOf
addrOf.linkParents(parent)
}
}
}
private fun addVarDecl(scope: INameScope, variable: VarDecl) {
if(scope !in vardeclsToAdd)
vardeclsToAdd[scope] = mutableListOf()
val declList = vardeclsToAdd.getValue(scope)
if(declList.all{it.name!=variable.name})
declList.add(variable)
}
}

View File

@ -3,13 +3,14 @@ package prog8.ast.statements
import prog8.ast.*
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.IAstModifyingVisitor
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstVisitor
sealed class Statement : Node {
abstract fun accept(visitor: IAstModifyingVisitor) : Statement
abstract fun accept(visitor: IAstVisitor)
abstract fun accept(visitor: AstWalker, parent: Node)
fun makeScopedName(name: String): String {
// easy way out is to always return the full scoped name.
// it would be nicer to find only the minimal prefixed scoped name, but that's too much hassle for now.
@ -37,12 +38,16 @@ sealed class Statement : Node {
}
}
class BuiltinFunctionStatementPlaceholder(val name: String, override val position: Position) : Statement() {
override var parent: Node = ParentSentinel
override fun linkParents(parent: Node) {}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun definingScope(): INameScope = BuiltinFunctionScopePlaceholder
override fun replaceChildNode(node: Node, replacement: Node) {
replacement.parent = this
}
override val expensiveToInline = false
}
@ -62,8 +67,15 @@ class Block(override val name: String,
statements.forEach {it.linkParents(this)}
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOf(node)
statements[idx] = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Block(name=$name, address=$address, ${statements.size} statements)"
@ -81,8 +93,9 @@ data class Directive(val directive: String, val args: List<DirectiveArg>, overri
args.forEach{it.linkParents(this)}
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
data class DirectiveArg(val str: String?, val name: String?, val int: Int?, override val position: Position) : Node {
@ -91,6 +104,7 @@ data class DirectiveArg(val str: String?, val name: String?, val int: Int?, over
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
}
data class Label(val name: String, override val position: Position) : Statement() {
@ -101,8 +115,9 @@ data class Label(val name: String, override val position: Position) : Statement(
this.parent = parent
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Label(name=$name, pos=$position)"
@ -118,8 +133,14 @@ open class Return(var value: Expression?, override val position: Position) : Sta
value?.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression)
value = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Return($value, pos=$position)"
@ -127,12 +148,12 @@ open class Return(var value: Expression?, override val position: Position) : Sta
}
class ReturnFromIrq(override val position: Position) : Return(null, position) {
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun toString(): String {
return "ReturnFromIrq(pos=$position)"
}
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
}
class Continue(override val position: Position) : Statement() {
@ -143,8 +164,9 @@ class Continue(override val position: Position) : Statement() {
this.parent=parent
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class Break(override val position: Position) : Statement() {
@ -155,8 +177,9 @@ class Break(override val position: Position) : Statement() {
this.parent=parent
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@ -167,7 +190,8 @@ enum class ZeropageWish {
NOT_IN_ZEROPAGE
}
class VarDecl(val type: VarDeclType,
open class VarDecl(val type: VarDeclType,
private val declaredDatatype: DataType,
val zeropage: ZeropageWish,
var arraysize: ArrayIndex?,
@ -192,23 +216,27 @@ class VarDecl(val type: VarDeclType,
private var autoHeapValueSequenceNumber = 0
fun createAuto(string: StringLiteralValue): VarDecl {
if(string.heapId==null)
throw FatalAstException("can only create autovar for a string that has a heapid $string")
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
return VarDecl(VarDeclType.VAR, string.type, ZeropageWish.NOT_IN_ZEROPAGE, null, autoVarName, null, string,
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 {
if(array.heapId==null)
throw FatalAstException("can only create autovar for an array that has a heapid $array")
val autoVarName = "auto_heap_value_${++autoHeapValueSequenceNumber}"
val declaredType = ArrayElementTypes.getValue(array.type)
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)
}
fun defaultZero(dt: DataType, position: Position) = when(dt) {
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only determine default zero value for a numeric type")
}
}
val datatypeErrors = mutableListOf<SyntaxError>() // don't crash at init time, report them in the AstChecker
@ -237,29 +265,20 @@ class VarDecl(val type: VarDeclType,
}
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===value)
value = replacement
replacement.parent = this
}
val scopedname: String by lazy { makeScopedName(name) }
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "VarDecl(name=$name, vartype=$type, datatype=$datatype, struct=$structName, value=$value, pos=$position)"
}
fun asDefaultValueDecl(parent: Node?): VarDecl {
val constValue = when(declaredDatatype) {
DataType.UBYTE -> NumericLiteralValue(DataType.UBYTE, 0, position)
DataType.BYTE -> NumericLiteralValue(DataType.BYTE, 0, position)
DataType.UWORD -> NumericLiteralValue(DataType.UWORD, 0, position)
DataType.WORD -> NumericLiteralValue(DataType.WORD, 0, position)
DataType.FLOAT -> NumericLiteralValue(DataType.FLOAT, 0.0, position)
else -> throw FatalAstException("can only set a default value for a numeric type")
}
val decl = VarDecl(type, declaredDatatype, zeropage, arraysize, name, structName, constValue, isArray, false, position)
if(parent!=null)
decl.linkParents(parent)
return decl
}
fun zeroElementValue() = defaultZero(declaredDatatype, position)
fun flattenStructMembers(): MutableList<Statement> {
val result = struct!!.statements.withIndex().map {
@ -283,6 +302,11 @@ class VarDecl(val type: VarDeclType,
}
}
// a vardecl used only for subroutine parameters
class ParameterVarDecl(name: String, declaredDatatype: DataType, position: Position)
: VarDecl(VarDeclType.VAR, declaredDatatype, ZeropageWish.NOT_IN_ZEROPAGE, null, name, null, null, false, true, position)
class ArrayIndex(var index: Expression, override val position: Position) : Node {
override lateinit var parent: Node
@ -291,19 +315,20 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
index.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===index)
index = replacement
replacement.parent = this
}
companion object {
fun forArray(v: ArrayLiteralValue): ArrayIndex {
return ArrayIndex(NumericLiteralValue.optimalNumeric(v.value.size, v.position), v.position)
}
}
fun accept(visitor: IAstModifyingVisitor) {
index = index.accept(visitor)
}
fun accept(visitor: IAstVisitor) {
index.accept(visitor)
}
fun accept(visitor: IAstVisitor) = index.accept(visitor)
fun accept(visitor: AstWalker, parent: Node) = index.accept(visitor, this)
override fun toString(): String {
return("ArrayIndex($index, pos=$position)")
@ -312,7 +337,7 @@ class ArrayIndex(var index: Expression, override val position: Position) : Node
fun size() = (index as? NumericLiteralValue)?.number?.toInt()
}
open class Assignment(var target: AssignTarget, val aug_op : String?, var value: Expression, override val position: Position) : Statement() {
open class Assignment(var target: AssignTarget, var aug_op : String?, var value: Expression, override val position: Position) : Statement() {
override lateinit var parent: Node
override val expensiveToInline
get() = value !is NumericLiteralValue
@ -323,18 +348,46 @@ open class Assignment(var target: AssignTarget, val aug_op : String?, var value:
value.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===target -> target = replacement as AssignTarget
node===value -> value = replacement as Expression
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return("Assignment(augop: $aug_op, target: $target, value: $value, pos=$position)")
}
}
// This is a special class so the compiler can see if the assignments are for initializing the vars in the scope,
// or just a regular assignment. It may optimize the initialization step from this.
class VariableInitializationAssignment(target: AssignTarget, aug_op: String?, value: Expression, position: Position)
: Assignment(target, aug_op, value, position)
fun asDesugaredNonaugmented(): Assignment {
val augmented = aug_op ?: return this
val leftOperand: Expression =
when {
target.register != null -> RegisterExpr(target.register!!, target.position)
target.identifier != null -> target.identifier!!
target.arrayindexed != null -> target.arrayindexed!!
target.memoryAddress != null -> DirectMemoryRead(target.memoryAddress!!.addressExpression, value.position)
else -> throw FatalAstException("strange this")
}
val assignment =
if(augmented=="setvalue") {
Assignment(target, null, value, position)
} else {
val expression = BinaryExpression(leftOperand, augmented.substringBeforeLast('='), value, position)
Assignment(target, null, expression, position)
}
assignment.linkParents(parent)
return assignment
}
}
data class AssignTarget(val register: Register?,
var identifier: IdentifierReference?,
@ -350,8 +403,17 @@ data class AssignTarget(val register: Register?,
memoryAddress?.linkParents(this)
}
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===identifier -> identifier = replacement as IdentifierReference
node===arrayindexed -> arrayindexed = replacement as ArrayIndexedExpression
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
companion object {
fun fromExpr(expr: Expression): AssignTarget {
@ -386,7 +448,13 @@ data class AssignTarget(val register: Register?,
infix fun isSameAs(value: Expression): Boolean {
return when {
this.memoryAddress!=null -> false
this.memoryAddress!=null -> {
// if the target is a memory write, and the value is a memory read, they're the same if the address matches
if(value is DirectMemoryRead)
this.memoryAddress.addressExpression isSameAs value.addressExpression
else
false
}
this.register!=null -> value is RegisterExpr && value.register==register
this.identifier!=null -> value is IdentifierReference && value.nameInSource==identifier!!.nameInSource
this.arrayindexed!=null -> value is ArrayIndexedExpression &&
@ -448,8 +516,14 @@ class PostIncrDecr(var target: AssignTarget, val operator: String, override val
target.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AssignTarget && node===target)
target = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "PostIncrDecr(op: $operator, target: $target, pos=$position)"
@ -468,8 +542,9 @@ class Jump(val address: Int?,
identifier?.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Jump(addr: $address, identifier: $identifier, label: $generatedLabel; pos=$position)"
@ -477,20 +552,31 @@ 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 replaceChildNode(node: Node, replacement: Node) {
if(node===target)
target = replacement as IdentifierReference
else {
val idx = args.indexOf(node)
args[idx] = replacement as Expression
}
replacement.parent = this
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "FunctionCallStatement(target=$target, pos=$position)"
@ -505,8 +591,9 @@ class InlineAssembly(val assembly: String, override val position: Position) : St
this.parent = parent
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class AnonymousScope(override var statements: MutableList<Statement>,
@ -530,8 +617,15 @@ class AnonymousScope(override var statements: MutableList<Statement>,
statements.forEach { it.linkParents(this) }
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOf(node)
statements[idx] = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class NopStatement(override val position: Position): Statement() {
@ -542,8 +636,9 @@ class NopStatement(override val position: Position): Statement() {
this.parent = parent
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) = throw FatalAstException("can't replace here")
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
companion object {
fun insteadOf(stmt: Statement): NopStatement {
@ -584,13 +679,22 @@ class Subroutine(override val name: String,
statements.forEach { it.linkParents(this) }
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOf(node)
statements[idx] = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
override fun toString(): String {
return "Subroutine(name=$name, parameters=$parameters, returntypes=$returntypes, ${statements.size} statements, address=$asmAddress)"
}
fun regXasResult() = asmReturnvaluesRegisters.any { it.registerOrPair in setOf(RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY) }
fun amountOfRtsInAsm(): Int = statements
.asSequence()
.filter { it is InlineAssembly }
@ -598,6 +702,7 @@ class Subroutine(override val name: String,
.count { " rti" in it || "\trti" in it || " rts" in it || "\trts" in it || " jmp" in it || "\tjmp" in it }
}
open class SubroutineParameter(val name: String,
val type: DataType,
override val position: Position) : Node {
@ -606,6 +711,10 @@ open class SubroutineParameter(val name: String,
override fun linkParents(parent: Node) {
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
throw FatalAstException("can't replace anything in a subroutineparameter node")
}
}
class IfStatement(var condition: Expression,
@ -623,8 +732,19 @@ class IfStatement(var condition: Expression,
elsepart.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===condition -> condition = replacement as Expression
node===truepart -> truepart = replacement as AnonymousScope
node===elsepart -> elsepart = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class BranchStatement(var condition: BranchCondition,
@ -641,8 +761,18 @@ class BranchStatement(var condition: BranchCondition,
elsepart.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===truepart -> truepart = replacement as AnonymousScope
node===elsepart -> elsepart = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class ForLoop(val loopRegister: Register?,
@ -660,12 +790,28 @@ class ForLoop(val loopRegister: Register?,
body.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===loopVar -> loopVar = replacement as IdentifierReference
node===iterable -> iterable = replacement as Expression
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
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,
@ -680,8 +826,36 @@ class WhileLoop(var condition: Expression,
body.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===condition -> condition = replacement as Expression
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
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 replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===body)
body = replacement
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class RepeatLoop(var body: AnonymousScope,
@ -696,8 +870,17 @@ class RepeatLoop(var body: AnonymousScope,
body.linkParents(this)
}
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun replaceChildNode(node: Node, replacement: Node) {
when {
node===untilCondition -> untilCondition = replacement as Expression
node===body -> body = replacement as AnonymousScope
else -> throw FatalAstException("invalid replace")
}
replacement.parent = this
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class WhenStatement(var condition: Expression,
@ -712,6 +895,16 @@ class WhenStatement(var condition: Expression,
choices.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
if(node===condition)
condition = replacement as Expression
else {
val idx = choices.indexOf(node)
choices[idx] = replacement as WhenChoice
}
replacement.parent = this
}
fun choiceValues(program: Program): List<Pair<List<Int>?, WhenChoice>> {
// only gives sensible results when the choices are all valid (constant integers)
val result = mutableListOf<Pair<List<Int>?, WhenChoice>>()
@ -730,7 +923,7 @@ class WhenStatement(var condition: Expression,
}
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
class WhenChoice(var values: List<Expression>?, // if null, this is the 'else' part
@ -744,12 +937,18 @@ class WhenChoice(var values: List<Expression>?, // if null, this is t
this.parent = parent
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is AnonymousScope && node===statements)
statements = replacement
replacement.parent = this
}
override fun toString(): String {
return "Choice($values at $position)"
}
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}
@ -765,11 +964,18 @@ class StructDecl(override val name: String,
this.statements.forEach { it.linkParents(this) }
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Statement)
val idx = statements.indexOf(node)
statements[idx] = replacement
replacement.parent = this
}
val numberOfElements: Int
get() = this.statements.size
override fun accept(visitor: IAstVisitor) = visitor.visit(this)
override fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
override fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
fun nameOfFirstMember() = (statements.first() as VarDecl).name
}
@ -782,10 +988,16 @@ class DirectMemoryWrite(var addressExpression: Expression, override val position
this.addressExpression.linkParents(this)
}
override fun replaceChildNode(node: Node, replacement: Node) {
require(replacement is Expression && node===addressExpression)
addressExpression = replacement
replacement.parent = this
}
override fun toString(): String {
return "DirectMemoryWrite($addressExpression)"
}
fun accept(visitor: IAstVisitor) = visitor.visit(this)
fun accept(visitor: IAstModifyingVisitor) = visitor.visit(this)
fun accept(visitor: AstWalker, parent: Node) = visitor.visit(this, parent)
}

View File

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

View File

@ -0,0 +1,106 @@
package prog8.compiler
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
internal class BeforeAsmGenerationAstChanger(val program: Program, val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
if (decl.value == null && decl.type == VarDeclType.VAR && decl.datatype in NumericDatatypes) {
// a numeric vardecl without an initial value is initialized with zero.
decl.value = decl.zeroElementValue()
}
return noModifications
}
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val decls = scope.statements.filterIsInstance<VarDecl>()
val sub = scope.definingSubroutine()
if (sub != null) {
val existingVariables = sub.statements.filterIsInstance<VarDecl>().associateBy { it.name }
var conflicts = false
decls.forEach {
val existing = existingVariables[it.name]
if (existing != null) {
errors.err("variable ${it.name} already defined in subroutine ${sub.name} at ${existing.position}", it.position)
conflicts = true
}
}
if (!conflicts) {
val numericVarsWithValue = decls.filter { it.value != null && it.datatype in NumericDatatypes }
return numericVarsWithValue.map {
val initValue = it.value!! // assume here that value has always been set by now
it.value = null // make sure no value init assignment for this vardecl will be created later (would be superfluous)
val target = AssignTarget(null, IdentifierReference(listOf(it.name), it.position), null, null, it.position)
val assign = Assignment(target, null, initValue, it.position)
initValue.parent = assign
IAstModification.InsertFirst(assign, scope)
} + decls.map { IAstModification.ReplaceNode(it, NopStatement(it.position), scope) } +
decls.map { IAstModification.InsertFirst(it, sub) } // move it up to the subroutine
}
}
return noModifications
}
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
// add the implicit return statement at the end (if it's not there yet), but only if it's not a kernel routine.
// and if an assembly block doesn't contain a rts/rti, and some other situations.
val mods = mutableListOf<IAstModification>()
val returnStmt = Return(null, subroutine.position)
if (subroutine.asmAddress == null
&& subroutine.statements.isNotEmpty()
&& subroutine.amountOfRtsInAsm() == 0
&& subroutine.statements.lastOrNull { it !is VarDecl } !is Return
&& subroutine.statements.last() !is Subroutine) {
mods += IAstModification.InsertLast(returnStmt, subroutine)
}
// precede a subroutine with a return to avoid falling through into the subroutine from code above it
val outerScope = subroutine.definingScope()
val outerStatements = outerScope.statements
val subroutineStmtIdx = outerStatements.indexOf(subroutine)
if (subroutineStmtIdx > 0
&& outerStatements[subroutineStmtIdx - 1] !is Jump
&& outerStatements[subroutineStmtIdx - 1] !is Subroutine
&& outerStatements[subroutineStmtIdx - 1] !is Return
&& outerScope !is Block) {
mods += IAstModification.InsertAfter(outerStatements[subroutineStmtIdx - 1], returnStmt, outerScope as Node)
}
return mods
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
// see if we can remove superfluous typecasts (outside of expressions)
// such as casting byte<->ubyte, word<->uword
// Also the special typecast of a reference type (str, array) to an UWORD will be changed into address-of.
val sourceDt = typecast.expression.inferType(program).typeOrElse(DataType.STRUCT)
if (typecast.type in ByteDatatypes && sourceDt in ByteDatatypes
|| typecast.type in WordDatatypes && sourceDt in WordDatatypes) {
if(typecast.parent !is Expression) {
return listOf(IAstModification.ReplaceNode(typecast, typecast.expression, parent))
}
}
else if(sourceDt in PassByReferenceDatatypes) {
if(typecast.type==DataType.UWORD) {
return listOf(IAstModification.ReplaceNode(
typecast,
AddressOf(typecast.expression as IdentifierReference, typecast.position),
parent
))
} else {
errors.err("cannot cast pass-by-reference value to type ${typecast.type} (only to UWORD)", typecast.position)
}
}
return noModifications
}
}

View File

@ -4,14 +4,13 @@ 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.codegen.AsmGen
import prog8.compiler.target.CompilationTarget
import prog8.optimizer.UnusedCodeRemover
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
@ -27,89 +26,31 @@ fun compileProgram(filepath: Path,
optimize: Boolean,
writeAssembly: Boolean,
outputDir: Path): CompilationResult {
var programName = ""
lateinit var programAst: Program
var programName: String? = null
var importedFiles: List<Path> = emptyList()
var success=false
lateinit var importedFiles: List<Path>
val errors = ErrorReporter()
try {
val totalTime = measureTimeMillis {
// import main module and everything it needs
println("Parsing...")
programAst = Program(moduleName(filepath.fileName), mutableListOf())
importModule(programAst, filepath)
val (ast, compilationOptions, imported) = parseImports(filepath, errors)
programAst = ast
importedFiles = imported
processAst(programAst, errors, compilationOptions)
if (optimize)
optimizeAst(programAst, errors)
postprocessAst(programAst, errors, compilationOptions)
importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map{ it.source }
// printAst(programAst) // TODO
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
importLibraryModule(programAst, "c64lib")
importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importLibraryModule(programAst, "math")
importLibraryModule(programAst, "prog8lib")
// perform initial syntax checks and constant folding
println("Syntax check...")
val time1 = measureTimeMillis {
programAst.checkIdentifiers()
}
//println(" time1: $time1")
val time2 = measureTimeMillis {
programAst.constantFold()
}
//println(" time2: $time2")
val time3 = measureTimeMillis {
programAst.removeNopsFlattenAnonScopes()
programAst.reorderStatements()
programAst.addTypecasts()
}
//println(" time3: $time3")
val time4 = measureTimeMillis {
programAst.checkValid(compilerOptions) // check if tree is valid
}
//println(" time4: $time4")
programAst.checkIdentifiers()
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()
if (optsDone1 + optsDone2 == 0)
break
}
}
programAst.addTypecasts()
programAst.removeNopsFlattenAnonScopes()
programAst.checkValid(compilerOptions) // check if final tree is valid
programAst.checkRecursion() // check if there are recursive subroutine calls
// printAst(programAst)
if(writeAssembly) {
// asm generation directly from the Ast, no need for intermediate code
val zeropage = MachineDefinition.C64Zeropage(compilerOptions)
programAst.anonscopeVarsCleanup()
val assembly = AsmGen(programAst, zeropage, compilerOptions, outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
programName = assembly.name
}
success = true
if(writeAssembly)
programName = writeAssembly(programAst, errors, outputDir, optimize, compilationOptions)
}
System.out.flush()
System.err.flush()
println("\nTotal compilation+assemble time: ${totalTime / 1000.0} sec.")
return CompilationResult(true, programAst, programName, importedFiles)
} catch (px: ParsingFailedError) {
System.err.print("\u001b[91m") // bright red
@ -132,16 +73,35 @@ fun compileProgram(filepath: Path,
System.out.flush()
throw x
}
return CompilationResult(success, programAst, programName ?: "", importedFiles)
return CompilationResult(false, Program("failed", mutableListOf()), programName, emptyList())
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}
private fun parseImports(filepath: Path, errors: ErrorReporter): Triple<Program, CompilationOptions, List<Path>> {
println("Parsing...")
val importer = ModuleImporter(errors)
val programAst = Program(moduleName(filepath.fileName), mutableListOf())
importer.importModule(programAst, filepath)
errors.handle()
val importedFiles = programAst.modules.filter { !it.source.startsWith("@embedded@") }.map { it.source }
val compilerOptions = determineCompilationOptions(programAst)
if (compilerOptions.launcher == LauncherType.BASIC && compilerOptions.output != OutputType.PRG)
throw ParsingFailedError("${programAst.modules.first().position} BASIC launcher requires output type PRG.")
// if we're producing a PRG or BASIC program, include the c64utils and c64lib libraries
if (compilerOptions.launcher == LauncherType.BASIC || compilerOptions.output == OutputType.PRG) {
importer.importLibraryModule(programAst, "c64lib")
importer.importLibraryModule(programAst, "c64utils")
}
// always import prog8lib and math
importer.importLibraryModule(programAst, "math")
importer.importLibraryModule(programAst, "prog8lib")
errors.handle()
return Triple(programAst, compilerOptions, importedFiles)
}
private fun determineCompilationOptions(program: Program): CompilationOptions {
val mainModule = program.modules.first()
@ -178,3 +138,78 @@ private fun determineCompilationOptions(program: Program): CompilationOptions {
zpType, zpReserved, floatsEnabled
)
}
private fun processAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
// perform initial syntax checks and processings
println("Processing...")
programAst.checkIdentifiers(errors)
errors.handle()
programAst.constantFold(errors)
errors.handle()
programAst.removeNopsFlattenAnonScopes()
programAst.reorderStatements()
programAst.addTypecasts(errors)
errors.handle()
programAst.checkValid(compilerOptions, errors)
errors.handle()
programAst.checkIdentifiers(errors)
errors.handle()
}
private fun optimizeAst(programAst: Program, errors: ErrorReporter) {
// 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(errors)
programAst.constantFold(errors) // because simplified statements and expressions could give rise to more constants that can be folded away:
errors.handle()
if (optsDone1 + optsDone2 == 0)
break
}
val remover = UnusedCodeRemover()
remover.visit(programAst)
remover.applyModifications()
}
private fun postprocessAst(programAst: Program, errors: ErrorReporter, compilerOptions: CompilationOptions) {
programAst.transformAssignments(errors)
errors.handle()
programAst.addTypecasts(errors)
errors.handle()
programAst.removeNopsFlattenAnonScopes()
programAst.checkValid(compilerOptions, errors) // check if final tree is still valid
errors.handle()
programAst.checkRecursion(errors) // check if there are recursive subroutine calls
errors.handle()
}
private fun writeAssembly(programAst: Program, errors: ErrorReporter, outputDir: Path,
optimize: Boolean, compilerOptions: CompilationOptions): String {
// asm generation directly from the Ast,
val zeropage = CompilationTarget.machine.getZeropage(compilerOptions)
programAst.processAstBeforeAsmGeneration(errors)
errors.handle()
// printAst(programAst)
val assembly = CompilationTarget.asmGenerator(
programAst,
errors,
zeropage,
compilerOptions,
outputDir).compileToAssembly(optimize)
assembly.assemble(compilerOptions)
errors.handle()
return assembly.name
}
fun printAst(programAst: Program) {
println()
val printer = AstToSourceCode(::print, programAst)
printer.visit(programAst)
println()
}

View File

@ -15,7 +15,7 @@ abstract class Zeropage(protected val options: CompilationOptions) {
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", position ?: Position.DUMMY)
5
} else throw CompilerException("floating point option not enabled")
}

View File

@ -0,0 +1,18 @@
package prog8.compiler.target
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
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, ErrorReporter, Zeropage, CompilationOptions, Path) -> IAssemblyGenerator
}
}

View File

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

View File

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

View File

@ -2,36 +2,24 @@ package prog8.compiler.target.c64
import prog8.compiler.CompilationOptions
import prog8.compiler.OutputType
import prog8.compiler.target.IAssemblyProgram
import prog8.compiler.target.generatedLabelPrefix
import java.nio.file.Path
import kotlin.system.exitProcess
class AssemblyProgram(val name: String, outputDir: Path) {
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.toString(), "--no-monitor")
val outFile = when(options.output) {
val outFile = when (options.output) {
OutputType.PRG -> {
command.add("--cbm-prg")
println("\nCreating C-64 prg.")
@ -47,27 +35,39 @@ class AssemblyProgram(val name: String, outputDir: Path) {
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 viceMonListFile.toFile().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")
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n")+"\n")
viceMonListFile.toFile().appendText(breakpoints.joinToString("\n") + "\n")
}
}

View File

@ -4,18 +4,19 @@ import prog8.compiler.CompilationOptions
import prog8.compiler.CompilerException
import prog8.compiler.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

View File

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

View File

@ -1,48 +0,0 @@
package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.AstException
import prog8.ast.base.NameError
import prog8.ast.processing.IAstModifyingVisitor
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()
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>().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))
conflicts = true
}
}
if (!conflicts) {
decls.forEach { scope.remove(it) }
sub.statements.addAll(0, decls)
decls.forEach { it.parent = sub }
}
}
}
override fun visit(scope: AnonymousScope): Statement {
val scope2 = super.visit(scope) as AnonymousScope
val vardecls = scope2.statements.filterIsInstance<VarDecl>()
varsToMove[scope2] = vardecls
return scope2
}
}

View File

@ -7,28 +7,29 @@ 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.MachineDefinition
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
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.FunctionSignature
import prog8.functions.FSignature
import java.math.RoundingMode
import java.nio.file.Path
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.ArrayDeque
import java.util.*
import kotlin.math.absoluteValue
internal class AssemblyError(msg: String) : RuntimeException(msg)
internal class AsmGen(private val program: Program,
private val errors: ErrorReporter,
private val zeropage: Zeropage,
private val options: CompilationOptions,
private val outputDir: Path) {
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)
@ -38,12 +39,13 @@ internal class AsmGen(private val program: Program,
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 assignmentAsmGen = AssignmentAsmGen(program, errors, this)
private val expressionsAsmGen = ExpressionsAsmGen(program, this)
internal val loopEndLabels = ArrayDeque<String>()
internal val loopContinueLabels = ArrayDeque<String>()
internal val blockLevelVarInits = mutableMapOf<Block, MutableSet<VarDecl>>()
internal fun compileToAssembly(optimize: Boolean): AssemblyProgram {
override fun compileToAssembly(optimize: Boolean): IAssemblyProgram {
assemblyLines.clear()
loopEndLabels.clear()
loopContinueLabels.clear()
@ -84,7 +86,7 @@ internal class AsmGen(private val program: Program,
program.actualLoadAddress = program.definedLoadAddress
if (program.actualLoadAddress == 0) // fix load address
program.actualLoadAddress = if (options.launcher == LauncherType.BASIC)
MachineDefinition.BASIC_LOAD_ADDRESS else MachineDefinition.RAW_LOAD_ADDRESS
C64MachineDefinition.BASIC_LOAD_ADDRESS else C64MachineDefinition.RAW_LOAD_ADDRESS
when {
options.launcher == LauncherType.BASIC -> {
@ -97,11 +99,15 @@ internal class AsmGen(private val program: Program,
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 -> {
@ -121,11 +127,10 @@ internal class AsmGen(private val program: Program,
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(" ; initialize the variables in each block that has globals")
program.allBlocks().forEach {
if(it.statements.filterIsInstance<VarDecl>().any { vd->vd.value!=null && vd.type==VarDeclType.VAR && vd.datatype in NumericDatatypes})
out(" jsr ${it.name}.prog8_init_vars")
}
out(" clc")
@ -138,14 +143,13 @@ internal class AsmGen(private val program: Program,
out(" jmp (c64.RESET_VEC)\t; cold reset")
}
}
out("")
}
private fun footer() {
// the global list of all floating point constants for the whole program
out("; global float constants")
for (flt in globalFloatConsts) {
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(flt.key)
val mflpt5 = C64MachineDefinition.Mflpt5.fromNumber(flt.key)
val floatFill = makeFloatFill(mflpt5)
val floatvalue = flt.key
out("${flt.value}\t.byte $floatFill ; float $floatvalue")
@ -172,6 +176,21 @@ internal class AsmGen(private val program: Program,
stmts.forEach { translate(it) }
subroutine.forEach { translateSubroutine(it as Subroutine) }
// if any global vars need to be initialized, generate a subroutine that does this
// it will be called from program init.
if(block in blockLevelVarInits) {
out("prog8_init_vars\t.proc\n")
blockLevelVarInits.getValue(block).forEach { decl ->
val scopedFullName = decl.makeScopedName(decl.name).split('.')
require(scopedFullName.first()==block.name)
val target = AssignTarget(null, IdentifierReference(scopedFullName.drop(1), decl.position), null, null, decl.position)
val assign = Assignment(target, null, decl.value!!, decl.position)
assign.linkParents(decl.parent)
assignmentAsmGen.translate(assign)
}
out(" rts\n .pend")
}
out(if("force_output" in block.options()) "\n\t.bend\n" else "\n\t.pend\n")
}
@ -179,7 +198,7 @@ internal class AsmGen(private val program: Program,
internal fun makeLabel(postfix: String): String {
generatedLabelSequenceNumber++
return "_prog8_label_${generatedLabelSequenceNumber}_$postfix"
return "${generatedLabelPrefix}${generatedLabelSequenceNumber}_$postfix"
}
private fun outputSourceLine(node: Node) {
@ -198,7 +217,7 @@ internal class AsmGen(private val program: Program,
} else assemblyLines.add(fragment)
}
private fun makeFloatFill(flt: MachineDefinition.Mflpt5): String {
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')
@ -207,18 +226,9 @@ internal class AsmGen(private val program: Program,
return "$b0, $b1, $b2, $b3, $b4"
}
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 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>) {
@ -226,7 +236,7 @@ internal class AsmGen(private val program: Program,
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 fullName = variable.makeScopedName(variable.name)
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)
@ -235,7 +245,9 @@ internal class AsmGen(private val program: Program,
&& variable.datatype != DataType.FLOAT
&& options.zeropage != ZeropageType.DONTUSE) {
try {
val address = zeropage.allocate(fullName, variable.datatype, null)
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)
@ -255,10 +267,9 @@ internal class AsmGen(private val program: Program,
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, DataType.STR_S -> {
val string = (decl.value as StringLiteralValue).value
val encoded = encodeStr(string, decl.datatype)
outputStringvar(decl, encoded)
DataType.STR -> {
val str = decl.value as StringLiteralValue
outputStringvar(decl, encode(str.value, str.altEncoding))
}
DataType.ARRAY_UB -> {
val data = makeArrayFillDataUnsigned(decl)
@ -301,10 +312,17 @@ internal class AsmGen(private val program: Program,
}
}
DataType.ARRAY_F -> {
val array = (decl.value as ArrayLiteralValue).value
val array =
if(decl.value!=null)
(decl.value as ArrayLiteralValue).value
else {
// no init value, use zeros
val zero = decl.zeroElementValue()
Array(decl.arraysize!!.size()!!) { zero }
}
val floatFills = array.map {
val number = (it as NumericLiteralValue).number
makeFloatFill(MachineDefinition.Mflpt5.fromNumber(number))
makeFloatFill(C64MachineDefinition.Mflpt5.fromNumber(number))
}
out(decl.name)
for (f in array.zip(floatFills))
@ -341,8 +359,11 @@ internal class AsmGen(private val program: Program,
// special treatment for string types: merge strings that are identical
val encodedstringVars = normalVars
.filter {it.datatype in StringDatatypes }
.map { it to encodeStr((it.value as StringLiteralValue).value, it.datatype) }
.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) }
@ -351,8 +372,8 @@ internal class AsmGen(private val program: Program,
}
// non-string variables
normalVars.filter{ it.datatype !in StringDatatypes}.sortedBy { it.datatype }.forEach {
if(it.scopedname !in allocatedZeropageVariables)
normalVars.filter{ it.datatype != DataType.STR }.sortedBy { it.datatype }.forEach {
if(it.makeScopedName(it.name) !in allocatedZeropageVariables)
vardecl2asm(it)
}
}
@ -366,15 +387,22 @@ internal class AsmGen(private val program: Program,
}
private fun makeArrayFillDataUnsigned(decl: VarDecl): List<String> {
val array = (decl.value as ArrayLiteralValue).value
return when {
decl.datatype == DataType.ARRAY_UB ->
val array =
if(decl.value!=null)
(decl.value as ArrayLiteralValue).value
else {
// no array init value specified, use a list of zeros
val zero = decl.zeroElementValue()
Array(decl.arraysize!!.size()!!) { zero }
}
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')
}
decl.datatype== DataType.ARRAY_UW -> array.map {
DataType.ARRAY_UW -> array.map {
if(it is NumericLiteralValue) {
"$" + it.number.toInt().toString(16).padStart(4, '0')
} else {
@ -386,17 +414,22 @@ internal class AsmGen(private val program: Program,
}
private fun makeArrayFillDataSigned(decl: VarDecl): List<String> {
val array = (decl.value as ArrayLiteralValue).value
return when {
decl.datatype == DataType.ARRAY_UB ->
val array =
if(decl.value!=null)
(decl.value as ArrayLiteralValue).value
else {
// no array init value specified, use a list of zeros
val zero = decl.zeroElementValue()
Array(decl.arraysize!!.size()!!) { zero }
}
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"
"$"+number.toString(16).padStart(2, '0')
}
decl.datatype == DataType.ARRAY_B ->
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()
@ -406,12 +439,11 @@ internal class AsmGen(private val program: Program,
else
"-$$hexnum"
}
decl.datatype== DataType.ARRAY_UW -> array.map {
DataType.ARRAY_UW -> array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.toString(16).padStart(4, '0')
"$$hexnum"
"$" + number.toString(16).padStart(4, '0')
}
decl.datatype== DataType.ARRAY_W -> array.map {
DataType.ARRAY_W -> array.map {
val number = (it as NumericLiteralValue).number.toInt()
val hexnum = number.absoluteValue.toString(16).padStart(4, '0')
if(number>=0)
@ -425,7 +457,7 @@ internal class AsmGen(private val program: Program,
internal fun getFloatConst(number: Double): String {
// try to match the ROM float constants to save memory
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(number)
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"
@ -511,7 +543,7 @@ internal class AsmGen(private val program: Program,
internal fun readAndPushArrayvalueWithIndexA(arrayDt: DataType, variable: IdentifierReference) {
val variablename = asmIdentifierName(variable)
when (arrayDt) {
DataType.STR, DataType.STR_S, DataType.ARRAY_UB, DataType.ARRAY_B ->
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")
@ -573,7 +605,9 @@ internal class AsmGen(private val program: Program,
internal fun translate(stmt: Statement) {
outputSourceLine(stmt)
when(stmt) {
is VarDecl, is StructDecl, is NopStatement -> {}
is ParameterVarDecl -> { /* subroutine parameter vardecls don't get any special treatment here */ }
is VarDecl -> translate(stmt)
is StructDecl, is NopStatement -> {}
is Directive -> translate(stmt)
is Return -> translate(stmt)
is Subroutine -> translateSubroutine(stmt)
@ -606,11 +640,13 @@ internal class AsmGen(private val program: Program,
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 -> throw AssemblyError("missing asm translation for $stmt")
}
}
@ -636,6 +672,19 @@ internal class AsmGen(private val program: Program,
}
}
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")
@ -737,7 +786,7 @@ internal class AsmGen(private val program: Program,
}
private fun translate(stmt: Label) {
out(stmt.name)
out("_${stmt.name}") // underscore prefix to make sure it's a local label
}
private fun translate(scope: AnonymousScope) {
@ -776,6 +825,27 @@ internal class AsmGen(private val program: Program,
}
}
private fun translate(stmt: VarDecl) {
if(stmt.value!=null && stmt.type==VarDeclType.VAR && stmt.datatype in NumericDatatypes) {
// generate an assignment statement to (re)initialize the variable's value.
// if the vardecl is not in a subroutine however, we have to initialize it globally.
if(stmt.definingSubroutine()==null) {
val block = stmt.definingBlock()
var inits = blockLevelVarInits[block]
if(inits==null) {
inits = mutableSetOf()
blockLevelVarInits[block] = inits
}
inits.add(stmt)
} else {
val target = AssignTarget(null, IdentifierReference(listOf(stmt.name), stmt.position), null, null, stmt.position)
val assign = Assignment(target, null, stmt.value!!, stmt.position)
assign.linkParents(stmt.parent)
translate(assign)
}
}
}
private fun translate(stmt: Directive) {
when(stmt.directive) {
"%asminclude" -> {
@ -806,7 +876,14 @@ internal class AsmGen(private val program: Program,
private fun getJumpTarget(jmp: Jump): String {
return when {
jmp.identifier!=null -> asmIdentifierName(jmp.identifier)
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 -> "????"
@ -837,6 +914,7 @@ internal class AsmGen(private val program: Program,
val indexName = asmIdentifierName(index)
out(" lda $indexName")
}
// TODO optimize more cases
else -> {
expressionsAsmGen.translateExpression(index)
out(" inx | lda $ESTACK_LO_HEX,x")
@ -844,10 +922,32 @@ internal class AsmGen(private val program: Program,
}
}
internal fun translateArrayIndexIntoY(expr: ArrayIndexedExpression) {
when (val index = expr.arrayspec.index) {
is NumericLiteralValue -> throw AssemblyError("this should be optimized directly")
is RegisterExpr -> {
when (index.register) {
Register.A -> out(" tay")
Register.X -> out(" txa | tay")
Register.Y -> {}
}
}
is IdentifierReference -> {
val indexName = asmIdentifierName(index)
out(" ldy $indexName")
}
// TODO optimize more cases, see translateArrayIndexIntoA
else -> {
expressionsAsmGen.translateExpression(index)
out(" inx | ldy $ESTACK_LO_HEX,x")
}
}
}
internal fun translateExpression(expression: Expression) =
expressionsAsmGen.translateExpression(expression)
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FunctionSignature) =
internal fun translateFunctioncallExpression(functionCall: FunctionCall, signature: FSignature) =
builtinFunctionsAsmGen.translateFunctioncallExpression(functionCall, signature)
internal fun translateFunctionCall(functionCall: FunctionCall) =

View File

@ -1,7 +1,7 @@
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
@ -13,43 +13,45 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
var linesByFour = getLinesBy(lines, 4)
var removeLines = optimizeUselessStackByteWrites(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
var mods = optimizeUselessStackByteWrites(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeIncDec(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeIncDec(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeCmpSequence(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeCmpSequence(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
removeLines = optimizeStoreLoadSame(linesByFour)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeStoreLoadSame(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
mods= optimizeJsrRts(linesByFour)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFour = getLinesBy(lines, 4)
numberOfOptimizations++
}
var linesByFourteen = getLinesBy(lines, 14)
removeLines = optimizeSameAssignments(linesByFourteen)
if(removeLines.isNotEmpty()) {
for (i in removeLines.reversed())
lines.removeAt(i)
mods = optimizeSameAssignments(linesByFourteen)
if(mods.isNotEmpty()) {
apply(mods, lines)
linesByFourteen = getLinesBy(lines, 14)
numberOfOptimizations++
}
@ -59,7 +61,22 @@ fun optimizeAssembly(lines: MutableList<String>): Int {
return numberOfOptimizations
}
fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
private class Modification(val lineIndex: Int, val remove: Boolean, val replacement: String?)
private fun apply(modifications: List<Modification>, lines: MutableList<String>) {
for (modification in modifications.sortedBy { it.lineIndex }.reversed()) {
if(modification.remove)
lines.removeAt(modification.lineIndex)
else
lines[modification.lineIndex] = modification.replacement!!
}
}
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
// all lines (that aren't empty or comments) in sliding windows of certain size
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// the when statement (on bytes) generates a sequence of:
// lda $ce01,x
// cmp #$20
@ -68,42 +85,42 @@ fun optimizeCmpSequence(linesByFour: List<List<IndexedValue<String>>>): List<Int
// cmp #$21
// beq check_prog8_s73choice_33
// the repeated lda can be removed
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x" &&
lines[1].value.trim().startsWith("cmp ") &&
lines[2].value.trim().startsWith("beq ") &&
lines[3].value.trim()=="lda $ESTACK_LO_PLUS1_HEX,x") {
removeLines.add(lines[3].index) // remove the second lda
mods.add(Modification(lines[3].index, true, null)) // remove the second lda
}
}
return removeLines
return mods
}
fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeUselessStackByteWrites(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta on stack, dex, inx, lda from stack -> eliminate this useless stack byte write
// this is a lot harder for word values because the instruction sequence varies.
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for(lines in linesByFour) {
if(lines[0].value.trim()=="sta $ESTACK_LO_HEX,x" &&
lines[1].value.trim()=="dex" &&
lines[2].value.trim()=="inx" &&
lines[3].value.trim()=="lda $ESTACK_LO_HEX,x") {
removeLines.add(lines[1].index)
removeLines.add(lines[2].index)
removeLines.add(lines[3].index)
mods.add(Modification(lines[1].index, true, null))
mods.add(Modification(lines[2].index, true, null))
mods.add(Modification(lines[3].index, true, null))
}
}
return removeLines
return mods
}
fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>): List<Modification> {
// 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, or the AsmGen, and never even create the inefficient asm in the first place...
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for (pair in linesByFourteen) {
val first = pair[0].value.trimStart()
val second = pair[1].value.trimStart()
@ -122,8 +139,8 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
val fourthvalue = sixth.substring(4)
if(firstvalue==thirdvalue && secondvalue==fourthvalue) {
// lda/ldy sta/sty twice the isSameAs word --> remove second lda/ldy pair (fifth and sixth lines)
removeLines.add(pair[4].index)
removeLines.add(pair[5].index)
mods.add(Modification(pair[4].index, true, null))
mods.add(Modification(pair[5].index, true, null))
}
}
@ -132,7 +149,7 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
val secondvalue = third.substring(4)
if(firstvalue==secondvalue) {
// lda value / sta ? / lda isSameAs-value / sta ? -> remove second lda (third line)
removeLines.add(pair[2].index)
mods.add(Modification(pair[2].index, true, null))
}
}
@ -151,24 +168,20 @@ fun optimizeSameAssignments(linesByFourteen: List<List<IndexedValue<String>>>):
if(first.substring(4) == eighth.substring(4) && second.substring(4)==nineth.substring(4)) {
// identical float init
removeLines.add(pair[7].index)
removeLines.add(pair[8].index)
removeLines.add(pair[9].index)
removeLines.add(pair[10].index)
mods.add(Modification(pair[7].index, true, null))
mods.add(Modification(pair[8].index, true, null))
mods.add(Modification(pair[9].index, true, null))
mods.add(Modification(pair[10].index, true, null))
}
}
}
}
return removeLines
return mods
}
private fun getLinesBy(lines: MutableList<String>, windowSize: Int) =
// all lines (that aren't empty or comments) in sliding windows of certain size
lines.withIndex().filter { it.value.isNotBlank() && !it.value.trimStart().startsWith(';') }.windowed(windowSize, partialWindows = false)
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sta X + lda X, sty X + ldy X, stx X + ldx X -> the second instruction can be eliminated
val removeLines = mutableListOf<Int>()
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value.trimStart()
val second = pair[1].value.trimStart()
@ -186,26 +199,40 @@ private fun optimizeStoreLoadSame(linesByFour: List<List<IndexedValue<String>>>)
val firstLoc = first.substring(4)
val secondLoc = second.substring(4)
if (firstLoc == secondLoc) {
removeLines.add(pair[1].index)
mods.add(Modification(pair[1].index, true, null))
}
}
}
return removeLines
return mods
}
private fun optimizeIncDec(linesByTwo: List<List<IndexedValue<String>>>): List<Int> {
private fun optimizeIncDec(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// sometimes, iny+dey / inx+dex / dey+iny / dex+inx sequences are generated, these can be eliminated.
val removeLines = mutableListOf<Int>()
for (pair in linesByTwo) {
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value
val second = pair[1].value
if ((" iny" in first || "\tiny" in first) && (" dey" in second || "\tdey" in second)
|| (" inx" in first || "\tinx" in first) && (" dex" in second || "\tdex" in second)
|| (" dey" in first || "\tdey" in first) && (" iny" in second || "\tiny" in second)
|| (" dex" in first || "\tdex" in first) && (" inx" in second || "\tinx" in second)) {
removeLines.add(pair[0].index)
removeLines.add(pair[1].index)
mods.add(Modification(pair[0].index, true, null))
mods.add(Modification(pair[1].index, true, null))
}
}
return removeLines
return mods
}
private fun optimizeJsrRts(linesByFour: List<List<IndexedValue<String>>>): List<Modification> {
// jsr Sub + rts -> jmp Sub
val mods = mutableListOf<Modification>()
for (pair in linesByFour) {
val first = pair[0].value
val second = pair[1].value
if ((" jsr" in first || "\tjsr" in first ) && (" rts" in second || "\trts" in second)) {
mods += Modification(pair[0].index, false, pair[0].value.replace("jsr", "jmp"))
mods += Modification(pair[1].index, true, null)
}
}
return mods
}

View File

@ -3,7 +3,13 @@ package prog8.compiler.target.c64.codegen
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.AssemblyError
import prog8.compiler.target.c64.C64MachineDefinition
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
@ -22,7 +28,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
is RegisterExpr -> translateExpression(expression)
is IdentifierReference -> translateExpression(expression)
is FunctionCall -> translateExpression(expression)
is ArrayLiteralValue, is StringLiteralValue -> TODO("string/array/struct assignment?")
is ArrayLiteralValue, is StringLiteralValue -> throw AssemblyError("no asm gen for string/array literal value assignment - should have been replaced by a variable")
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")
}
@ -34,18 +40,29 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if (builtinFunc != null) {
asmgen.translateFunctioncallExpression(expression, builtinFunc)
} else {
asmgen.translateFunctionCall(expression)
val sub = expression.target.targetSubroutine(program.namespace)!!
asmgen.translateFunctionCall(expression)
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 ${MachineDefinition.ESTACK_LO_HEX},x | dex")
RegisterOrPair.Y -> asmgen.out(" tya | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
RegisterOrPair.AY -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | tya | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
RegisterOrPair.X, RegisterOrPair.AX, RegisterOrPair.XY -> throw AssemblyError("can't push X register - use a variable")
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 -> {
// return value in X register has been discarded, just push a zero
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | dex")
}
RegisterOrPair.AX -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" sta $ESTACK_LO_HEX,x | lda #0 | sta $ESTACK_HI_HEX,x | dex")
}
RegisterOrPair.XY -> {
// return value in X register has been discarded, just push a zero in this place
asmgen.out(" lda #0 | sta $ESTACK_LO_HEX,x | tya | sta $ESTACK_HI_HEX,x | dex")
}
}
}
// return value from a statusregister is not put on the stack, it should be acted on via a conditional branch such as if_cc
@ -60,7 +77,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
DataType.UBYTE -> {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> asmgen.out(" lda #0 | sta ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x")
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")
@ -69,7 +86,7 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
DataType.BYTE -> {
when(expr.type) {
DataType.UBYTE, DataType.BYTE -> {}
DataType.UWORD, DataType.WORD -> asmgen.out(" lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | ${asmgen.signExtendAtoMsb("${MachineDefinition.ESTACK_HI_PLUS1_HEX},x")}")
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")
@ -104,42 +121,50 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
else -> throw AssemblyError("weird type")
}
}
in PassByReferenceDatatypes -> throw AssemblyError("cannot case a pass-by-reference datatypes into something else")
in PassByReferenceDatatypes -> throw AssemblyError("cannot cast pass-by-reference value into another type")
else -> throw AssemblyError("weird type")
}
}
private fun translateExpression(expr: AddressOf) {
val name = asmgen.asmIdentifierName(expr.identifier)
asmgen.out(" lda #<$name | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda #>$name | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
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 ${MachineDefinition.ESTACK_LO_HEX},x | dex")
asmgen.out(" lda ${address.toHex()} | sta $ESTACK_LO_HEX,x | dex")
}
is IdentifierReference -> {
// the identifier is a pointer variable, so read the value from the address in it
val sourceName = asmgen.asmIdentifierName(expr.addressExpression as IdentifierReference)
asmgen.out(" lda $sourceName | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
asmgen.out("""
lda $sourceName
sta (+) +1
lda $sourceName+1
sta (+) +2
+ lda ${'$'}ffff ; modified
sta $ESTACK_LO_HEX,x
dex""")
}
else -> {
translateExpression(expr.addressExpression)
asmgen.out(" jsr prog8_lib.read_byte_from_address")
asmgen.out(" sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x")
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 ${MachineDefinition.ESTACK_LO_HEX},x | dex")
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 ${MachineDefinition.ESTACK_LO_HEX},x
sta $ESTACK_LO_HEX,x
lda #>${expr.number.toHex()}
sta ${MachineDefinition.ESTACK_HI_HEX},x
sta $ESTACK_HI_HEX,x
dex
""")
DataType.FLOAT -> {
@ -152,9 +177,9 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
private fun translateExpression(expr: RegisterExpr) {
when(expr.register) {
Register.A -> asmgen.out(" sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
Register.X -> throw AssemblyError("cannot push X - use a variable instead of the X register")
Register.Y -> asmgen.out(" tya | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
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")
}
}
@ -162,17 +187,17 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val varname = asmgen.asmIdentifierName(expr)
when(expr.inferType(program).typeOrElse(DataType.STRUCT)) {
DataType.UBYTE, DataType.BYTE -> {
asmgen.out(" lda $varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
asmgen.out(" lda $varname | sta $ESTACK_LO_HEX,x | dex")
}
DataType.UWORD, DataType.WORD -> {
asmgen.out(" lda $varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $varname+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
}
in ArrayDatatypes, in StringDatatypes -> {
asmgen.out(" lda #<$varname | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda #>$varname | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
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")
}
}
@ -196,10 +221,36 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
translateExpression(expr.left)
val amount = expr.right.constValue(program)!!.number.toInt()
when (leftDt) {
DataType.UBYTE -> repeat(amount) { asmgen.out(" lsr ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.BYTE -> repeat(amount) { asmgen.out(" lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | asl a | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.UWORD -> repeat(amount) { asmgen.out(" lsr ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.WORD -> repeat(amount) { asmgen.out(" lda ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | asl a | ror ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x | ror ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
DataType.UBYTE -> {
if(amount<=2)
repeat(amount) { asmgen.out(" lsr $ESTACK_LO_PLUS1_HEX,x") }
else {
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x")
repeat(amount) { asmgen.out(" lsr a") }
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
}
}
DataType.BYTE -> {
if(amount<=2)
repeat(amount) { asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | asl a | ror $ESTACK_LO_PLUS1_HEX,x") }
else {
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x | sta ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}")
repeat(amount) { asmgen.out(" asl a | ror ${C64MachineDefinition.C64Zeropage.SCRATCH_B1} | lda ${C64MachineDefinition.C64Zeropage.SCRATCH_B1}") }
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
}
}
DataType.UWORD -> {
if(amount<=2)
repeat(amount) { asmgen.out(" lsr $ESTACK_HI_PLUS1_HEX,x | ror $ESTACK_LO_PLUS1_HEX,x") }
else
asmgen.out(" jsr math.shift_right_uw_$amount") // 3-7 (8+ is done via other optimizations)
}
DataType.WORD -> {
if(amount<=2)
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
asmgen.out(" jsr math.shift_right_w_$amount") // 3-7 (8+ is done via other optimizations)
}
else -> throw AssemblyError("weird type")
}
return
@ -208,10 +259,22 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
// 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 ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x") }
else
repeat(amount) { asmgen.out(" asl ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x | rol ${MachineDefinition.ESTACK_HI_PLUS1_HEX},x") }
if (leftDt in ByteDatatypes) {
if(amount<=2)
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x") }
else {
asmgen.out(" lda $ESTACK_LO_PLUS1_HEX,x")
repeat(amount) { asmgen.out(" asl a") }
asmgen.out(" sta $ESTACK_LO_PLUS1_HEX,x")
}
}
else {
if(amount<=2) {
repeat(amount) { asmgen.out(" asl $ESTACK_LO_PLUS1_HEX,x | rol $ESTACK_HI_PLUS1_HEX,x") }
} else {
asmgen.out(" jsr math.shift_left_w_$amount") // 3-7 (8+ is done via other optimizations)
}
}
return
}
"*" -> {
@ -219,8 +282,6 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
if(value!=null) {
if(rightDt in IntegerDatatypes) {
val amount = value.number.toInt()
if(amount in powersOfTwo)
printWarning("${expr.right.position} multiplication by power of 2 should have been optimized into a left shift instruction: $amount")
when(rightDt) {
DataType.UBYTE -> {
if(amount in optimizedByteMultiplications) {
@ -270,8 +331,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
// 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?
if((leftDt in ByteDatatypes && rightDt !in ByteDatatypes)
|| (leftDt in WordDatatypes && rightDt !in WordDatatypes))
throw AssemblyError("binary operator ${expr.operator} left/right dt not identical")
when (leftDt) {
in ByteDatatypes -> translateBinaryOperatorBytes(expr.operator, leftDt)
in WordDatatypes -> translateBinaryOperatorWords(expr.operator, leftDt)
@ -297,9 +360,9 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
when(type) {
in ByteDatatypes ->
asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
lda $ESTACK_LO_PLUS1_HEX,x
eor #255
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
sta $ESTACK_LO_PLUS1_HEX,x
""")
in WordDatatypes -> asmgen.out(" jsr prog8_lib.inv_word")
else -> throw AssemblyError("weird type")
@ -326,10 +389,10 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
val indexValue = index.number.toInt() * elementDt.memorySize()
when(elementDt) {
in ByteDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | dex")
asmgen.out(" lda $arrayVarName+$indexValue | sta $ESTACK_LO_HEX,x | dex")
}
in WordDatatypes -> {
asmgen.out(" lda $arrayVarName+$indexValue | sta ${MachineDefinition.ESTACK_LO_HEX},x | lda $arrayVarName+$indexValue+1 | sta ${MachineDefinition.ESTACK_HI_HEX},x | dex")
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")
@ -353,18 +416,18 @@ internal class ExpressionsAsmGen(private val program: Program, private val asmge
asmgen.out(" jsr prog8_lib.remainder_ub")
}
"+" -> asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_PLUS2_HEX},x
lda $ESTACK_LO_PLUS2_HEX,x
clc
adc ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
adc $ESTACK_LO_PLUS1_HEX,x
inx
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
sta $ESTACK_LO_PLUS1_HEX,x
""")
"-" -> asmgen.out("""
lda ${MachineDefinition.ESTACK_LO_PLUS2_HEX},x
lda $ESTACK_LO_PLUS2_HEX,x
sec
sbc ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
sbc $ESTACK_LO_PLUS1_HEX,x
inx
sta ${MachineDefinition.ESTACK_LO_PLUS1_HEX},x
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")

View File

@ -8,9 +8,10 @@ import prog8.ast.expressions.RangeExpr
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Assignment
import prog8.ast.statements.ForLoop
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_LO_PLUS1_HEX
import prog8.compiler.target.c64.MachineDefinition.ESTACK_HI_PLUS1_HEX
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
@ -36,7 +37,7 @@ internal class ForLoopsAsmGen(private val program: Program, private val asmgen:
is IdentifierReference -> {
translateForOverIterableVar(stmt, iterableDt.typeOrElse(DataType.STRUCT), stmt.iterable as IdentifierReference)
}
else -> throw AssemblyError("can't iterate over ${stmt.iterable}")
else -> throw AssemblyError("can't iterate over ${stmt.iterable.javaClass} - should have been replaced by a variable")
}
}
@ -317,7 +318,7 @@ $endLabel inx""")
val iterableName = asmgen.asmIdentifierName(ident)
val decl = ident.targetVarDecl(program.namespace)!!
when(iterableDt) {
DataType.STR, DataType.STR_S -> {
DataType.STR -> {
if(stmt.loopRegister!=null && stmt.loopRegister!= Register.A)
throw AssemblyError("can only use A")
asmgen.out("""

View File

@ -7,27 +7,31 @@ import prog8.ast.expressions.*
import prog8.ast.statements.AssignTarget
import prog8.ast.statements.Subroutine
import prog8.ast.statements.SubroutineParameter
import prog8.compiler.target.c64.MachineDefinition
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)
val saveX = Register.X in sub.asmClobbers || sub.regXasResult()
if(saveX)
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.arglist.isNotEmpty()) {
for(arg in sub.parameters.withIndex().zip(stmt.arglist)) {
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)
if(saveX)
asmgen.out(" ldx c64.SCRATCH_ZPREGX") // restore X again
}
@ -39,7 +43,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
if(!argumentTypeCompatible(sourceDt, parameter.value.type))
throw AssemblyError("argument type incompatible")
if(sub.asmParameterRegisters.isEmpty()) {
// pass parameter via a variable
// pass parameter via a regular variable (not via registers)
val paramVar = parameter.value
val scopedParamVar = (sub.scopedname+"."+paramVar.name).split(".")
val target = AssignTarget(null, IdentifierReference(scopedParamVar, sub.position), null, null, sub.position)
@ -51,7 +55,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
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?")
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
else -> throw AssemblyError("weird parameter datatype")
}
}
@ -61,7 +65,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
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?")
in PassByReferenceDatatypes -> throw AssemblyError("can't pass string/array as argument via a variable?") // TODO huh
else -> throw AssemblyError("weird parameter datatype")
}
}
@ -139,7 +143,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
asmgen.translateExpression(value)
asmgen.out("""
inx
lda ${MachineDefinition.ESTACK_LO_HEX},x
lda $ESTACK_LO_HEX,x
beq +
sec
bcs ++
@ -166,9 +170,9 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
else -> {
asmgen.translateExpression(value)
when(register) {
RegisterOrPair.A -> asmgen.out(" inx | lda ${MachineDefinition.ESTACK_LO_HEX},x")
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 ${MachineDefinition.ESTACK_LO_HEX},x")
RegisterOrPair.Y -> asmgen.out(" inx | ldy $ESTACK_LO_HEX,x")
else -> throw AssemblyError("cannot assign to register pair")
}
}
@ -199,11 +203,20 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
}
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 -> {}
if(sourceDt in PassByReferenceDatatypes) {
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 -> {}
}
} else {
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 -> {
@ -211,7 +224,7 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
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 ${MachineDefinition.ESTACK_LO_HEX},x | ldy ${MachineDefinition.ESTACK_HI_HEX},x")
asmgen.out(" inx | lda $ESTACK_LO_HEX,x | ldy $ESTACK_HI_HEX,x")
}
}
}
@ -222,12 +235,16 @@ internal class FunctionCallAsmGen(private val program: Program, private val asmg
private fun argumentTypeCompatible(argType: DataType, paramType: DataType): Boolean {
if(argType isAssignableTo paramType)
return true
if(argType in ByteDatatypes && paramType in ByteDatatypes)
return true
if(argType in WordDatatypes && paramType in WordDatatypes)
return true
// we have a special rule for some types.
// strings are assignable to UWORD, for example, and vice versa
if(argType in StringDatatypes && paramType==DataType.UWORD)
if(argType==DataType.STR && paramType==DataType.UWORD)
return true
if(argType==DataType.UWORD && paramType in StringDatatypes)
if(argType==DataType.UWORD && paramType == DataType.STR)
return true
return false

View File

@ -6,9 +6,11 @@ import prog8.ast.expressions.IdentifierReference
import prog8.ast.expressions.NumericLiteralValue
import prog8.ast.expressions.RegisterExpr
import prog8.ast.statements.PostIncrDecr
import prog8.compiler.target.c64.MachineDefinition
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=="++"
@ -64,7 +66,11 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
is IdentifierReference -> {
val what = asmgen.asmIdentifierName(addressExpr)
asmgen.out(if(incr) " inc $what" else " dec $what")
asmgen.out(" lda $what | sta (+) +1 | lda $what+1 | sta (+) +2")
if(incr)
asmgen.out("+\tinc ${'$'}ffff\t; modified")
else
asmgen.out("+\tdec ${'$'}ffff\t; modified")
}
else -> throw AssemblyError("weird target type $targetMemory")
}
@ -98,17 +104,14 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
}
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)
}
@ -119,9 +122,9 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
private fun incrDecrArrayvalueWithIndexA(incr: Boolean, arrayDt: DataType, arrayVarName: String) {
asmgen.out(" stx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X} | tax")
asmgen.out(" stx ${C64Zeropage.SCRATCH_REG_X} | tax")
when(arrayDt) {
DataType.STR, DataType.STR_S,
DataType.STR,
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out(if(incr) " inc $arrayVarName,x" else " dec $arrayVarName,x")
}
@ -142,7 +145,7 @@ internal class PostIncrDecrAsmGen(private val program: Program, private val asmg
}
else -> throw AssemblyError("weird array dt")
}
asmgen.out(" ldx ${MachineDefinition.C64Zeropage.SCRATCH_REG_X}")
asmgen.out(" ldx ${C64Zeropage.SCRATCH_REG_X}")
}
}

View File

@ -7,88 +7,100 @@ 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),
"sort" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), null),
"reverse" to FunctionSignature(false, listOf(BuiltinFunctionParam("array", ArrayDatatypes)), 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, prg -> collectionArg(a, p, prg, ::builtinMax) }, // type depends on args
"min" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinMin) }, // type depends on args
"sum" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), null) { a, p, prg -> collectionArg(a, p, prg, ::builtinSum) }, // 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:
"sgn" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", NumericDatatypes)), DataType.BYTE, ::builtinSgn ),
"sin" to FunctionSignature(true, listOf(BuiltinFunctionParam("rads", setOf(DataType.FLOAT))), DataType.FLOAT) { a, p, prg -> oneDoubleArg(a, p, prg, Math::sin) },
"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, prg -> collectionArg(a, p, prg, ::builtinAny) },
"all" to FunctionSignature(true, listOf(BuiltinFunctionParam("values", ArrayDatatypes)), DataType.UBYTE) { a, p, prg -> collectionArg(a, p, prg, ::builtinAll) },
"lsb" to FunctionSignature(true, listOf(BuiltinFunctionParam("value", setOf(DataType.UWORD, DataType.WORD))), DataType.UBYTE) { a, p, prg -> oneIntArgOutputInt(a, p, prg) { x: Int -> x and 255 }},
"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 + DataType.UWORD),
BuiltinFunctionParam("to", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numbytes", setOf(DataType.UBYTE))), null),
"memset" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + DataType.UWORD),
BuiltinFunctionParam("numbytes", setOf(DataType.UWORD)),
BuiltinFunctionParam("bytevalue", ByteDatatypes)), null),
"memsetw" to FunctionSignature(false, listOf(
BuiltinFunctionParam("address", IterableDatatypes + 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)
"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),
"substr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("start", setOf(DataType.UBYTE)),
FParam("length", setOf(DataType.UBYTE))), null),
"leftstr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null),
"rightstr" to FSignature(false, listOf(
FParam("source", IterableDatatypes + DataType.UWORD),
FParam("target", IterableDatatypes + DataType.UWORD),
FParam("length", setOf(DataType.UBYTE))), null)
)
fun builtinMax(array: List<Number>): Number = array.maxBy { it.toDouble() }!!
@ -121,8 +133,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
if(!idt.isKnown)
throw FatalAstException("couldn't determine type of iterable $arglist")
return when(val dt = idt.typeOrElse(DataType.STRUCT)) {
in NumericDatatypes -> dt
in StringDatatypes -> dt
DataType.STR, in NumericDatatypes -> dt
in ArrayDatatypes -> ArrayElementTypes.getValue(dt)
else -> throw FatalAstException("function '$function' requires one argument which is an iterable")
}
@ -145,8 +156,8 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
}
"max", "min" -> {
when(val dt = datatypeFromIterableArg(args.single())) {
DataType.STR -> InferredTypes.knownFor(DataType.UBYTE)
in NumericDatatypes -> InferredTypes.knownFor(dt)
in StringDatatypes -> InferredTypes.knownFor(DataType.UBYTE)
in ArrayDatatypes -> InferredTypes.knownFor(ArrayElementTypes.getValue(dt))
else -> InferredTypes.unknown()
}
@ -159,7 +170,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
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)
in StringDatatypes -> InferredTypes.knownFor(DataType.UWORD)
DataType.STR -> InferredTypes.knownFor(DataType.UWORD)
else -> InferredTypes.unknown()
}
}
@ -174,6 +185,7 @@ fun builtinFunctionReturnType(function: String, args: List<Expression>, program:
class NotConstArgumentException: AstException("not a const argument to a built-in function")
class CannotEvaluateException(func:String, msg: String): FatalAstException("cannot evaluate built-in function $func: $msg")
private fun oneDoubleArg(args: List<Expression>, position: Position, program: Program, function: (arg: Double)->Number): NumericLiteralValue {
@ -232,7 +244,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
@ -254,22 +266,27 @@ private fun builtinLen(args: List<Expression>, position: Position, program: Prog
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)!!
val target = (args[0] as IdentifierReference).targetVarDecl(program.namespace)
?: throw CannotEvaluateException("len", "no target vardecl")
return when(target.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
arraySize = target.arraysize!!.size()!!
arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
DataType.ARRAY_F -> {
arraySize = target.arraysize!!.size()!!
arraySize = target.arraysize?.size()
if(arraySize==null)
throw CannotEvaluateException("len", "arraysize unknown")
if(arraySize>256)
throw CompilerException("array length exceeds byte limit ${target.position}")
NumericLiteralValue.optimalInteger(arraySize, args[0].position)
}
in StringDatatypes -> {
DataType.STR -> {
val refLv = target.value as StringLiteralValue
if(refLv.value.length>255)
throw CompilerException("string length exceeds byte limit ${refLv.position}")

View File

@ -0,0 +1,157 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.ErrorReporter
import prog8.ast.expressions.BinaryExpression
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Assignment
import prog8.ast.statements.PostIncrDecr
internal class AssignmentTransformer(val program: Program, val errors: ErrorReporter) : AstWalker() {
var optimizationsDone: Int = 0
private val noModifications = emptyList<IAstModification>()
override fun before(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// modify A = A + 5 back into augmented form A += 5 for easier code generation for optimized in-place assignments
// also to put code generation stuff together, single value assignment (A = 5) is converted to a special
// augmented form as wel (with the operator "setvalue")
if (assignment.aug_op == null) {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
if (assignment.target.isSameAs(binExpr.left)) {
assignment.value = binExpr.right
assignment.aug_op = binExpr.operator + "="
assignment.value.parent = assignment
optimizationsDone++
return noModifications
}
}
assignment.aug_op = "setvalue"
optimizationsDone++
} else if(assignment.aug_op == "+=") {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
if(binExpr.operator == "+") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x += y + 1 -> x += y , x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x += y + 2 -> x += y , x++, x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
}
} else if(binExpr.operator == "-") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x += y - 1 -> x += y , x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x += y - 2 -> x += y , x--, x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
}
}
}
} else if(assignment.aug_op == "-=") {
val binExpr = assignment.value as? BinaryExpression
if (binExpr != null) {
val leftnum = binExpr.left.constValue(program)?.number?.toDouble()
val rightnum = binExpr.right.constValue(program)?.number?.toDouble()
if(binExpr.operator == "+") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x -= y + 1 -> x -= y , x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x -= y + 2 -> x -= y , x--, x--
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "--", assignment.position), parent)
)
}
}
} else if(binExpr.operator == "-") {
when {
leftnum == 1.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
leftnum == 2.0 -> {
optimizationsDone++
return listOf(IAstModification.SwapOperands(binExpr))
}
rightnum == 1.0 -> {
// x -= y - 1 -> x -= y , x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
rightnum == 2.0 -> {
// x -= y - 2 -> x -= y , x++, x++
return listOf(
IAstModification.ReplaceNode(assignment.value, binExpr.left, assignment),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent),
IAstModification.InsertAfter(assignment, PostIncrDecr(assignment.target, "++", assignment.position), parent)
)
}
}
}
}
}
return noModifications
}
}

View File

@ -6,22 +6,30 @@ import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.DataType
import prog8.ast.base.ParentSentinel
import prog8.ast.base.VarDeclType
import prog8.ast.base.initvarsSubName
import prog8.ast.expressions.FunctionCall
import prog8.ast.expressions.IdentifierReference
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
// TODO add dataflow graph: what statements use what variables - can be used to eliminate unused vars
val usedSymbols = mutableSetOf<Statement>()
init {
@ -31,9 +39,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 +73,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 +83,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 +99,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 +107,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.definingModule().isLibraryModule) {
// make sure the entrypoint is mentioned in the used symbols
addNodeAndParentScopes(subroutine)
}
@ -124,12 +126,12 @@ class CallGraph(private val program: Program): IAstVisitor {
}
override fun visit(decl: VarDecl) {
if(decl.autogeneratedDontRemove || (decl.definingModule().isLibraryModule && decl.type!=VarDeclType.VAR)) {
// make sure autogenerated vardecls are in the used symbols
if (decl.autogeneratedDontRemove || decl.definingModule().isLibraryModule) {
// make sure autogenerated vardecls are in the used symbols and are never removed as 'unused'
addNodeAndParentScopes(decl)
}
if(decl.datatype==DataType.STRUCT)
if (decl.datatype == DataType.STRUCT)
addNodeAndParentScopes(decl)
super.visit(decl)
@ -137,7 +139,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 +150,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 +161,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 +183,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 +192,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 +204,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)

View File

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

View File

@ -1,722 +0,0 @@
package prog8.optimizer
import prog8.ast.IFunctionCall
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.codegen.AssemblyError
import prog8.functions.BuiltinFunctions
import kotlin.math.floor
class ConstantFolding(private val program: Program) : 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))
return decl
}
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
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? ArrayLiteralValue
if(arrayval!=null) {
decl.arraysize = ArrayIndex(NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position), decl.position)
optimizationsDone++
}
}
else if(decl.arraysize?.size()==null) {
val size = decl.arraysize!!.index.accept(this)
if(size is NumericLiteralValue) {
decl.arraysize = ArrayIndex(size, decl.position)
optimizationsDone++
}
}
}
when(decl.datatype) {
DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
decl.value = newValue
optimizationsDone++
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
if(rangeExpr!=null) {
// 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!!))
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).typeOrElse(DataType.UBYTE)
if(eltType in ByteDatatypes) {
decl.value = ArrayLiteralValue(decl.datatype,
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
decl.value = ArrayLiteralValue(decl.datatype,
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
decl.value!!.linkParents(decl)
optimizationsDone++
return super.visit(decl)
}
}
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.add(ExpressionError("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.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.add(ExpressionError("ubyte value overflow", numericLv.position))
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.add(ExpressionError("byte value overflow", numericLv.position))
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.add(ExpressionError("uword value overflow", numericLv.position))
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.add(ExpressionError("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 = ArrayLiteralValue(decl.datatype, array, position = numericLv.position)
refValue.addToHeap()
decl.value = refValue
refValue.parent=decl
optimizationsDone++
return super.visit(decl)
}
}
DataType.ARRAY_F -> {
val size = decl.arraysize?.size() ?: return decl
val litval = decl.value as? NumericLiteralValue
if(litval==null) {
// there's no initialization value, but the size is known, so we're ok.
return super.visit(decl)
} 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))
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 = ArrayLiteralValue(DataType.ARRAY_F, array, position = litval.position)
refValue.addToHeap()
decl.value = refValue
refValue.parent=decl
optimizationsDone++
return super.visit(decl)
}
}
}
else -> {
// nothing to do for this type
}
}
}
return super.visit(decl)
}
/**
* 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 -> {
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")
else -> identifier
}
} catch (ax: AstException) {
addError(ax)
identifier
}
}
override fun visit(functionCall: FunctionCall): Expression {
super.visit(functionCall)
typeCastConstArguments(functionCall)
return try {
functionCall.constValue(program) ?: functionCall
} catch (ax: AstException) {
addError(ax)
functionCall
}
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
super.visit(functionCallStatement)
typeCastConstArguments(functionCallStatement)
return functionCallStatement
}
private fun typeCastConstArguments(functionCall: IFunctionCall) {
if(functionCall.target.nameInSource.size==1) {
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)) {
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())
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
return
}
}
// match the arguments of a subroutine.
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
// if types differ, try to typecast constant arguments to the function call to the desired data type of the parameter
for(arg in functionCall.arglist.withIndex().zip(subroutine.parameters)) {
val expectedDt = arg.second.type
val argConst = arg.first.value.constValue(program)
if(argConst!=null && argConst.type!=expectedDt) {
val convertedValue = argConst.cast(expectedDt)
functionCall.arglist[arg.first.index] = convertedValue
optimizationsDone++
}
}
}
}
override fun visit(memread: DirectMemoryRead): Expression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
return super.visit(addrOf.identifier)
return super.visit(memread)
}
/**
* Try to accept a unary prefix expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
*/
override fun visit(expr: PrefixExpression): Expression {
return try {
val prefixExpr=super.visit(expr)
if(prefixExpr !is PrefixExpression)
return prefixExpr
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 -> {
optimizationsDone++
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position)
}
subexpr.type == 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 -> {
optimizationsDone++
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position)
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
prefixExpr.operator == "not" -> {
optimizationsDone++
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position)
}
else -> throw ExpressionError(prefixExpr.operator, subexpr.position)
}
}
return prefixExpr
} catch (ax: AstException) {
addError(ax)
expr
}
}
/**
* Try to accept a binary expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
*
* More complex stuff: reordering to group constants:
* If one of our operands is a Constant,
* and the other operand is a Binary expression,
* and one of ITS operands is a Constant,
* and ITS other operand is NOT a Constant,
* ...it may be possible to rewrite the expression to group the two Constants together,
* to allow them to be const-folded away.
*
* examples include:
* (X / c1) * c2 -> X / (c2/c1)
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun visit(expr: BinaryExpression): Expression {
return try {
super.visit(expr)
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)
val rightconst = expr.right.constValue(program)
val subExpr: BinaryExpression? = when {
leftconst!=null -> expr.right as? BinaryExpression
rightconst!=null -> expr.left as? BinaryExpression
else -> null
}
if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(program)
val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
// try reordering.
return groupTwoConstsTogether(expr, subExpr,
leftconst != null, rightconst != null,
subleftconst != null, subrightconst != null)
}
}
// const fold when both operands are a const
return when {
leftconst != null && rightconst != null -> {
optimizationsDone++
val evaluator = ConstExprEvaluator()
evaluator.evaluate(leftconst, expr.operator, rightconst)
}
else -> expr
}
} catch (ax: AstException) {
addError(ax)
expr
}
}
private fun groupTwoConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression,
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
subrightIsConst: Boolean): Expression
{
// 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.
if(expr.operator=="+" || expr.operator=="*") {
if(leftIsConst) {
if(subleftIsConst)
expr.left = subExpr.right.also { subExpr.right = expr.left }
else
expr.left = subExpr.left.also { subExpr.left = expr.left }
} else {
if(subleftIsConst)
expr.right = subExpr.right.also {subExpr.right = expr.right }
else
expr.right = subExpr.left.also { subExpr.left = expr.right }
}
optimizationsDone++
return expr
}
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
if(expr.operator=="-" || expr.operator=="/") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
val tmp = subExpr.right
subExpr.right = subExpr.left
subExpr.left = expr.left
expr.left = tmp
expr.operator = if(expr.operator=="-") "+" else "*"
expr
} else
BinaryExpression(
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.operator, subExpr.left, expr.position)
} else {
return if(subleftIsConst) {
expr.right = subExpr.right.also { subExpr.right = expr.right }
expr
} else
BinaryExpression(
subExpr.left, expr.operator,
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.position)
}
}
return expr
}
else
{
if(expr.operator=="/" && subExpr.operator=="*") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// C1/(C2*V) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1/(V*C2) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"/",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (C1*V)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
"*",
subExpr.right, expr.position)
} else {
// (V*C1)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="*" && subExpr.operator=="/") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
BinaryExpression(
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1*(V/C2) -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (C1/V)*C2 -> (C1*C2)/V
BinaryExpression(
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// (V/C1)*C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="+" && subExpr.operator=="-") {
optimizationsDone++
if(leftIsConst){
return if(subleftIsConst){
// c1+(c2-v) -> (c1+c2)-v
BinaryExpression(
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1+(v-c2) -> v+(c1-c2)
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (c1-v)+c2 -> (c1+c2)-v
BinaryExpression(
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// (v-c1)+c2 -> v+(c2-c1)
BinaryExpression(
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
}
}
else if(expr.operator=="-" && subExpr.operator=="+") {
optimizationsDone++
if(leftIsConst) {
return if(subleftIsConst) {
// c1-(c2+v) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1-(v+c2) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"-",
subExpr.left, expr.position)
}
} else {
return if(subleftIsConst) {
// (c1+v)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
"+",
subExpr.right, expr.position)
} else {
// (v+c1)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
}
}
return expr
}
}
override fun visit(forLoop: ForLoop): Statement {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
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)
array2.addToHeap()
}
}
// adjust the datatype of a range expression in for loops to the loop variable.
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 forLoop2
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
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.BYTE -> {
if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.UWORD -> {
if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
DataType.WORD -> {
if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values
forLoop2.iterable = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
}
}
return forLoop2
}
override fun visit(arrayLiteral: ArrayLiteralValue): Expression {
val array = super.visit(arrayLiteral)
if(array is ArrayLiteralValue) {
array.addToHeap()
val vardecl = array.parent as? VarDecl
return if (vardecl!=null) {
fixupArrayDatatype(array, vardecl, program)
} else {
// it's not an array associated with a vardecl, attempt to guess the data type from the array values
fixupArrayDatatype(array, program)
}
}
return array
}
override fun visit(assignment: Assignment): Statement {
super.visit(assignment)
val lv = assignment.value as? NumericLiteralValue
if(lv!=null) {
// see if we can promote/convert a literal value to the required datatype
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)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.BYTE && lv.number.toInt()>=0)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.WORD && lv.number.toInt()>=0)
assignment.value = NumericLiteralValue(DataType.UWORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d>=0 && d<=65535)
assignment.value = NumericLiteralValue(DataType.UWORD, floor(d).toInt(), lv.position)
}
}
DataType.UBYTE -> {
// we can convert to UBYTE: UWORD <=255, BYTE >=0, FLOAT that's an integer 0..255,
if(lv.type== DataType.UWORD && lv.number.toInt() <= 255)
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.BYTE && lv.number.toInt() >=0)
assignment.value = NumericLiteralValue(DataType.UBYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d >=0 && d<=255)
assignment.value = NumericLiteralValue(DataType.UBYTE, floor(d).toShort(), lv.position)
}
}
DataType.BYTE -> {
// we can convert to BYTE: UWORD/UBYTE <= 127, FLOAT that's an integer 0..127
if(lv.type== DataType.UWORD && lv.number.toInt() <= 127)
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.UBYTE && lv.number.toInt() <= 127)
assignment.value = NumericLiteralValue(DataType.BYTE, lv.number.toShort(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d>=0 && d<=127)
assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
}
}
DataType.WORD -> {
// we can convert to WORD: any UBYTE/BYTE, UWORD <= 32767, FLOAT that's an integer -32768..32767,
if(lv.type== DataType.UBYTE || lv.type== DataType.BYTE)
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.UWORD && lv.number.toInt() <= 32767)
assignment.value = NumericLiteralValue(DataType.WORD, lv.number.toInt(), lv.position)
else if(lv.type== DataType.FLOAT) {
val d = lv.number.toDouble()
if(floor(d)==d && d>=-32768 && d<=32767)
assignment.value = NumericLiteralValue(DataType.BYTE, floor(d).toShort(), lv.position)
}
}
DataType.FLOAT -> {
assignment.value = NumericLiteralValue(DataType.FLOAT, lv.number.toDouble(), lv.position)
}
else -> {}
}
}
return assignment
}
}

View File

@ -0,0 +1,594 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.*
import prog8.compiler.target.CompilationTarget
// First thing to do is replace all constant identifiers with their actual value,
// and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun after(identifier: IdentifierReference, parent: Node): Iterable<IAstModification> {
// replace identifiers that refer to const value, with the value itself
// if it's a simple type and if it's not a left hand side variable
if(identifier.parent is AssignTarget)
return noModifications
var forloop = identifier.parent as? ForLoop
if(forloop==null)
forloop = identifier.parent.parent as? ForLoop
if(forloop!=null && identifier===forloop.loopVar)
return noModifications
val cval = identifier.constValue(program) ?: return noModifications
return when (cval.type) {
in NumericDatatypes -> listOf(IAstModification.ReplaceNode(identifier, NumericLiteralValue(cval.type, cval.number, identifier.position), identifier.parent))
in PassByReferenceDatatypes -> throw FatalAstException("pass-by-reference type should not be considered a constant")
else -> noModifications
}
}
override fun before(decl: VarDecl, parent: Node): Iterable<IAstModification> {
// the initializer value can't refer to the variable itself (recursive definition)
// TODO: use call graph for this?
if(decl.value?.referencesIdentifiers(decl.name) == true || decl.arraysize?.index?.referencesIdentifiers(decl.name) == true) {
errors.err("recursive var declaration", decl.position)
return noModifications
}
if(decl.type==VarDeclType.CONST || decl.type==VarDeclType.VAR) {
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? ArrayLiteralValue
if(arrayval!=null) {
return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
NumericLiteralValue.optimalInteger(arrayval.value.size, decl.position),
decl
))
}
}
else if(decl.arraysize?.size()==null) {
val size = decl.arraysize!!.index.constValue(program)
if(size!=null) {
return listOf(IAstModification.SetExpression(
{ decl.arraysize = ArrayIndex(it, decl.position) },
size, decl
))
}
}
}
when(decl.datatype) {
DataType.FLOAT -> {
// vardecl: for scalar float vars, promote constant integer initialization values to floats
val litval = decl.value as? NumericLiteralValue
if (litval!=null && litval.type in IntegerDatatypes) {
val newValue = NumericLiteralValue(DataType.FLOAT, litval.number.toDouble(), litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W -> {
val numericLv = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array
val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
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).typeOrElse(DataType.UBYTE)
val newValue = if(eltType in ByteDatatypes) {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it.toShort(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteralValue(eltType, it, decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(numericLv!=null && numericLv.type==DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.size() ?: return noModifications
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteralValue(ArrayElementTypes.getValue(decl.datatype), it, numericLv.position) as Expression}.toTypedArray()
val refValue = ArrayLiteralValue(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
DataType.ARRAY_F -> {
val size = decl.arraysize?.size() ?: return noModifications
val litval = decl.value as? NumericLiteralValue
val rangeExpr = decl.value as? RangeExpr
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.size()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size doesn't match declared array size", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
val newValue = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteralValue(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, newValue, decl))
}
}
if(rangeExpr==null && litval!=null) {
// arraysize initializer is a single int, and we know the size.
val fillvalue = litval.number.toDouble()
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 = ArrayLiteralValue(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = litval.position)
return listOf(IAstModification.ReplaceNode(decl.value!!, refValue, decl))
}
}
}
else -> {
// nothing to do for this type
// this includes strings and structs
}
}
}
val declValue = decl.value
if(declValue!=null && decl.type==VarDeclType.VAR
&& declValue is NumericLiteralValue && !declValue.inferType(program).istype(decl.datatype)) {
// cast the numeric literal to the appropriate datatype of the variable
return listOf(IAstModification.ReplaceNode(decl.value!!, declValue.cast(decl.datatype), decl))
}
return noModifications
}
}
internal class ConstantFoldingOptimizer(private val program: Program, private val errors: ErrorReporter) : AstWalker() {
private val noModifications = emptyList<IAstModification>()
override fun before(memread: DirectMemoryRead, parent: Node): Iterable<IAstModification> {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
return if(addrOf!=null)
listOf(IAstModification.ReplaceNode(memread, addrOf.identifier, parent))
else
noModifications
}
override fun after(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
// Try to turn a unary prefix expression into a single constant value.
// Compile-time constant sub expressions will be evaluated on the spot.
// For instance, the expression for "- 4.5" will be optimized into the float literal -4.5
val subexpr = expr.expression
if (subexpr is NumericLiteralValue) {
// accept prefixed literal values (such as -3, not true)
return when (expr.operator) {
"+" -> listOf(IAstModification.ReplaceNode(expr, subexpr, parent))
"-" -> when (subexpr.type) {
in IntegerDatatypes -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalNumeric(-subexpr.number.toInt(), subexpr.position),
parent))
}
DataType.FLOAT -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue(DataType.FLOAT, -subexpr.number.toDouble(), subexpr.position),
parent))
}
else -> throw ExpressionError("can only take negative of int or float", subexpr.position)
}
"~" -> when (subexpr.type) {
in IntegerDatatypes -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.optimalNumeric(subexpr.number.toInt().inv(), subexpr.position),
parent))
}
else -> throw ExpressionError("can only take bitwise inversion of int", subexpr.position)
}
"not" -> {
listOf(IAstModification.ReplaceNode(expr,
NumericLiteralValue.fromBoolean(subexpr.number.toDouble() == 0.0, subexpr.position),
parent))
}
else -> throw ExpressionError(expr.operator, subexpr.position)
}
}
return noModifications
}
/**
* Try to constfold a binary expression.
* Compile-time constant sub expressions will be evaluated on the spot.
* For instance, "9 * (4 + 2)" will be optimized into the integer literal 54.
*
* More complex stuff: reordering to group constants:
* If one of our operands is a Constant,
* and the other operand is a Binary expression,
* and one of ITS operands is a Constant,
* and ITS other operand is NOT a Constant,
* ...it may be possible to rewrite the expression to group the two Constants together,
* to allow them to be const-folded away.
*
* examples include:
* (X / c1) * c2 -> X / (c2/c1)
* (X + c1) - c2 -> X + (c1-c2)
*/
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftconst = expr.left.constValue(program)
val rightconst = expr.right.constValue(program)
val subExpr: BinaryExpression? = when {
leftconst!=null -> expr.right as? BinaryExpression
rightconst!=null -> expr.left as? BinaryExpression
else -> null
}
if(subExpr!=null) {
val subleftconst = subExpr.left.constValue(program)
val subrightconst = subExpr.right.constValue(program)
if ((subleftconst != null && subrightconst == null) || (subleftconst==null && subrightconst!=null)) {
// try reordering.
val change = groupTwoConstsTogether(expr, subExpr,
leftconst != null, rightconst != null,
subleftconst != null, subrightconst != null)
return change?.let { listOf(it) } ?: noModifications
}
}
// const fold when both operands are a const
if(leftconst != null && rightconst != null) {
val evaluator = ConstExprEvaluator()
return listOf(IAstModification.ReplaceNode(
expr,
evaluator.evaluate(leftconst, expr.operator, rightconst),
parent
))
}
return noModifications
}
override fun after(array: ArrayLiteralValue, parent: Node): Iterable<IAstModification> {
// 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.
if(array.type.isKnown)
return noModifications
// if the array literalvalue is inside an array vardecl, take the type from that
// otherwise infer it from the elements of the array
val vardeclType = (array.parent as? VarDecl)?.datatype
if(vardeclType!=null) {
val newArray = array.cast(vardeclType)
if (newArray != null && newArray != array)
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
} else {
val arrayDt = array.guessDatatype(program)
if (arrayDt.isKnown) {
val newArray = array.cast(arrayDt.typeOrElse(DataType.STRUCT))
if (newArray != null && newArray != array)
return listOf(IAstModification.ReplaceNode(array, newArray, parent))
}
}
return noModifications
}
override fun after(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// the args of a fuction are constfolded via recursion already.
val constvalue = functionCall.constValue(program)
return if(constvalue!=null)
listOf(IAstModification.ReplaceNode(functionCall, constvalue, parent))
else
noModifications
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
fun adjustRangeDt(rangeFrom: NumericLiteralValue, targetDt: DataType, rangeTo: NumericLiteralValue, stepLiteral: NumericLiteralValue?, range: RangeExpr): RangeExpr {
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)
}
// adjust the datatype of a range expression in for loops to the loop variable.
val iterableRange = forLoop.iterable as? RangeExpr ?: return noModifications
val rangeFrom = iterableRange.from as? NumericLiteralValue
val rangeTo = iterableRange.to as? NumericLiteralValue
if(rangeFrom==null || rangeTo==null) return noModifications
val loopvar = forLoop.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
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.BYTE -> {
if(rangeFrom.type!= DataType.BYTE) {
// attempt to translate the iterable into byte values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.UWORD -> {
if(rangeFrom.type!= DataType.UWORD) {
// attempt to translate the iterable into uword values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
DataType.WORD -> {
if(rangeFrom.type!= DataType.WORD) {
// attempt to translate the iterable into word values
val newIter = adjustRangeDt(rangeFrom, loopvar.datatype, rangeTo, stepLiteral, iterableRange)
return listOf(IAstModification.ReplaceNode(forLoop.iterable, newIter, forLoop))
}
}
else -> throw FatalAstException("invalid loopvar datatype $loopvar")
}
}
return noModifications
}
private class ShuffleOperands(val expr: BinaryExpression,
val exprOperator: String?,
val subExpr: BinaryExpression,
val newExprLeft: Expression?,
val newExprRight: Expression?,
val newSubexprLeft: Expression?,
val newSubexprRight: Expression?
): IAstModification {
override fun perform() {
if(exprOperator!=null) expr.operator = exprOperator
if(newExprLeft!=null) expr.left = newExprLeft
if(newExprRight!=null) expr.right = newExprRight
if(newSubexprLeft!=null) subExpr.left = newSubexprLeft
if(newSubexprRight!=null) subExpr.right = newSubexprRight
}
}
private fun groupTwoConstsTogether(expr: BinaryExpression,
subExpr: BinaryExpression,
leftIsConst: Boolean,
rightIsConst: Boolean,
subleftIsConst: Boolean,
subrightIsConst: Boolean): IAstModification?
{
// todo: this implements only a small set of possible reorderings at this time
if(expr.operator==subExpr.operator) {
// both operators are the same.
// If + or *, we can simply shuffle the const operands around to optimize.
if(expr.operator=="+" || expr.operator=="*") {
return if(leftIsConst) {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, subExpr.right, null, null, expr.left)
else
ShuffleOperands(expr, null, subExpr, subExpr.left, null, expr.left, null)
} else {
if(subleftIsConst)
ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
else
ShuffleOperands(expr, null, subExpr, null, subExpr.left, expr.right, null)
}
}
// If - or /, we simetimes must reorder more, and flip operators (- -> +, / -> *)
if(expr.operator=="-" || expr.operator=="/") {
if(leftIsConst) {
return if (subleftIsConst) {
ShuffleOperands(expr, if (expr.operator == "-") "+" else "*", subExpr, subExpr.right, null, expr.left, subExpr.left)
} else {
IAstModification.ReplaceNode(expr,
BinaryExpression(
BinaryExpression(expr.left, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.operator, subExpr.left, expr.position),
expr.parent)
}
} else {
return if(subleftIsConst) {
return ShuffleOperands(expr, null, subExpr, null, subExpr.right, null, expr.right)
} else {
IAstModification.ReplaceNode(expr,
BinaryExpression(
subExpr.left, expr.operator,
BinaryExpression(expr.right, if (expr.operator == "-") "+" else "*", subExpr.right, subExpr.position),
expr.position),
expr.parent)
}
}
}
return null
}
else
{
if(expr.operator=="/" && subExpr.operator=="*") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1/(C2*V) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1/(V*C2) -> (C1/C2)/V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"/",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (C1*V)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.left, "/", expr.right, subExpr.position),
"*",
subExpr.right, expr.position)
} else {
// (V*C1)/C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(subExpr.right, "/", expr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="*" && subExpr.operator=="/") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// C1*(C2/V) -> (C1*C2)/V
BinaryExpression(
BinaryExpression(expr.left, "*", subExpr.left, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// C1*(V/C2) -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.left, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (C1/V)*C2 -> (C1*C2)/V
BinaryExpression(
BinaryExpression(subExpr.left, "*", expr.right, subExpr.position),
"/",
subExpr.right, expr.position)
} else {
// (V/C1)*C2 -> (C1/C2)*V
BinaryExpression(
BinaryExpression(expr.right, "/", subExpr.right, subExpr.position),
"*",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="+" && subExpr.operator=="-") {
if(leftIsConst){
val change = if(subleftIsConst){
// c1+(c2-v) -> (c1+c2)-v
BinaryExpression(
BinaryExpression(expr.left, "+", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1+(v-c2) -> v+(c1-c2)
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (c1-v)+c2 -> (c1+c2)-v
BinaryExpression(
BinaryExpression(subExpr.left, "+", expr.right, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// (v-c1)+c2 -> v+(c2-c1)
BinaryExpression(
BinaryExpression(expr.right, "-", subExpr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
else if(expr.operator=="-" && subExpr.operator=="+") {
if(leftIsConst) {
val change = if(subleftIsConst) {
// c1-(c2+v) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.left, subExpr.position),
"-",
subExpr.right, expr.position)
} else {
// c1-(v+c2) -> (c1-c2)-v
BinaryExpression(
BinaryExpression(expr.left, "-", subExpr.right, subExpr.position),
"-",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
} else {
val change = if(subleftIsConst) {
// (c1+v)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.left, "-", expr.right, subExpr.position),
"+",
subExpr.right, expr.position)
} else {
// (v+c1)-c2 -> v+(c1-c2)
BinaryExpression(
BinaryExpression(subExpr.right, "-", expr.right, subExpr.position),
"+",
subExpr.left, expr.position)
}
return IAstModification.ReplaceNode(expr, change, expr.parent)
}
}
return null
}
}
}

View File

@ -0,0 +1,669 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.base.*
import prog8.ast.expressions.*
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Assignment
import kotlin.math.abs
import kotlin.math.log2
import kotlin.math.pow
/*
todo add more expression optimizations
Investigate what optimizations binaryen has, also see https://egorbo.com/peephole-optimizations.html
*/
internal class ExpressionSimplifier(private val program: Program) : AstWalker() {
private val powersOfTwo = (1..16).map { (2.0).pow(it) }.toSet()
private val negativePowersOfTwo = powersOfTwo.map { -it }.toSet()
private val noModifications = emptyList<IAstModification>()
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if (assignment.aug_op != null)
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
return noModifications
}
override fun after(typecast: TypecastExpression, parent: Node): Iterable<IAstModification> {
val mods = mutableListOf<IAstModification>()
// try to statically convert a literal value into one of the desired type
val literal = typecast.expression as? NumericLiteralValue
if (literal != null) {
val newLiteral = literal.cast(typecast.type)
if (newLiteral !== literal)
mods += IAstModification.ReplaceNode(typecast.expression, newLiteral, typecast)
}
// remove redundant nested typecasts:
// if the typecast casts a value to the same type, remove the cast.
// if the typecast contains another typecast, remove the inner typecast.
val subTypecast = typecast.expression as? TypecastExpression
if (subTypecast != null) {
mods += IAstModification.ReplaceNode(typecast.expression, subTypecast.expression, typecast)
} else {
if (typecast.expression.inferType(program).istype(typecast.type))
mods += IAstModification.ReplaceNode(typecast, typecast.expression, parent)
}
return mods
}
override fun before(expr: PrefixExpression, parent: Node): Iterable<IAstModification> {
if (expr.operator == "+") {
// +X --> X
return listOf(IAstModification.ReplaceNode(expr, expr.expression, parent))
} else if (expr.operator == "not") {
when(expr.expression) {
is PrefixExpression -> {
// NOT(NOT(...)) -> ...
val pe = expr.expression as PrefixExpression
if(pe.operator == "not")
return listOf(IAstModification.ReplaceNode(expr, pe.expression, parent))
}
is BinaryExpression -> {
// NOT (xxxx) -> invert the xxxx
val be = expr.expression as BinaryExpression
val newExpr = when (be.operator) {
"<" -> BinaryExpression(be.left, ">=", be.right, be.position)
">" -> BinaryExpression(be.left, "<=", be.right, be.position)
"<=" -> BinaryExpression(be.left, ">", be.right, be.position)
">=" -> BinaryExpression(be.left, "<", be.right, be.position)
"==" -> BinaryExpression(be.left, "!=", be.right, be.position)
"!=" -> BinaryExpression(be.left, "==", be.right, be.position)
else -> null
}
if (newExpr != null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
else -> return noModifications
}
}
return noModifications
}
override fun after(expr: BinaryExpression, parent: Node): Iterable<IAstModification> {
val leftVal = expr.left.constValue(program)
val rightVal = expr.right.constValue(program)
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")
// ConstValue <associativeoperator> X --> X <associativeoperator> ConstValue
if (leftVal != null && expr.operator in associativeOperators && rightVal == null)
return listOf(IAstModification.SwapOperands(expr))
// X + (-A) --> X - A
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "-", (expr.right as PrefixExpression).expression, expr.position),
parent
))
}
// (-A) + X --> X - A
if (expr.operator == "+" && (expr.left as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.right, "-", (expr.left as PrefixExpression).expression, expr.position),
parent
))
}
// X - (-A) --> X + A
if (expr.operator == "-" && (expr.right as? PrefixExpression)?.operator == "-") {
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "+", (expr.right as PrefixExpression).expression, expr.position),
parent
))
}
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
val rightDt = rightIDt.typeOrElse(DataType.STRUCT)
if (expr.operator == "+" || expr.operator == "-"
&& leftVal == null && rightVal == null
&& leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if (leftBinExpr?.operator == "*") {
if (expr.operator == "+") {
// Y*X + X -> X*(Y + 1)
// X*Y + X -> X*(Y + 1)
val x = expr.right
val y = determineY(x, leftBinExpr)
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
} else {
// Y*X - X -> X*(Y - 1)
// X*Y - X -> X*(Y - 1)
val x = expr.right
val y = determineY(x, leftBinExpr)
if (y != null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yMinus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
}
} else if (rightBinExpr?.operator == "*") {
if (expr.operator == "+") {
// X + Y*X -> X*(Y + 1)
// X + X*Y -> X*(Y + 1)
val x = expr.left
val y = determineY(x, rightBinExpr)
if (y != null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue.optimalInteger(1, y.position), y.position)
val newExpr = BinaryExpression(x, "*", yPlus1, x.position)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
}
}
}
}
if(expr.operator == ">=" && rightVal?.number == 0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned >= 0 --> true
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(true, expr.position), parent))
}
when(leftDt) {
DataType.BYTE -> {
// signed >=0 --> signed ^ $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
DataType.WORD -> {
// signedw >=0 --> msb(signedw) ^ $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
mutableListOf(expr.left),
expr.position
), "^", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
else -> {}
}
}
if(expr.operator == "<" && rightVal?.number == 0) {
if (leftDt == DataType.UBYTE || leftDt == DataType.UWORD) {
// unsigned < 0 --> false
return listOf(IAstModification.ReplaceNode(expr, NumericLiteralValue.fromBoolean(false, expr.position), parent))
}
when(leftDt) {
DataType.BYTE -> {
// signed < 0 --> signed & $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(expr.left, "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
DataType.WORD -> {
// signedw < 0 --> msb(signedw) & $80
return listOf(IAstModification.ReplaceNode(
expr,
BinaryExpression(FunctionCall(IdentifierReference(listOf("msb"), expr.position),
mutableListOf(expr.left),
expr.position
), "&", NumericLiteralValue.optimalInteger(0x80, expr.position), expr.position),
parent
))
}
else -> {}
}
}
// simplify when a term is constant and directly determines the outcome
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
val newExpr: Expression? = when (expr.operator) {
"or" -> {
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue))
constTrue
else if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else
null
}
"and" -> {
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue))
constFalse
else if (leftVal != null && leftVal.asBooleanValue)
expr.right
else if (rightVal != null && rightVal.asBooleanValue)
expr.left
else
null
}
"xor" -> {
if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else if (leftVal != null && leftVal.asBooleanValue)
PrefixExpression("not", expr.right, expr.right.position)
else if (rightVal != null && rightVal.asBooleanValue)
PrefixExpression("not", expr.left, expr.left.position)
else
null
}
"|", "^" -> {
if (leftVal != null && !leftVal.asBooleanValue)
expr.right
else if (rightVal != null && !rightVal.asBooleanValue)
expr.left
else
null
}
"&" -> {
if (leftVal != null && !leftVal.asBooleanValue)
constFalse
else if (rightVal != null && !rightVal.asBooleanValue)
constFalse
else
null
}
"*" -> optimizeMultiplication(expr, leftVal, rightVal)
"/" -> optimizeDivision(expr, leftVal, rightVal)
"+" -> optimizeAdd(expr, leftVal, rightVal)
"-" -> optimizeSub(expr, leftVal, rightVal)
"**" -> optimizePower(expr, leftVal, rightVal)
"%" -> optimizeRemainder(expr, leftVal, rightVal)
">>" -> optimizeShiftRight(expr, rightVal)
"<<" -> optimizeShiftLeft(expr, rightVal)
else -> null
}
if(newExpr != null)
return listOf(IAstModification.ReplaceNode(expr, newExpr, parent))
return noModifications
}
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when {
subBinExpr.left isSameAs x -> subBinExpr.right
subBinExpr.right isSameAs x -> subBinExpr.left
else -> null
}
}
private fun optimizeAdd(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if(expr.left.isSameAs(expr.right)) {
// optimize X+X into X *2
expr.operator = "*"
expr.right = NumericLiteralValue.optimalInteger(2, expr.right.position)
expr.right.linkParents(expr)
return expr
}
if (leftVal == null && rightVal == null)
return null
val (expr2, _, rightVal2) = reorderAssociative(expr, leftVal)
if (rightVal2 != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal2
when (rightConst.number.toDouble()) {
0.0 -> {
// left
return expr2.left
}
}
}
// no need to check for left val constant (because of associativity)
return null
}
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if(expr.left.isSameAs(expr.right)) {
// optimize X-X into 0
return NumericLiteralValue.optimalInteger(0, expr.position)
}
if (leftVal == null && rightVal == null)
return null
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when (rightConst.number.toDouble()) {
0.0 -> {
// left
return expr.left
}
}
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
0.0 -> {
// -right
return PrefixExpression("-", expr.right, expr.position)
}
}
}
return null
}
private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if (leftVal == null && rightVal == null)
return null
if (rightVal != null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when (rightConst.number.toDouble()) {
-3.0 -> {
// -1/(left*left*left)
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
expr.position)
}
-2.0 -> {
// -1/(left*left)
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", expr.left, expr.position),
expr.position)
}
-1.0 -> {
// -1/left
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
expr.left, expr.position)
}
0.0 -> {
// 1
return NumericLiteralValue(rightConst.type, 1, expr.position)
}
0.5 -> {
// sqrt(left)
return FunctionCall(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
}
1.0 -> {
// left
return expr.left
}
2.0 -> {
// left*left
return BinaryExpression(expr.left, "*", expr.left, expr.position)
}
3.0 -> {
// left*left*left
return BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position)
}
}
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
-1.0 -> {
// -1
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
}
0.0 -> {
// 0
return NumericLiteralValue(leftVal.type, 0, expr.position)
}
1.0 -> {
//1
return NumericLiteralValue(leftVal.type, 1, expr.position)
}
}
}
return null
}
private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if (leftVal == null && rightVal == null)
return null
// simplify assignments A = B <operator> C
val cv = rightVal?.number?.toInt()?.toDouble()
when (expr.operator) {
"%" -> {
if (cv == 1.0) {
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) {
expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
return null
}
}
}
return null
}
private fun optimizeDivision(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if (leftVal == null && rightVal == null)
return null
// 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 leftIDt = expr.left.inferType(program)
if (!leftIDt.isKnown)
return null
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
when (cv) {
-1.0 -> {
// '/' -> -left
if (expr.operator == "/") {
return PrefixExpression("-", expr.left, expr.position)
}
}
1.0 -> {
// '/' -> left
if (expr.operator == "/") {
return expr.left
}
}
in powersOfTwo -> {
if (leftDt in IntegerDatatypes) {
// divided by a power of two => shift right
val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
if (leftDt in IntegerDatatypes) {
// divided by a negative power of two => negate, then shift right
val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
}
if (leftDt == DataType.UBYTE) {
if (abs(rightConst.number.toDouble()) >= 256.0) {
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
}
} else if (leftDt == DataType.UWORD) {
if (abs(rightConst.number.toDouble()) >= 65536.0) {
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
}
}
}
if (leftVal != null) {
// left value is a constant, see if we can optimize
when (leftVal.number.toDouble()) {
0.0 -> {
// 0
return NumericLiteralValue(leftVal.type, 0, expr.position)
}
}
}
return null
}
private fun optimizeMultiplication(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression? {
if (leftVal == null && rightVal == null)
return null
val (expr2, _, rightVal2) = reorderAssociative(expr, leftVal)
if (rightVal2 != null) {
// right value is a constant, see if we can optimize
val leftValue: Expression = expr2.left
val rightConst: NumericLiteralValue = rightVal2
when (val cv = rightConst.number.toDouble()) {
-1.0 -> {
// -left
return PrefixExpression("-", leftValue, expr.position)
}
0.0 -> {
// 0
return NumericLiteralValue(rightConst.type, 0, expr.position)
}
1.0 -> {
// left
return expr2.left
}
in powersOfTwo -> {
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a power of two => shift left
val numshifts = log2(cv).toInt()
return BinaryExpression(expr2.left, "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
if (leftValue.inferType(program).typeOrElse(DataType.STRUCT) in IntegerDatatypes) {
// times a negative power of two => negate, then shift left
val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr2.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
}
}
// no need to check for left val constant (because of associativity)
return null
}
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression? {
if (amountLv == null)
return null
val amount = amountLv.number.toInt()
if (amount == 0) {
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when (targetDt) {
DataType.UBYTE, DataType.BYTE -> {
if (amount >= 8) {
return NumericLiteralValue(targetDt, 0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if (amount >= 16) {
return NumericLiteralValue(targetDt, 0, expr.position)
} else if (amount >= 8) {
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 null
}
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression? {
if (amountLv == null)
return null
val amount = amountLv.number.toInt()
if (amount == 0) {
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when (targetDt) {
DataType.UBYTE -> {
if (amount >= 8) {
return NumericLiteralValue.optimalInteger(0, expr.position)
}
}
DataType.BYTE -> {
if (amount > 8) {
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position)
return null
}
}
DataType.UWORD -> {
if (amount >= 16) {
return NumericLiteralValue.optimalInteger(0, expr.position)
} else if (amount >= 8) {
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 null
} else if (amount >= 8) {
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 null
}
private fun reorderAssociative(expr: BinaryExpression, leftVal: NumericLiteralValue?): ReorderedAssociativeBinaryExpr {
if (expr.operator in associativeOperators && leftVal != null) {
// swap left and right so that right is always the constant
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(program), leftVal)
}
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
}
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
}

View File

@ -1,42 +1,44 @@
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 {
internal fun Program.constantFold(errors: ErrorReporter) {
val replacer = ConstantIdentifierReplacer(this, errors)
replacer.visit(this)
if(errors.isEmpty()) {
replacer.applyModifications()
val optimizer = ConstantFoldingOptimizer(this, errors)
optimizer.visit(this)
} catch (ax: AstException) {
optimizer.addError(ax)
while (errors.isEmpty() && optimizer.applyModifications() > 0) {
optimizer.visit(this)
}
if(errors.isEmpty()) {
replacer.visit(this)
replacer.applyModifications()
}
}
while(optimizer.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)
val optimizationCount = optimizer.applyModifications()
modules.forEach { it.linkParents(this.namespace) } // re-link in final configuration
return optimizer.optimizationsDone
return optimizationCount
}
internal fun Program.simplifyExpressions() : Int {
val optimizer = SimplifyExpressions(this)
optimizer.visit(this)
return optimizer.optimizationsDone
val opti = ExpressionSimplifier(this)
opti.visit(this)
return opti.applyModifications()
}

View File

@ -0,0 +1,46 @@
package prog8.optimizer
import prog8.ast.INameScope
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.AnonymousScope
import prog8.ast.statements.NopStatement
import prog8.ast.statements.Statement
internal class FlattenAnonymousScopesAndNopRemover: IAstVisitor {
private var scopesToFlatten = mutableListOf<INameScope>()
private val nopStatements = mutableListOf<NopStatement>()
override fun visit(program: Program) {
super.visit(program)
for(scope in scopesToFlatten.reversed()) {
val namescope = scope.parent as INameScope
val idx = namescope.statements.indexOf(scope as Statement)
if(idx>=0) {
val nop = NopStatement.insteadOf(namescope.statements[idx])
nop.parent = namescope as Node
namescope.statements[idx] = nop
namescope.statements.addAll(idx, scope.statements)
scope.statements.forEach { it.parent = namescope }
visit(nop)
}
}
this.nopStatements.forEach {
it.definingScope().remove(it)
}
}
override fun visit(scope: AnonymousScope) {
if(scope.parent is INameScope) {
scopesToFlatten.add(scope) // get rid of the anonymous scope
}
return super.visit(scope)
}
override fun visit(nopStatement: NopStatement) {
nopStatements.add(nopStatement)
}
}

View File

@ -1,828 +0,0 @@
package prog8.optimizer
import prog8.ast.Program
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 add more expression optimizations
Also see https://egorbo.com/peephole-optimizations.html
*/
internal class SimplifyExpressions(private val program: Program) : IAstModifyingVisitor {
var optimizationsDone: Int = 0
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")
return super.visit(assignment)
}
override fun visit(memread: DirectMemoryRead): Expression {
// @( &thing ) --> thing
val addrOf = memread.addressExpression as? AddressOf
if(addrOf!=null)
return super.visit(addrOf.identifier)
return super.visit(memread)
}
override fun visit(typecast: TypecastExpression): Expression {
var tc = typecast
// try to statically convert a literal value into one of the desired type
val literal = tc.expression as? NumericLiteralValue
if(literal!=null) {
val newLiteral = literal.cast(tc.type)
if(newLiteral!==literal) {
optimizationsDone++
return newLiteral
}
}
// remove redundant typecasts
while(true) {
val expr = tc.expression
if(expr !is TypecastExpression || expr.type!=tc.type) {
val assignment = typecast.parent as? Assignment
if(assignment!=null) {
val targetDt = assignment.target.inferType(program, assignment)
if(tc.expression.inferType(program)==targetDt) {
optimizationsDone++
return tc.expression
}
}
val subTc = tc.expression as? TypecastExpression
if(subTc!=null) {
// if the previous typecast was casting to a 'bigger' type, just ignore that one
// if the previous typecast was casting to a similar type, ignore that one
if(subTc.type largerThan tc.type || subTc.type equalsSize tc.type) {
subTc.type = tc.type
subTc.parent = tc.parent
optimizationsDone++
return subTc
}
}
return super.visit(tc)
}
optimizationsDone++
tc = expr
}
}
override fun visit(expr: PrefixExpression): Expression {
if (expr.operator == "+") {
// +X --> X
optimizationsDone++
return expr.expression.accept(this)
} else if (expr.operator == "not") {
(expr.expression as? BinaryExpression)?.let {
// NOT (...) -> invert ...
when (it.operator) {
"<" -> {
it.operator = ">="
optimizationsDone++
return it
}
">" -> {
it.operator = "<="
optimizationsDone++
return it
}
"<=" -> {
it.operator = ">"
optimizationsDone++
return it
}
">=" -> {
it.operator = "<"
optimizationsDone++
return it
}
"==" -> {
it.operator = "!="
optimizationsDone++
return it
}
"!=" -> {
it.operator = "=="
optimizationsDone++
return it
}
else -> {
}
}
}
}
return super.visit(expr)
}
override fun visit(expr: BinaryExpression): Expression {
super.visit(expr)
val leftVal = expr.left.constValue(program)
val rightVal = expr.right.constValue(program)
val constTrue = NumericLiteralValue.fromBoolean(true, expr.position)
val constFalse = NumericLiteralValue.fromBoolean(false, expr.position)
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++
return expr
}
}
// Value <associativeoperator> X --> X <associativeoperator> Value
if (leftVal != null && expr.operator in associativeOperators && rightVal == null) {
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
optimizationsDone++
return expr
}
// X + (-A) --> X - A
if (expr.operator == "+" && (expr.right as? PrefixExpression)?.operator == "-") {
expr.operator = "-"
expr.right = (expr.right as PrefixExpression).expression
optimizationsDone++
return expr
}
// (-A) + X --> X - A
if (expr.operator == "+" && (expr.left as? PrefixExpression)?.operator == "-") {
expr.operator = "-"
val newRight = (expr.left as PrefixExpression).expression
expr.left = expr.right
expr.right = newRight
optimizationsDone++
return expr
}
// X + (-value) --> X - value
if (expr.operator == "+" && rightVal != null) {
val rv = rightVal.number.toDouble()
if (rv < 0.0) {
expr.operator = "-"
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
optimizationsDone++
return expr
}
}
// (-value) + X --> X - value
if (expr.operator == "+" && leftVal != null) {
val lv = leftVal.number.toDouble()
if (lv < 0.0) {
expr.operator = "-"
expr.right = NumericLiteralValue(leftVal.type, -lv, leftVal.position)
optimizationsDone++
return expr
}
}
// X - (-A) --> X + A
if (expr.operator == "-" && (expr.right as? PrefixExpression)?.operator == "-") {
expr.operator = "+"
expr.right = (expr.right as PrefixExpression).expression
optimizationsDone++
return expr
}
// X - (-value) --> X + value
if (expr.operator == "-" && rightVal != null) {
val rv = rightVal.number.toDouble()
if (rv < 0.0) {
expr.operator = "+"
expr.right = NumericLiteralValue(rightVal.type, -rv, rightVal.position)
optimizationsDone++
return expr
}
}
if (expr.operator == "+" || expr.operator == "-"
&& leftVal == null && rightVal == null
&& leftDt in NumericDatatypes && rightDt in NumericDatatypes) {
val leftBinExpr = expr.left as? BinaryExpression
val rightBinExpr = expr.right as? BinaryExpression
if (leftBinExpr?.operator == "*") {
if (expr.operator == "+") {
// Y*X + X -> X*(Y - 1)
// X*Y + X -> X*(Y - 1)
val x = expr.right
val y = determineY(x, leftBinExpr)
if(y!=null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue(leftDt, 1, y.position), y.position)
return BinaryExpression(x, "*", yPlus1, x.position)
}
} else {
// Y*X - X -> X*(Y - 1)
// X*Y - X -> X*(Y - 1)
val x = expr.right
val y = determineY(x, leftBinExpr)
if(y!=null) {
val yMinus1 = BinaryExpression(y, "-", NumericLiteralValue(leftDt, 1, y.position), y.position)
return BinaryExpression(x, "*", yMinus1, x.position)
}
}
}
else if(rightBinExpr?.operator=="*") {
if(expr.operator=="+") {
// X + Y*X -> X*(Y + 1)
// X + X*Y -> X*(Y + 1)
val x = expr.left
val y = determineY(x, rightBinExpr)
if(y!=null) {
val yPlus1 = BinaryExpression(y, "+", NumericLiteralValue.optimalInteger(1, y.position), y.position)
return BinaryExpression(x, "*", yPlus1, x.position)
}
} else {
// X - Y*X -> X*(1 - Y)
// X - X*Y -> X*(1 - Y)
val x = expr.left
val y = determineY(x, rightBinExpr)
if(y!=null) {
val oneMinusY = BinaryExpression(NumericLiteralValue.optimalInteger(1, y.position), "-", y, y.position)
return BinaryExpression(x, "*", oneMinusY, x.position)
}
}
}
}
// simplify when a term is constant and determines the outcome
when (expr.operator) {
"or" -> {
if ((leftVal != null && leftVal.asBooleanValue) || (rightVal != null && rightVal.asBooleanValue)) {
optimizationsDone++
return constTrue
}
if (leftVal != null && !leftVal.asBooleanValue) {
optimizationsDone++
return expr.right
}
if (rightVal != null && !rightVal.asBooleanValue) {
optimizationsDone++
return expr.left
}
}
"and" -> {
if ((leftVal != null && !leftVal.asBooleanValue) || (rightVal != null && !rightVal.asBooleanValue)) {
optimizationsDone++
return constFalse
}
if (leftVal != null && leftVal.asBooleanValue) {
optimizationsDone++
return expr.right
}
if (rightVal != null && rightVal.asBooleanValue) {
optimizationsDone++
return expr.left
}
}
"xor" -> {
if (leftVal != null && !leftVal.asBooleanValue) {
optimizationsDone++
return expr.right
}
if (rightVal != null && !rightVal.asBooleanValue) {
optimizationsDone++
return expr.left
}
if (leftVal != null && leftVal.asBooleanValue) {
optimizationsDone++
return PrefixExpression("not", expr.right, expr.right.position)
}
if (rightVal != null && rightVal.asBooleanValue) {
optimizationsDone++
return PrefixExpression("not", expr.left, expr.left.position)
}
}
"|", "^" -> {
if (leftVal != null && !leftVal.asBooleanValue) {
optimizationsDone++
return expr.right
}
if (rightVal != null && !rightVal.asBooleanValue) {
optimizationsDone++
return expr.left
}
}
"&" -> {
if (leftVal != null && !leftVal.asBooleanValue) {
optimizationsDone++
return constFalse
}
if (rightVal != null && !rightVal.asBooleanValue) {
optimizationsDone++
return constFalse
}
}
"*" -> return optimizeMultiplication(expr, leftVal, rightVal)
"/" -> return optimizeDivision(expr, leftVal, rightVal)
"+" -> return optimizeAdd(expr, leftVal, rightVal)
"-" -> 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
}
private fun determineY(x: Expression, subBinExpr: BinaryExpression): Expression? {
return when {
subBinExpr.left isSameAs x -> subBinExpr.right
subBinExpr.right isSameAs x -> subBinExpr.left
else -> null
}
}
private fun adjustDatatypes(expr: BinaryExpression,
leftConstVal: NumericLiteralValue?, leftDt: DataType,
rightConstVal: NumericLiteralValue?, rightDt: DataType): Boolean {
fun adjust(value: NumericLiteralValue, targetDt: DataType): Pair<Boolean, NumericLiteralValue>{
if(value.type==targetDt)
return Pair(false, value)
when(value.type) {
DataType.UBYTE -> {
if (targetDt == DataType.BYTE) {
if(value.number.toInt() < 127)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.UWORD || targetDt == DataType.WORD)
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
DataType.BYTE -> {
if (targetDt == DataType.UBYTE) {
if(value.number.toInt() >= 0)
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
else if (targetDt == DataType.UWORD) {
if(value.number.toInt() >= 0)
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
else if (targetDt == DataType.WORD) return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
DataType.UWORD -> {
if (targetDt == DataType.UBYTE) {
if(value.number.toInt() <= 255)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.BYTE) {
if(value.number.toInt() <= 127)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.WORD) {
if(value.number.toInt() <= 32767)
return Pair(true, NumericLiteralValue(targetDt, value.number.toInt(), value.position))
}
}
DataType.WORD -> {
if (targetDt == DataType.UBYTE) {
if(value.number.toInt() in 0..255)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.BYTE) {
if(value.number.toInt() in -128..127)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
else if (targetDt == DataType.UWORD) {
if(value.number.toInt() >= 0)
return Pair(true, NumericLiteralValue(targetDt, value.number.toShort(), value.position))
}
}
else -> {}
}
return Pair(false, value)
}
if(leftConstVal==null && rightConstVal!=null) {
if(leftDt largerThan rightDt) {
val (adjusted, newValue) = adjust(rightConstVal, leftDt)
if (adjusted) {
expr.right = newValue
optimizationsDone++
return true
}
}
return false
} else if(leftConstVal!=null && rightConstVal==null) {
if(rightDt largerThan leftDt) {
val (adjusted, newValue) = adjust(leftConstVal, rightDt)
if (adjusted) {
expr.left = newValue
optimizationsDone++
return true
}
}
return false
} else {
return false // two const values, don't adjust (should have been const-folded away)
}
}
private data class ReorderedAssociativeBinaryExpr(val expr: BinaryExpression, val leftVal: NumericLiteralValue?, val rightVal: NumericLiteralValue?)
private fun reorderAssociative(expr: BinaryExpression, leftVal: NumericLiteralValue?): ReorderedAssociativeBinaryExpr {
if(expr.operator in associativeOperators && leftVal!=null) {
// swap left and right so that right is always the constant
val tmp = expr.left
expr.left = expr.right
expr.right = tmp
optimizationsDone++
return ReorderedAssociativeBinaryExpr(expr, expr.right.constValue(program), leftVal)
}
return ReorderedAssociativeBinaryExpr(expr, leftVal, expr.right.constValue(program))
}
private fun optimizeAdd(pexpr: BinaryExpression, pleftVal: NumericLiteralValue?, prightVal: NumericLiteralValue?): Expression {
if(pleftVal==null && prightVal==null)
return pexpr
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when(rightConst.number.toDouble()) {
0.0 -> {
// left
optimizationsDone++
return expr.left
}
}
}
// no need to check for left val constant (because of associativity)
return expr
}
private fun optimizeSub(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null)
return expr
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when(rightConst.number.toDouble()) {
0.0 -> {
// left
optimizationsDone++
return expr.left
}
}
}
if(leftVal!=null) {
// left value is a constant, see if we can optimize
when(leftVal.number.toDouble()) {
0.0 -> {
// -right
optimizationsDone++
return PrefixExpression("-", expr.right, expr.position)
}
}
}
return expr
}
private fun optimizePower(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null)
return expr
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val rightConst: NumericLiteralValue = rightVal
when(rightConst.number.toDouble()) {
-3.0 -> {
// -1/(left*left*left)
optimizationsDone++
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position),
expr.position)
}
-2.0 -> {
// -1/(left*left)
optimizationsDone++
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
BinaryExpression(expr.left, "*", expr.left, expr.position),
expr.position)
}
-1.0 -> {
// -1/left
optimizationsDone++
return BinaryExpression(NumericLiteralValue(DataType.FLOAT, -1.0, expr.position), "/",
expr.left, expr.position)
}
0.0 -> {
// 1
optimizationsDone++
return NumericLiteralValue(rightConst.type, 1, expr.position)
}
0.5 -> {
// sqrt(left)
optimizationsDone++
return FunctionCall(IdentifierReference(listOf("sqrt"), expr.position), mutableListOf(expr.left), expr.position)
}
1.0 -> {
// left
optimizationsDone++
return expr.left
}
2.0 -> {
// left*left
optimizationsDone++
return BinaryExpression(expr.left, "*", expr.left, expr.position)
}
3.0 -> {
// left*left*left
optimizationsDone++
return BinaryExpression(expr.left, "*", BinaryExpression(expr.left, "*", expr.left, expr.position), expr.position)
}
}
}
if(leftVal!=null) {
// left value is a constant, see if we can optimize
when(leftVal.number.toDouble()) {
-1.0 -> {
// -1
optimizationsDone++
return NumericLiteralValue(DataType.FLOAT, -1.0, expr.position)
}
0.0 -> {
// 0
optimizationsDone++
return NumericLiteralValue(leftVal.type, 0, expr.position)
}
1.0 -> {
//1
optimizationsDone++
return NumericLiteralValue(leftVal.type, 1, expr.position)
}
}
}
return expr
}
private fun optimizeRemainder(expr: BinaryExpression, leftVal: NumericLiteralValue?, rightVal: NumericLiteralValue?): Expression {
if(leftVal==null && rightVal==null)
return expr
// simplify assignments A = B <operator> C
val cv = rightVal?.number?.toInt()?.toDouble()
when(expr.operator) {
"%" -> {
if (cv == 1.0) {
optimizationsDone++
return NumericLiteralValue(expr.inferType(program).typeOrElse(DataType.STRUCT), 0, expr.position)
} else if (cv == 2.0) {
optimizationsDone++
expr.operator = "&"
expr.right = NumericLiteralValue.optimalInteger(1, expr.position)
return expr
}
}
}
return expr
}
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 leftIDt = expr.left.inferType(program)
if(!leftIDt.isKnown)
return expr
val leftDt = leftIDt.typeOrElse(DataType.STRUCT)
when(cv) {
-1.0 -> {
// '/' -> -left
if (expr.operator == "/") {
optimizationsDone++
return PrefixExpression("-", expr.left, expr.position)
}
}
1.0 -> {
// '/' -> left
if (expr.operator == "/") {
optimizationsDone++
return expr.left
}
}
in powersOfTwo -> {
if(leftDt in IntegerDatatypes) {
// divided by a power of two => shift right
optimizationsDone++
val numshifts = log2(cv).toInt()
return BinaryExpression(expr.left, ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
in negativePowersOfTwo -> {
if(leftDt in IntegerDatatypes) {
// divided by a negative power of two => negate, then shift right
optimizationsDone++
val numshifts = log2(-cv).toInt()
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), ">>", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
}
if (leftDt == DataType.UBYTE) {
if(abs(rightConst.number.toDouble()) >= 256.0) {
optimizationsDone++
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
}
}
else if (leftDt == DataType.UWORD) {
if(abs(rightConst.number.toDouble()) >= 65536.0) {
optimizationsDone++
return NumericLiteralValue(DataType.UBYTE, 0, expr.position)
}
}
}
if(leftVal!=null) {
// left value is a constant, see if we can optimize
when(leftVal.number.toDouble()) {
0.0 -> {
// 0
optimizationsDone++
return NumericLiteralValue(leftVal.type, 0, expr.position)
}
}
}
return expr
}
private fun optimizeMultiplication(pexpr: BinaryExpression, pleftVal: NumericLiteralValue?, prightVal: NumericLiteralValue?): Expression {
if(pleftVal==null && prightVal==null)
return pexpr
val (expr, _, rightVal) = reorderAssociative(pexpr, pleftVal)
if(rightVal!=null) {
// right value is a constant, see if we can optimize
val leftValue: Expression = expr.left
val rightConst: NumericLiteralValue = rightVal
when(val cv = rightConst.number.toDouble()) {
-1.0 -> {
// -left
optimizationsDone++
return PrefixExpression("-", leftValue, expr.position)
}
0.0 -> {
// 0
optimizationsDone++
return NumericLiteralValue(rightConst.type, 0, expr.position)
}
1.0 -> {
// left
optimizationsDone++
return expr.left
}
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)
}
}
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()
return BinaryExpression(PrefixExpression("-", expr.left, expr.position), "<<", NumericLiteralValue.optimalInteger(numshifts, expr.position), expr.position)
}
}
}
}
// no need to check for left val constant (because of associativity)
return expr
}
private fun optimizeShiftLeft(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
if(amountLv==null)
return expr
val amount=amountLv.number.toInt()
if(amount==0) {
optimizationsDone++
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE, DataType.BYTE -> {
if(amount>=8) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
}
DataType.UWORD, DataType.WORD -> {
if(amount>=16) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
else if(amount>=8) {
optimizationsDone++
val lsb=TypecastExpression(expr.left, DataType.UBYTE, true, expr.position)
if(amount==8) {
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), lsb), expr.position)
}
val shifted = BinaryExpression(lsb, "<<", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
return FunctionCall(IdentifierReference(listOf("mkword"), expr.position), mutableListOf(NumericLiteralValue.optimalInteger(0, expr.position), shifted), expr.position)
}
}
else -> {}
}
return expr
}
private fun optimizeShiftRight(expr: BinaryExpression, amountLv: NumericLiteralValue?): Expression {
if(amountLv==null)
return expr
val amount=amountLv.number.toInt()
if(amount==0) {
optimizationsDone++
return expr.left
}
val targetDt = expr.left.inferType(program).typeOrElse(DataType.STRUCT)
when(targetDt) {
DataType.UBYTE -> {
if(amount>=8) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
}
DataType.BYTE -> {
if(amount>8) {
expr.right = NumericLiteralValue.optimalInteger(8, expr.right.position)
return expr
}
}
DataType.UWORD -> {
if(amount>=16) {
optimizationsDone++
return NumericLiteralValue.optimalInteger(0, expr.position)
}
else if(amount>=8) {
optimizationsDone++
val msb=FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position)
if(amount==8)
return msb
return BinaryExpression(msb, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
}
}
DataType.WORD -> {
if(amount>16) {
expr.right = NumericLiteralValue.optimalInteger(16, expr.right.position)
return expr
} else if(amount>=8) {
optimizationsDone++
val msbAsByte = TypecastExpression(
FunctionCall(IdentifierReference(listOf("msb"), expr.position), mutableListOf(expr.left), expr.position),
DataType.BYTE,
true, expr.position)
if(amount==8)
return msbAsByte
return BinaryExpression(msbAsByte, ">>", NumericLiteralValue.optimalInteger(amount-8, expr.position), expr.position)
}
}
else -> {}
}
return expr
}
}

View File

@ -1,108 +1,53 @@
package prog8.optimizer
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.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.processing.IAstVisitor
import prog8.ast.statements.*
import prog8.compiler.target.c64.Petscii
import prog8.compiler.target.c64.codegen.AssemblyError
import prog8.compiler.target.CompilationTarget
import prog8.functions.BuiltinFunctions
import kotlin.math.floor
/*
TODO: remove unreachable code?
TODO: proper inlining of tiny subroutines (correctly renaming/relocating all variables in them and refs to those as well, or restrict to subs without variables?)
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 {
var optimizationsDone: Int = 0
private set
internal class StatementOptimizer(private val program: Program,
private val errors: ErrorReporter) : AstWalker() {
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
private val noModifications = emptyList<IAstModification>()
private val callgraph = CallGraph(program)
private val vardeclsToRemove = mutableListOf<VarDecl>()
private val pureBuiltinFunctions = BuiltinFunctions.filter { it.value.pure }
override fun visit(program: Program) {
removeUnusedCode(callgraph)
super.visit(program)
for(decl in vardeclsToRemove) {
decl.definingScope().remove(decl)
}
}
private fun removeUnusedCode(callgraph: CallGraph) {
// remove all subroutines that aren't called, or are empty
val removeSubroutines = mutableSetOf<Subroutine>()
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
removeSubroutines.add(sub)
}
}
if (removeSubroutines.isNotEmpty()) {
removeSubroutines.forEach {
it.definingScope().remove(it)
}
}
val removeBlocks = mutableSetOf<Block>()
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removeBlocks.add(block)
}
if (removeBlocks.isNotEmpty()) {
removeBlocks.forEach { it.definingScope().remove(it) }
}
// remove modules that are not imported, or are empty (unless it's a library modules)
val removeModules = mutableSetOf<Module>()
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removeModules.add(it)
}
if (removeModules.isNotEmpty()) {
program.modules.removeAll(removeModules)
}
}
override fun visit(block: Block): Statement {
override fun after(block: Block, parent: Node): Iterable<IAstModification> {
if("force_output" !in block.options()) {
if (block.containsNoCodeNorVars()) {
optimizationsDone++
printWarning("removing empty block '${block.name}'", block.position)
return NopStatement.insteadOf(block)
errors.warn("removing empty block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent))
}
if (block !in callgraph.usedSymbols) {
optimizationsDone++
printWarning("removing unused block '${block.name}'", block.position)
return NopStatement.insteadOf(block) // remove unused block
errors.warn("removing unused block '${block.name}'", block.position)
return listOf(IAstModification.Remove(block, parent))
}
}
return super.visit(block)
return noModifications
}
override fun visit(subroutine: Subroutine): Statement {
super.visit(subroutine)
override fun after(subroutine: Subroutine, parent: Node): Iterable<IAstModification> {
val forceOutput = "force_output" in subroutine.definingBlock().options()
if(subroutine.asmAddress==null && !forceOutput) {
if(subroutine.containsNoCodeNorVars()) {
printWarning("removing empty subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement.insteadOf(subroutine)
errors.warn("removing empty subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, parent))
}
}
@ -112,24 +57,342 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
}
if(subroutine !in callgraph.usedSymbols && !forceOutput) {
printWarning("removing unused subroutine '${subroutine.name}'", subroutine.position)
optimizationsDone++
return NopStatement.insteadOf(subroutine)
errors.warn("removing unused subroutine '${subroutine.name}'", subroutine.position)
return listOf(IAstModification.Remove(subroutine, parent))
}
return subroutine
return noModifications
}
override fun visit(decl: VarDecl): Statement {
override fun after(scope: AnonymousScope, parent: Node): Iterable<IAstModification> {
val linesToRemove = deduplicateAssignments(scope.statements)
return linesToRemove.reversed().map { IAstModification.Remove(scope.statements[it], scope) }
}
override fun after(decl: VarDecl, parent: Node): Iterable<IAstModification> {
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)
optimizationsDone++
return NopStatement.insteadOf(decl)
errors.warn("removing unused variable '${decl.name}'", decl.position)
return listOf(IAstModification.Remove(decl, parent))
}
return super.visit(decl)
return noModifications
}
override fun after(functionCallStatement: FunctionCallStatement, parent: Node): Iterable<IAstModification> {
if(functionCallStatement.target.nameInSource.size==1 && functionCallStatement.target.nameInSource[0] in BuiltinFunctions) {
val functionName = functionCallStatement.target.nameInSource[0]
if (functionName in pureBuiltinFunctions) {
errors.warn("statement has no effect (function return value is discarded)", functionCallStatement.position)
return listOf(IAstModification.Remove(functionCallStatement, parent))
}
}
// printing a literal string of just 2 or 1 characters is replaced by directly outputting those characters
// this is a C-64 specific optimization
if(functionCallStatement.target.nameInSource==listOf("c64scr", "print")) {
val arg = functionCallStatement.args.single()
val stringVar: IdentifierReference?
stringVar = if(arg is AddressOf) {
arg.identifier
} else {
arg as? IdentifierReference
}
if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program.namespace)!!
val string = vardecl.value!! as StringLiteralValue
val pos = functionCallStatement.position
if(string.value.length==1) {
val firstCharEncoded = CompilationTarget.encodeString(string.value, string.altEncoding)[0]
val chrout = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstCharEncoded.toInt(), pos)),
functionCallStatement.void, pos
)
return listOf(IAstModification.ReplaceNode(functionCallStatement, chrout, parent))
} else if(string.value.length==2) {
val firstTwoCharsEncoded = CompilationTarget.encodeString(string.value.take(2), string.altEncoding)
val chrout1 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[0].toInt(), pos)),
functionCallStatement.void, pos
)
val chrout2 = FunctionCallStatement(
IdentifierReference(listOf("c64", "CHROUT"), pos),
mutableListOf(NumericLiteralValue(DataType.UBYTE, firstTwoCharsEncoded[1].toInt(), pos)),
functionCallStatement.void, pos
)
val anonscope = AnonymousScope(mutableListOf(), pos)
anonscope.statements.add(chrout1)
anonscope.statements.add(chrout2)
return listOf(IAstModification.ReplaceNode(functionCallStatement, anonscope, parent))
}
}
}
// if the first instruction in the called subroutine is a return statement, remove the jump altogeter
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is ReturnFromIrq || first is Return)
return listOf(IAstModification.Remove(functionCallStatement, parent))
}
return noModifications
}
override fun before(functionCall: FunctionCall, parent: Node): Iterable<IAstModification> {
// if the first instruction in the called subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program)
if(constval!=null)
return listOf(IAstModification.ReplaceNode(functionCall, constval, parent))
}
}
return noModifications
}
override fun after(ifStatement: IfStatement, parent: Node): Iterable<IAstModification> {
// remove empty if statements
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars())
return listOf(IAstModification.Remove(ifStatement, parent))
// empty true part? switch with the else part
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
val invertedCondition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
val emptyscope = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
val truepart = AnonymousScope(ifStatement.elsepart.statements, ifStatement.truepart.position)
return listOf(
IAstModification.ReplaceNode(ifStatement.condition, invertedCondition, ifStatement),
IAstModification.ReplaceNode(ifStatement.truepart, truepart, ifStatement),
IAstModification.ReplaceNode(ifStatement.elsepart, emptyscope, ifStatement)
)
}
val constvalue = ifStatement.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
errors.warn("condition is always true", ifStatement.position)
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.truepart, parent))
} else {
// always false -> keep only else-part
errors.warn("condition is always false", ifStatement.position)
listOf(IAstModification.ReplaceNode(ifStatement, ifStatement.elsepart, parent))
}
}
return noModifications
}
override fun after(forLoop: ForLoop, parent: Node): Iterable<IAstModification> {
if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop
return listOf(IAstModification.Remove(forLoop, parent))
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
// remove empty for loop (only loopvar decl in it)
return listOf(IAstModification.Remove(forLoop, parent))
}
}
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
if(range.size()==1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
val iterable = (forLoop.iterable as? IdentifierReference)?.targetVarDecl(program.namespace)
if(iterable!=null) {
if(iterable.datatype==DataType.STR) {
val sv = iterable.value as StringLiteralValue
val size = sv.value.length
if(size==1) {
// loop over string of length 1 -> just assign the single character
val character = CompilationTarget.encodeString(sv.value, sv.altEncoding)[0]
val byte = NumericLiteralValue(DataType.UBYTE, character, iterable.position)
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, byte, forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
else if(iterable.datatype in ArrayDatatypes) {
val size = iterable.arraysize!!.size()
if(size==1) {
// loop over array of length 1 -> just assign the single value
val av = (iterable.value as ArrayLiteralValue).value[0].constValue(program)?.number
if(av!=null) {
val scope = AnonymousScope(mutableListOf(), forLoop.position)
scope.statements.add(Assignment(
AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, NumericLiteralValue.optimalInteger(av.toInt(), iterable.position),
forLoop.position))
scope.statements.addAll(forLoop.body.statements)
return listOf(IAstModification.ReplaceNode(forLoop, scope, parent))
}
}
}
}
return noModifications
}
override fun before(repeatLoop: RepeatLoop, parent: Node): Iterable<IAstModification> {
val constvalue = repeatLoop.untilCondition.constValue(program)
if(constvalue!=null) {
if(constvalue.asBooleanValue) {
// always true -> keep only the statement block (if there are no continue and break statements)
errors.warn("condition is always true", repeatLoop.untilCondition.position)
if(!hasContinueOrBreak(repeatLoop.body))
return listOf(IAstModification.ReplaceNode(repeatLoop, repeatLoop.body, parent))
} else {
// always false
val forever = ForeverLoop(repeatLoop.body, repeatLoop.position)
return listOf(IAstModification.ReplaceNode(repeatLoop, forever, parent))
}
}
return noModifications
}
override fun before(whileLoop: WhileLoop, parent: Node): Iterable<IAstModification> {
val constvalue = whileLoop.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue) {
// always true
val forever = ForeverLoop(whileLoop.body, whileLoop.position)
listOf(IAstModification.ReplaceNode(whileLoop, forever, parent))
} else {
// always false -> remove the while statement altogether
errors.warn("condition is always false", whileLoop.condition.position)
listOf(IAstModification.Remove(whileLoop, parent))
}
}
return noModifications
}
override fun after(whenStatement: WhenStatement, parent: Node): Iterable<IAstModification> {
// remove empty choices
class ChoiceRemover(val choice: WhenChoice) : IAstModification {
override fun perform() {
whenStatement.choices.remove(choice)
}
}
return whenStatement.choices
.filter { !it.statements.containsCodeOrVars() }
.map { ChoiceRemover(it) }
}
override fun after(jump: Jump, parent: Node): Iterable<IAstModification> {
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
if(label!=null && scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1)
return listOf(IAstModification.Remove(jump, parent))
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
if(assignment.aug_op!=null)
throw FatalAstException("augmented assignments should have been converted to normal assignments before this optimizer: $assignment")
// remove assignments to self
if(assignment.target isSameAs assignment.value) {
if(assignment.target.isNotMemory(program.namespace))
return listOf(IAstModification.Remove(assignment, parent))
}
val targetIDt = assignment.target.inferType(program, assignment)
if(!targetIDt.isKnown)
throw FatalAstException("can't infer type of assignment target")
// optimize binary expressions a bit
val targetDt = targetIDt.typeOrElse(DataType.STRUCT)
val bexpr=assignment.value as? BinaryExpression
if(bexpr!=null) {
val cv = bexpr.right.constValue(program)?.number?.toDouble()
if (cv != null && assignment.target isSameAs bexpr.left) {
// assignments of the form: X = X <operator> <expr>
// remove assignments that have no effect (such as X=X+0)
// optimize/rewrite some other expressions
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) {
"+" -> {
if (cv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several INCs (a bit less when dealing with memory targets)
val incs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
incs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, incs, parent))
}
}
}
"-" -> {
if (cv == 0.0) {
return listOf(IAstModification.Remove(assignment, parent))
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several DECs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
}
return listOf(IAstModification.ReplaceNode(assignment, decs, parent))
}
}
}
"*" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"/" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"**" -> if (cv == 1.0) return listOf(IAstModification.Remove(assignment, parent))
"|" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"^" -> if (cv == 0.0) return listOf(IAstModification.Remove(assignment, parent))
"<<" -> {
if (cv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
// replace by in-place lsl(...) call
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), true, assignment.position))
numshifts--
}
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
}
">>" -> {
if (cv == 0.0)
return listOf(IAstModification.Remove(assignment, parent))
// replace by in-place lsr(...) call
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), true, assignment.position))
numshifts--
}
return listOf(IAstModification.ReplaceNode(assignment, scope, parent))
}
}
}
}
return noModifications
}
private fun deduplicateAssignments(statements: List<Statement>): MutableList<Int> {
@ -157,234 +420,21 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
return linesToRemove
}
override fun visit(functionCallStatement: FunctionCallStatement): Statement {
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)
optimizationsDone++
return NopStatement.insteadOf(functionCallStatement)
}
}
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 arg = functionCallStatement.arglist.single()
val stringVar: IdentifierReference?
stringVar = if(arg is AddressOf) {
arg.identifier
} else {
arg as? IdentifierReference
}
if(stringVar!=null) {
val vardecl = stringVar.targetVarDecl(program.namespace)!!
val string = vardecl.value!! as StringLiteralValue
val encodedString = Petscii.encodePetscii(string.value, true)
if(string.value.length==1) {
functionCallStatement.arglist.clear()
functionCallStatement.arglist.add(NumericLiteralValue.optimalInteger(encodedString[0].toInt(), functionCallStatement.position))
functionCallStatement.target = IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position)
vardeclsToRemove.add(vardecl)
optimizationsDone++
return functionCallStatement
} else if(string.value.length==2) {
val scope = AnonymousScope(mutableListOf(), functionCallStatement.position)
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(NumericLiteralValue.optimalInteger(encodedString[0].toInt(), functionCallStatement.position)), functionCallStatement.position))
scope.statements.add(FunctionCallStatement(IdentifierReference(listOf("c64", "CHROUT"), functionCallStatement.target.position),
mutableListOf(NumericLiteralValue.optimalInteger(encodedString[1].toInt(), functionCallStatement.position)), functionCallStatement.position))
vardeclsToRemove.add(vardecl)
optimizationsDone++
return scope
}
}
}
// if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement, replace with a nop instruction
val subroutine = functionCallStatement.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCallStatement(first.identifier, functionCallStatement.arglist, functionCallStatement.position)
}
if(first is ReturnFromIrq || first is Return) {
optimizationsDone++
return NopStatement.insteadOf(functionCallStatement)
}
}
return super.visit(functionCallStatement)
}
override fun visit(functionCall: FunctionCall): Expression {
// if it calls a subroutine,
// and the first instruction in the subroutine is a jump, call that jump target instead
// if the first instruction in the subroutine is a return statement with constant value, replace with the constant value
val subroutine = functionCall.target.targetSubroutine(program.namespace)
if(subroutine!=null) {
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump && first.identifier!=null) {
optimizationsDone++
return FunctionCall(first.identifier, functionCall.arglist, functionCall.position)
}
if(first is Return && first.value!=null) {
val constval = first.value?.constValue(program)
if(constval!=null)
return constval
}
}
return super.visit(functionCall)
}
override fun visit(ifStatement: IfStatement): Statement {
super.visit(ifStatement)
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsNoCodeNorVars()) {
optimizationsDone++
return NopStatement.insteadOf(ifStatement)
}
if(ifStatement.truepart.containsNoCodeNorVars() && ifStatement.elsepart.containsCodeOrVars()) {
// invert the condition and move else part to true part
ifStatement.truepart = ifStatement.elsepart
ifStatement.elsepart = AnonymousScope(mutableListOf(), ifStatement.elsepart.position)
ifStatement.condition = PrefixExpression("not", ifStatement.condition, ifStatement.condition.position)
optimizationsDone++
return ifStatement
}
val constvalue = ifStatement.condition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only if-part
printWarning("condition is always true", ifStatement.position)
optimizationsDone++
ifStatement.truepart
} else {
// always false -> keep only else-part
printWarning("condition is always false", ifStatement.position)
optimizationsDone++
ifStatement.elsepart
}
}
return ifStatement
}
override fun visit(forLoop: ForLoop): Statement {
super.visit(forLoop)
if(forLoop.body.containsNoCodeNorVars()) {
// remove empty for loop
optimizationsDone++
return NopStatement.insteadOf(forLoop)
} else if(forLoop.body.statements.size==1) {
val loopvar = forLoop.body.statements[0] as? VarDecl
if(loopvar!=null && loopvar.name==forLoop.loopVar?.nameInSource?.singleOrNull()) {
// remove empty for loop
optimizationsDone++
return NopStatement.insteadOf(forLoop)
}
}
val range = forLoop.iterable as? RangeExpr
if(range!=null) {
if(range.size()==1) {
// for loop over a (constant) range of just a single value-- optimize the loop away
// loopvar/reg = range value , follow by block
val assignment = Assignment(AssignTarget(forLoop.loopRegister, forLoop.loopVar, null, null, forLoop.position), null, range.from, forLoop.position)
forLoop.body.statements.add(0, assignment)
optimizationsDone++
return forLoop.body
}
}
return forLoop
}
override fun visit(whileLoop: WhileLoop): Statement {
super.visit(whileLoop)
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))
optimizationsDone++
return whileLoop.body
} else {
// always false -> ditch whole statement
printWarning("condition is always false", whileLoop.position)
optimizationsDone++
NopStatement.insteadOf(whileLoop)
}
}
return whileLoop
}
override fun visit(repeatLoop: RepeatLoop): Statement {
super.visit(repeatLoop)
val constvalue = repeatLoop.untilCondition.constValue(program)
if(constvalue!=null) {
return if(constvalue.asBooleanValue){
// always true -> keep only the statement block (if there are no continue and break statements)
printWarning("condition is always true", repeatLoop.position)
if(hasContinueOrBreak(repeatLoop.body))
repeatLoop
else {
optimizationsDone++
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))
optimizationsDone++
return repeatLoop.body
}
}
return repeatLoop
}
override fun visit(whenStatement: WhenStatement): Statement {
val choices = whenStatement.choices.toList()
for(choice in choices) {
if(choice.statements.containsNoCodeNorVars())
whenStatement.choices.remove(choice)
}
return super.visit(whenStatement)
}
private fun hasContinueOrBreak(scope: INameScope): Boolean {
class Searcher: IAstModifyingVisitor
class Searcher: IAstVisitor
{
var count=0
override fun visit(breakStmt: Break): Statement {
override fun visit(breakStmt: Break) {
count++
return super.visit(breakStmt)
}
override fun visit(contStmt: Continue): Statement {
override fun visit(contStmt: Continue) {
count++
return super.visit(contStmt)
}
}
val s=Searcher()
for(stmt in scope.statements) {
stmt.accept(s)
@ -394,219 +444,4 @@ internal class StatementOptimizer(private val program: Program) : IAstModifyingV
return s.count > 0
}
override fun visit(jump: Jump): Statement {
val subroutine = jump.identifier?.targetSubroutine(program.namespace)
if(subroutine!=null) {
// if the first instruction in the subroutine is another jump, shortcut this one
val first = subroutine.statements.asSequence().filterNot { it is VarDecl || it is Directive }.firstOrNull()
if(first is Jump) {
optimizationsDone++
return first
}
}
// if the jump is to the next statement, remove the jump
val scope = jump.definingScope()
val label = jump.identifier?.targetStatement(scope)
if(label!=null) {
if(scope.statements.indexOf(label) == scope.statements.indexOf(jump)+1) {
optimizationsDone++
return NopStatement.insteadOf(jump)
}
}
return jump
}
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")
if(assignment.target isSameAs assignment.value) {
if(assignment.target.isNotMemory(program.namespace)) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
}
val targetIDt = assignment.target.inferType(program, assignment)
if(!targetIDt.isKnown)
throw AssemblyError("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()
if (cv == null) {
if (bexpr.operator == "+" && targetDt != DataType.FLOAT) {
if (bexpr.left isSameAs bexpr.right && assignment.target isSameAs bexpr.left) {
bexpr.operator = "*"
bexpr.right = NumericLiteralValue.optimalInteger(2, assignment.value.position)
optimizationsDone++
return assignment
}
}
} else {
if (assignment.target isSameAs bexpr.left) {
// remove assignments that have no effect X=X , X+=0, X-=0, X*=1, X/=1, X//=1, A |= 0, A ^= 0, A<<=0, etc etc
// A = A <operator> B
val vardeclDt = (assignment.target.identifier?.targetVarDecl(program.namespace))?.type
when (bexpr.operator) {
"+" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several INCs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "++", assignment.position))
}
return decs
}
}
}
"-" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
} else if (targetDt in IntegerDatatypes && floor(cv) == cv) {
if ((vardeclDt == VarDeclType.MEMORY && cv in 1.0..3.0) || (vardeclDt != VarDeclType.MEMORY && cv in 1.0..8.0)) {
// replace by several DECs (a bit less when dealing with memory targets)
val decs = AnonymousScope(mutableListOf(), assignment.position)
repeat(cv.toInt()) {
decs.statements.add(PostIncrDecr(assignment.target, "--", assignment.position))
}
return decs
}
}
}
"*" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"/" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"**" -> if (cv == 1.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"|" -> if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"^" -> if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
"<<" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
if (((targetDt == DataType.UWORD || targetDt == DataType.WORD) && cv > 15.0) ||
((targetDt == DataType.UBYTE || targetDt == DataType.BYTE) && cv > 7.0)) {
assignment.value = NumericLiteralValue.optimalInteger(0, assignment.value.position)
assignment.value.linkParents(assignment)
optimizationsDone++
} else {
// replace by in-place lsl(...) call
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))
numshifts--
}
optimizationsDone++
return scope
}
}
">>" -> {
if (cv == 0.0) {
optimizationsDone++
return NopStatement.insteadOf(assignment)
}
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++
} else {
// replace by in-place lsr(...) call
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))
numshifts--
}
optimizationsDone++
return scope
}
}
}
}
}
}
return super.visit(assignment)
}
override fun visit(scope: AnonymousScope): Statement {
val linesToRemove = deduplicateAssignments(scope.statements)
if(linesToRemove.isNotEmpty()) {
linesToRemove.reversed().forEach{scope.statements.removeAt(it)}
}
return super.visit(scope)
}
override fun visit(label: Label): Statement {
// remove duplicate labels
val stmts = label.definingScope().statements
val startIdx = stmts.indexOf(label)
if(startIdx<(stmts.size-1) && stmts[startIdx+1] == label)
return NopStatement.insteadOf(label)
return super.visit(label)
}
}
internal class FlattenAnonymousScopesAndRemoveNops: IAstVisitor {
private var scopesToFlatten = mutableListOf<INameScope>()
private val nopStatements = mutableListOf<NopStatement>()
override fun visit(program: Program) {
super.visit(program)
for(scope in scopesToFlatten.reversed()) {
val namescope = scope.parent as INameScope
val idx = namescope.statements.indexOf(scope as Statement)
if(idx>=0) {
val nop = NopStatement.insteadOf(namescope.statements[idx])
nop.parent = namescope as Node
namescope.statements[idx] = nop
namescope.statements.addAll(idx, scope.statements)
scope.statements.forEach { it.parent = namescope }
visit(nop)
}
}
this.nopStatements.forEach {
it.definingScope().remove(it)
}
}
override fun visit(scope: AnonymousScope) {
if(scope.parent is INameScope) {
scopesToFlatten.add(scope) // get rid of the anonymous scope
}
return super.visit(scope)
}
override fun visit(nopStatement: NopStatement) {
nopStatements.add(nopStatement)
}
}

View File

@ -0,0 +1,38 @@
package prog8.optimizer
import prog8.ast.Node
import prog8.ast.Program
import prog8.ast.processing.AstWalker
import prog8.ast.processing.IAstModification
import prog8.ast.statements.Block
internal class UnusedCodeRemover: AstWalker() {
override fun before(program: Program, parent: Node): Iterable<IAstModification> {
val callgraph = CallGraph(program)
val removals = mutableListOf<IAstModification>()
// remove all subroutines that aren't called, or are empty
val entrypoint = program.entrypoint()
program.modules.forEach {
callgraph.forAllSubroutines(it) { sub ->
if (sub !== entrypoint && !sub.keepAlways && (sub.calledBy.isEmpty() || (sub.containsNoCodeNorVars() && !sub.isAsmSubroutine)))
removals.add(IAstModification.Remove(sub, sub.definingScope() as Node))
}
}
program.modules.flatMap { it.statements }.filterIsInstance<Block>().forEach { block ->
if (block.containsNoCodeNorVars() && "force_output" !in block.options())
removals.add(IAstModification.Remove(block, block.definingScope() as Node))
}
// remove modules that are not imported, or are empty (unless it's a library modules)
program.modules.forEach {
if (!it.isLibraryModule && (it.importedBy.isEmpty() || it.containsNoCodeNorVars()))
removals.add(IAstModification.Remove(it, it.parent))
}
return removals
}
}

View File

@ -4,6 +4,7 @@ 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
@ -33,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(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
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()
return importedModule
}
private fun tryGetEmbeddedResource(name: String): InputStream? {
return object{}.javaClass.getResourceAsStream("/prog8lib/$name")
}
}

View File

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

View File

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

View File

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

View File

@ -1,665 +0,0 @@
package prog8.vm
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.StringLiteralValue
import prog8.compiler.target.c64.Petscii
import prog8.vm.astvm.VmExecutionException
import java.util.Objects
import kotlin.math.abs
import kotlin.math.pow
/**
* Rather than a literal value (NumericLiteralValue) that occurs in the parsed source code,
* this runtime value can be used to *execute* the parsed Ast (or another intermediary form)
* It contains a value of a variable during run time of the program and provides arithmetic operations on the value.
*/
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?
val floatval: Double?
val asBoolean: Boolean
companion object {
fun fromLv(literalValue: NumericLiteralValue): RuntimeValueNumeric {
return RuntimeValueNumeric(literalValue.type, num = literalValue.number)
}
}
init {
when (type) {
DataType.UBYTE -> {
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()
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()
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()
require(inum in -32768..32767) { "invalid value for word: $inum" }
wordval = inum
byteval = null
floatval = null
asBoolean = wordval != 0
}
DataType.FLOAT -> {
floatval = num.toDouble()
byteval = null
wordval = null
asBoolean = floatval != 0.0
}
else -> throw VmExecutionException("not a numeric value")
}
}
override fun toString(): String {
return when (type) {
DataType.UBYTE -> "ub:%02x".format(byteval)
DataType.BYTE -> {
if (byteval!! < 0)
"b:-%02x".format(abs(byteval.toInt()))
else
"b:%02x".format(byteval)
}
DataType.UWORD -> "uw:%04x".format(wordval)
DataType.WORD -> {
if (wordval!! < 0)
"w:-%04x".format(abs(wordval))
else
"w:%04x".format(wordval)
}
DataType.FLOAT -> "f:$floatval"
else -> "???"
}
}
override fun numericValue(): Number {
return when (type) {
in ByteDatatypes -> byteval!!
in WordDatatypes -> wordval!!
DataType.FLOAT -> floatval!!
else -> throw ArithmeticException("invalid datatype for numeric value: $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")
else -> throw ArithmeticException("invalid datatype for integer value: $type")
}
}
override fun hashCode(): Int = Objects.hash(byteval, wordval, floatval, type)
override fun equals(other: Any?): Boolean {
if (other == null || other !is RuntimeValueNumeric)
return false
return compareTo(other) == 0 // note: datatype doesn't matter
}
operator fun compareTo(other: RuntimeValueNumeric): Int = numericValue().toDouble().compareTo(other.numericValue().toDouble())
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) {
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)
RuntimeValueNumeric(DataType.UBYTE, (number xor 255) + 1)
else
RuntimeValueNumeric(DataType.UWORD, (number xor 65535) + 1)
}
DataType.BYTE -> {
val v = result.toInt() and 255
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
else
RuntimeValueNumeric(DataType.BYTE, v - 256)
}
DataType.WORD -> {
val v = result.toInt() and 65535
if (v < 32768)
RuntimeValueNumeric(DataType.WORD, v)
else
RuntimeValueNumeric(DataType.WORD, v - 65536)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
return when (leftDt) {
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, result.toInt() and 255)
DataType.BYTE -> {
val v = result.toInt() and 255
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
else
RuntimeValueNumeric(DataType.BYTE, v - 256)
}
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, result.toInt() and 65535)
DataType.WORD -> {
val v = result.toInt() and 65535
if (v < 32768)
RuntimeValueNumeric(DataType.WORD, v)
else
RuntimeValueNumeric(DataType.WORD, v - 65536)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, result)
else -> throw ArithmeticException("$op on non-numeric type")
}
}
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()
val result = v1.toDouble() + v2.toDouble()
return arithResult(type, result, other.type, "add")
}
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()
val result = v1.toDouble() - v2.toDouble()
return arithResult(type, result, other.type, "sub")
}
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()
val result = v1.toDouble() * v2.toDouble()
return arithResult(type, result, other.type, "mul")
}
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) {
when (type) {
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 -> 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: 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: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = numericValue()
val v2 = other.numericValue()
val result = v1.toDouble().pow(v2.toDouble())
return arithResult(type, result, other.type, "pow")
}
fun shl(): RuntimeValueNumeric {
val v = integerValue()
return when (type) {
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)
RuntimeValueNumeric(type, value)
else
RuntimeValueNumeric(type, value - 256)
}
DataType.WORD -> {
val value = v shl 1
if (value < 32768)
RuntimeValueNumeric(type, value)
else
RuntimeValueNumeric(type, value - 65536)
}
else -> throw ArithmeticException("invalid type for shl: $type")
}
}
fun shr(): RuntimeValueNumeric {
val v = integerValue()
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<RuntimeValueNumeric, Boolean> {
// 9 or 17 bit rotate left (with carry))
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = (v and 0x80) != 0
val newval = (v and 0x7f shl 1) or (if (carry) 1 else 0)
Pair(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(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("rol can only work on byte/word")
}
}
fun ror(carry: Boolean): Pair<RuntimeValueNumeric, Boolean> {
// 9 or 17 bit rotate right (with carry)
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val newCarry = v and 1 != 0
val newval = (v ushr 1) or (if (carry) 0x80 else 0)
Pair(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(RuntimeValueNumeric(DataType.UWORD, newval), newCarry)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
fun rol2(): RuntimeValueNumeric {
// 8 or 16 bit rotate left
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = (v and 0x80) ushr 7
val newval = (v and 0x7f shl 1) or carry
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
RuntimeValueNumeric(DataType.UWORD, newval)
}
else -> throw ArithmeticException("rol2 can only work on byte/word")
}
}
fun ror2(): RuntimeValueNumeric {
// 8 or 16 bit rotate right
return when (type) {
DataType.UBYTE, DataType.BYTE -> {
val v = byteval!!.toInt()
val carry = v and 1 shl 7
val newval = (v ushr 1) or carry
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
RuntimeValueNumeric(DataType.UWORD, newval)
}
else -> throw ArithmeticException("ror2 can only work on byte/word")
}
}
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(): 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: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 and v2
return RuntimeValueNumeric(type, result)
}
fun bitor(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 or v2
return RuntimeValueNumeric(type, result)
}
fun bitxor(other: RuntimeValueNumeric): RuntimeValueNumeric {
val v1 = integerValue()
val v2 = other.integerValue()
val result = v1 xor v2
return RuntimeValueNumeric(type, result)
}
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(): 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(): 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)
RuntimeValueNumeric(type, -128)
else
RuntimeValueNumeric(type, newval)
}
DataType.WORD -> {
val newval = wordval!! + 1
if (newval == 32768)
RuntimeValueNumeric(type, -32768)
else
RuntimeValueNumeric(type, newval)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, floatval!! + 1)
else -> throw ArithmeticException("inc can only work on numeric types")
}
}
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)
RuntimeValueNumeric(type, 127)
else
RuntimeValueNumeric(type, newval)
}
DataType.WORD -> {
val newval = wordval!! - 1
if (newval == -32769)
RuntimeValueNumeric(type, 32767)
else
RuntimeValueNumeric(type, newval)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, floatval!! - 1)
else -> throw ArithmeticException("dec can only work on numeric types")
}
}
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): RuntimeValueNumeric {
return when (type) {
DataType.UBYTE -> {
when (targetType) {
DataType.UBYTE -> this
DataType.BYTE -> {
val nval = byteval!!.toInt()
if (nval < 128)
RuntimeValueNumeric(DataType.BYTE, nval)
else
RuntimeValueNumeric(DataType.BYTE, nval - 256)
}
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, numericValue())
DataType.WORD -> {
val nval = numericValue().toInt()
if (nval < 32768)
RuntimeValueNumeric(DataType.WORD, nval)
else
RuntimeValueNumeric(DataType.WORD, nval - 65536)
}
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 -> 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)
RuntimeValueNumeric(DataType.BYTE, v)
else
RuntimeValueNumeric(DataType.BYTE, v - 256)
}
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 255)
DataType.UWORD -> this
DataType.WORD -> {
val v = integerValue()
if (v < 32768)
RuntimeValueNumeric(DataType.WORD, v)
else
RuntimeValueNumeric(DataType.WORD, v - 65536)
}
DataType.FLOAT -> RuntimeValueNumeric(DataType.FLOAT, numericValue())
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
DataType.WORD -> {
when (targetType) {
DataType.BYTE -> {
val v = integerValue() and 255
if (v < 128)
RuntimeValueNumeric(DataType.BYTE, v)
else
RuntimeValueNumeric(DataType.BYTE, v - 256)
}
DataType.UBYTE -> RuntimeValueNumeric(DataType.UBYTE, integerValue() and 65535)
DataType.UWORD -> RuntimeValueNumeric(DataType.UWORD, integerValue())
DataType.WORD -> this
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)
RuntimeValueNumeric(DataType.BYTE, integer)
else
throw ArithmeticException("overflow when casting float to byte: $this")
}
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)
RuntimeValueNumeric(DataType.WORD, integer)
else
throw ArithmeticException("overflow when casting float to word: $this")
}
DataType.FLOAT -> this
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
else -> throw ArithmeticException("invalid type cast from $type to $targetType")
}
}
}
class RuntimeValueString(type: DataType, val str: String, val heapId: Int?): RuntimeValueBase(type) {
companion object {
fun fromLv(string: StringLiteralValue): RuntimeValueString {
return RuntimeValueString(string.type, string.value, string.heapId!!)
}
}
override fun toString(): String {
return when (type) {
DataType.STR -> "str:$str"
DataType.STR_S -> "str_s:$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> = Petscii.encodePetscii(str, true).iterator()
override fun numericValue(): Number {
throw VmExecutionException("string is not a number")
}
override fun integerValue(): Int {
throw VmExecutionException("string is not a number")
}
}
open class RuntimeValueArray(type: DataType, val array: Array<Number>, val heapId: Int?): RuntimeValueBase(type) {
companion object {
fun fromLv(array: ArrayLiteralValue): RuntimeValueArray {
return if (array.type == DataType.ARRAY_F) {
val doubleArray = array.value.map { (it as NumericLiteralValue).number }.toTypedArray()
RuntimeValueArray(array.type, doubleArray, array.heapId!!)
} else {
val resultArray = mutableListOf<Number>()
for (elt in array.value.withIndex()) {
if (elt.value is NumericLiteralValue)
resultArray.add((elt.value as NumericLiteralValue).number.toInt())
else {
TODO("ADDRESSOF ${elt.value}")
}
}
RuntimeValueArray(array.type, resultArray.toTypedArray(), array.heapId!!)
}
}
}
override fun toString(): String {
return when (type) {
DataType.ARRAY_UB -> "array_ub:..."
DataType.ARRAY_B -> "array_b:..."
DataType.ARRAY_UW -> "array_uw:..."
DataType.ARRAY_W -> "array_w:..."
DataType.ARRAY_F -> "array_f:..."
else -> "???"
}
}
override fun hashCode(): Int = Objects.hash(type, array)
override fun equals(other: Any?): Boolean {
if (other == null || other !is RuntimeValueArray)
return false
return type == other.type && array.contentEquals(other.array)
}
open fun iterator(): Iterator<Number> = array.iterator()
override fun numericValue(): Number {
throw VmExecutionException("array is not a number")
}
override fun integerValue(): Int {
throw VmExecutionException("array is not a number")
}
}
class RuntimeValueRange(type: DataType, val range: IntProgression): RuntimeValueArray(type, range.toList().toTypedArray(), null) {
override fun iterator(): Iterator<Number> {
return range.iterator()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,178 +0,0 @@
package prog8.vm.astvm
import prog8.ast.Program
import prog8.ast.base.ArrayElementTypes
import prog8.ast.base.DataType
import prog8.ast.base.FatalAstException
import prog8.ast.base.VarDeclType
import prog8.ast.expressions.*
import prog8.ast.statements.BuiltinFunctionStatementPlaceholder
import prog8.ast.statements.Label
import prog8.ast.statements.Subroutine
import prog8.ast.statements.VarDecl
import prog8.vm.*
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,
val runtimeVars: RuntimeVariables,
val performBuiltinFunction: BuiltinfunctionCaller,
val executeSubroutine: SubroutineCaller)
fun evaluate(expr: Expression, ctx: EvalContext): RuntimeValueBase {
val constval = expr.constValue(ctx.program)
if(constval!=null)
return RuntimeValueNumeric.fromLv(constval)
when(expr) {
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) 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) as RuntimeValueNumeric
val right = evaluate(expr.right, ctx) as RuntimeValueNumeric
return when(expr.operator) {
"<" -> 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)
"/" -> left.div(right)
"**" -> left.pow(right)
"<<" -> {
var result = left
repeat(right.integerValue()) {result = result.shl()}
result
}
">>" -> {
var result = left
repeat(right.integerValue()) {result = result.shr()}
result
}
"%" -> left.remainder(right)
"|" -> left.bitor(right)
"&" -> left.bitand(right)
"^" -> left.bitxor(right)
"and" -> left.and(right)
"or" -> left.or(right)
"xor" -> left.xor(right)
else -> throw VmExecutionException("unsupported operator "+expr.operator)
}
}
is ArrayIndexedExpression -> {
val array = evaluate(expr.identifier, ctx)
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) 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)
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
RuntimeValueNumeric(DataType.UWORD, address)
}
}
is DirectMemoryRead -> {
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 -> {
val scope = expr.definingScope()
val variable = scope.lookup(expr.nameInSource, expr)
if(variable is VarDecl) {
when {
variable.type==VarDeclType.VAR -> return ctx.runtimeVars.get(variable.definingScope(), variable.name)
variable.datatype==DataType.STRUCT -> throw VmExecutionException("cannot process structs by-value. at ${expr.position}")
else -> {
val address = ctx.runtimeVars.getMemoryAddress(variable.definingScope(), variable.name)
return when(variable.datatype) {
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(DataType.STR, ctx.mem.getString(address), null)
DataType.STR_S -> RuntimeValueString(DataType.STR_S, ctx.mem.getScreencodeString(address)!!, null)
else -> throw VmExecutionException("unexpected datatype $variable")
}
}
}
} else
throw VmExecutionException("weird identifier reference $variable")
}
is FunctionCall -> {
val sub = expr.target.targetStatement(ctx.program.namespace)
val args = expr.arglist.map { evaluate(it, ctx) as RuntimeValueNumeric }
return when(sub) {
is Subroutine -> {
val result = ctx.executeSubroutine(sub, args, null)
?: throw VmExecutionException("expected a result from functioncall $expr")
result
}
is BuiltinFunctionStatementPlaceholder -> {
val result = ctx.performBuiltinFunction(sub.name, args, ctx.statusflags)
?: throw VmExecutionException("expected 1 result from functioncall $expr")
result
}
else -> {
throw VmExecutionException("unimplemented function call target $sub")
}
}
}
is RangeExpr -> {
val cRange = expr.toConstantIntegerRange()
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")
}
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")
}
}
}

View File

@ -1,144 +0,0 @@
package prog8.vm.astvm
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import kotlin.math.abs
class Memory(private val readObserver: (address: Int, value: Short) -> Short,
private val writeObserver: (address: Int, value: Short) -> Short)
{
private val mem = ShortArray(65536) // shorts because byte is signed and we store values 0..255
private val observed = BooleanArray(65536) // what addresses are observed
fun observe(vararg address: Int) {
address.forEach { observed[it]=true }
}
fun getUByte(address: Int): Short {
return if(observed[address]) readObserver(address, mem[address])
else mem[address]
}
fun getUByteDirectly(address: Int): Short {
return mem[address]
}
fun getSByte(address: Int): Short {
val ubyte = getUByte(address)
return if(ubyte <= 127) ubyte
else (-((ubyte.toInt() xor 255)+1)).toShort() // 2's complement
}
fun setUByte(address: Int, value: Short) {
if(value !in 0..255)
throw VmExecutionException("ubyte value out of range $value")
mem[address] =
if(observed[address]) writeObserver(address, value)
else value
}
fun setUByteDirectly(address: Int, value: Short) {
if(value !in 0..255)
throw VmExecutionException("ubyte value out of range $value")
mem[address] = value
}
fun setSByte(address: Int, value: Short) {
if(value !in -128..127) throw VmExecutionException("byte value out of range $value")
val ubyte =
if(value>=0) value
else ((abs(value.toInt()) xor 255)+1).toShort() // 2's complement
setUByte(address, ubyte)
}
fun getUWord(address: Int): Int {
return getUByte(address) + 256*getUByte(address+1)
}
fun getSWord(address: Int): Int {
val uword = getUWord(address)
if(uword <= 32767)
return uword
return -((uword xor 65535)+1) // 2's complement
}
fun setUWord(address: Int, value: Int) {
if(value !in 0..65535)
throw VmExecutionException("uword value out of range $value")
setUByte(address, value.and(255).toShort())
setUByte(address+1, (value / 256).toShort())
}
fun setSWord(address: Int, value: Int) {
if(value !in -32768..32767) throw VmExecutionException("word value out of range $value")
if(value>=0)
setUWord(address, value)
else
setUWord(address, (abs(value) xor 65535)+1) // 2's complement
}
fun setFloat(address: Int, value: Double) {
val mflpt5 = MachineDefinition.Mflpt5.fromNumber(value)
setUByte(address, mflpt5.b0)
setUByte(address+1, mflpt5.b1)
setUByte(address+2, mflpt5.b2)
setUByte(address+3, mflpt5.b3)
setUByte(address+4, mflpt5.b4)
}
fun getFloat(address: Int): Double {
return MachineDefinition.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)
var addr = address
for (c in petscii) setUByte(addr++, c)
setUByte(addr, 0)
}
fun getString(strAddress: Int): String {
// lowercase PETSCII
val petscii = mutableListOf<Short>()
var addr = strAddress
while(true) {
val byte = getUByte(addr++)
if(byte==0.toShort()) break
petscii.add(byte)
}
return Petscii.decodePetscii(petscii, true)
}
fun clear() {
for(i in 0..65535) setUByte(i, 0)
}
fun copy(from: Int, to: Int, numbytes: Int) {
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)
}
}

View File

@ -1,213 +0,0 @@
package prog8.vm.astvm
import prog8.compiler.target.c64.MachineDefinition
import prog8.compiler.target.c64.Petscii
import java.awt.*
import java.awt.event.KeyEvent
import java.awt.event.KeyListener
import java.awt.image.BufferedImage
import java.util.ArrayDeque
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.Timer
class BitmapScreenPanel : KeyListener, JPanel() {
private val image = BufferedImage(SCREENWIDTH, SCREENHEIGHT, BufferedImage.TYPE_INT_ARGB)
private val g2d = image.graphics as Graphics2D
private var cursorX: Int=0
private var cursorY: Int=0
val keyboardBuffer = ArrayDeque<Char>()
init {
val size = Dimension(image.width * SCALING, image.height * SCALING)
minimumSize = size
maximumSize = size
preferredSize = size
clearScreen(6)
isFocusable = true
requestFocusInWindow()
addKeyListener(this)
}
override fun keyTyped(p0: KeyEvent) {
keyboardBuffer.add(p0.keyChar)
}
override fun keyPressed(p0: KeyEvent) {
}
override fun keyReleased(p0: KeyEvent?) {
}
override fun paint(graphics: Graphics?) {
val g2d = graphics as Graphics2D?
g2d!!.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF)
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE)
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)
g2d.drawImage(image, 0, 0, image.width * 3, image.height * 3, null)
}
fun clearScreen(color: Short) {
g2d.background = MachineDefinition.colorPalette[color % MachineDefinition.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)
}
fun drawLine(x1: Int, y1: Int, x2: Int, y2: Int, color: Short) {
g2d.color = MachineDefinition.colorPalette[color % MachineDefinition.colorPalette.size]
g2d.drawLine(x1, y1, x2, y2)
}
fun printText(text: String, lowercase: Boolean, inverseVideo: Boolean=false) {
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
}
}
}
fun printText(text: Iterable<Short>) {
text.forEach { printPetscii(it, false) }
}
fun printPetscii(char: Short, inverseVideo: Boolean=false) {
if(char==13.toShort() || char==141.toShort()) {
cursorX=0
cursorY++
} else {
setPetscii(cursorX, cursorY, char, 1, inverseVideo)
cursorX++
if (cursorX >= (SCREENWIDTH / 8)) {
cursorY++
cursorX = 0
}
}
while(cursorY>=(SCREENHEIGHT/8)) {
// scroll the screen up because the cursor went past the last line
Thread.sleep(10)
val screen = image.copy()
val graphics = image.graphics as Graphics2D
graphics.drawImage(screen, 0, -8, null)
val color = graphics.color
graphics.color = MachineDefinition.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) {
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)
g2d.drawImage(coloredImage, 8*x, 8*y , null)
}
fun setCursorPos(x: Int, y: Int) {
cursorX = x
cursorY = y
}
fun getCursorPos(): Pair<Int, Int> {
return Pair(cursorX, cursorY)
}
companion object {
const val SCREENWIDTH = 320
const val SCREENHEIGHT = 200
const val SCALING = 3
}
}
class ScreenDialog(title: String) : JFrame(title) {
val canvas = BitmapScreenPanel()
val keyboardBuffer = canvas.keyboardBuffer
init {
val borderWidth = 16
layout = GridBagLayout()
defaultCloseOperation = EXIT_ON_CLOSE
isResizable = false
// the borders (top, left, right, bottom)
val borderTop = JPanel().apply {
preferredSize = Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = MachineDefinition.colorPalette[14]
}
val borderBottom = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * (BitmapScreenPanel.SCREENWIDTH +2*borderWidth), BitmapScreenPanel.SCALING * borderWidth)
background = MachineDefinition.colorPalette[14]
}
val borderLeft = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = MachineDefinition.colorPalette[14]
}
val borderRight = JPanel().apply {
preferredSize =Dimension(BitmapScreenPanel.SCALING * borderWidth, BitmapScreenPanel.SCALING * BitmapScreenPanel.SCREENHEIGHT)
background = MachineDefinition.colorPalette[14]
}
var c = GridBagConstraints()
c.gridx=0; c.gridy=1; c.gridwidth=3
add(borderTop, c)
c = GridBagConstraints()
c.gridx=0; c.gridy=2
add(borderLeft, c)
c = GridBagConstraints()
c.gridx=2; c.gridy=2
add(borderRight, c)
c = GridBagConstraints()
c.gridx=0; c.gridy=3; c.gridwidth=3
add(borderBottom, c)
// the screen canvas(bitmap)
c = GridBagConstraints()
c.gridx = 1; c.gridy = 2
add(canvas, c)
canvas.requestFocusInWindow()
}
fun start() {
val repaintTimer = Timer(1000 / 60) { repaint() }
repaintTimer.start()
}
}
private fun BufferedImage.copy(): BufferedImage {
val bcopy = BufferedImage(this.width, this.height, this.type)
val g = bcopy.graphics
g.drawImage(this, 0, 0, null)
g.dispose()
return bcopy
}

View File

@ -1,80 +0,0 @@
package prog8.vm.astvm
import prog8.ast.Program
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.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.vm.RuntimeValueArray
import prog8.vm.RuntimeValueNumeric
import prog8.vm.RuntimeValueString
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, 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,
NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
val vdX = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.X.name, null,
NumericLiteralValue.optimalInteger(255, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
val vdY = VarDecl(VarDeclType.VAR, DataType.UBYTE, ZeropageWish.DONTCARE, null, Register.Y.name, null,
NumericLiteralValue.optimalInteger(0, globalpos), isArray = false, autogeneratedDontRemove = true, position = globalpos)
vdA.linkParents(program.namespace)
vdX.linkParents(program.namespace)
vdY.linkParents(program.namespace)
program.namespace.statements.add(vdA)
program.namespace.statements.add(vdX)
program.namespace.statements.add(vdY)
super.visit(program)
}
override fun visit(decl: VarDecl): Statement {
// if the decl is part of a struct, just skip it
if(decl.parent !is StructDecl) {
when (decl.type) {
VarDeclType.VAR -> {
if(decl.datatype!=DataType.STRUCT) {
val numericLv = decl.value as? NumericLiteralValue
val value = if(numericLv!=null) {
RuntimeValueNumeric.fromLv(numericLv)
} else {
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)
}
}
VarDeclType.MEMORY -> {
runtimeVariables.defineMemory(decl.definingScope(), decl.name, (decl.value as NumericLiteralValue).number.toInt())
}
VarDeclType.CONST -> {
// consts should have been const-folded away
}
}
}
return super.visit(decl)
}
}

View File

@ -5,6 +5,7 @@ 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.StringLiteralValue
import kotlin.test.assertEquals
@ -83,8 +84,11 @@ class TestParserNumericLiteralValue {
@Test
fun testEqualsRef() {
assertTrue(StringLiteralValue(DataType.STR, "hello", dummyPos) == StringLiteralValue(DataType.STR, "hello", dummyPos))
assertFalse(StringLiteralValue(DataType.STR, "hello", dummyPos) == StringLiteralValue(DataType.STR, "bye", 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)
@ -93,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 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOne, lvTwo, lvThree), null, dummyPos)
val lv2 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvThreeR), null, dummyPos)
val lv3 = ArrayLiteralValue(DataType.ARRAY_UB, arrayOf(lvOneR, lvTwoR, lvFour), null, 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)
}

View File

@ -1,352 +0,0 @@
package prog8tests
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import prog8.ast.base.DataType
import prog8.vm.RuntimeValueNumeric
import kotlin.test.*
private fun sameValueAndType(v1: RuntimeValueNumeric, v2: RuntimeValueNumeric): Boolean {
return v1.type==v2.type && v1==v2
}
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestRuntimeValueNumeric {
@Test
fun testValueRanges() {
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, 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, 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, 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(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(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 = RuntimeValueNumeric(DataType.UWORD, 12345)
assertEquals(v, v)
assertFalse(v != v)
assertTrue(v<=v)
assertTrue(v>=v)
assertFalse(v<v)
assertFalse(v>v)
assertTrue(sameValueAndType(RuntimeValueNumeric(DataType.UBYTE, 100), RuntimeValueNumeric(DataType.UBYTE, 100)))
}
@Test
fun testEqualsAndNotEquals() {
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(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(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(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(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(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(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(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(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(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(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(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> {
RuntimeValueNumeric(DataType.UWORD, 100).add(RuntimeValueNumeric(DataType.UBYTE, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.UBYTE, 100).add(RuntimeValueNumeric(DataType.UWORD, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.FLOAT, 100.22).add(RuntimeValueNumeric(DataType.UWORD, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.UWORD, 1002).add(RuntimeValueNumeric(DataType.FLOAT, 120.22))
}
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.FLOAT, 100.22).add(RuntimeValueNumeric(DataType.UBYTE, 120))
}
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.UBYTE, 12).add(RuntimeValueNumeric(DataType.FLOAT, 120.22))
}
}
@Test
fun testNoAutoFloatConversion() {
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.UBYTE, 233).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.UWORD, 233).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.UBYTE, 233).mul(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.UWORD, 233).mul(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.UBYTE, 233).div(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
assertFailsWith<ArithmeticException> {
RuntimeValueNumeric(DataType.UWORD, 233).div(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
val result = RuntimeValueNumeric(DataType.FLOAT, 233.333).add(RuntimeValueNumeric(DataType.FLOAT, 1.234))
}
@Test
fun arithmetictestUbyte() {
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, 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, 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, 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, RuntimeValueNumeric(DataType.UBYTE, 20).mul(RuntimeValueNumeric(DataType.UBYTE, 10)).integerValue())
assertEquals(144, RuntimeValueNumeric(DataType.UBYTE, 20).mul(RuntimeValueNumeric(DataType.UBYTE, 20)).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, 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, 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, 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, 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, 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, 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, RuntimeValueNumeric(DataType.UWORD, 5).pow(RuntimeValueNumeric(DataType.UWORD, 6)).integerValue())
assertEquals(12589, RuntimeValueNumeric(DataType.UWORD, 5).pow(RuntimeValueNumeric(DataType.UWORD, 7)).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, 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, 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, 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, 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, RuntimeValueNumeric(DataType.BYTE, 10).mul(RuntimeValueNumeric(DataType.BYTE, 10)).integerValue())
assertEquals(-56, RuntimeValueNumeric(DataType.BYTE, 20).mul(RuntimeValueNumeric(DataType.BYTE, 10)).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, 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, 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, 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, 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, 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, RuntimeValueNumeric(DataType.WORD, 100).mul(RuntimeValueNumeric(DataType.WORD, 100)).integerValue())
assertEquals(-25536, RuntimeValueNumeric(DataType.WORD, 200).mul(RuntimeValueNumeric(DataType.WORD, 200)).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, RuntimeValueNumeric(DataType.WORD, 9000).shl().integerValue())
assertEquals(-25536, RuntimeValueNumeric(DataType.WORD, 20000).shl().integerValue())
assertEquals(-2, RuntimeValueNumeric(DataType.WORD, -1).shl().integerValue())
}
}

View File

@ -6,16 +6,16 @@ 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.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.RuntimeValueNumeric
import java.io.CharConversionException
import kotlin.test.*
@ -95,60 +95,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
@ -173,7 +176,7 @@ class TestZeropage {
println(zp.free)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
zp.allocate("", DataType.BYTE, null)
zp.allocate("", DataType.BYTE, null, errors)
}
}
@ -218,19 +221,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)
}
}
@ -238,29 +241,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)
}
}
@ -268,16 +271,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())
}
}
@ -352,8 +355,8 @@ class TestPetscii {
@Test
fun testLiteralValueComparisons() {
val ten = NumericLiteralValue(DataType.UWORD, 10, Position("", 0, 0, 0))
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position("", 0, 0, 0))
val ten = NumericLiteralValue(DataType.UWORD, 10, Position.DUMMY)
val nine = NumericLiteralValue(DataType.UBYTE, 9, Position.DUMMY)
assertEquals(ten, ten)
assertNotEquals(ten, nine)
assertFalse(ten != ten)
@ -369,30 +372,10 @@ class TestPetscii {
assertTrue(ten <= ten)
assertFalse(ten < ten)
val abc = StringLiteralValue(DataType.STR, "abc", Position("", 0, 0, 0))
val abd = StringLiteralValue(DataType.STR, "abd", Position("", 0, 0, 0))
val abc = StringLiteralValue("abc", false, Position.DUMMY)
val abd = StringLiteralValue("abd", false, Position.DUMMY)
assertEquals(abc, abc)
assertTrue(abc!=abd)
assertFalse(abc!=abc)
}
@Test
fun testStackvmValueComparisons() {
val ten = RuntimeValueNumeric(DataType.FLOAT, 10)
val nine = RuntimeValueNumeric(DataType.UWORD, 9)
assertEquals(ten, ten)
assertNotEquals(ten, nine)
assertFalse(ten != ten)
assertTrue(ten != nine)
assertTrue(ten > nine)
assertTrue(ten >= nine)
assertTrue(ten >= ten)
assertFalse(ten > ten)
assertFalse(ten < nine)
assertFalse(ten <= nine)
assertTrue(ten <= ten)
assertFalse(ten < ten)
}
}

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>

View File

@ -168,18 +168,3 @@ or::
$ ./p8compile.sh -emu examples/rasterbars.p8
Virtual Machine / Simulator
---------------------------
You may have noticed the ``-sim`` command line option for the compiler:
-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.

View File

@ -135,30 +135,29 @@ Design principles and features
- It is a cross-compiler running on modern machines (Linux, MacOS, Windows, ...)
The generated output is a machine code program runnable on actual 8-bit 6502 hardware.
- Usable on most operating systems.
- Based on simple and familiar imperative structured programming paradigm.
- 'One statement per line' code style, resulting in clear readable programs.
- Based on simple and familiar imperative structured programming (it looks like a mix of C and Python)
- 'One statement per line' code, resulting in clear readable programs.
- Modular programming and scoping via modules, code blocks, and subroutines.
- Provide high level programming constructs but stay close to the metal;
- Provide high level programming constructs but at the same time stay close to the metal;
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)
- Arbitrary number of subroutine parameters
- Complex nested expressions are possible
- Values are typed. Types supported include signed and unsigned bytes and words, arrays, strings and floats.
- Nested subroutines can access variables from outer scopes to avoids the overhead to pass everything via parameters
- Values are typed. Available data types 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.
- Provide various quality of life language features and library subroutines specifically for the target platform.
- Provide a very convenient edit/compile/run cycle by being able to directly launch
the compiled program in an emulator and provide debugging information to the emulator.
- The compiler outputs a regular 6502 assembly source code file, but doesn't assemble this itself.
The (separate) '64tass' cross-assembler tool is used for that.
the compiled program in an emulator and provide debugging information to this emulator.
- Arbitrary control flow jumps and branches are possible,
and will usually translate directly into the appropriate single 6502 jump/branch instruction.
- There are no complicated built-in error handling or overflow checks, you'll have to take care
of this yourself if required. This keeps the language and code simple and efficient.
- The compiler tries to optimize the program and generated code, but hand-tuning of the
- The compiler tries to optimize the program and generated code a bit, 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``
- There are many built-in functions, such as ``sin``, ``cos``, ``rnd``, ``abs``, ``min``, ``max``, ``sqrt``, ``msb``, ``rol``, ``ror``, ``swap``, ``memset``, ``memcopy``, ``substr``, ``sort`` and ``reverse`` (and others)
- Assembling the generated code into a program wil be done by an external cross-assembler tool.
.. _requirements:
@ -167,26 +166,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::

View File

@ -12,57 +12,56 @@ Elements of a program
---------------------
Program
Consists of one or more *modules*.
Consists of one or more *modules*.
Module
A file on disk with the ``.p8`` suffix. It contains *directives* and *code blocks*.
Whitespace and indentation in the source code are arbitrary and can be tabs or spaces or both.
You can also add *comments* to the source code.
One moudule file can *import* others, and also import *library modules*.
A file on disk with the ``.p8`` suffix. It can contain *directives* and *code blocks*.
Whitespace and indentation in the source code are arbitrary and can be mixed tabs or spaces.
A module file can *import* other modules, including *library modules*.
Comments
Everything after a semicolon ``;`` is a comment and is ignored by the compiler.
If the whole line is just a comment, it will be copied into the resulting assembly source code.
This makes it easier to understand and relate the generated code. Examples::
A = 42 ; set the initial value to 42
; next is the code that...
Everything after a semicolon ``;`` is a comment and is ignored by the compiler.
If the whole line is just a comment, this line will be copied into the resulting assembly source code for reference.
Directive
These are special instructions for the compiler, to change how it processes the code
and what kind of program it creates. A directive is on its own line in the file, and
starts with ``%``, optionally followed by some arguments.
These are special instructions for the compiler, to change how it processes the code
and what kind of program it creates. A directive is on its own line in the file, and
starts with ``%``, optionally followed by some arguments.
Code block
A block of actual program code. It defines a *scope* (also known as 'namespace') and
can contain Prog8 *code*, *variable declarations* and *subroutines*.
More details about this below: :ref:`blocks`.
A block of actual program code. It has a starting address in memory,
and defines a *scope* (also known as 'namespace').
It contains variables and subroutines.
More details about this below: :ref:`blocks`.
Variable declarations
The data that the code works on is stored in variables ('named values that can change').
The compiler allocates the required memory for them.
There is *no dynamic memory allocation*. The storage size of all variables
is fixed and is determined at compile time.
Variable declarations tend to appear at the top of the code block that uses them.
They define the name and type of the variable, and its initial value.
Prog8 supports a small list of data types, including special 'memory mapped' types
that don't allocate storage but instead point to a fixed location in the address space.
The data that the code works on is stored in variables ('named values that can change').
The compiler allocates the required memory for them.
There is *no dynamic memory allocation*. The storage size of all variables
is fixed and is determined at compile time.
Variable declarations tend to appear at the top of the code block that uses them.
They define the name and type of the variable, and its initial value.
Prog8 supports a small list of data types, including special 'memory mapped' types
that don't allocate storage but instead point to a fixed location in the address space.
Code
These are the instructions that make up the program's logic. There are different kinds of instructions
('statements' is a better name):
These are the instructions that make up the program's logic.
Code can only occur inside a subroutine.
There are different kinds of instructions ('statements' is a better name) such as:
- value assignment
- looping (for, while, repeat, unconditional jumps)
- conditional execution (if - then - else, when, and conditional jumps)
- subroutine calls
- label definition
- value assignment
- looping (for, while, repeat, unconditional jumps)
- conditional execution (if - then - else, when, and conditional jumps)
- subroutine calls
- label definition
Subroutine
Defines a piece of code that can be called by its name from different locations in your code.
It accepts parameters and can return a value (optional).
It can define its own variables, and it is even possible to define subroutines nested inside other subroutines.
Their contents is scoped accordingly.
Nested subroutines can access the variables from outer scopes.
This removes the need and overhead to pass everything via parameters.
Label
This is a named position in your code where you can jump to from another place.
@ -90,16 +89,20 @@ Scope
Blocks, Scopes, and accessing Symbols
-------------------------------------
**Blocks** are the top level separate pieces of code and data of your program. They are combined
into a single output program. No code or data can occur outside a block. Here's an example::
**Blocks** are the top level separate pieces of code and data of your program. They have a
starting address in memory and will be combined together into a single output program.
They can only contain *directives*, *variable declarations*, *subroutines* and *inline assembly code*.
Your actual program code can only exist inside these subroutines.
(except the occasional inline assembly)
main $c000 {
; this is code inside the block...
}
Here's an example::
main $c000 {
; this is code inside the block...
}
The name of a block must be unique in your entire program.
Also be careful when importing other modules; blocks in your own code cannot have
Be careful when importing other modules; blocks in your own code cannot have
the same name as a block defined in an imported module or library.
If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed).
@ -109,7 +112,7 @@ want to work on later, because the contents of the ignored block are not fully p
The address can be used to place a block at a specific location in memory.
Usually it is omitted, and the compiler will automatically choose the location (usually immediately after
the previous block in memory).
The address must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$200`` is the cpu stack).
It must be >= ``$0200`` (because ``$00``--``$ff`` is the ZP and ``$100``--``$1ff`` is the cpu stack).
.. _scopes:
@ -132,15 +135,18 @@ Scopes are created using either of these two statements:
- blocks (top-level named scope)
- subroutines (nested named scope)
.. note::
In contrast to many other programming languages, a new scope is *not* created inside
for, while and repeat statements, nor for the if statement and branching conditionals.
This is a bit restrictive because you have to think harder about what variables you
want to use inside a subroutine. But it is done precisely for this reason; memory in the
target system is very limited and it would be a waste to allocate a lot of variables.
Right now the prog8 compiler is not advanced enough to be able to 'share' or 'overlap'
variables intelligently by itself. So for now, it's something the programmer has to think about.
.. important::
Unlike most other programming languages, a new scope is *not* created inside
for, while and repeat statements, the if statement, and the branching conditionals.
These all share the same scope from the subroutine they're defined in.
You can define variables in these blocks, but these will be treated as if they
were defined in the subroutine instead.
This can seem a bit restrictive because you have to think harder about what variables you
want to use inside the subroutine, to avoid clashes.
But this decision was made for a good reason: memory in prog8's
target systems is usually very limited and it would be a waste to allocate a lot of variables.
The prog8 compiler is not yet advanced enough to be able to share or overlap
variables intelligently. So for now that is something you have to think about yourself.
Program Start and Entry Point
@ -150,21 +156,14 @@ Your program must have a single entry point where code execution begins.
The compiler expects a ``start`` subroutine in the ``main`` block for this,
taking no parameters and having no return value.
.. sidebar::
60hz IRQ entry point
When running the generated code on the StackVm virtual machine,
it will use the ``irq`` subroutine in the ``irq`` block for the
60hz irq routine. This is optional.
As any subroutine, it has to end with a ``return`` statement (or a ``goto`` call)::
main {
sub start () {
; program entrypoint code here
return
}
}
main {
sub start () {
; program entrypoint code here
return
}
}
The ``main`` module is always relocated to the start of your programs
@ -175,12 +174,11 @@ calls with the SYS statement.
Variables and values
--------------------
Variables are named values that can change during the execution of the program.
They can be defined inside any scope (blocks, subroutines, for loops, etc.) See :ref:`Scopes <scopes>`.
They can be defined inside any scope (blocks, subroutines etc.) See :ref:`Scopes <scopes>`.
When declaring a numeric variable it is possible to specify the initial value, if you don't want it to be zero.
For other data types it is required to specify that initial value it should get.
Values will usually be part of an expression or assignment statement::
@ -188,9 +186,11 @@ Values will usually be part of an expression or assignment statement::
12345 ; integer number
$aa43 ; hex integer number
%100101 ; binary integer number (% is also remainder operator so be careful)
"Hi, I am a string" ; text string
'a' ; petscii value (byte) for the letter a
-33.456e52 ; floating point number
"Hi, I am a string" ; text string, encoded with compiler target default encoding
'a' ; byte value (ubyte) for the letter a
@"Alternate" ; text string, encoded with alternate encoding
@'a' ; byte value of the letter a, using alternate encoding
byte counter = 42 ; variable of size 8 bits, with initial value 42
@ -271,22 +271,32 @@ Strings
Strings are a sequence of characters enclosed in ``"`` quotes. The length is limited to 255 characters.
They're stored and treated much the same as a byte array,
but they have some special properties because they are considered to be *text*.
Strings in your source code files will be encoded (translated from ASCII/UTF-8) into either CBM PETSCII or C-64 screencodes.
PETSCII is the default choice. If you need screencodes (also called 'poke' codes) instead,
you have to use the ``str_s`` variants of the string type identifier.
Strings in your source code files will be encoded (translated from ASCII/UTF-8) into bytes via the
default encoding that is used on the target platform. For the C-64, this is CBM PETSCII.
Alternate-encoding strings (prefixed with ``@``) will be encoded via the alternate encoding for the
platform (if defined). For the C-64, that is SCREEN CODES (also known as POKE codes).
This @-prefix can also be used for character byte values.
You can concatenate two string literals using '+' (not very useful though) or repeat
a string literal a given number of times using '*'::
a string literal a given number of times using '*'. You can also assign a new string
value to another string. No bounds check is done so be sure the destination string is
large enough to contain the new value::
str string1 = "first part" + "second part"
str string2 = "hello!" * 10
string1 = string2
string1 = "new value"
.. caution::
It's probably best that you don't change strings after they're created.
It's probably best to avoid changing strings after they've been created. This
includes changing certain letters by index, or by assigning a new value, or by
modifying the string via other means for example ``substr`` function and its cousins.
This is because if your program exits and is restarted (without loading it again),
it will then operate on the changed strings instead of the original ones.
The same is true for arrays by the way.
it will then start working with the changed strings instead of the original ones!
The same is true for arrays.
Structs
@ -369,10 +379,8 @@ Initial values across multiple runs of the program
When declaring values with an initial value, this value will be set into the variable each time
the program reaches the declaration again. This can be in loops, multiple subroutine calls,
or even multiple invocations of the entire program. If you omit an initial value, it will
be set to zero *but only for the first run of the program*. A second run will utilize the last value
where it left off (but your code will be a bit smaller because no initialization instructions
are generated)
or even multiple invocations of the entire program.
If you omit the initial value, zero will be used instead.
This only works for simple types, *and not for string variables and arrays*.
It is assumed these are left unchanged by the program; they are not re-initialized on
@ -381,15 +389,6 @@ If you do modify them in-place, you should take care yourself that they work as
expected when the program is restarted.
(This is an optimization choice to avoid having to store two copies of every string and array)
.. caution::
variables that get allocated in zero-page will *not* have a zero starting value when you omit
the variable's initialization. They'll be whatever the last value in that zero page
location was. So it's best to don't depend on the uninitialized starting value!
.. warning::
this behavior may change in a future version so that subsequent runs always
use the same initial values
Loops
-----
@ -401,6 +400,10 @@ Iterating with a floating point variable is not supported. If you want to loop o
The *while*-loop is used to repeat a piece of code while a certain condition is still true.
The *repeat--until* loop is used to repeat a piece of code until a certain condition is true.
The *forever*-loop is used to simply run a piece of code in a loop, forever. You can still
break out of this loop if desired. A "while true" or "until false" loop is equivalent to
a forever-loop.
You can also create loops by using the ``goto`` statement, but this should usually be avoided.
.. attention::
@ -608,8 +611,8 @@ Calling a subroutine
^^^^^^^^^^^^^^^^^^^^
The arguments in parentheses after the function name, should match the parameters in the subroutine definition.
It is possible to not store the return value but the compiler
will issue a warning then telling you the result values of a subroutine call are discarded.
If you want to ignore a return value of a subroutine, you should prefix the call with the ``void`` keyword.
Otherwise the compiler will issue a warning about discarding a result value.
.. caution::
Note that due to the way parameters are processed by the compiler,
@ -708,9 +711,13 @@ sum(x)
sort(array)
Sort the array in ascending order (in-place)
Note: sorting a floating-point array is not supported right now, as a general sorting routine for this will
be extremely slow. Either build one yourself or find another solution that doesn't require sorting
floating point values.
reverse(array)
Reverse the values in the array (in-place). Can be used after sort() to sort an array in descending order.
Reverse the values in the array (in-place). Supports all data types including floats.
Can be used after sort() to sort an array in descending order.
len(x)
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
@ -802,6 +809,22 @@ memsetw(address, numwords, wordvalue)
Efficiently set a part of memory to the given (u)word value.
But the most efficient will always be to write a specialized fill routine in assembly yourself!
leftstr(source, target, length)
Copies the left side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
rightstr(source, target, length)
Copies the right side of the source string of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
substr(source, target, start, length)
Copies a segment from the source string, starting at the given index,
and of the given length to target string.
It is assumed the target string buffer is large enough to contain the result.
Modifies in-place, doesn't return a value (so can't be used in an expression).
swap(x, y)
Swap the values of numerical variables (or memory locations) x and y in a fast way.
@ -827,6 +850,10 @@ rrestore()
read_flags()
Returns the current value of the CPU status register.
exit(returncode)
Immediately stops the program and exits it, with the returncode in the A register.
Note: custom interrupt handlers remain active unless manually cleared first!
Library routines

View File

@ -172,13 +172,13 @@ Code blocks
-----------
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
can contain Prog8 *code*, *directives*, *variable declarations* and *subroutines*::
can only contain *directives*, *variable declarations*, *subroutines* or *inline assembly*::
<blockname> [<address>] {
<directives>
<variables>
<statements>
<subroutines>
<inline asm>
}
The <blockname> must be a valid identifier.
@ -191,7 +191,6 @@ Also read :ref:`blocks`. Here is an example of a code block, to be loaded at ``
}
Labels
------
@ -217,7 +216,8 @@ Variable declarations
Variables should be declared with their exact type and size so the compiler can allocate storage
for them. You can give them an initial value as well. That value can be a simple literal value,
or an expression. You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
or an expression. If you don't provide an intial value yourself, zero will be used.
You can add a ``@zp`` zeropage-tag, to tell the compiler to prioritize it
when selecting variables to be put into zeropage.
The syntax is::
@ -230,6 +230,7 @@ Various examples::
byte age = 2018 - 1974
float wallet = 55.25
str name = "my name is Irmen"
str name = @"my name is Irmen" ; string with alternative byte encoding
uword address = &counter
byte[] values = [11, 22, 33, 44, 55]
byte[5] values ; array of 5 bytes, initially set to zero
@ -248,7 +249,7 @@ Prog8 supports the following data types:
type identifier type storage size example var declaration and literal value
=============== ======================= ================= =========================================
``byte`` signed byte 1 byte = 8 bits ``byte myvar = -22``
``ubyte`` unsigned byte 1 byte = 8 bits ``ubyte myvar = $8f``
``ubyte`` unsigned byte 1 byte = 8 bits ``ubyte myvar = $8f``, ``ubyte c = 'a'``, ``ubyte c2 = @'a'``
-- boolean 1 byte = 8 bits ``byte myvar = true`` or ``byte myvar == false``
The true and false are actually just aliases
for the byte values 1 and 0.
@ -268,8 +269,6 @@ type identifier type storage size example var declara
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
``str`` string (petscii) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte
``str_s`` string (screencodes) varies ``str_s myvar = "hello."``
implicitly terminated by a 0-byte
=============== ======================= ================= =========================================
**arrays:** you can split an array initializer list over several lines if you want. When an initialization
@ -323,7 +322,7 @@ Constants
^^^^^^^^^
All variables can be assigned new values unless you use the ``const`` keyword.
The initial value will now be evaluated at compile time (it must be a compile time constant expression).
The initial value must be known at compile time (it must be a compile time constant expression).
This is only valid for the simple numeric types (byte, word, float)::
const byte max_age = 99
@ -342,9 +341,16 @@ The following names are reserved, they have a special meaning::
Range expression
^^^^^^^^^^^^^^^^
A special value is the *range expression* ( ``<startvalue> to <endvalue>`` )
which represents a range of numbers or characters,
from the starting value to (and including) the ending value.
A special value is the *range expression* which represents a range of numbers or characters,
from the starting value to (and including) the ending value::
<start> to <end> [ step <step> ]
<start> downto <end> [ step <step> ]
You an provide a step value if you need something else than the default increment which is one (or,
in case of downto, a decrement of one). Because a step of minus one is so common you can just use
the downto variant to avoid having to specify the step as well.
If used in the place of a literal value, it expands into the actual array of values::
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
@ -453,19 +459,22 @@ Subroutine / function calls
You call a subroutine like this::
[ result = ] subroutinename_or_address ( [argument...] )
[ void / result = ] subroutinename_or_address ( [argument...] )
; example:
resultvariable = subroutine(arg1, arg2, arg3)
void noresultvaluesub(arg)
Arguments are separated by commas. The argument list can also be empty if the subroutine
takes no parameters. If the subroutine returns a value, you can still omit the assignment to
a result variable (but the compiler will warn you about discarding the result of the call).
takes no parameters. If the subroutine returns a value, usually you assign it to a variable.
If you're not interested in the return value, prefix the function call with the ``void`` keyword.
Otherwise the compiler will warn you about discarding the result of the call.
Normal subroutines can only return zero or one return values.
However, the special ``asmsub`` routines (implemented in assembly code or referencing
a routine in kernel ROM) can return more than one return values, for instance a status
in the carry bit and a number in A, or a 16-bit value in A/Y registers.
However, the special ``asmsub`` routines (implemented in assembly code) or ``romsub`` routines
(referencing a routine in kernel ROM) can return more than one return value.
For example a status in the carry bit and a number in A, or a 16-bit value in A/Y registers.
It is not possible to process the results of a call to these kind of routines
directly from the language, because only single value assignments are possible.
You can still call the subroutine and not store the results.
@ -493,9 +502,34 @@ and can have nothing following it. The close curly brace must be on its own line
The parameters is a (possibly empty) comma separated list of "<datatype> <parametername>" pairs specifying the input parameters.
The return type has to be specified if the subroutine returns a value.
.. todo::
asmsub with assigning memory address to refer to predefined ROM subroutines
asmsub with a regular body to precisely control what registers are used to call the subroutine
Assembly / ROM subroutines
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Subroutines implemented in ROM are usually defined by compiler library files, with the following syntax::
romsub $FFD5 = LOAD(ubyte verify @ A, uword address @ XY) -> clobbers() -> ubyte @Pc, ubyte @ A, ubyte @ X, ubyte @ Y
This defines the ``LOAD`` subroutine at ROM memory address $FFD5, taking arguments in all three registers A, X and Y,
and returning stuff in several registers as well. The ``clobbers`` clause is used to signify to the compiler
what CPU registers are clobbered by the call instead of being unchanged or returning a meaningful result value.
Subroutines that are implemented purely in assembly code and which have an assembly calling convention (i.e.
the parameters are strictly passed via cpu registers), are defined like this::
asmsub FREADS32() clobbers(A,X,Y) {
%asm {{
lda $62
eor #$ff
asl a
lda #0
ldx #$a0
jmp $bc4f
}}
}
the statement body of such a subroutine should consist of just an inline assembly block.
Expressions
@ -564,8 +598,8 @@ You can use a single statement, or a statement block like in the example below::
}
repeat--until loop
^^^^^^^^^^^^^^^^^^
repeat-until loop
^^^^^^^^^^^^^^^^^
Until the given condition is true (1), repeat the given statement(s).
You can use a single statement, or a statement block like in the example below::
@ -577,6 +611,19 @@ You can use a single statement, or a statement block like in the example below::
} until <condition>
forever loop
^^^^^^^^^^^^
Simply run the code in a loop, forever. It's the same as a while true or until false loop,
or just a jump back to a previous label. You can still break out of this loop as well, if you want::
forever {
; .. do stuff
if something
break ; you can exit the loop if you want
}
Conditional Execution and Jumps
-------------------------------

View File

@ -2,20 +2,11 @@
TODO
====
Memory Block Operations integrated in language?
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
array/string memory block operations?
- array operations
copy (from another array with the same length), shift-N(left,right), rotate-N(left,right)
clear (set whole array to the given value, default 0)
- array operations ofcourse work identical on vars and on memory mapped vars of these types.
- strings: identical operations as on array.
For now, we have the ``memcopy`` and ``memset`` builtin functions.
- finalize (most) of the still missing "new" assignment asm code generation
- aliases for imported symbols for example perhaps '%alias print = c64scr.print'
- option to load library files from a directory instead of the embedded ones (easier library development/debugging)
- investigate support for 8bitguy's Commander X16 platform https://murray2.com/forums/commander-x16.9/ and https://github.com/commanderx16/x16-docs
- see if we can group some errors together for instance the (now single) errors about unidentified symbols
More optimizations
@ -23,12 +14,16 @@ More optimizations
Add more compiler optimizations to the existing ones.
- on the language AST level
- on the final assembly source level
- more targeted optimizations for assigment asm code, such as the following:
- subroutine calling convention? like: 1 byte arg -> pass in A, 2 bytes -> pass in A+Y, return value likewise.
- remove unreachable code after an exit(), return or goto
- working subroutine inlining (start with trivial routines, grow to taking care of vars and identifier refs to them)
- add a compiler option to not include variable initialization code (useful if the program is expected to run only once, such as a game)
the program will then rely solely on the values as they are in memory at the time of program startup.
- Also some library routines and code patterns could perhaps be optimized further
- can the parameter passing to subroutines be optimized to avoid copying?
- working subroutine inlining (taking care of vars and identifier refs to them)
Also some library routines and code patterns could perhaps be optimized further
- more optimizations on the language AST level
- more optimizations on the final assembly source level
Eval stack redesign? (lot of work)
@ -36,17 +31,18 @@ Eval stack redesign? (lot of work)
The eval stack is now a split lsb/msb stack using X as the stackpointer.
Is it easier/faster to just use a single page unsplit stack?
It could then even be moved into the zeropage to greatly reduce code size and slowness.
It could then even be moved into the zeropage to reduce code size and slowness.
Or just move the LSB portion into a slab of the zeropage.
Allocate a fixed word in ZP that is the TOS so we can always operate on TOS directly
without having to to index into the stack?
Allocate a fixed word in ZP that is the Top Of Stack value so we can always operate on TOS directly
without having to index with X into the eval stack all the time?
This could GREATLY improve code size and speed for operations that work on just a single value.
Bugs
^^^^
Ofcourse there are still bugs to fix ;)
Bug Fixing
^^^^^^^^^^
Ofcourse there are always bugs to fix ;)
Misc
@ -54,4 +50,3 @@ Misc
Several ideas were discussed on my reddit post
https://www.reddit.com/r/programming/comments/alhj59/creating_a_programming_language_and_cross/

View File

@ -104,7 +104,17 @@ main {
ub = all(farr)
if ub==0 c64scr.print("error all10\n")
check_eval_stack()
c64scr.print("\nyou should see no errors above.")
c64scr.print("\nyou should see no errors printed above (only at first run).")
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@ main {
div_float(0,1,0)
div_float(999.9,111.0,9.008108108108107)
check_eval_stack()
}
sub div_ubyte(ubyte a1, ubyte a2, ubyte c) {
@ -102,4 +103,12 @@ main {
c64flt.print_f(r)
c64.CHROUT('\n')
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -33,6 +33,7 @@ main {
minus_float(2.5,1.5,1.0)
minus_float(-1.5,3.5,-5.0)
check_eval_stack()
}
sub minus_ubyte(ubyte a1, ubyte a2, ubyte c) {
@ -110,4 +111,13 @@ main {
c64flt.print_f(r)
c64.CHROUT('\n')
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

View File

@ -27,6 +27,7 @@ main {
mul_float(2.5,10,25)
mul_float(-1.5,10,-15)
check_eval_stack()
}
sub mul_ubyte(ubyte a1, ubyte a2, ubyte c) {
@ -104,4 +105,12 @@ main {
c64flt.print_f(r)
c64.CHROUT('\n')
}
sub check_eval_stack() {
if X!=255 {
c64scr.print("x=")
c64scr.print_ub(X)
c64scr.print(" error!\n")
}
}
}

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